|
| 1 | +import {Layout} from '../../../src/Layout'; |
| 2 | +export default Layout; |
| 3 | + |
| 4 | +export const tags = ['style', 'macro', 'spectrum', 'custom', 'advanced']; |
| 5 | +export const description = 'Style macro advanced usage guide'; |
| 6 | + |
| 7 | +# Advanced style macro usage |
| 8 | + |
| 9 | +A guide to advanced use cases with the `style` macro. |
| 10 | + |
| 11 | +## Runtime conditions |
| 12 | + |
| 13 | +When runtime conditions are detected (e.g., variants, UI states), the macro returns a function to resolve styles at runtime. |
| 14 | + |
| 15 | +```tsx |
| 16 | +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; |
| 17 | + |
| 18 | +const styles = style({ |
| 19 | + backgroundColor: { |
| 20 | + variant: { |
| 21 | + primary: 'accent', |
| 22 | + secondary: 'neutral' |
| 23 | + } |
| 24 | + } |
| 25 | +}); |
| 26 | + |
| 27 | +function MyComponent({variant}: {variant: 'primary' | 'secondary'}) { |
| 28 | + return <div className={styles({variant})} /> |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +Boolean conditions starting with `is` or `allows` can be used directly without nesting: |
| 33 | + |
| 34 | +```tsx |
| 35 | +const styles = style({ |
| 36 | + backgroundColor: { |
| 37 | + default: 'gray-100', |
| 38 | + isSelected: 'gray-900', |
| 39 | + allowsRemoving: 'gray-400' |
| 40 | + } |
| 41 | +}); |
| 42 | + |
| 43 | +<div className={styles({isSelected: true})} /> |
| 44 | +``` |
| 45 | + |
| 46 | +Runtime conditions work well with render props in React Aria Components. If you inline styles, you’ll get autocomplete for available conditions. |
| 47 | + |
| 48 | +```tsx |
| 49 | +import {Checkbox} from 'react-aria-components'; |
| 50 | +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; |
| 51 | + |
| 52 | +<Checkbox |
| 53 | + className={style({ |
| 54 | + backgroundColor: { |
| 55 | + default: 'gray-100', |
| 56 | + isHovered: 'gray-200', |
| 57 | + isSelected: 'gray-900' |
| 58 | + } |
| 59 | + })} |
| 60 | +/> |
| 61 | +``` |
| 62 | + |
| 63 | +### Nesting conditions |
| 64 | + |
| 65 | +Nest conditions to apply styles when multiple conditions are true. Conditions at the same level are mutually exclusive; order determines precedence. |
| 66 | + |
| 67 | +```tsx |
| 68 | +const styles = style({ |
| 69 | + backgroundColor: { |
| 70 | + default: 'gray-25', |
| 71 | + isSelected: { |
| 72 | + default: 'neutral', |
| 73 | + isEmphasized: 'accent', |
| 74 | + forcedColors: 'Highlight', |
| 75 | + isDisabled: { |
| 76 | + default: 'gray-400', |
| 77 | + forcedColors: 'GrayText' |
| 78 | + } |
| 79 | + } |
| 80 | + } |
| 81 | +}); |
| 82 | + |
| 83 | +<div className={styles({isSelected, isEmphasized, isDisabled})} /> |
| 84 | +``` |
| 85 | + |
| 86 | +## Reusing styles |
| 87 | + |
| 88 | +Extract common styles into constants and spread them into `style` calls. These must be in the same file or imported from another file as a macro. |
| 89 | + |
| 90 | +```tsx |
| 91 | +// style-utils.ts |
| 92 | +export const bannerBackground = () => 'blue-1000' as const; |
| 93 | + |
| 94 | +// component.tsx |
| 95 | +import {bannerBackground} from './style-utils' with {type: 'macro'}; |
| 96 | +const horizontalStack = { |
| 97 | + display: 'flex', |
| 98 | + alignItems: 'center', |
| 99 | + columnGap: 8 |
| 100 | +} as const; |
| 101 | + |
| 102 | +const styles = style({ |
| 103 | + ...horizontalStack, |
| 104 | + backgroundColor: bannerBackground(), |
| 105 | + columnGap: 4 |
| 106 | +}); |
| 107 | +``` |
| 108 | + |
| 109 | +Create custom utilities by defining your own macros. |
| 110 | + |
| 111 | +```ts |
| 112 | +// style-utils.ts |
| 113 | +export function horizontalStack(gap: number) { |
| 114 | + return { |
| 115 | + display: 'flex', |
| 116 | + alignItems: 'center', |
| 117 | + columnGap: gap |
| 118 | + } as const; |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +Usage: |
| 123 | + |
| 124 | +```tsx |
| 125 | +// component.tsx |
| 126 | +import {horizontalStack} from './style-utils' with {type: 'macro'}; |
| 127 | +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; |
| 128 | + |
| 129 | +const styles = style({ |
| 130 | + ...horizontalStack(4), |
| 131 | + backgroundColor: 'base' |
| 132 | +}); |
| 133 | +``` |
| 134 | + |
| 135 | +### Built-in utilities |
| 136 | + |
| 137 | +Use `focusRing()` to add the standard Spectrum focus ring. |
| 138 | + |
| 139 | +```tsx |
| 140 | +"use client"; |
| 141 | +import {style, focusRing} from '@react-spectrum/s2/style' with {type: 'macro'}; |
| 142 | +import {Button} from '@react-spectrum/s2'; |
| 143 | + |
| 144 | +const buttonStyle = style({ |
| 145 | + ...focusRing(), |
| 146 | + // ...other styles |
| 147 | +}); |
| 148 | + |
| 149 | +<Button styles={buttonStyle}>Press me</Button> |
| 150 | +``` |
| 151 | + |
| 152 | +## Setting CSS variables |
| 153 | + |
| 154 | +CSS variables can be directly defined in a `style` macro, allowing child elements to then access them in their own styles. |
| 155 | +A `type` should be provided to specify the CSS property type the `value` represents. |
| 156 | + |
| 157 | +```tsx |
| 158 | +const parentStyle = style({ |
| 159 | + '--rowBackgroundColor': { |
| 160 | + type: 'backgroundColor', |
| 161 | + value: 'gray-400' |
| 162 | + } |
| 163 | +}); |
| 164 | + |
| 165 | +const childStyle = style({ |
| 166 | + backgroundColor: '--rowBackgroundColor' |
| 167 | +}); |
| 168 | +``` |
| 169 | + |
| 170 | +## Creating custom components |
| 171 | + |
| 172 | +In-depth examples are coming soon! |
| 173 | + |
| 174 | +`mergeStyles` can be used to merge the style strings from multiple macros together, with the latter styles taking precedence similar to object spreading. |
| 175 | +This behavior can be leveraged to create a component API that allows an end user to only override specific styles conditionally. |
| 176 | + |
| 177 | +```tsx |
| 178 | +// User can override the component's background color ONLY if it isn't "quiet" |
| 179 | +const baselineStyles = style({backgroundColor: 'gray-100'}, ['backgroundColor']); |
| 180 | +const quietStyles = style({backgroundColor: 'transparent'}); |
| 181 | +const userStyles = style({backgroundColor: 'celery-600'}); |
| 182 | + |
| 183 | +function MyComponent({isQuiet, styles}: {isQuiet?: boolean, styles?: StyleString}) { |
| 184 | + let result = mergeStyles( |
| 185 | + baselineStyles(null, styles), |
| 186 | + isQuiet ? quietStyles : null |
| 187 | + ); |
| 188 | + |
| 189 | + return <div className={result}>My component</div> |
| 190 | +} |
| 191 | + |
| 192 | +// Displays quiet styles |
| 193 | +<MyComponent isQuiet styles={userStyles} /> |
| 194 | + |
| 195 | +// Displays user overrides |
| 196 | +<MyComponent styles={userStyles} /> |
| 197 | +``` |
| 198 | + |
| 199 | +The `iconStyle` macro should be used when styling Icons, see the [docs](../icons.html#iconstyle) for more information. |
0 commit comments