# Converged Theme styled helper: 1. rename classNamesMap to classNames 2. write hoc 3. keep in mind recipes pattern 4. tokens ## Vocabulary **Recipe**: A function which takes a theme (and only a theme) and returns a literal. Recipes can encapsulate complex functions to calculate new colors, fonts, sizes, and other literals. They can chain. **Token**: A component-specific literal or a recipe which equates to a component specific slot. **Theme**: An abstract set of knobs which encapsulate the design language and other design system settings. ## Vocabulary Examples ### Recipe Example of calculating an accessible accent: ```jsx const accentBackground = (theme) => { return accessibleColor( theme.colors.accentPalette, // target theme.colors.background, // on this background color 1.4 // contrast target ); } const accentForeground = (theme) => { return accessibleColor( theme.colors.textPalette, // target accentBackground(theme), // on the accessible background, 1.4 // constrast target ) } // Font and Space Recipes (and the logic behind them) const fontSize = (steps) => { const {fontSizeBase, fontSizeScale, fontUnit} = theme.fonts; // 1em, 1.2, em return baseSize * Math.pow(baseScale, steps) + unit; } const theme = { //partial fontSizes: { sm: fontSize(-1) } } const fontSizeSm(theme) => { return theme.fontSizes.sm; } ``` ### Tokens A set of flat variables default values. Tokens are `names` that map to a string literal, or a function of `theme` to literal: Example: ```jsx { tokens: { thumbBackground: 'red', background: theme => theme.colors.accentBackground } } ``` #### Token Questions/Thoughts - Should margin/padding/flex be provided inline? (style={{}}) - For 1offs... yes? - Or, define a css class and use that (className) - But NOT tokens ### Theme ```jsx // Theme can contain component tokens as well as colors/typography etc const theme = { // not set on globals name globals: { typgraphy: {}, spacing: {}, colors: {}, effects: {}, }, components: { Slider: { tokens: { thumbBackground: theme => literal } } } } ``` ## Project plan We plan to have 2 utilities which separate concerns: In a new package `@uifabric/merge-styles-react`: ### Styled Function ```jsx styled(Component)({styles, tokens}) ``` #### `styleOptions.tokens` ```jsx const tokens = (theme) => { tokens: { thumbBackground: 'red', background: theme => theme.colors.accentBackground } } ``` #### `styleOptions.styles` ```jsx const styles = ({theme, tokens}) => { return { root: { background: theme.background }, label: {IStyle} } } ``` #### Styled Function Definition ```jsx const styled = Component => ({ styles, tokens }) => { return p => { const theme = useTheme(); const classNames = resolveClassNames(theme, styles, tokens); return <Component {...p} classNames={classNames} />; } } ``` #### Styled Function Examples Creating a styled Button off of unstyled BaseButton ```jsx const buttonStyles = ({theme, tokens}) => { root: { background: tokens.background || accentBackground(theme) fontSize: tokens.fontSize || fontSize(2); // root * scale^2 padding: size(1); // root * scale^1 } } const Button = styled(BaseButton)({ buttonStyles, tokens }); ``` Creating a variant of an existing styled control ```jsx const FancyButtonTokens = (theme) => { return { background: theme.background } } const FancyButton = styled(Button) ``` #### Static CSS Example If you just want to simply pass classNames to a component, you don't need styled ```jsx const SimpleComp = p => Component({...p, classNames: styles }); ``` #### A Composite Control ```jsx const dialogStyles = ({theme, tokens}) => { root: { background: tokens.background || fontSize: tokens.fontSize || fontSize(2); // root * scale^2 padding: size(1); // root * scale^1 } } const Dialog = styled(BaseDialog)({ dialogStyles, tokens }); const Button = styled(BaseButton({ styles, tokens }); // Composed allows you to combine two styled controls together const FancyDialog = composed(Dialog: { slots: { button: Button } }); ``` ## Compose Function The `styled` function is an opionated helper that takes an unstyled components and passes in classNames to the control via a function called in the context of the theme. > Need Example ```jsx DialogBase = composed({ slots: { okButton: 'button', cancelButton: 'button' } }); <Dialog> <Dialog.Thing> <Button/> </Dialog.Thing> </Dialog> composed(CommandBar, { slots: { item: CommandBarItem } }); ``` ```jsx const SliderWithSlotOpinions = composed(SliderBase, { slots: { slots: { thumb: 'div', cancelButton: StyledButton } }}); 1. extend html attributes 2. tokens inline const SliderStyles: ISliderTokens = { root: { background: accentBackground, backgroundSelected: accent2Background, 'complex selector': { ... } } } ``` ### ResolveClassNames ```jsx const resolveClassNames = (theme, styles, tokens) => { const classNames = lookup(theme, styles, tokens); if (!classNames) { const resolvedTokens = resolve(tokens, theme); const resolvedStyles = } return classNames; } ``` ```jsx const resolve = (obj, ...args) = { const newObj = {}; for (let name in obj) { newObj[name] = (typeof obj[name] ==='function') ? obj[name].apply(this, args) : obj[name]; } return newObj; } ```