@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 +293 -21
- package/dist/index.d.mts +711 -102
- package/dist/index.d.ts +711 -102
- package/dist/index.js +919 -142
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +914 -138
- package/dist/index.mjs.map +1 -1
- package/package.json +24 -5
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)
|
|
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
|
-
```
|
|
13
|
+
```tsx
|
|
13
14
|
import {css} from "@mochi-css/vanilla"
|
|
14
15
|
|
|
15
16
|
const buttonStyles = css({
|
|
16
17
|
borderRadius: 10,
|
|
17
|
-
border: "
|
|
18
|
+
border: "2px solid red"
|
|
18
19
|
})
|
|
19
20
|
|
|
20
|
-
|
|
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: "
|
|
34
|
+
border: "2px solid red"
|
|
33
35
|
})
|
|
34
36
|
```
|
|
35
37
|
|
|
36
38
|
### `styled(component, ...styles)`
|
|
37
39
|
|
|
38
|
-
`styled`
|
|
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: "
|
|
47
|
+
border: "2px solid red"
|
|
46
48
|
})
|
|
47
49
|
```
|
|
48
50
|
|
|
49
51
|
## Style definitions
|
|
50
52
|
|
|
51
|
-
|
|
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
|
|
55
|
-
* any number of
|
|
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
|
-
|
|
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
|
|
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
|
|
80
|
-
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
+
})
|