@mochi-css/vanilla 0.0.3 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Mochi-CSS/vanilla
2
2
 
3
- This package is part of [Mochi-CSS project](https://github.com/Niikelion/mochi-css) that provides styling functions and type definitions.
3
+ This package is part of the [Mochi-CSS project](https://github.com/Niikelion/mochi-css).
4
+ It provides type-safe CSS-in-JS styling functions with static extraction support, allowing you to write styles in TypeScript that get extracted to plain CSS at build time.
4
5
 
5
6
  ## Functions
6
7
 
@@ -9,15 +10,16 @@ This package is part of [Mochi-CSS project](https://github.com/Niikelion/mochi-c
9
10
  `css` is the fundamental styling function of Mochi-CSS.
10
11
  It takes style definitions as parameters, marks them for static extraction and returns class names to apply on elements.
11
12
 
12
- ```ts
13
+ ```tsx
13
14
  import {css} from "@mochi-css/vanilla"
14
15
 
15
16
  const buttonStyles = css({
16
17
  borderRadius: 10,
17
- border: "10px solid red"
18
+ border: "2px solid red"
18
19
  })
19
20
 
20
- console.log(`${buttonStyles}`)
21
+ // Use in JSX
22
+ const button = <button className={buttonStyles}>Click me</button>
21
23
  ```
22
24
 
23
25
  Output of the `css` function is also a valid style definition, so you can split your styles:
@@ -29,38 +31,250 @@ const textStyles = css({
29
31
 
30
32
  const buttonStyles = css(textStyles, {
31
33
  borderRadius: 10,
32
- border: "10px solid red"
34
+ border: "2px solid red"
33
35
  })
34
36
  ```
35
37
 
36
38
  ### `styled(component, ...styles)`
37
39
 
38
- `styled` is just a simple wrapper around `css` that takes a component as the first argument and applies all the classnames for you.
40
+ `styled` creates a styled component by combining a base element or component with style definitions. It automatically applies the generated class names and forwards variant props.
39
41
 
40
42
  ```tsx
41
43
  import {styled} from "@mochi-css/vanilla"
42
44
 
43
45
  const Button = styled("button", {
44
46
  borderRadius: 10,
45
- border: "10px solid red"
47
+ border: "2px solid red"
46
48
  })
47
49
  ```
48
50
 
49
51
  ## Style definitions
50
52
 
51
- Now, that we know how to use our style definitions, we probably should learn how to write them, right?
52
- Style definition is either bundle of styles returned by `css` or object containing:
53
+ A style definition is either a bundle of styles returned by `css`, or an object containing:
53
54
 
54
- * any number of valid css properties converted to camelCase, like in reacts styles property
55
- * any number of css variable assignments, more on that later
55
+ * any number of valid CSS properties converted to camelCase, like in React's style property
56
+ * any number of CSS variable assignments (see [Tokens](#tokens))
56
57
  * optional variants definition
57
58
  * optional default variants definition
58
59
 
59
- In the future, nested css selectors and media queries will be available as parts of style definitions
60
+ ## Nested Selectors
61
+
62
+ Mochi-CSS supports nested selectors, allowing you to define styles for child elements, pseudo-classes, and pseudo-elements directly within your style definitions.
63
+
64
+ The `&` character represents the parent selector and must be included in every nested selector:
65
+
66
+ ```ts
67
+ import { css } from "@mochi-css/vanilla"
68
+
69
+ const buttonStyle = css({
70
+ backgroundColor: "blue",
71
+ color: "white",
72
+
73
+ // Pseudo-classes
74
+ "&:hover": {
75
+ backgroundColor: "darkblue"
76
+ },
77
+ "&:active": {
78
+ backgroundColor: "navy"
79
+ },
80
+ "&:disabled": {
81
+ backgroundColor: "gray",
82
+ cursor: "not-allowed"
83
+ },
84
+
85
+ // Pseudo-elements
86
+ "&::before": {
87
+ content: '""',
88
+ display: "block"
89
+ },
90
+
91
+ // Child selectors
92
+ "& > span": {
93
+ fontWeight: "bold"
94
+ },
95
+
96
+ // Descendant selectors
97
+ "& p": {
98
+ margin: 0
99
+ },
100
+
101
+ // Compound selectors (when element also has another class)
102
+ "&.active": {
103
+ borderColor: "green"
104
+ },
105
+
106
+ // Adjacent sibling
107
+ "& + &": {
108
+ marginTop: 8
109
+ }
110
+ })
111
+ ```
112
+
113
+ ### Selector Position
114
+
115
+ The `&` can appear anywhere in the selector, allowing for flexible parent-child relationships:
116
+
117
+ ```ts
118
+ const linkStyle = css({
119
+ color: "blue",
120
+
121
+ // Parent context: style this element when inside .dark-mode
122
+ ".dark-mode &": {
123
+ color: "lightblue"
124
+ },
125
+
126
+ // Multiple parent contexts
127
+ "nav &, footer &": {
128
+ textDecoration: "underline"
129
+ }
130
+ })
131
+ ```
132
+
133
+ ## Media Selectors
134
+
135
+ Media selectors allow you to apply styles conditionally based on viewport size, color scheme preferences, and other media features. Media selectors start with the `@` symbol and are compiled into CSS media queries:
136
+
137
+ ```ts
138
+ import { css } from "@mochi-css/vanilla"
139
+
140
+ const responsiveContainer = css({
141
+ display: "flex",
142
+ flexDirection: "row",
143
+ padding: 32,
144
+
145
+ // Responsive breakpoints
146
+ "@max-width: 768px": {
147
+ flexDirection: "column",
148
+ padding: 16
149
+ },
150
+
151
+ "@max-width: 480px": {
152
+ padding: 8
153
+ },
154
+
155
+ // Modern range syntax
156
+ "@width <= 1024px": {
157
+ gap: 16
158
+ },
159
+
160
+ // Color scheme preferences
161
+ "@prefers-color-scheme: dark": {
162
+ backgroundColor: "#1a1a1a",
163
+ color: "white"
164
+ },
165
+
166
+ // Reduced motion
167
+ "@prefers-reduced-motion: reduce": {
168
+ transition: "none"
169
+ }
170
+ })
171
+ ```
172
+
173
+ ### Combined Media Selectors
174
+
175
+ You can combine multiple media conditions using `and` and `not` operators:
176
+
177
+ ```ts
178
+ const responsiveLayout = css({
179
+ display: "grid",
180
+ gridTemplateColumns: "1fr",
181
+
182
+ // Screen media type with width condition
183
+ "@screen and (width > 1000px)": {
184
+ gridTemplateColumns: "1fr 1fr"
185
+ },
186
+
187
+ // Multiple conditions with 'and'
188
+ "@screen and (min-width: 768px) and (max-width: 1024px)": {
189
+ gridTemplateColumns: "1fr 1fr",
190
+ gap: 16
191
+ },
192
+
193
+ // Print styles
194
+ "@print": {
195
+ display: "block"
196
+ },
197
+
198
+ // Combining media type with preference
199
+ "@screen and (not (prefers-color-scheme: dark))": {
200
+ backgroundColor: "#121212"
201
+ }
202
+ })
203
+ ```
204
+
205
+ ### Combining Nested Selectors with Media Selectors
206
+
207
+ Nested selectors and media selectors can be combined for fine-grained control:
208
+
209
+ ```ts
210
+ const buttonStyle = css({
211
+ backgroundColor: "blue",
212
+
213
+ "&:hover": {
214
+ backgroundColor: "darkblue",
215
+
216
+ // Media selector inside nested selector
217
+ "@width <= 480px": {
218
+ // Disable hover effects on mobile (touch devices)
219
+ backgroundColor: "blue"
220
+ }
221
+ },
222
+
223
+ // Media selector with nested selectors inside
224
+ "@max-width: 768px": {
225
+ padding: 8,
226
+
227
+ "& > span": {
228
+ display: "none"
229
+ }
230
+ }
231
+ })
232
+ ```
233
+
234
+ ### Using with Variants
235
+
236
+ Nested selectors and media selectors work seamlessly inside variant definitions:
237
+
238
+ ```ts
239
+ const cardStyle = css({
240
+ padding: 16,
241
+ borderRadius: 8,
242
+
243
+ variants: {
244
+ size: {
245
+ small: {
246
+ padding: 8,
247
+ "@max-width: 480px": {
248
+ padding: 4
249
+ }
250
+ },
251
+ large: {
252
+ padding: 32,
253
+ "@max-width: 480px": {
254
+ padding: 16
255
+ }
256
+ }
257
+ },
258
+ interactive: {
259
+ true: {
260
+ cursor: "pointer",
261
+ "&:hover": {
262
+ transform: "translateY(-2px)"
263
+ }
264
+ },
265
+ false: {}
266
+ }
267
+ },
268
+ defaultVariants: {
269
+ size: "small",
270
+ interactive: false
271
+ }
272
+ })
273
+ ```
60
274
 
61
275
  ## Variants
62
276
 
63
- You may want to create many variants of a button, with a shared set of css:
277
+ You may want to create multiple variants of a button that share a common base style:
64
278
 
65
279
  ```ts
66
280
  import {css} from "@mochi-css/vanilla"
@@ -76,8 +290,8 @@ const redButtonStyle = css(baseButtonStyle, {
76
290
  })
77
291
  ```
78
292
 
79
- This works, but now, either you have to wrap your component manually and decide which style to apply or use different components.
80
- To make the code simpler and cleaner, Mochi-CSS allows you to specify variants inside the style definition, and even handles variant params for you!
293
+ This works, but requires you to either manually select which style to apply in your component logic, or create separate components for each variant.
294
+ Mochi-CSS allows you to define variants directly in your style definition and automatically generates the corresponding props for your component.
81
295
 
82
296
  ```tsx
83
297
  import {styled, css} from "@mochi-css/vanilla"
@@ -109,12 +323,13 @@ const SomeComponent = () => <div>
109
323
  </div>
110
324
  ```
111
325
 
112
- `defaultVariants` is optional, but we recommend that you specify defaults for all the variants.
326
+ `defaultVariants` is optional, but specifying defaults for all variants ensures predictable styling when variant props are omitted.
113
327
 
114
328
  ## Tokens
115
329
 
116
- To help with type safety, Mochi-CSS provides typed wrappers around concept of css variables.
117
- Variables can be created using `createToken<T>(name)` function:
330
+ Mochi-CSS provides typed wrappers around CSS variables to help with type safety. Tokens ensure that only valid values are assigned to your CSS variables.
331
+
332
+ Create tokens using the `createToken<T>(name)` function, where `T` is a CSS value type like `CssColorLike`, `CssLengthLike`, or `string`:
118
333
 
119
334
  ```ts
120
335
  import {createToken, css, CssColorLike} from "@mochi-css/vanilla"
@@ -128,10 +343,10 @@ const buttonStyle = css({
128
343
  variants: {
129
344
  variant: {
130
345
  primary: {
131
- [buttonColor]: primaryColor
346
+ [buttonColor.variable]: primaryColor
132
347
  },
133
348
  secondary: {
134
- [buttonColor]: secondaryColor
349
+ [buttonColor.variable]: secondaryColor
135
350
  }
136
351
  }
137
352
  },
@@ -141,4 +356,61 @@ const buttonStyle = css({
141
356
  })
142
357
  ```
143
358
 
144
- As you can see, tokens can be used as css values and as keys in style definition objects.
359
+ Tokens can be used in two ways:
360
+ - **As values**: Use the token directly (e.g., `backgroundColor: buttonColor`) to reference the CSS variable
361
+ - **As keys**: Use `token.variable` (e.g., `[buttonColor.variable]: primaryColor`) to assign a value to the CSS variable
362
+
363
+ ## Keyframes
364
+
365
+ The `keyframes` function lets you define CSS animations using the same type-safe syntax as style definitions.
366
+
367
+ ### Basic Usage
368
+
369
+ Define animation stops using `from`/`to` or percentage keys:
370
+
371
+ ```ts
372
+ import { keyframes, css } from "@mochi-css/vanilla"
373
+
374
+ const fadeIn = keyframes({
375
+ from: { opacity: 0 },
376
+ to: { opacity: 1 }
377
+ })
378
+
379
+ const fadeInStyle = css({
380
+ animation: `${fadeIn} 0.3s ease`
381
+ })
382
+ ```
383
+
384
+ ### Percentage Stops
385
+
386
+ For more control over animation timing, use percentage-based stops:
387
+
388
+ ```ts
389
+ const bounce = keyframes({
390
+ "0%": { transform: "translateY(0)" },
391
+ "50%": { transform: "translateY(-20px)" },
392
+ "100%": { transform: "translateY(0)" }
393
+ })
394
+
395
+ const bouncingElement = css({
396
+ animation: `${bounce} 1s ease-in-out infinite`
397
+ })
398
+ ```
399
+
400
+ ### Multiple Properties
401
+
402
+ Each stop can contain multiple CSS properties with auto-units:
403
+
404
+ ```ts
405
+ const grow = keyframes({
406
+ from: {
407
+ opacity: 0,
408
+ transform: "scale(0.5)",
409
+ fontSize: 12 // becomes 12px
410
+ },
411
+ to: {
412
+ opacity: 1,
413
+ transform: "scale(1)",
414
+ fontSize: 24 // becomes 24px
415
+ }
416
+ })