创建主题组件
了解如何创建完全自定义的组件,使其接受您的应用程序的主题。
简介
Joy UI 提供了强大的主题功能,让您可以将自己的组件添加到主题中,并将它们视为内置组件。
如果您正在 Joy UI 之上构建组件库,您可以按照下面的分步指南创建一个自定义组件,该组件可以在多个项目中进行主题化。
或者,您可以使用提供的 模板 作为组件的起点。
分步指南
本指南将引导您构建此统计信息组件,该组件接受应用程序的主题,就像它是内置的 Joy UI 组件一样
1. 创建组件插槽
插槽允许您通过在主题的 styleOverrides中定位其各自的名称来自定义组件的每个单独元素。
此统计信息组件由三个插槽组成
root
:组件的容器value
:统计信息的数字unit
:统计信息的单位或描述
使用带有 name
和 slot
参数的 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>;
};
}
}
模板
此模板是上述分步指南的最终产品,演示了如何构建可以使用主题设置样式的自定义组件,就像它是内置组件一样。