跳到内容
+

创建主题组件

了解如何创建完全自定义的组件,使其接受您的应用程序的主题。

简介

Joy UI 提供了强大的主题功能,让您可以将自己的组件添加到主题中,并将它们视为内置组件。

如果您正在 Joy UI 之上构建组件库,您可以按照下面的分步指南创建一个自定义组件,该组件可以在多个项目中进行主题化。

或者,您可以使用提供的 模板 作为组件的起点。

分步指南

本指南将引导您构建此统计信息组件,该组件接受应用程序的主题,就像它是内置的 Joy UI 组件一样

19,267
活跃用户 / 月

1. 创建组件插槽

插槽允许您通过在主题的 styleOverrides中定位其各自的名称来自定义组件的每个单独元素。

此统计信息组件由三个插槽组成

  • root:组件的容器
  • value:统计信息的数字
  • unit:统计信息的单位或描述
19,267
value
活跃用户 / 月
unit
root

使用带有 nameslot 参数的 styled API 来创建插槽,如下所示

import * as React from 'react';
import { styled } from '@mui/joy/styles';

const StatRoot = styled('div', {
  name: 'JoyStat', // The component name
  slot: 'root', // The slot name
})(({ theme }) => ({
  display: 'flex',
  flexDirection: 'column',
  gap: theme.spacing(0.5),
  padding: theme.spacing(3, 4),
  backgroundColor: theme.vars.palette.background.surface,
  borderRadius: theme.vars.radius.sm,
  boxShadow: theme.vars.shadow.md,
}));

const StatValue = styled('div', {
  name: 'JoyStat',
  slot: 'value',
})(({ theme }) => ({
  ...theme.typography.h2,
}));

const StatUnit = styled('div', {
  name: 'JoyStat',
  slot: 'unit',
})(({ theme }) => ({
  ...theme.typography['body-sm'],
  color: theme.vars.palette.text.tertiary,
}));

2. 创建组件

使用上一步中创建的插槽组装组件

// /path/to/Stat.js
import * as React from 'react';

const StatRoot = styled('div', {
  name: 'JoyStat',
  slot: 'root',
})();

const StatValue = styled('div', {
  name: 'JoyStat',
  slot: 'value',
})();

const StatUnit = styled('div', {
  name: 'JoyStat',
  slot: 'unit',
})();

const Stat = React.forwardRef(function Stat(props, ref) {
  const { value, unit, ...other } = props;

  return (
    <StatRoot ref={ref} {...other}>
      <StatValue>{value}</StatValue>
      <StatUnit>{unit}</StatUnit>
    </StatRoot>
  );
});

export default Stat;

此时,您将能够像这样将主题应用于 Stat 组件

import { extendTheme } from '@mui/joy/styles';

const theme = extendTheme({
  components: {
    // the component name defined in the `name` parameter
    // of the `styled` API
    JoyStat: {
      styleOverrides: {
        // the slot name defined in the `slot` and `overridesResolver` parameters
        // of the `styled` API
        root: {
          backgroundColor: '#121212',
        },
        value: {
          color: '#fff',
        },
        unit: {
          color: '#888',
        },
      },
    },
  },
});

3. 使用 ownerState 设置插槽样式

当您需要设置基于插槽的 props 或内部状态的样式时,请将它们包装在 ownerState 对象中,并将其作为 prop 传递给每个插槽。 ownerState 是一个特殊名称,不会通过 styled API 传播到 DOM。

Stat 组件添加 variant prop,并使用它来设置 root 插槽的样式,如下所示

  const Stat = React.forwardRef(function Stat(props, ref) {
+   const { value, unit, variant, ...other } = props;
+
+   const ownerState = { ...props, variant };

    return (
-      <StatRoot ref={ref} {...other}>
-        <StatValue>{value}</StatValue>
-        <StatUnit>{unit}</StatUnit>
-      </StatRoot>
+      <StatRoot ref={ref} ownerState={ownerState} {...other}>
+        <StatValue ownerState={ownerState}>{value}</StatValue>
+        <StatUnit ownerState={ownerState}>{unit}</StatUnit>
+      </StatRoot>
    );
  });

然后您可以在插槽中读取 ownerState,以根据 variant prop 设置其样式。

  const StatRoot = styled('div', {
    name: 'JoyStat',
    slot: 'root',
-  })(({ theme }) => ({
+  })(({ theme, ownerState }) => ({
    display: 'flex',
    flexDirection: 'column',
    gap: theme.spacing(0.5),
    padding: theme.spacing(3, 4),
    backgroundColor: theme.palette.background.paper,
    borderRadius: theme.shape.borderRadius,
    boxShadow: theme.shadows[2],
    letterSpacing: '-0.025em',
    fontWeight: 600,
+   ...ownerState.variant === 'outlined' && {
+    border: `2px solid ${theme.palette.divider}`,
+   },
  }));

4. 支持主题默认 props

要为不同的项目自定义组件的默认 props,您需要使用 useThemeProps API。

+ import { useThemeProps } from '@mui/joy/styles';

- const Stat = React.forwardRef(function Stat(props, ref) {
+ const Stat = React.forwardRef(function Stat(inProps, ref) {
+   const props = useThemeProps({ props: inProps, name: 'JoyStat' });
    const { value, unit, ...other } = props;

    return (
      <StatRoot ref={ref} {...other}>
        <StatValue>{value}</StatValue>
        <StatUnit>{unit}</StatUnit>
      </StatRoot>
    );
  });

然后您可以像这样自定义组件的默认 props

import { extendTheme } from '@mui/joy/styles';

const theme = extendTheme({
  components: {
    JoyStat: {
      defaultProps: {
        variant: 'outlined',
      },
    },
  },
});

TypeScript

如果您使用 TypeScript,则必须为组件 props 和 ownerState 创建接口

interface StatProps {
  value: number | string;
  unit: string;
  variant?: 'outlined';
}

interface StatOwnerState extends StatProps {
  // …key value pairs for the internal state that you want to style the slot
  // but don't want to expose to the users
}

然后您可以在组件和插槽中使用它们。

const StatRoot = styled('div', {
  name: 'JoyStat',
  slot: 'root',
})<{ ownerState: StatOwnerState }>(({ theme, ownerState }) => ({
  display: 'flex',
  flexDirection: 'column',
  gap: theme.spacing(0.5),
  padding: theme.spacing(3, 4),
  backgroundColor: theme.palette.background.paper,
  borderRadius: theme.shape.borderRadius,
  boxShadow: theme.shadows[2],
  letterSpacing: '-0.025em',
  fontWeight: 600,
  // typed-safe access to the `variant` prop
  ...(ownerState.variant === 'outlined' && {
    border: `2px solid ${theme.palette.divider}`,
    boxShadow: 'none',
  }),
}));

// …do the same for other slots

const Stat = React.forwardRef<HTMLDivElement, StatProps>(function Stat(inProps, ref) {
  const props = useThemeProps({ props: inProps, name: 'JoyStat' });
  const { value, unit, variant, ...other } = props;

  const ownerState = { ...props, variant };

  return (
    <StatRoot ref={ref} ownerState={ownerState} {...other}>
      <StatValue ownerState={ownerState}>{value}</StatValue>
      <StatUnit ownerState={ownerState}>{unit}</StatUnit>
    </StatRoot>
  );
});

最后,将 Stat 组件添加到主题类型。

import { Theme, StyleOverrides } from '@mui/joy/styles';
import { StatProps, StatOwnerState } from '/path/to/Stat';

declare module '@mui/joy/styles' {
  interface Components {
    JoyStat?: {
      defaultProps?: Partial<StatProps>;
      styleOverrides?: StyleOverrides<StatProps, StatOwnerState, Theme>;
    };
  }
}

模板

此模板是上述分步指南的最终产品,演示了如何构建可以使用主题设置样式的自定义组件,就像它是内置组件一样。

1.9M
收藏夹
5.1M
浏览量
Enter 开始编辑