|
2 | 2 |
|
3 | 3 | Single function to create, manage, compose variants, for any CSS-in-JS libraries.
|
4 | 4 |
|
| 5 | + |
| 6 | +## Installation |
| 7 | + |
| 8 | +```bash |
| 9 | +npm install build-variants |
| 10 | +``` |
| 11 | + |
| 12 | +## How to use |
| 13 | + |
| 14 | +### Step 1 - build-variants configuration |
| 15 | + |
| 16 | +To be able to use build-variants with your CSS-in-JS library and get valid typings |
| 17 | +for your CSS/styles, you need to specify the type of the CSS object. |
| 18 | + |
| 19 | +To do so, you can create a simple function that expose `newBuildVariants` with |
| 20 | +the CSS interface that you want to use. |
| 21 | + |
| 22 | +For example with styled-components: |
| 23 | + |
| 24 | +```ts |
| 25 | +import { newBuildVariants } from 'build-variants' |
| 26 | +import { CSSObject } from 'styled-components' |
| 27 | + |
| 28 | +/** |
| 29 | + * Create a BuildVariants instance, typed to use styled-components's `CSSObject`s. |
| 30 | + */ |
| 31 | +export function buildVariants<TProps extends object>(props: TProps) { |
| 32 | + return newBuildVariants<TProps, CSSObject>(props) |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | +Note: If your library doesn't expose typings or if you are doing raw CSS only with |
| 37 | +React, you can use `React.CSSProperties` for example. |
| 38 | + |
| 39 | +You can even use you own CSS declaration if you want to use build-variants in a |
| 40 | +totally different context: |
| 41 | + |
| 42 | +```ts |
| 43 | +import { newBuildVariants } from 'build-variants' |
| 44 | + |
| 45 | +// build-variants typings will only tolerate CSS with color and background properties! |
| 46 | +interface IMyStyles { |
| 47 | + color: string |
| 48 | + background: string |
| 49 | +} |
| 50 | + |
| 51 | +/** |
| 52 | + * Create a BuildVariants instance, typed to use styled-components's `CSSObject`s. |
| 53 | + */ |
| 54 | +export function buildVariants<TProps extends object>(props: TProps) { |
| 55 | + return newBuildVariants<TProps, Partial<IMyStyles>>(props) |
| 56 | +} |
| 57 | +``` |
| 58 | + |
| 59 | + |
| 60 | +### Step 2 - Build your variants! |
| 61 | + |
| 62 | + |
| 63 | +```tsx |
| 64 | +import { buildVariants } from 'path/to/buildVariants' |
| 65 | + |
| 66 | +// Use here styled-components but you can used any CSS-in-JS library you want |
| 67 | +import { styled } from 'styled-components' |
| 68 | + |
| 69 | +// Define the interface of the props used by your component |
| 70 | +interface Props { |
| 71 | + // Define a 'private' property for the button font color |
| 72 | + _color?: 'default' | 'primary' | 'secondary' |
| 73 | + |
| 74 | + // Define a 'private' property for the button background |
| 75 | + _background?: 'default' | 'primary' | 'secondary' |
| 76 | + |
| 77 | + // Define a 'private' property for font variants. |
| 78 | + // This is an array, meaning that you can apply several values at once. |
| 79 | + _font?: Array<'default' | 'bold' | 'italic'>, |
| 80 | + |
| 81 | + type?: 'default' | 'primary' | 'secondary' |
| 82 | +} |
| 83 | + |
| 84 | +// Style a div component by using styled-components here. |
| 85 | +const Div = styled.div<Props>props => { |
| 86 | + // Get a new instance of build-variant. |
| 87 | + // Note that we use here `buildVariants()` defined in step 1 to be able to write |
| 88 | + // CSS styles as styled-components' CSS objects. |
| 89 | + return buildVariants(props) |
| 90 | + // Add some CSS. |
| 91 | + .css({ |
| 92 | + background: 'white' |
| 93 | + }) |
| 94 | + |
| 95 | + // Add more CSS. |
| 96 | + // You can add as many CSS blocks as you want. |
| 97 | + .css({ |
| 98 | + '> button': { |
| 99 | + all: 'unset' |
| 100 | + } |
| 101 | + }) |
| 102 | + |
| 103 | + // Implement CSS for each case of the color variant. |
| 104 | + // Everything is typed checked here. You have to implement all cases of the |
| 105 | + // union value. |
| 106 | + // Note that because _color is optional, we have to default on a default value, |
| 107 | + // here 'default', which is the first value of the union. |
| 108 | + .variant('_color', props._color || 'default', { |
| 109 | + default: { |
| 110 | + // No color for the default case, it will be inherited from a parent |
| 111 | + }, |
| 112 | + |
| 113 | + primary: { |
| 114 | + color: 'white' |
| 115 | + }, |
| 116 | + |
| 117 | + secondary: { |
| 118 | + color: 'black' |
| 119 | + } |
| 120 | + }) |
| 121 | + |
| 122 | + // Same thing with the background variant |
| 123 | + .variant('_background', props._background || 'default', { |
| 124 | + default: { |
| 125 | + // No background override. |
| 126 | + // As we have define a background in the first CSS block, we should have |
| 127 | + // a background: white at the end. |
| 128 | + }, |
| 129 | + |
| 130 | + primary: { |
| 131 | + background: 'blue' |
| 132 | + }, |
| 133 | + |
| 134 | + secondary: { |
| 135 | + background: 'white' |
| 136 | + } |
| 137 | + }) |
| 138 | + |
| 139 | + // Same thing with the font variant. |
| 140 | + // Notice that we use `variants` to manipulate an array of unions. |
| 141 | + .variants('_font', props._font || [], { |
| 142 | + default: { |
| 143 | + // Inherits from the parent |
| 144 | + }, |
| 145 | + |
| 146 | + bold: { |
| 147 | + fontWeight: 'bold' |
| 148 | + }, |
| 149 | + |
| 150 | + italic: { |
| 151 | + fontStyle: 'italic' |
| 152 | + } |
| 153 | + }) |
| 154 | + |
| 155 | + // Now, compose with your 'private' variants |
| 156 | + .compoundVariant('type', props.type || 'default', { |
| 157 | + // When composing, we get a new instance of the builder to get existing |
| 158 | + // private variants definitions. |
| 159 | + // Final `end()` function merges all CSS definitions get from the composition. |
| 160 | + // Here we don't want to style the default case, so we directly call the |
| 161 | + // end function. |
| 162 | + default: builder.end() |
| 163 | + |
| 164 | + // Here we define the type=primary variant from existing color, background |
| 165 | + // and font variants. |
| 166 | + // In this example, we use two definitions for the font variant. So the font |
| 167 | + // will be bold and italic. |
| 168 | + primary: builder |
| 169 | + .get('_color', 'primary') |
| 170 | + .get('_background', 'primary') |
| 171 | + .get('_font', ['bold', 'italic']) |
| 172 | + .end(), |
| 173 | + |
| 174 | + // In the same way, compose to define type=secondary variant. |
| 175 | + secondary: builder |
| 176 | + .get('_color', 'secondary') |
| 177 | + .get('_background', 'secondary') |
| 178 | + .get('_font', ['bold', 'italic']) |
| 179 | + .end() |
| 180 | + }) |
| 181 | + |
| 182 | + // You can also compose with an array of compoundVariant by using compoundVariants: |
| 183 | + // .compoundVariants('types', props.types || [], { |
| 184 | + // ... |
| 185 | + // }) |
| 186 | + |
| 187 | + // You can conditionate any CSS or variant definition by using `if()` block. |
| 188 | + .if( |
| 189 | + // Implement the predicate function here to have a pink color in your button |
| 190 | + true // OR `props.variants?.include('fancy') === true, |
| 191 | + builder => { |
| 192 | + return builder |
| 193 | + .css({ |
| 194 | + color: 'pink' |
| 195 | + }) |
| 196 | + |
| 197 | + // The nice trick with `if` is that this variant can be automatically |
| 198 | + // "skipped" in compound variants if |
| 199 | + .variant('_color', props._color || 'default', { |
| 200 | + // ... |
| 201 | + } |
| 202 | + |
| 203 | + .end() |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + // The nice trick with `if` is that variants will be automatically "skipped" |
| 208 | + // from compound variants when being disabled. |
| 209 | + // So your compount variants dont need to have any logic to apply or not |
| 210 | + // internal variants, you can conditionnate them outside the composition. |
| 211 | + .if( |
| 212 | + false, |
| 213 | + builder => { |
| 214 | + return builder |
| 215 | + .variant('_color', props._color || 'default', { |
| 216 | + // ... |
| 217 | + } |
| 218 | + |
| 219 | + .end() |
| 220 | + } |
| 221 | + } |
| 222 | + |
| 223 | + // And finally, for various reason, you want to override the color defined |
| 224 | + // previously. |
| 225 | + // So you can use the `weight` option to ponderate your CSS definition(s). |
| 226 | + // Weight is also available for variant(s), compoundVariant(s) and if blocks. |
| 227 | + css({ |
| 228 | + color: 'lime' |
| 229 | + }, { |
| 230 | + // By default, weight is 0 when not set. So here, color:lime will be applied last, |
| 231 | + // overriding previous definitions blocks. |
| 232 | + weight: 10 |
| 233 | + }) |
| 234 | + |
| 235 | + // If you have some issues and unexpected CSS applied, you may want debug things |
| 236 | + // so you can use `debug()` function that will log props, variants, CSS parts and |
| 237 | + // final merged CSS object. |
| 238 | + .debug() |
| 239 | + |
| 240 | + // Finally, merge all CSS definitions and variants. |
| 241 | + // End function will return a CSS object. |
| 242 | + .end() |
| 243 | +}) |
| 244 | + |
| 245 | +// Create a component and render a "primary" button. |
| 246 | +function ButtonComponent() { |
| 247 | + return ( |
| 248 | + <Div type="primary"> |
| 249 | + <button>Button</button> |
| 250 | + </Div> |
| 251 | + ) |
| 252 | +} |
| 253 | + |
| 254 | +/* CSS will be: |
| 255 | +
|
| 256 | +{ |
| 257 | + // defined in the first block |
| 258 | + '> button': { |
| 259 | + all: 'unset' |
| 260 | + }, |
| 261 | +
|
| 262 | + // get from the primary background variant, it will override the background set in the second CSS block |
| 263 | + background: 'blue', |
| 264 | +
|
| 265 | + // get from the primary font variant, two variants applyed at the same time |
| 266 | + fontWeight: 'bold', |
| 267 | + fontStyle: 'italic' |
| 268 | +
|
| 269 | + // finally, the color will be lime, because the weight option overrides the color defined in the primary variant. |
| 270 | + color: 'lime' |
| 271 | +} |
| 272 | +
|
| 273 | +*/ |
| 274 | +``` |
| 275 | +
|
| 276 | +Have fun building variants! :) |
0 commit comments