-
-
Notifications
You must be signed in to change notification settings - Fork 31.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Joy] Improve variant customization experience #30878
Changes from 21 commits
71337a0
da4fd85
f70996d
b27c23d
069b03f
ac39c9a
cc33f74
abd043c
2ee1c0c
c7f3f44
a5a1a77
6741ec0
22d7c5e
7f8875e
8d5885e
321c90a
23f5d4a
87fc23e
df69def
e5654f7
4bcd7e7
c28d963
6e0d45c
96a4888
b6ad6f6
364a57f
c19d937
4cb6800
a8a9a41
721046b
f90bacf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import { deepmerge } from '@mui/utils'; | ||
import { | ||
unstable_createCssVarsProvider as createCssVarsProvider, | ||
BreakpointsOptions, | ||
|
@@ -15,14 +16,23 @@ import { Variants } from './types/variants'; | |
import { ColorSystem } from './types/colorSystem'; | ||
import { TypographySystem } from './types/typography'; | ||
import { Components } from './components'; | ||
import { createVariant, createContainedOverrides } from './variantUtils'; | ||
|
||
type PartialDeep<T> = { | ||
[K in keyof T]?: PartialDeep<T[K]>; | ||
type PartialNested<T> = { | ||
[K in keyof T]?: T[K] extends Record<any, any> | ||
? { | ||
[J in keyof T[K]]?: T[K][J]; | ||
} | ||
: T[K]; | ||
}; | ||
|
||
type PartialNested<T> = { | ||
type PartialNestedNested<T> = { | ||
[K in keyof T]?: { | ||
[J in keyof T[K]]?: T[K][J]; | ||
[J in keyof T[K]]?: T[K][J] extends Record<any, any> | ||
? { | ||
[P in keyof T[K][J]]?: T[K][J][P]; | ||
} | ||
: T[K][J]; | ||
}; | ||
}; | ||
|
||
|
@@ -31,7 +41,7 @@ type ThemeInput = PartialNested< | |
ThemeScales & { | ||
focus: Focus; | ||
typography: TypographySystem; | ||
variants: Variants; | ||
variants: PartialNested<Variants>; | ||
} | ||
> & { | ||
breakpoints?: BreakpointsOptions; | ||
|
@@ -40,11 +50,11 @@ type ThemeInput = PartialNested< | |
}; | ||
|
||
type JoyThemeInput = ThemeInput & { | ||
colorSchemes: Record<DefaultColorScheme, PartialDeep<ColorSystem>>; | ||
colorSchemes: Record<DefaultColorScheme, PartialNestedNested<ColorSystem>>; | ||
}; | ||
|
||
type ApplicationThemeInput = ThemeInput & { | ||
colorSchemes: Record<ExtendedColorScheme, PartialDeep<ColorSystem>>; | ||
colorSchemes: Record<ExtendedColorScheme, PartialNestedNested<ColorSystem>>; | ||
}; | ||
|
||
const { palette, ...rest } = defaultTheme; | ||
|
@@ -67,6 +77,32 @@ const { CssVarsProvider, useColorScheme, getInitColorSchemeScript } = createCssV | |
dark: 'dark', | ||
}, | ||
prefix: 'joy', | ||
resolveTheme: (mergedTheme: JoyTheme) => { | ||
mergedTheme.variants = deepmerge( | ||
{ | ||
text: createVariant('text', mergedTheme), | ||
textHover: createVariant('textHover', mergedTheme), | ||
textActive: createVariant('textActive', mergedTheme), | ||
textDisabled: createVariant('textDisabled', mergedTheme), | ||
outlined: createVariant('outlined', mergedTheme), | ||
outlinedHover: createVariant('outlinedHover', mergedTheme), | ||
outlinedActive: createVariant('outlinedActive', mergedTheme), | ||
outlinedDisabled: createVariant('outlinedDisabled', mergedTheme), | ||
light: createVariant('light', mergedTheme), | ||
lightHover: createVariant('lightHover', mergedTheme), | ||
lightActive: createVariant('lightActive', mergedTheme), | ||
lightDisabled: createVariant('lightDisabled', mergedTheme), | ||
contained: createVariant('contained', mergedTheme), | ||
containedHover: createVariant('containedHover', mergedTheme), | ||
containedActive: createVariant('containedActive', mergedTheme), | ||
containedDisabled: createVariant('containedDisabled', mergedTheme), | ||
containedOverrides: createContainedOverrides(mergedTheme), | ||
} as typeof mergedTheme.variants, | ||
mergedTheme.variants, | ||
{ clone: false }, | ||
); | ||
return mergedTheme; | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the main change of this PR. |
||
shouldSkipGeneratingVar: (keys) => | ||
keys[0] === 'typography' || | ||
keys[0] === 'variants' || | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,6 @@ import { deepmerge } from '@mui/utils'; | |
import { ThemeProvider as SystemThemeProvider, useTheme as useSystemTheme } from '@mui/system'; | ||
import defaultTheme, { JoyTheme } from './defaultTheme'; | ||
import { Components } from './components'; | ||
import { ExtendedColorScheme } from './types/colorScheme'; | ||
|
||
type PartialDeep<T> = { | ||
[K in keyof T]?: PartialDeep<T[K]>; | ||
|
@@ -17,7 +16,7 @@ export default function ThemeProvider({ | |
children, | ||
theme, | ||
}: React.PropsWithChildren<{ | ||
theme?: PartialDeep<Omit<JoyTheme<ExtendedColorScheme>, 'vars'>> & { | ||
theme?: PartialDeep<Omit<JoyTheme, 'vars'>> & { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JoyTheme should not be a generic. |
||
components?: Components; | ||
}; | ||
}>) { | ||
|
@@ -27,6 +26,6 @@ export default function ThemeProvider({ | |
...mergedTheme, | ||
vars: mergedTheme, | ||
components, | ||
} as JoyTheme<ExtendedColorScheme> & { components: Components }; | ||
} as JoyTheme & { components: Components }; | ||
return <SystemThemeProvider theme={mergedTheme}>{children}</SystemThemeProvider>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,19 +9,14 @@ import { | |
} from '@mui/system'; | ||
import colors from '../colors'; | ||
import { | ||
ColorPaletteProp, | ||
ColorSystem, | ||
ColorPaletteProp, | ||
Palette, | ||
PaletteText, | ||
PaletteRange, | ||
PaletteBackground, | ||
} from './types/colorSystem'; | ||
import { Variants, DefaultVariantKey, DefaultContextualOverrides } from './types/variants'; | ||
import { | ||
createLightModeVariantVariables, | ||
createDarkModeVariantVariables, | ||
createVariant, | ||
} from './variantUtils'; | ||
import { Variants } from './types/variants'; | ||
import { DefaultColorScheme, ExtendedColorScheme } from './types/colorScheme'; | ||
import { Shadow } from './types/shadow'; | ||
import { Radius } from './types/radius'; | ||
|
@@ -36,7 +31,9 @@ import { | |
|
||
type CSSProperties = CSS.Properties<number | string>; | ||
|
||
type Split<T, K extends keyof T = keyof T> = K extends string | number ? { [k in K]: T[K] } : never; | ||
type Split<T, K extends keyof T = keyof T> = K extends string | number | ||
? { [k in K]: Exclude<T[K], undefined> } | ||
: never; | ||
|
||
type ConcatDeep<T> = T extends Record<string | number, infer V> | ||
? keyof T extends string | number | ||
|
@@ -59,47 +56,17 @@ export interface Focus { | |
* Internal type for definfing default Joy theme. | ||
* ============================================== | ||
*/ | ||
type BasePaletteRange = | ||
| 50 | ||
| 100 | ||
| 200 | ||
| 300 | ||
| 400 | ||
| 500 | ||
| 600 | ||
| 700 | ||
| 800 | ||
| 900 | ||
| 'textColor' | ||
| 'textHoverBg' | ||
| 'textActiveBg' | ||
| 'textDisabledColor' | ||
| 'outlinedColor' | ||
| 'outlinedBorder' | ||
| 'outlinedHoverBg' | ||
| 'outlinedHoverBorder' | ||
| 'outlinedActiveBg' | ||
| 'outlinedDisabledColor' | ||
| 'outlinedDisabledBorder' | ||
| 'lightColor' | ||
| 'lightBg' | ||
| 'lightHoverBg' | ||
| 'lightActiveBg' | ||
| 'lightDisabledColor' | ||
| 'lightDisabledBg' | ||
| 'containedColor' | ||
| 'containedBg' | ||
| 'containedHoverBg' | ||
| 'containedActiveBg' | ||
| 'containedDisabledBg'; | ||
type BasePaletteRange = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; | ||
type PartialRest<T, U extends keyof T> = Pick<T, U> & Partial<Exclude<T, U>>; | ||
type BaseDesignTokens = { | ||
palette: { | ||
primary: Pick<PaletteRange, BasePaletteRange>; | ||
neutral: Pick<PaletteRange, BasePaletteRange>; | ||
danger: Pick<PaletteRange, BasePaletteRange>; | ||
info: Pick<PaletteRange, BasePaletteRange>; | ||
success: Pick<PaletteRange, BasePaletteRange>; | ||
warning: Pick<PaletteRange, BasePaletteRange>; | ||
// variant tokens are optional because the style will be generated after CSS variables has been prepared. | ||
primary: PartialRest<PaletteRange, BasePaletteRange>; | ||
neutral: PartialRest<PaletteRange, BasePaletteRange>; | ||
danger: PartialRest<PaletteRange, BasePaletteRange>; | ||
info: PartialRest<PaletteRange, BasePaletteRange>; | ||
success: PartialRest<PaletteRange, BasePaletteRange>; | ||
warning: PartialRest<PaletteRange, BasePaletteRange>; | ||
text: Pick<PaletteText, 'primary' | 'secondary' | 'tertiary'>; | ||
background: Pick<PaletteBackground, 'body' | 'level1' | 'level2' | 'level3'>; | ||
focusVisible: Palette['focusVisible']; | ||
|
@@ -120,6 +87,62 @@ type BaseDesignTokens = { | |
|
||
type BaseColorSystem = Pick<BaseDesignTokens, 'palette' | 'shadowRing' | 'shadowChannel'>; | ||
|
||
const createLightModeVariantVariables = (color: ColorPaletteProp) => ({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no change on these functions, just moved there to prevent dependency cycle. |
||
textColor: `var(--joy-palette-${color}-600)`, | ||
textHoverBg: `var(--joy-palette-${color}-100)`, | ||
textActiveBg: `var(--joy-palette-${color}-200)`, | ||
textDisabledColor: `var(--joy-palette-${color}-200)`, | ||
|
||
outlinedColor: `var(--joy-palette-${color}-600)`, | ||
outlinedBorder: `var(--joy-palette-${color}-200)`, | ||
outlinedHoverBg: `var(--joy-palette-${color}-100)`, | ||
outlinedHoverBorder: `var(--joy-palette-${color}-300)`, | ||
outlinedActiveBg: `var(--joy-palette-${color}-200)`, | ||
outlinedDisabledColor: `var(--joy-palette-${color}-200)`, | ||
outlinedDisabledBorder: `var(--joy-palette-${color}-100)`, | ||
|
||
lightColor: `var(--joy-palette-${color}-700)`, | ||
lightBg: `var(--joy-palette-${color}-100)`, | ||
lightHoverBg: `var(--joy-palette-${color}-200)`, | ||
lightActiveBg: `var(--joy-palette-${color}-300)`, | ||
lightDisabledColor: `var(--joy-palette-${color}-300)`, | ||
lightDisabledBg: `var(--joy-palette-${color}-50)`, | ||
|
||
containedColor: '#fff', | ||
containedBg: `var(--joy-palette-${color}-600)`, | ||
containedHoverBg: `var(--joy-palette-${color}-700)`, | ||
containedActiveBg: `var(--joy-palette-${color}-800)`, | ||
containedDisabledBg: `var(--joy-palette-${color}-200)`, | ||
}); | ||
|
||
const createDarkModeVariantVariables = (color: ColorPaletteProp) => ({ | ||
textColor: `var(--joy-palette-${color}-300)`, | ||
textHoverBg: `var(--joy-palette-${color}-800)`, | ||
textActiveBg: `var(--joy-palette-${color}-700)`, | ||
textDisabledColor: `var(--joy-palette-${color}-800)`, | ||
|
||
outlinedColor: `var(--joy-palette-${color}-200)`, | ||
outlinedBorder: `var(--joy-palette-${color}-700)`, | ||
outlinedHoverBg: `var(--joy-palette-${color}-900)`, | ||
outlinedHoverBorder: `var(--joy-palette-${color}-600)`, | ||
outlinedActiveBg: `var(--joy-palette-${color}-900)`, | ||
outlinedDisabledColor: `var(--joy-palette-${color}-800)`, | ||
outlinedDisabledBorder: `var(--joy-palette-${color}-800)`, | ||
|
||
lightColor: `var(--joy-palette-${color}-200)`, | ||
lightBg: `var(--joy-palette-${color}-900)`, | ||
lightHoverBg: `var(--joy-palette-${color}-800)`, | ||
lightActiveBg: `var(--joy-palette-${color}-700)`, | ||
lightDisabledColor: `var(--joy-palette-${color}-800)`, | ||
lightDisabledBg: `var(--joy-palette-${color}-900)`, | ||
|
||
containedColor: `#fff`, | ||
containedBg: `var(--joy-palette-${color}-600)`, | ||
containedHoverBg: `var(--joy-palette-${color}-700)`, | ||
containedActiveBg: `var(--joy-palette-${color}-800)`, | ||
containedDisabledBg: `var(--joy-palette-${color}-300)`, | ||
}); | ||
|
||
export const lightColorSystem: BaseColorSystem = { | ||
palette: { | ||
primary: { | ||
|
@@ -275,8 +298,7 @@ const internalDefaultTheme: BaseDesignTokens & { | |
TypographySystem, | ||
'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'body1' | 'body2' | 'body3' | ||
>; | ||
variants: Pick<Variants, DefaultVariantKey> & | ||
Record<DefaultContextualOverrides, Record<Exclude<ColorPaletteProp, 'context'>, CSSObject>>; | ||
variants: {}; | ||
vars: BaseDesignTokens & BaseColorSystem; | ||
spacing: Spacing; | ||
breakpoints: Breakpoints; | ||
|
@@ -289,7 +311,7 @@ const internalDefaultTheme: BaseDesignTokens & { | |
focus: { | ||
default: { | ||
outline: '4px solid', | ||
outlineColor: 'var(--joy-palette-focusVisible)', | ||
outlineColor: 'var(--variant-focusVisible, var(--joy-palette-focusVisible))', | ||
}, | ||
}, | ||
typography: { | ||
|
@@ -359,25 +381,7 @@ const internalDefaultTheme: BaseDesignTokens & { | |
color: 'var(--joy-palette-text-tertiary)', | ||
}, | ||
}, | ||
variants: { | ||
text: createVariant('text'), | ||
textHover: createVariant('textHover'), | ||
textActive: createVariant('textActive'), | ||
textDisabled: createVariant('textDisabled'), | ||
outlined: createVariant('outlined'), | ||
outlinedHover: createVariant('outlinedHover'), | ||
outlinedActive: createVariant('outlinedActive'), | ||
outlinedDisabled: createVariant('outlinedDisabled'), | ||
light: createVariant('light'), | ||
lightHover: createVariant('lightHover'), | ||
lightActive: createVariant('lightActive'), | ||
lightDisabled: createVariant('lightDisabled'), | ||
contained: createVariant('contained'), | ||
containedHover: createVariant('containedHover'), | ||
containedActive: createVariant('containedActive'), | ||
containedDisabled: createVariant('containedDisabled'), | ||
containedOverrides: createVariant('containedOverrides'), | ||
}, | ||
variants: {}, | ||
vars: baseDesignTokens, | ||
breakpoints: defaultSystemTheme.breakpoints, | ||
spacing: defaultSystemTheme.spacing, | ||
|
@@ -401,15 +405,14 @@ export type ThemeVar = NormalizeVars<Vars>; | |
|
||
export const createGetCssVar = (prefix = 'joy') => systemCreateGetCssVar<ThemeVar>(prefix); | ||
|
||
export interface JoyTheme<ApplicationColorScheme extends string = ExtendedColorScheme> | ||
extends ThemeScales, | ||
ColorSystem { | ||
colorSchemes: Record<DefaultColorScheme | ApplicationColorScheme, ColorSystem>; | ||
export interface JoyTheme extends ThemeScales, ColorSystem { | ||
colorSchemes: Record<DefaultColorScheme | ExtendedColorScheme, ColorSystem>; | ||
focus: Focus; | ||
typography: TypographySystem; | ||
variants: Variants; | ||
spacing: Spacing; | ||
breakpoints: Breakpoints; | ||
prefix: string; | ||
vars: Vars; | ||
getCssVar: ReturnType<typeof createGetCssVar>; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename to match the variant variables.