next-style 1.1.1 → 1.1.3

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,227 +1,303 @@
1
- # NextStyle
1
+ # next-style
2
2
 
3
- A lightweight runtime CSS-in-JS engine designed for React and Next.js.
4
- It supports nested pseudo selectors, responsive media queries, and automatic vendor prefixing via PostCSS — all at runtime with zero build-time CSS extraction.
3
+ **next-style** is a lightweight runtime CSS-in-JS engine designed for React, Next.js, and Bun.
5
4
 
6
- NextStyle focuses on **runtime flexibility**, **type safety**, and **minimal abstraction**, making it suitable for design systems, theming, and dynamic UI styling.
5
+ It is intentionally designed to be **page-scoped and component-scoped**, ensuring predictable
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.
9
+
10
+ ---
11
+
12
+ ## Core Design Principles
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
20
+
21
+ Because of this design:
22
+ - `new NextStyle()` must be created inside the page or component scope
23
+ - Styles must be injected manually using `ns.StyleText`
7
24
 
8
25
  ---
9
26
 
10
27
  ## Features
11
28
 
12
- - Runtime CSS generation
13
- - Strongly typed style objects (powered by `csstype`)
29
+ - Object-based styling with strong TypeScript support
30
+ - Deterministic hashing (same styles always produce the same class name)
14
31
  - Nested pseudo selectors (`_hover`, `_focus`, `_active`)
15
- - Responsive media queries (`_sm`, `_md`, `_lg`, `_xl`, `_xxl`)
16
- - Automatic vendor prefixing (PostCSS + Autoprefixer)
17
- - Deterministic hashed class names
18
- - React `<style>` provider component
19
- - No bundler or build-step CSS required
32
+ - Built-in responsive media queries
33
+ - Page-scoped global styles
34
+ - `@keyframes` support
35
+ - `@font-face` support
36
+ - PostCSS + Autoprefixer integration
37
+ - Automatic rule deduplication (per instance)
38
+ - Single `<style>` injection per page or component
39
+ - No side effects (`sideEffects: false`)
20
40
 
21
41
  ---
22
42
 
23
43
  ## Installation
24
44
 
25
- Install the package along with its peer dependencies:
45
+ ### npm
26
46
 
27
- ```bash
28
- npm install next-style postcss autoprefixer react
47
+ ```sh
48
+ npm install next-style
29
49
  ```
30
50
 
31
- or with Bun:
51
+ ### Bun
32
52
 
33
- ```bash
34
- bun add next-style postcss autoprefixer react
53
+ ```sh
54
+ bun add next-style
35
55
  ```
36
56
 
37
57
  ---
38
58
 
39
- ## Basic Usage
59
+ ## Peer Dependencies
40
60
 
41
- ```tsx
42
- import { NextStyle } from "next-style"
61
+ For most **Next.js applications**, you **do not need to install these manually**.
43
62
 
44
- const style = new NextStyle('home') // output className = home_{hash} default "next_{hash}"
63
+ - **React** is already included with Next.js
64
+ - **PostCSS** and **Autoprefixer** are bundled and used internally by Next.js
45
65
 
46
- const className = style.css({
47
- display: "flex",
48
- alignItems: "center",
49
- padding: "12px",
50
- backgroundColor: "#111",
51
- color: "white",
52
- _hover: {
53
- backgroundColor: "#222"
54
- },
55
- _md: {
56
- padding: "16px"
57
- }
58
- })
66
+ This section mainly applies if you are using **next-style outside of Next.js**, such as:
67
+ - Custom React setups
68
+ - Bun + React
69
+ - Vite or other non-Next runtimes
70
+
71
+ If required, install them manually:
72
+
73
+ ```sh
74
+ npm install react postcss autoprefixer
59
75
  ```
60
76
 
61
- Apply the generated class name to your component:
77
+ ---
78
+
79
+ ## Basic Usage (Page Scoped)
62
80
 
63
- ```tsx
64
- export default function App() {
81
+ Create a `NextStyle` instance **inside the page or component**.
82
+ You may optionally provide a **custom prefix** to control generated class names.
83
+
84
+ ```ts
85
+ import { NextStyle } from "next-style"
86
+
87
+ export default function Page() {
88
+ const ns = new NextStyle("home")
89
+ const btn = ns.css({
90
+ padding: "8px 16px",
91
+ backgroundColor: "black",
92
+ color: "white",
93
+ borderRadius: "6px"
94
+ })
65
95
  return (
66
96
  <>
67
- <div className={className}>
68
- Hello NextStyle
69
- </div>
70
- <style.Provider />
97
+ <style>{ ns.StyleText }</style>
98
+ <button className={ btn }>Click me</button>
71
99
  </>
72
100
  )
73
101
  }
74
102
  ```
75
103
 
76
- ---
104
+ Generated class names will look like:
77
105
 
78
- ## Style Object API
79
-
80
- ### Base Properties
81
-
82
- All standard CSS properties are supported and fully typed:
83
-
84
- ```ts
85
- {
86
- margin: "8px",
87
- fontSize: "14px",
88
- backgroundColor: "#000"
89
- }
106
+ ```txt
107
+ home_ab12cd3
90
108
  ```
91
109
 
92
- Types are derived from `csstype`, ensuring correctness and editor autocomplete.
110
+ Notes:
111
+ - The prefix is optional
112
+ - If omitted, a default prefix is used
113
+ - Prefixes help identify styles per page or component
114
+ - Each page or component should create its own `NextStyle` instance
93
115
 
94
116
  ---
95
117
 
96
- ### Pseudo Selectors
118
+ ## Pseudo Selectors
97
119
 
98
- Use underscored keys for pseudo selectors:
120
+ Pseudo selectors are defined using keys prefixed with `_`.
99
121
 
100
122
  ```ts
101
- {
123
+ const card = ns.css({
124
+ backgroundColor: "#fff",
125
+ transition: "0.2s ease",
102
126
  _hover: {
103
- opacity: 0.8
104
- },
105
- _focus: {
106
- outline: "2px solid blue"
127
+ backgroundColor: "#f5f5f5"
107
128
  },
108
129
  _active: {
109
130
  transform: "scale(0.98)"
110
131
  }
111
- }
132
+ })
112
133
  ```
113
134
 
114
- Supported pseudos:
135
+ Supported pseudo selectors:
115
136
  - `_hover`
116
137
  - `_focus`
117
138
  - `_active`
118
139
 
119
140
  ---
120
141
 
121
- ### Responsive Media Queries
142
+ ## Responsive Styles (Media Queries)
122
143
 
123
- NextStyle provides built-in responsive keys:
144
+ Built-in breakpoints:
124
145
 
125
- | Key | Media Query |
126
- |------|-------------|
127
- | `_sm` | `(min-width: 640px)` |
128
- | `_md` | `(min-width: 768px)` |
129
- | `_lg` | `(min-width: 1024px)` |
130
- | `_xl` | `(min-width: 1280px)` |
131
- | `_xxl` | `(min-width: 1536px)` |
132
-
133
- Example:
146
+ - `_sm` min-width: 640px
147
+ - `_md` → min-width: 768px
148
+ - `_lg` min-width: 1024px
149
+ - `_xl` min-width: 1280px
150
+ - `_xxl` min-width: 1536px
134
151
 
135
152
  ```ts
136
- {
137
- fontSize: "14px",
153
+ const box = ns.css({
154
+ width: 100,
155
+ _md: {
156
+ width: 200
157
+ },
138
158
  _lg: {
139
- fontSize: "18px"
159
+ width: 300
140
160
  }
141
- }
161
+ })
142
162
  ```
143
163
 
164
+ Media queries can be nested and are automatically merged.
165
+
144
166
  ---
145
167
 
146
- ## `<style.Provider />`
168
+ ## Global Styles (Page Scoped)
147
169
 
148
- The `Provider` component injects all generated CSS rules into a `<style>` tag.
170
+ Global styles are scoped to the current page or component instance.
149
171
 
150
- Important notes:
172
+ ```ts
173
+ ns.global("body", {
174
+ margin: 0,
175
+ fontFamily: "Inter, sans-serif"
176
+ })
177
+ ```
151
178
 
152
- - It must be rendered **once per style instance**
153
- - It should be rendered **after** calling `css(...)`
154
- - It performs no side effects if no styles were generated
179
+ These styles exist only for the lifetime of the page or component.
155
180
 
156
- ```tsx
157
- <style.Provider />
181
+ ---
182
+
183
+ ## Keyframes
184
+
185
+ Create animations using `keyframes()`:
186
+
187
+ ```ts
188
+ const fadeIn = ns.keyframes({
189
+ from: { opacity: 0 },
190
+ to: { opacity: 1 }
191
+ })
192
+ ```
193
+
194
+ Use the animation in styles:
195
+
196
+ ```ts
197
+ const modal = ns.css({
198
+ animation: `${ fadeIn } 0.3s ease-out`
199
+ })
158
200
  ```
159
201
 
202
+ Keyframes are scoped to the current `NextStyle` instance.
203
+
160
204
  ---
161
205
 
162
- ## Hashing Strategy
206
+ ## Font Face
163
207
 
164
- Class names are generated using a deterministic hash based on the style object:
208
+ Declare fonts using `fontFace()`:
165
209
 
166
- ```text
167
- next_x9k3a2m1
210
+ ```ts
211
+ ns.fontFace({
212
+ fontFamily: "Inter",
213
+ src: "url(/fonts/inter.woff2) format('woff2')",
214
+ fontWeight: 400,
215
+ fontStyle: "normal",
216
+ fontDisplay: "swap"
217
+ })
168
218
  ```
169
219
 
170
- This ensures:
171
- - Stable class names
172
- - No duplicates
173
- - Automatic caching of generated rules
220
+ Font-face rules are injected only for the current page or component.
174
221
 
175
222
  ---
176
223
 
177
- ## Runtime Behavior
224
+ ## Deterministic Hashing
178
225
 
179
- - CSS is generated lazily on first usage
180
- - Rules are cached in memory
181
- - PostCSS transformation runs only once per unique rule
182
- - No DOM mutation outside React rendering
226
+ - Styles are hashed using a stable algorithm
227
+ - Object keys are sorted before hashing
228
+ - Semantically identical styles always produce the same class name
229
+ - Prevents unnecessary class regeneration
183
230
 
184
231
  ---
185
232
 
186
- ## Type Safety
233
+ ## Server-Side Rendering (SSR)
187
234
 
188
- NextStyle provides full TypeScript support:
235
+ `next-style` is SSR-safe.
189
236
 
190
- - All CSS properties are typed
191
- - Invalid properties are caught at compile time
192
- - Media and pseudo keys are strictly controlled
237
+ Because the core API only generates strings:
238
+ - No JSX is exported from the library
239
+ - No React runtime is required
240
+ - No side effects occur during render
193
241
 
194
- Types are generated via `.d.ts` and require no runtime dependency on `csstype`.
242
+ You can safely inject styles during SSR:
243
+
244
+ ```tsx
245
+ <style>{ ns.StyleText }</style>
246
+ ```
247
+
248
+ The same output will be produced on both the server and the client.
195
249
 
196
250
  ---
197
251
 
198
- ## Requirements
252
+ ## Performance Characteristics
199
253
 
200
- ### Peer Dependencies
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
201
258
 
202
- - React >= 18
203
- - PostCSS >= 8
204
- - Autoprefixer >= 10
259
+ Ideal for:
260
+ - Next.js App Router
261
+ - Page-level isolation
262
+ - Component-driven design systems
263
+
264
+ ---
205
265
 
206
- ### Runtime Environment
266
+ ## Common Gotchas
207
267
 
208
- - Node.js >= 18
209
- - Bun >= 1.0.0
268
+ - Do not create a shared `NextStyle` instance across pages
269
+ - Do not treat `next-style` as a global style manager
270
+ - Always inject `ns.StyleText` before elements that use generated class names
271
+ - Avoid calling `ns.css()` conditionally with different order between renders
210
272
 
211
273
  ---
212
274
 
213
- ## License
275
+ ## Package Information
214
276
 
215
- MIT © kingslimes
277
+ - Name: `next-style`
278
+ - Version: `1.1.2`
279
+ - License: MIT
280
+ - Module type: ESM
281
+ - Side effects: false
282
+
283
+ Repository:
284
+ https://github.com/kingslimes/next-style
285
+
286
+ Issues:
287
+ https://github.com/kingslimes/next-style/issues
216
288
 
217
289
  ---
218
290
 
219
- ## Philosophy
291
+ ## Roadmap
220
292
 
221
- NextStyle is intentionally minimal.
293
+ - `&` selector nesting
294
+ - `_dark` / `_light` helpers
295
+ - `@layer` support
296
+ - Optional React helpers
297
+ - Dev-time warnings for incorrect usage
222
298
 
223
- - No build-time CSS extraction
224
- - No global runtime side effects
225
- - No magic conventions
299
+ ---
300
+
301
+ ## License
226
302
 
227
- Just predictable runtime styling with strong types and explicit behavior.
303
+ MIT © kingslimes
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
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)}};get StyleText(){if(this.rules.size===0)return null;let cssText="";for(let rule of this.rules.values())cssText+=rule+`
2
+ `;return cssText}}export{NextStyle};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-style",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
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",
@@ -16,22 +16,22 @@
16
16
  "LICENSE",
17
17
  "README.md"
18
18
  ],
19
- "main": "./dist/index.jsx",
19
+ "main": "./dist/index.js",
20
20
  "types": "./dist/index.d.ts",
21
21
  "exports": {
22
22
  ".": {
23
- "import": "./dist/index.jsx",
23
+ "import": "./dist/index.js",
24
24
  "types": "./dist/index.d.ts"
25
25
  }
26
26
  },
27
27
  "repository": {
28
28
  "type": "git",
29
- "url": "https://github.com/kingslimes/zed-style.git"
29
+ "url": "https://github.com/kingslimes/next-style.git"
30
30
  },
31
31
  "bugs": {
32
- "url": "https://github.com/kingslimes/zed-style/issues"
32
+ "url": "https://github.com/kingslimes/next-style/issues"
33
33
  },
34
- "homepage": "https://github.com/kingslimes/zed-style#readme",
34
+ "homepage": "https://github.com/kingslimes/next-style#readme",
35
35
  "keywords": [
36
36
  "css",
37
37
  "css-in-js",
@@ -52,15 +52,16 @@
52
52
  "font-face"
53
53
  ],
54
54
  "peerDependencies": {
55
- "react": ">=18",
56
55
  "postcss": "^8",
57
56
  "autoprefixer": "^10"
58
57
  },
59
58
  "devDependencies": {
60
- "csstype": "^3"
59
+ "csstype": "latest"
61
60
  },
62
61
  "engines": {
63
- "bun": ">=1.0.0",
64
62
  "node": ">=18"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public"
65
66
  }
66
67
  }
package/dist/index.d.ts DELETED
@@ -1,22 +0,0 @@
1
- import type { Properties } from "csstype";
2
- export type NextStyleProperties = {
3
- [K in keyof Properties<string | number>]?: Properties<string | number>[K];
4
- } & {
5
- _hover?: NextStyleObject;
6
- _focus?: NextStyleObject;
7
- _active?: NextStyleObject;
8
- _sm?: NextStyleObject;
9
- _md?: NextStyleObject;
10
- _lg?: NextStyleObject;
11
- _xl?: NextStyleObject;
12
- _xxl?: NextStyleObject;
13
- };
14
- type NextStyleObject = Omit<NextStyleProperties, "_sm" | "_md" | "_lg" | "_xl" | "_xxl">;
15
- export declare class NextStyle {
16
- private prefix;
17
- private rules;
18
- constructor(prefix?: string);
19
- css: (style: NextStyleProperties) => string;
20
- Provider: () => import("react").JSX.Element | null;
21
- }
22
- export {};
package/dist/index.jsx DELETED
@@ -1,92 +0,0 @@
1
- import postcss from "postcss";
2
- import autoprefixer from "autoprefixer";
3
- const processor = postcss([autoprefixer({
4
- overrideBrowserslist: [">0.2%", "not dead", "not op_mini all"]
5
- })]);
6
- const postcssCache = new Map();
7
- function postcssTransform(cssText) {
8
- const cached = postcssCache.get(cssText);
9
- if (cached) return cached;
10
- const result = processor.process(cssText, {
11
- from: undefined
12
- }).css;
13
- postcssCache.set(cssText, result);
14
- return result;
15
- }
16
- function createHashName(seed) {
17
- let hash = BigInt("0xcbf29ce484222325");
18
- const prime = BigInt("0x100000001b3");
19
- for (let i = 0; i < seed.length; i++) {
20
- hash ^= BigInt(seed.charCodeAt(i));
21
- hash *= prime;
22
- hash &= BigInt("0xffffffffffffffff");
23
- }
24
- return hash.toString(36).slice(0, 9);
25
- }
26
- function toKebabCase(prop) {
27
- return prop.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
28
- }
29
- const MEDIA_MAP = {
30
- _sm: "( min-width: 640px )",
31
- _md: "( min-width: 768px )",
32
- _lg: "( min-width: 1024px )",
33
- _xl: "( min-width: 1280px )",
34
- _xxl: "( min-width: 1536px )"
35
- };
36
- function serializeNested(style, ctx) {
37
- let css = "";
38
- let base = "";
39
- for (const key in style) {
40
- const value = style[key];
41
- if (value == null || typeof value === "object" || key.startsWith("_")) continue;
42
- base += `${toKebabCase(key)}:${value};`;
43
- }
44
- if (base) {
45
- const rule = `${ctx.selector}{${base}}`;
46
- css += ctx.media ? `${ctx.media}{${rule}}` : rule;
47
- }
48
- for (const pseudo of ["_hover", "_focus", "_active"]) {
49
- const value = style[pseudo];
50
- if (!value) continue;
51
- css += serializeNested(value, {
52
- ...ctx,
53
- selector: `${ctx.selector}:${pseudo.slice(1)}`
54
- });
55
- }
56
- for (const key in MEDIA_MAP) {
57
- const mediaKey = key;
58
- const value = style[mediaKey];
59
- if (!value) continue;
60
- css += serializeNested(value, {
61
- ...ctx,
62
- media: `@media ${MEDIA_MAP[mediaKey]}`
63
- });
64
- }
65
- return css;
66
- }
67
- export class NextStyle {
68
- rules = new Map();
69
- constructor(prefix = "next") {
70
- this.prefix = prefix;
71
- }
72
- css = style => {
73
- const hash = createHashName(JSON.stringify(style));
74
- const className = `${this.prefix}_${hash}`;
75
- if (!this.rules.has(className)) {
76
- const raw = serializeNested(style, {
77
- selector: `.${className}`
78
- });
79
- const cssText = postcssTransform(raw);
80
- this.rules.set(className, cssText);
81
- }
82
- return className;
83
- };
84
- Provider = () => {
85
- if (this.rules.size === 0) return null;
86
- let cssText = "";
87
- for (const rule of this.rules.values()) {
88
- cssText += rule + "\n";
89
- }
90
- return <style>{cssText}</style>;
91
- };
92
- }