next-style 1.1.4 → 1.1.6
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 +399 -170
- package/dist/index.d.ts +66 -1
- package/dist/index.js +3 -2
- package/package.json +3 -5
package/README.md
CHANGED
|
@@ -1,56 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# NextStyle
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A lightweight **runtime CSS-in-JS engine** for React with deterministic class names, nested pseudo selectors, media queries, global styles, keyframes, and font-face support.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
style isolation with zero global leakage between pages or routes.
|
|
7
|
-
|
|
8
|
-
The library is framework-agnostic at its core and does not depend on React internally.
|
|
5
|
+
Designed for **page-scoped and component-scoped usage** without build-time tooling.
|
|
9
6
|
|
|
10
7
|
---
|
|
11
8
|
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
- Styles are scoped to a **single page or component**
|
|
15
|
-
- No global singleton style registry
|
|
16
|
-
- No cross-page or cross-route CSS leakage
|
|
17
|
-
- Each page owns its own styles
|
|
18
|
-
- Explicit style injection via a `<style>` tag
|
|
19
|
-
- No implicit DOM side effects
|
|
9
|
+
## Package Information
|
|
20
10
|
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
11
|
+
- **Name:** next-style
|
|
12
|
+
- **Version:** 1.1.5
|
|
13
|
+
- **License:** MIT
|
|
14
|
+
- **Author:** kingslimes
|
|
15
|
+
https://github.com/kingslimes
|
|
16
|
+
- **Repository:**
|
|
17
|
+
https://github.com/kingslimes/next-style
|
|
18
|
+
- **Issue Tracker:**
|
|
19
|
+
https://github.com/kingslimes/next-style/issues
|
|
24
20
|
|
|
25
21
|
---
|
|
26
22
|
|
|
27
23
|
## Features
|
|
28
24
|
|
|
29
|
-
- Object-based styling
|
|
30
|
-
- Deterministic
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
25
|
+
- Object-based styling (TypeScript friendly)
|
|
26
|
+
- Deterministic class names (same style → same class)
|
|
27
|
+
- Pseudo selectors (`:hover`, `:focus`, `:active`)
|
|
28
|
+
- Responsive media queries (`sm` → `xxl`)
|
|
29
|
+
- Global styles
|
|
34
30
|
- `@keyframes` support
|
|
35
31
|
- `@font-face` support
|
|
36
|
-
- PostCSS + Autoprefixer
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
32
|
+
- Built-in PostCSS + Autoprefixer
|
|
33
|
+
- Zero DOM dependency
|
|
34
|
+
- Tree-shakeable (`sideEffects: false`)
|
|
35
|
+
- Copy–paste friendly API
|
|
40
36
|
|
|
41
37
|
---
|
|
42
38
|
|
|
43
39
|
## Installation
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```sh
|
|
41
|
+
``` bash
|
|
48
42
|
npm install next-style
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
### Bun
|
|
52
|
-
|
|
53
|
-
```sh
|
|
43
|
+
# or
|
|
54
44
|
bun add next-style
|
|
55
45
|
```
|
|
56
46
|
|
|
@@ -58,243 +48,482 @@ bun add next-style
|
|
|
58
48
|
|
|
59
49
|
## Peer Dependencies
|
|
60
50
|
|
|
61
|
-
|
|
51
|
+
NextStyle relies on the following peer dependencies:
|
|
62
52
|
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
``` txt
|
|
54
|
+
react >= 18
|
|
55
|
+
postcss ^8
|
|
56
|
+
autoprefixer ^10
|
|
57
|
+
```
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- Vite or other non-Next runtimes
|
|
59
|
+
Make sure they are installed in your project.
|
|
60
|
+
|
|
61
|
+
---
|
|
70
62
|
|
|
71
|
-
|
|
63
|
+
## Recommended Usage Pattern (Scoped)
|
|
72
64
|
|
|
73
|
-
|
|
74
|
-
|
|
65
|
+
The **recommended and official pattern** is to scope styles per page or per component using destructuring.
|
|
66
|
+
|
|
67
|
+
``` ts
|
|
68
|
+
const { css, StyleProvider } = new NextStyle("home")
|
|
75
69
|
```
|
|
76
70
|
|
|
77
|
-
|
|
71
|
+
Why this works well:
|
|
72
|
+
- Clear scope ownership
|
|
73
|
+
- No global side effects
|
|
74
|
+
- Easy to copy and reuse
|
|
75
|
+
- Matches React’s mental model
|
|
78
76
|
|
|
79
|
-
|
|
77
|
+
---
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
You may optionally provide a **custom prefix** to control generated class names.
|
|
79
|
+
## Basic Example (Page Scoped)
|
|
83
80
|
|
|
84
|
-
```
|
|
81
|
+
``` tsx
|
|
85
82
|
import { NextStyle } from "next-style"
|
|
86
83
|
|
|
87
|
-
export default function
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
84
|
+
export default function HomePage() {
|
|
85
|
+
const { css, StyleProvider } = new NextStyle("home")
|
|
86
|
+
|
|
87
|
+
const title = css({
|
|
88
|
+
fontSize: "32px",
|
|
89
|
+
fontWeight: 700,
|
|
90
|
+
marginBottom: "16px"
|
|
94
91
|
})
|
|
92
|
+
|
|
93
|
+
const button = css({
|
|
94
|
+
padding: "10px 20px",
|
|
95
|
+
borderRadius: "8px",
|
|
96
|
+
backgroundColor: "#2563eb",
|
|
97
|
+
color: "#fff",
|
|
98
|
+
|
|
99
|
+
_hover: {
|
|
100
|
+
backgroundColor: "#1d4ed8"
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
95
104
|
return (
|
|
96
105
|
<>
|
|
97
|
-
<
|
|
98
|
-
<
|
|
106
|
+
<StyleProvider />
|
|
107
|
+
<h1 className={title}>Home</h1>
|
|
108
|
+
<button className={button}>Click me</button>
|
|
99
109
|
</>
|
|
100
110
|
)
|
|
101
111
|
}
|
|
102
112
|
```
|
|
103
113
|
|
|
104
|
-
|
|
114
|
+
---
|
|
105
115
|
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
## Styling API
|
|
117
|
+
|
|
118
|
+
### `css(style): string`
|
|
119
|
+
|
|
120
|
+
Creates a class name from a style object.
|
|
121
|
+
|
|
122
|
+
``` ts
|
|
123
|
+
const className = css({
|
|
124
|
+
color: "red",
|
|
125
|
+
fontSize: "16px"
|
|
126
|
+
})
|
|
108
127
|
```
|
|
109
128
|
|
|
110
|
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
- Prefixes help identify styles per page or component
|
|
114
|
-
- Each page or component should create its own `NextStyle` instance
|
|
129
|
+
- Automatically converts camelCase → kebab-case
|
|
130
|
+
- Deduplicates styles using hashing
|
|
131
|
+
- Returns a stable class name
|
|
115
132
|
|
|
116
133
|
---
|
|
117
134
|
|
|
118
135
|
## Pseudo Selectors
|
|
119
136
|
|
|
120
|
-
|
|
137
|
+
Supported pseudo keys:
|
|
138
|
+
|
|
139
|
+
| Key | CSS Output |
|
|
140
|
+
|----|-----------|
|
|
141
|
+
| `_hover` | `:hover` |
|
|
142
|
+
| `_focus` | `:focus` |
|
|
143
|
+
| `_active` | `:active` |
|
|
121
144
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
145
|
+
Example:
|
|
146
|
+
|
|
147
|
+
``` ts
|
|
148
|
+
css({
|
|
149
|
+
color: "black",
|
|
126
150
|
_hover: {
|
|
127
|
-
|
|
128
|
-
},
|
|
129
|
-
_active: {
|
|
130
|
-
transform: "scale(0.98)"
|
|
151
|
+
color: "red"
|
|
131
152
|
}
|
|
132
153
|
})
|
|
133
154
|
```
|
|
134
155
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Relation Selector API
|
|
159
|
+
|
|
160
|
+
`next-style` provides a **relation-based selector API** that allows you to define styles based on the state of one element affecting another element, without manually writing complex CSS selectors.
|
|
161
|
+
|
|
162
|
+
This API is designed to be:
|
|
163
|
+
- Declarative and readable
|
|
164
|
+
- Type-safe (no string selectors)
|
|
165
|
+
- Fully compatible with runtime CSS-in-JS
|
|
139
166
|
|
|
140
167
|
---
|
|
141
168
|
|
|
142
|
-
|
|
169
|
+
### Basic Usage
|
|
143
170
|
|
|
144
|
-
|
|
171
|
+
``` ts
|
|
172
|
+
const { css, when } = new NextStyle()
|
|
173
|
+
|
|
174
|
+
const container = css({})
|
|
175
|
+
const button = css({})
|
|
176
|
+
|
|
177
|
+
when(container)
|
|
178
|
+
.hover()
|
|
179
|
+
.adjacent(button, {
|
|
180
|
+
backgroundColor: "red"
|
|
181
|
+
})
|
|
182
|
+
```
|
|
183
|
+
Equivalent CSS:
|
|
145
184
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
-
|
|
149
|
-
|
|
150
|
-
|
|
185
|
+
``` css
|
|
186
|
+
.container:hover + .button {
|
|
187
|
+
background-color: red;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
151
192
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
193
|
+
## Supported States
|
|
194
|
+
|
|
195
|
+
You can define relationships based on the following pseudo states:
|
|
196
|
+
|
|
197
|
+
- `hover()` → `:hover`
|
|
198
|
+
- `focus()` → `:focus`
|
|
199
|
+
- `active()` → `:active`
|
|
200
|
+
|
|
201
|
+
Example:
|
|
202
|
+
|
|
203
|
+
``` ts
|
|
204
|
+
when(input)
|
|
205
|
+
.focus()
|
|
206
|
+
.sibling(label, {
|
|
207
|
+
color: "blue"
|
|
208
|
+
})
|
|
209
|
+
```
|
|
210
|
+
Equivalent CSS:
|
|
211
|
+
|
|
212
|
+
``` css
|
|
213
|
+
input:focus ~ label {
|
|
214
|
+
color: blue;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Supported Relationships
|
|
221
|
+
|
|
222
|
+
### Adjacent Sibling (`+`)
|
|
223
|
+
|
|
224
|
+
Applies styles to the **immediately following sibling**.
|
|
225
|
+
|
|
226
|
+
``` ts
|
|
227
|
+
when(div)
|
|
228
|
+
.hover()
|
|
229
|
+
.adjacent(p, {
|
|
230
|
+
color: "red"
|
|
231
|
+
})
|
|
232
|
+
```
|
|
233
|
+
CSS equivalent:
|
|
234
|
+
|
|
235
|
+
``` css
|
|
236
|
+
div:hover + p {
|
|
237
|
+
color: red;
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
### General Sibling (`~`)
|
|
244
|
+
|
|
245
|
+
Applies styles to **all following siblings**.
|
|
246
|
+
|
|
247
|
+
``` ts
|
|
248
|
+
when(div)
|
|
249
|
+
.hover()
|
|
250
|
+
.sibling(p, {
|
|
251
|
+
color: "red"
|
|
252
|
+
})
|
|
253
|
+
```
|
|
254
|
+
CSS equivalent:
|
|
255
|
+
|
|
256
|
+
``` css
|
|
257
|
+
div:hover ~ p {
|
|
258
|
+
color: red;
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### Child (`>`)
|
|
265
|
+
|
|
266
|
+
Applies styles to **direct children only**.
|
|
267
|
+
|
|
268
|
+
``` ts
|
|
269
|
+
when(card)
|
|
270
|
+
.hover()
|
|
271
|
+
.child(icon, {
|
|
272
|
+
transform: "scale(1.1)"
|
|
273
|
+
})
|
|
274
|
+
```
|
|
275
|
+
CSS equivalent:
|
|
276
|
+
|
|
277
|
+
``` css
|
|
278
|
+
card:hover > icon {
|
|
279
|
+
transform: scale(1.1);
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
### Descendant (space)
|
|
286
|
+
|
|
287
|
+
Applies styles to **any nested element**.
|
|
288
|
+
|
|
289
|
+
``` ts
|
|
290
|
+
when(menu)
|
|
291
|
+
.hover()
|
|
292
|
+
.descendant(item, {
|
|
293
|
+
backgroundColor: "#eee"
|
|
294
|
+
})
|
|
295
|
+
```
|
|
296
|
+
CSS equivalent:
|
|
297
|
+
|
|
298
|
+
``` css
|
|
299
|
+
menu:hover item {
|
|
300
|
+
background-color: #eee;
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Why Use `when()` Instead of `global()`?
|
|
307
|
+
|
|
308
|
+
While `global()` allows full control over raw selectors, `when()` provides:
|
|
309
|
+
|
|
310
|
+
- Clear intent and better readability
|
|
311
|
+
- No need to manually concatenate class names
|
|
312
|
+
- Safer refactoring (class tokens, not strings)
|
|
313
|
+
- IDE autocomplete and JSDoc support
|
|
314
|
+
|
|
315
|
+
Comparison:
|
|
316
|
+
|
|
317
|
+
``` ts
|
|
318
|
+
// global selector
|
|
319
|
+
global(`.${a}:hover + .${b}`, { color: "red" })
|
|
320
|
+
|
|
321
|
+
// relation API
|
|
322
|
+
when(a).hover().adjacent(b, { color: "red" })
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Notes
|
|
328
|
+
|
|
329
|
+
- `when()` works with class names generated by `css()`
|
|
330
|
+
- Relation rules are injected via the same internal pipeline as `global()`
|
|
331
|
+
- Media queries and nested styles are fully supported inside relation styles
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Example With Media Query
|
|
336
|
+
|
|
337
|
+
``` ts
|
|
338
|
+
when(container)
|
|
339
|
+
.hover()
|
|
340
|
+
.sibling(text, {
|
|
341
|
+
color: "red",
|
|
155
342
|
_md: {
|
|
156
|
-
|
|
157
|
-
}
|
|
343
|
+
color: "blue"
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
This API is intentionally minimal and composable, allowing you to express complex UI relationships without leaking CSS selector syntax into your application code.
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Responsive Media Queries
|
|
355
|
+
|
|
356
|
+
Built-in breakpoints:
|
|
357
|
+
|
|
358
|
+
| Key | Media Query |
|
|
359
|
+
|----|-------------|
|
|
360
|
+
| `_sm` | `(min-width: 640px)` |
|
|
361
|
+
| `_md` | `(min-width: 768px)` |
|
|
362
|
+
| `_lg` | `(min-width: 1024px)` |
|
|
363
|
+
| `_xl` | `(min-width: 1280px)` |
|
|
364
|
+
| `_xxl` | `(min-width: 1536px)` |
|
|
365
|
+
|
|
366
|
+
Example:
|
|
367
|
+
|
|
368
|
+
``` ts
|
|
369
|
+
css({
|
|
370
|
+
fontSize: "14px",
|
|
158
371
|
_lg: {
|
|
159
|
-
|
|
372
|
+
fontSize: "18px"
|
|
160
373
|
}
|
|
161
374
|
})
|
|
162
375
|
```
|
|
163
376
|
|
|
164
|
-
Media queries can be nested and
|
|
377
|
+
Media queries can be nested and merged automatically.
|
|
165
378
|
|
|
166
379
|
---
|
|
167
380
|
|
|
168
|
-
## Global Styles
|
|
381
|
+
## Global Styles
|
|
382
|
+
|
|
383
|
+
### `global(selector, style)`
|
|
384
|
+
|
|
385
|
+
Apply styles globally without generating a class.
|
|
169
386
|
|
|
170
|
-
|
|
387
|
+
``` ts
|
|
388
|
+
const { global, StyleProvider } = new NextStyle("global")
|
|
171
389
|
|
|
172
|
-
|
|
173
|
-
ns.global("body", {
|
|
390
|
+
global("body", {
|
|
174
391
|
margin: 0,
|
|
175
|
-
fontFamily: "
|
|
392
|
+
fontFamily: "system-ui"
|
|
176
393
|
})
|
|
177
|
-
```
|
|
178
394
|
|
|
179
|
-
|
|
395
|
+
global("a", {
|
|
396
|
+
color: "inherit",
|
|
397
|
+
_hover: {
|
|
398
|
+
textDecoration: "underline"
|
|
399
|
+
}
|
|
400
|
+
})
|
|
401
|
+
```
|
|
180
402
|
|
|
181
403
|
---
|
|
182
404
|
|
|
183
|
-
##
|
|
405
|
+
## Animations
|
|
406
|
+
|
|
407
|
+
### `keyframes(frames): string`
|
|
184
408
|
|
|
185
|
-
|
|
409
|
+
Creates a `@keyframes` rule and returns its name.
|
|
186
410
|
|
|
187
|
-
```ts
|
|
188
|
-
const fadeIn =
|
|
411
|
+
``` ts
|
|
412
|
+
const fadeIn = keyframes({
|
|
189
413
|
from: { opacity: 0 },
|
|
190
414
|
to: { opacity: 1 }
|
|
191
415
|
})
|
|
192
|
-
```
|
|
193
416
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
```ts
|
|
197
|
-
const modal = ns.css({
|
|
198
|
-
animation: `${ fadeIn } 0.3s ease-out`
|
|
417
|
+
css({
|
|
418
|
+
animation: `${fadeIn} 300ms ease-in`
|
|
199
419
|
})
|
|
200
420
|
```
|
|
201
421
|
|
|
202
|
-
Keyframes are scoped to the current `NextStyle` instance.
|
|
203
|
-
|
|
204
422
|
---
|
|
205
423
|
|
|
206
|
-
##
|
|
424
|
+
## Fonts
|
|
207
425
|
|
|
208
|
-
|
|
426
|
+
### `fontFace(font)`
|
|
209
427
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
428
|
+
Registers a `@font-face` rule.
|
|
429
|
+
|
|
430
|
+
``` ts
|
|
431
|
+
fontFace({
|
|
432
|
+
fontFamily: "MyFont",
|
|
433
|
+
src: "url(/fonts/myfont.woff2)",
|
|
214
434
|
fontWeight: 400,
|
|
215
435
|
fontStyle: "normal",
|
|
216
436
|
fontDisplay: "swap"
|
|
217
437
|
})
|
|
218
438
|
```
|
|
219
439
|
|
|
220
|
-
Font-face rules are injected only for the current page or component.
|
|
221
|
-
|
|
222
440
|
---
|
|
223
441
|
|
|
224
|
-
##
|
|
442
|
+
## Rendering Styles
|
|
225
443
|
|
|
226
|
-
|
|
227
|
-
- Object keys are sorted before hashing
|
|
228
|
-
- Semantically identical styles always produce the same class name
|
|
229
|
-
- Prevents unnecessary class regeneration
|
|
444
|
+
### `<StyleProvider />`
|
|
230
445
|
|
|
231
|
-
|
|
446
|
+
Injects all generated CSS into a `<style>` tag.
|
|
447
|
+
|
|
448
|
+
``` tsx
|
|
449
|
+
<>
|
|
450
|
+
<StyleProvider />
|
|
451
|
+
<App />
|
|
452
|
+
</>
|
|
453
|
+
```
|
|
232
454
|
|
|
233
|
-
|
|
455
|
+
- Returns `null` if no styles exist
|
|
456
|
+
- Should be rendered **once per scope**
|
|
234
457
|
|
|
235
|
-
|
|
458
|
+
---
|
|
236
459
|
|
|
237
|
-
|
|
238
|
-
- No JSX is exported from the library
|
|
239
|
-
- No React runtime is required
|
|
240
|
-
- No side effects occur during render
|
|
460
|
+
### `toTextCss(): string | null`
|
|
241
461
|
|
|
242
|
-
|
|
462
|
+
Returns all generated CSS as a string.
|
|
243
463
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
464
|
+
Useful for:
|
|
465
|
+
- Server-side rendering (SSR)
|
|
466
|
+
- Manual injection
|
|
467
|
+
- Debugging
|
|
247
468
|
|
|
248
|
-
|
|
469
|
+
``` ts
|
|
470
|
+
const cssText = toTextCss()
|
|
471
|
+
```
|
|
249
472
|
|
|
250
473
|
---
|
|
251
474
|
|
|
252
|
-
##
|
|
253
|
-
|
|
254
|
-
- CSS rules are generated and deduplicated per instance
|
|
255
|
-
- PostCSS and Autoprefixer results are cached
|
|
256
|
-
- Only one `<style>` tag is required per page or component
|
|
257
|
-
- No global runtime mutations
|
|
475
|
+
## Component Scoped Example
|
|
258
476
|
|
|
259
|
-
|
|
260
|
-
- Next.js App Router
|
|
261
|
-
- Page-level isolation
|
|
262
|
-
- Component-driven design systems
|
|
477
|
+
Reusable, self-contained component.
|
|
263
478
|
|
|
264
|
-
|
|
479
|
+
``` tsx
|
|
480
|
+
import { NextStyle } from "next-style"
|
|
265
481
|
|
|
266
|
-
|
|
482
|
+
export function Card({ title, children }) {
|
|
483
|
+
const { css, StyleProvider } = new NextStyle("card")
|
|
267
484
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
485
|
+
const root = css({
|
|
486
|
+
padding: "16px",
|
|
487
|
+
borderRadius: "12px",
|
|
488
|
+
backgroundColor: "#fff",
|
|
489
|
+
boxShadow: "0 10px 25px rgba(0,0,0,.1)"
|
|
490
|
+
})
|
|
272
491
|
|
|
273
|
-
|
|
492
|
+
const heading = css({
|
|
493
|
+
fontSize: "18px",
|
|
494
|
+
fontWeight: 600,
|
|
495
|
+
marginBottom: "8px"
|
|
496
|
+
})
|
|
274
497
|
|
|
275
|
-
|
|
498
|
+
return (
|
|
499
|
+
<>
|
|
500
|
+
<StyleProvider />
|
|
501
|
+
<div className={root}>
|
|
502
|
+
<div className={heading}>{title}</div>
|
|
503
|
+
{children}
|
|
504
|
+
</div>
|
|
505
|
+
</>
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
```
|
|
276
509
|
|
|
277
|
-
|
|
278
|
-
- Version: `1.1.2`
|
|
279
|
-
- License: MIT
|
|
280
|
-
- Module type: ESM
|
|
281
|
-
- Side effects: false
|
|
510
|
+
---
|
|
282
511
|
|
|
283
|
-
|
|
284
|
-
https://github.com/kingslimes/next-style
|
|
512
|
+
## Best Practices
|
|
285
513
|
|
|
286
|
-
|
|
287
|
-
|
|
514
|
+
- Create **one NextStyle instance per page or component**
|
|
515
|
+
- Do **not** share instances globally
|
|
516
|
+
- Render `StyleProvider` only once per scope
|
|
517
|
+
- Use meaningful prefixes (`home`, `card`, `profile`)
|
|
288
518
|
|
|
289
519
|
---
|
|
290
520
|
|
|
291
|
-
##
|
|
521
|
+
## Design Intentions
|
|
292
522
|
|
|
293
|
-
-
|
|
294
|
-
-
|
|
295
|
-
-
|
|
296
|
-
-
|
|
297
|
-
- Dev-time warnings for incorrect usage
|
|
523
|
+
- No descendant selectors (`& > div`)
|
|
524
|
+
- No arbitrary selector nesting
|
|
525
|
+
- Predictable output over expressiveness
|
|
526
|
+
- Optimized for runtime and SSR safety
|
|
298
527
|
|
|
299
528
|
---
|
|
300
529
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Properties } from "csstype";
|
|
2
|
+
import type { DetailedReactHTMLElement } from "react";
|
|
2
3
|
export type NextStyleProperties = {
|
|
3
4
|
[K in keyof Properties<string | number>]?: Properties<string | number>[K];
|
|
4
5
|
} & {
|
|
@@ -23,14 +24,78 @@ type FontFaceObject = {
|
|
|
23
24
|
fontDisplay?: string;
|
|
24
25
|
unicodeRange?: string;
|
|
25
26
|
};
|
|
27
|
+
type PseudoState = "hover" | "focus" | "active";
|
|
28
|
+
declare class RelationBuilder {
|
|
29
|
+
private ns;
|
|
30
|
+
private source;
|
|
31
|
+
private pseudo?;
|
|
32
|
+
constructor(ns: NextStyle, source: string, pseudo?: PseudoState | undefined);
|
|
33
|
+
/** :hover */
|
|
34
|
+
hover(): RelationBuilder;
|
|
35
|
+
/** :focus */
|
|
36
|
+
focus(): RelationBuilder;
|
|
37
|
+
/** :active */
|
|
38
|
+
active(): RelationBuilder;
|
|
39
|
+
/**
|
|
40
|
+
* Adjacent sibling selector
|
|
41
|
+
*
|
|
42
|
+
* CSS:
|
|
43
|
+
* div:hover + p
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* when(div).hover().adjacent(p, { color: "red" })
|
|
47
|
+
*/
|
|
48
|
+
adjacent(target: string, style: NextStyleProperties): void;
|
|
49
|
+
/**
|
|
50
|
+
* Child selector
|
|
51
|
+
*
|
|
52
|
+
* CSS:
|
|
53
|
+
* div:hover > p
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* when(div).hover().child(p, { color: "red" })
|
|
57
|
+
*/
|
|
58
|
+
child(target: string, style: NextStyleProperties): void;
|
|
59
|
+
/**
|
|
60
|
+
* General sibling selector
|
|
61
|
+
*
|
|
62
|
+
* CSS:
|
|
63
|
+
* div:hover ~ p
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* when(div).hover().sibling(p, { color: "red" })
|
|
67
|
+
*/
|
|
68
|
+
sibling(target: string, style: NextStyleProperties): void;
|
|
69
|
+
/**
|
|
70
|
+
* Descendant selector
|
|
71
|
+
*
|
|
72
|
+
* CSS:
|
|
73
|
+
* div:hover p
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* when(div).hover().descendant(p, { color: "red" })
|
|
77
|
+
*/
|
|
78
|
+
descendant(target: string, style: NextStyleProperties): void;
|
|
79
|
+
private emit;
|
|
80
|
+
}
|
|
26
81
|
export declare class NextStyle {
|
|
27
82
|
private prefix;
|
|
28
83
|
private rules;
|
|
29
84
|
constructor(prefix?: string);
|
|
30
85
|
css: (style: NextStyleProperties) => string;
|
|
31
86
|
global: (selector: string, style: NextStyleProperties) => void;
|
|
87
|
+
/**
|
|
88
|
+
* Relation selector API
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* when(container).hover().sibling(text)
|
|
92
|
+
*/
|
|
93
|
+
when: (source: string) => RelationBuilder;
|
|
32
94
|
keyframes: (frames: KeyframesObject) => string;
|
|
33
95
|
fontFace: (font: FontFaceObject) => void;
|
|
34
|
-
|
|
96
|
+
toTextCss: () => string | null;
|
|
97
|
+
StyleProvider: () => DetailedReactHTMLElement<{
|
|
98
|
+
children: string;
|
|
99
|
+
}, HTMLStyleElement> | null;
|
|
35
100
|
}
|
|
36
101
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import postcss from"postcss";import autoprefixer from"autoprefixer";var processor=postcss([autoprefixer({overrideBrowserslist:[">0.2%","not dead","not op_mini all"]})]),postcssCache=new Map;function postcssTransform(cssText){let cached=postcssCache.get(cssText);if(cached)return cached;let result=processor.process(cssText,{from:void 0}).css;return postcssCache.set(cssText,result),result}function stableStringify(value){if(value==null||typeof value!=="object")return JSON.stringify(value);if(Array.isArray(value))return`[${value.map(stableStringify).join(",")}]`;return`{${Object.keys(value).sort().map((k)=>`"${k}":${stableStringify(value[k])}`).join(",")}}`}function createHashName(seed){let hash=BigInt("0xcbf29ce484222325"),prime=BigInt("0x100000001b3");for(let i=0;i<seed.length;i++)hash^=BigInt(seed.charCodeAt(i)),hash*=prime,hash&=BigInt("0xffffffffffffffff");return hash.toString(36).slice(0,9)}function toKebabCase(prop){return prop.replace(/[A-Z]/g,(m)=>`-${m.toLowerCase()}`)}var MEDIA_MAP={_sm:"(min-width:640px)",_md:"(min-width:768px)",_lg:"(min-width:1024px)",_xl:"(min-width:1280px)",_xxl:"(min-width:1536px)"};function mergeMedia(parent,current){if(!parent)return current;if(!current)return parent;return`${parent} and ${current}`}function serializeNested(style,ctx){let css="",base="";for(let key in style){let value=style[key];if(value==null||typeof value==="object"||key.startsWith("_"))continue;base+=`${toKebabCase(key)}:${value};`}if(base){let rule=`${ctx.selector}{${base}}`;css+=ctx.media?`@media ${ctx.media}{${rule}}`:rule}for(let pseudo of["_hover","_focus","_active"]){let value=style[pseudo];if(!value)continue;css+=serializeNested(value,{selector:`${ctx.selector}:${pseudo.slice(1)}`,media:ctx.media})}for(let key in MEDIA_MAP){let mediaKey=key,value=style[mediaKey];if(!value)continue;css+=serializeNested(value,{selector:ctx.selector,media:mergeMedia(ctx.media,MEDIA_MAP[mediaKey])})}return css}class NextStyle{prefix;rules=new Map;constructor(prefix="next"){this.prefix=prefix}css=(style)=>{let seed=stableStringify(style),hash=createHashName(seed),className=`${this.prefix}_${hash}`,key=`class:${className}`;if(!this.rules.has(key)){let raw=serializeNested(style,{selector:`.${className}`}),cssText=postcssTransform(raw);this.rules.set(key,cssText)}return className};global=(selector,style)=>{let key=`global:${selector}`;if(!this.rules.has(key)){let raw=serializeNested(style,{selector}),cssText=postcssTransform(raw);this.rules.set(key,cssText)}};keyframes=(frames)=>{let seed=stableStringify(frames),hash=createHashName(seed),name=`${this.prefix}_${hash}`,key=`@keyframes:${name}`;if(!this.rules.has(key)){let body="";for(let step in frames){let props="",frame=frames[step];for(let prop in frame)props+=`${toKebabCase(prop)}:${frame[prop]};`;body+=`${step}{${props}}`}let cssText=postcssTransform(`@keyframes ${name}{${body}}`);this.rules.set(key,cssText)}return name};fontFace=(font)=>{let seed=stableStringify(font),key=`@font-face:${createHashName(seed)}`;if(!this.rules.has(key)){let body="";for(let prop in font)body+=`${toKebabCase(prop)}:${font[prop]};`;let cssText=postcssTransform(`@font-face{${body}}`);this.rules.set(key,cssText)}};
|
|
2
|
-
`;return cssText}
|
|
1
|
+
import postcss from"postcss";import{createElement}from"react";import autoprefixer from"autoprefixer";var processor=postcss([autoprefixer({overrideBrowserslist:[">0.2%","not dead","not op_mini all"]})]),postcssCache=new Map;function postcssTransform(cssText){let cached=postcssCache.get(cssText);if(cached)return cached;let result=processor.process(cssText,{from:void 0}).css;return postcssCache.set(cssText,result),result}function stableStringify(value){if(value==null||typeof value!=="object")return JSON.stringify(value);if(Array.isArray(value))return`[${value.map(stableStringify).join(",")}]`;return`{${Object.keys(value).sort().map((k)=>`"${k}":${stableStringify(value[k])}`).join(",")}}`}function createHashName(seed){let hash=BigInt("0xcbf29ce484222325"),prime=BigInt("0x100000001b3");for(let i=0;i<seed.length;i++)hash^=BigInt(seed.charCodeAt(i)),hash*=prime,hash&=BigInt("0xffffffffffffffff");return hash.toString(36).slice(0,9)}function toKebabCase(prop){return prop.replace(/[A-Z]/g,(m)=>`-${m.toLowerCase()}`)}var MEDIA_MAP={_sm:"(min-width:640px)",_md:"(min-width:768px)",_lg:"(min-width:1024px)",_xl:"(min-width:1280px)",_xxl:"(min-width:1536px)"};function mergeMedia(parent,current){if(!parent)return current;if(!current)return parent;return`${parent} and ${current}`}function serializeNested(style,ctx){let css="",base="";for(let key in style){let value=style[key];if(value==null||typeof value==="object"||key.startsWith("_"))continue;base+=`${toKebabCase(key)}:${value};`}if(base){let rule=`${ctx.selector}{${base}}`;css+=ctx.media?`@media ${ctx.media}{${rule}}`:rule}for(let pseudo of["_hover","_focus","_active"]){let value=style[pseudo];if(!value)continue;css+=serializeNested(value,{selector:`${ctx.selector}:${pseudo.slice(1)}`,media:ctx.media})}for(let key in MEDIA_MAP){let mediaKey=key,value=style[mediaKey];if(!value)continue;css+=serializeNested(value,{selector:ctx.selector,media:mergeMedia(ctx.media,MEDIA_MAP[mediaKey])})}return css}class RelationBuilder{ns;source;pseudo;constructor(ns,source,pseudo){this.ns=ns;this.source=source;this.pseudo=pseudo}hover(){return new RelationBuilder(this.ns,this.source,"hover")}focus(){return new RelationBuilder(this.ns,this.source,"focus")}active(){return new RelationBuilder(this.ns,this.source,"active")}adjacent(target,style){this.emit("+",target,style)}child(target,style){this.emit(">",target,style)}sibling(target,style){this.emit("~",target,style)}descendant(target,style){this.emit(" ",target,style)}emit(combinator,target,style){let pseudo=this.pseudo?`:${this.pseudo}`:"",selector=`.${this.source}${pseudo}${combinator}.${target}`;this.ns.global(selector,style)}}class NextStyle{prefix;rules=new Map;constructor(prefix="next"){this.prefix=prefix}css=(style)=>{let seed=stableStringify(style),hash=createHashName(seed),className=`${this.prefix}_${hash}`,key=`class:${className}`;if(!this.rules.has(key)){let raw=serializeNested(style,{selector:`.${className}`}),cssText=postcssTransform(raw);this.rules.set(key,cssText)}return className};global=(selector,style)=>{let key=`global:${selector}`;if(!this.rules.has(key)){let raw=serializeNested(style,{selector}),cssText=postcssTransform(raw);this.rules.set(key,cssText)}};when=(source)=>{return new RelationBuilder(this,source)};keyframes=(frames)=>{let seed=stableStringify(frames),hash=createHashName(seed),name=`${this.prefix}_${hash}`,key=`@keyframes:${name}`;if(!this.rules.has(key)){let body="";for(let step in frames){let props="",frame=frames[step];for(let prop in frame)props+=`${toKebabCase(prop)}:${frame[prop]};`;body+=`${step}{${props}}`}let cssText=postcssTransform(`@keyframes ${name}{${body}}`);this.rules.set(key,cssText)}return name};fontFace=(font)=>{let seed=stableStringify(font),key=`@font-face:${createHashName(seed)}`;if(!this.rules.has(key)){let body="";for(let prop in font)body+=`${toKebabCase(prop)}:${font[prop]};`;let cssText=postcssTransform(`@font-face{${body}}`);this.rules.set(key,cssText)}};toTextCss=()=>{if(this.rules.size===0)return null;let cssText="";for(let rule of this.rules.values())cssText+=rule+`
|
|
2
|
+
`;return cssText};StyleProvider=()=>{if(this.rules.size===0)return null;let cssText="";for(let rule of this.rules.values())cssText+=rule+`
|
|
3
|
+
`;return createElement("style",{children:cssText})}}export{NextStyle};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-style",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Lightweight runtime CSS-in-JS engine with nested pseudo selectors, media queries, global styles, keyframes, and font-face support.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "kingslimes",
|
|
@@ -52,16 +52,14 @@
|
|
|
52
52
|
"font-face"
|
|
53
53
|
],
|
|
54
54
|
"peerDependencies": {
|
|
55
|
+
"react": ">=18",
|
|
55
56
|
"postcss": "^8",
|
|
56
57
|
"autoprefixer": "^10"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
60
|
"csstype": "latest"
|
|
60
61
|
},
|
|
61
|
-
"engines": {
|
|
62
|
-
"node": ">=18"
|
|
63
|
-
},
|
|
64
62
|
"publishConfig": {
|
|
65
63
|
"access": "public"
|
|
66
64
|
}
|
|
67
|
-
}
|
|
65
|
+
}
|