@renge-ui/react 1.0.0 → 1.0.2

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.
Files changed (2) hide show
  1. package/README.md +248 -29
  2. package/package.json +14 -5
package/README.md CHANGED
@@ -10,14 +10,14 @@ pnpm add @renge-ui/react
10
10
 
11
11
  ## Setup
12
12
 
13
- Wrap your app in `RengeProvider`. It injects the generated CSS variables into `<head>` automatically.
13
+ Wrap your app in `RengeProvider`. It injects the generated CSS variables into `<head>` automatically via `useInsertionEffect`.
14
14
 
15
15
  ```tsx
16
16
  import { RengeProvider } from '@renge-ui/react';
17
17
 
18
18
  function App() {
19
19
  return (
20
- <RengeProvider config={{ profile: 'ocean' }}>
20
+ <RengeProvider config={{ profile: 'ocean', mode: 'light' }}>
21
21
  <YourApp />
22
22
  </RengeProvider>
23
23
  );
@@ -37,13 +37,13 @@ function App() {
37
37
 
38
38
  ### `useRenge()`
39
39
 
40
- Returns the current theme and profile from context. Must be used inside `RengeProvider`.
40
+ Returns the current theme and profile from context. Must be inside `RengeProvider`.
41
41
 
42
42
  ```tsx
43
43
  const { theme, profile } = useRenge();
44
44
  // theme.vars['--renge-color-accent']
45
45
  // theme.css → full CSS string
46
- // profile → 'ocean' | 'earth' | 'twilight'
46
+ // profile → 'ocean' | 'earth' | 'twilight' | 'fire' | 'void' | 'leaf'
47
47
  ```
48
48
 
49
49
  ### `useRengeTheme(config?)`
@@ -51,7 +51,7 @@ const { theme, profile } = useRenge();
51
51
  Creates a theme without a provider — useful for SSR or static export.
52
52
 
53
53
  ```tsx
54
- const theme = useRengeTheme({ profile: 'twilight' });
54
+ const theme = useRengeTheme({ profile: 'twilight', mode: 'dark' });
55
55
  // Inject theme.css server-side, or pass to a <style> tag
56
56
  ```
57
57
 
@@ -59,9 +59,13 @@ const theme = useRengeTheme({ profile: 'twilight' });
59
59
 
60
60
  ## Components
61
61
 
62
- All components use `forwardRef`, accept all standard HTML props for their underlying element, and allow style overrides via the `style` prop. Most support a polymorphic `as` prop to change the rendered element.
62
+ All components use `forwardRef`, accept all standard HTML props for their underlying element, and allow style overrides via the `style` prop (applied last — overrides always win). Most support a polymorphic `as` prop to change the rendered element.
63
63
 
64
- ### `Stack`
64
+ ---
65
+
66
+ ### Layout
67
+
68
+ #### `Stack`
65
69
 
66
70
  Flexbox layout container.
67
71
 
@@ -80,7 +84,7 @@ Flexbox layout container.
80
84
  | `justify` | `'start' \| 'center' \| 'end' \| 'between' \| 'around'` | `'start'` |
81
85
  | `as` | `ElementType` | `'div'` |
82
86
 
83
- ### `Grid`
87
+ #### `Grid`
84
88
 
85
89
  CSS Grid layout container.
86
90
 
@@ -102,7 +106,30 @@ CSS Grid layout container.
102
106
  | `align` | `'start' \| 'center' \| 'end' \| 'stretch'` | `'stretch'` |
103
107
  | `justify` | `'start' \| 'center' \| 'end' \| 'stretch'` | `'stretch'` |
104
108
 
105
- ### `Text`
109
+ #### `Section`
110
+
111
+ Page section with max-width constraint and auto-centering.
112
+
113
+ ```tsx
114
+ <Section maxWidth="lg" paddingY="8">
115
+ <Heading>Welcome</Heading>
116
+ </Section>
117
+ ```
118
+
119
+ | Prop | Type | Default |
120
+ |------|------|---------|
121
+ | `maxWidth` | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full' \| 'none'` | `'lg'` |
122
+ | `padding` | `'0'–'8'` | — |
123
+ | `paddingX` | `'0'–'8'` | `'4'` |
124
+ | `paddingY` | `'0'–'8'` | `'6'` |
125
+ | `center` | `boolean` | `true` |
126
+ | `as` | `ElementType` | `'section'` |
127
+
128
+ ---
129
+
130
+ ### Text
131
+
132
+ #### `Text`
106
133
 
107
134
  Inline or block text with token-driven sizing.
108
135
 
@@ -120,7 +147,7 @@ Inline or block text with token-driven sizing.
120
147
  | `align` | `'left' \| 'center' \| 'right'` | — |
121
148
  | `as` | `ElementType` | `'span'` |
122
149
 
123
- ### `Heading`
150
+ #### `Heading`
124
151
 
125
152
  Semantic heading with automatic size defaults per level.
126
153
 
@@ -135,9 +162,13 @@ Semantic heading with automatic size defaults per level.
135
162
  | `size` | `'lg' \| 'xl' \| '2xl' \| '3xl' \| '4xl'` | auto (level-based) |
136
163
  | `color` | `'fg' \| 'fg-subtle' \| 'accent'` | `'fg'` |
137
164
 
138
- Default sizes by level: h1→3xl, h2→2xl, h3→xl, h4–h6→lg.
165
+ Default sizes: h1→3xl, h2→2xl, h3→xl, h4–h6→lg.
166
+
167
+ ---
168
+
169
+ ### Containers
139
170
 
140
- ### `Card`
171
+ #### `Card`
141
172
 
142
173
  Content container with three visual variants.
143
174
 
@@ -153,7 +184,11 @@ Content container with three visual variants.
153
184
  | `padding` | `'0'–'6'` | `'4'` |
154
185
  | `radius` | `'none' \| '1'–'5' \| 'full'` | `'3'` |
155
186
 
156
- ### `Button`
187
+ ---
188
+
189
+ ### Interactive
190
+
191
+ #### `Button`
157
192
 
158
193
  ```tsx
159
194
  <Button variant="outline" colorScheme="danger" size="sm">
@@ -168,7 +203,61 @@ Content container with three visual variants.
168
203
  | `colorScheme` | `'accent' \| 'danger' \| 'success'` | `'accent'` |
169
204
  | `fullWidth` | `boolean` | `false` |
170
205
 
171
- ### `Divider`
206
+ #### `Input`
207
+
208
+ ```tsx
209
+ <Input size="md" state="error" fullWidth placeholder="Enter email..." />
210
+ ```
211
+
212
+ | Prop | Type | Default |
213
+ |------|------|---------|
214
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` |
215
+ | `state` | `'default' \| 'error' \| 'success'` | `'default'` |
216
+ | `fullWidth` | `boolean` | `false` |
217
+
218
+ Note: `Input` uses `Omit<ComponentPropsWithoutRef<'input'>, 'size'>` to shadow the native `size: number` attribute with the semantic size prop.
219
+
220
+ Focus ring is applied via `onFocus`/`onBlur` handlers (inline styles cannot target `:focus-visible`). The outline uses `--renge-color-border-focus`.
221
+
222
+ #### `Chip`
223
+
224
+ Small inline tag — dismissible or selectable.
225
+
226
+ ```tsx
227
+ <Chip>Design systems</Chip>
228
+ ```
229
+
230
+ ---
231
+
232
+ ### Status & Decoration
233
+
234
+ #### `Badge`
235
+
236
+ Label badge with semantic color variants.
237
+
238
+ ```tsx
239
+ <Badge variant="success" size="sm">Active</Badge>
240
+ <Badge variant="danger">Deprecated</Badge>
241
+ ```
242
+
243
+ | Prop | Type | Default |
244
+ |------|------|---------|
245
+ | `variant` | `'accent' \| 'success' \| 'warning' \| 'danger' \| 'info' \| 'neutral'` | `'neutral'` |
246
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` |
247
+
248
+ #### `Alert`
249
+
250
+ Alert message with status-driven color.
251
+
252
+ ```tsx
253
+ <Alert status="warning">Variance is enabled.</Alert>
254
+ ```
255
+
256
+ | Prop | Type | Default |
257
+ |------|------|---------|
258
+ | `status` | `'success' \| 'warning' \| 'danger' \| 'info'` | `'info'` |
259
+
260
+ #### `Divider`
172
261
 
173
262
  ```tsx
174
263
  <Divider spacing="5" />
@@ -181,24 +270,115 @@ Content container with three visual variants.
181
270
  | `spacing` | `'0'–'6'` | `'3'` |
182
271
  | `color` | `'border' \| 'border-subtle'` | `'border-subtle'` |
183
272
 
184
- ### `Section`
273
+ #### `Spinner`
185
274
 
186
- Page section with max-width constraint and auto-centering.
275
+ Loading indicator. Injects `@keyframes rengeSpinnerSpin` once at module load.
187
276
 
188
277
  ```tsx
189
- <Section maxWidth="lg" paddingY="8">
190
- <Heading>Welcome</Heading>
191
- </Section>
278
+ <Spinner size="md" />
192
279
  ```
193
280
 
194
281
  | Prop | Type | Default |
195
282
  |------|------|---------|
196
- | `maxWidth` | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full' \| 'none'` | `'lg'` |
197
- | `padding` | `'0'–'8'` | — |
198
- | `paddingX` | `'0'–'8'` | `'4'` |
199
- | `paddingY` | `'0'–'8'` | `'6'` |
200
- | `center` | `boolean` | `true` |
201
- | `as` | `ElementType` | `'section'` |
283
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` |
284
+
285
+ #### `Progress`
286
+
287
+ Progress bar.
288
+
289
+ ```tsx
290
+ <Progress value={65} size="md" />
291
+ ```
292
+
293
+ | Prop | Type | Default |
294
+ |------|------|---------|
295
+ | `value` | `number` (0–100) | `0` |
296
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` |
297
+
298
+ #### `Avatar`
299
+
300
+ User avatar with size and shape options.
301
+
302
+ ```tsx
303
+ <Avatar src="/photo.jpg" alt="Vanessa" size="lg" shape="circle" />
304
+ ```
305
+
306
+ | Prop | Type | Default |
307
+ |------|------|---------|
308
+ | `size` | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` |
309
+ | `shape` | `'circle' \| 'square'` | `'circle'` |
310
+
311
+ ---
312
+
313
+ ### Forms
314
+
315
+ #### `FormField`
316
+
317
+ Label + input wrapper with consistent spacing.
318
+
319
+ ```tsx
320
+ <FormField label="Email" required>
321
+ <Input placeholder="you@example.com" />
322
+ </FormField>
323
+ ```
324
+
325
+ #### `Stat`
326
+
327
+ Metric display with optional trend direction.
328
+
329
+ ```tsx
330
+ <Stat label="Active users" value="1,284" trend="up" delta="+12%" />
331
+ ```
332
+
333
+ ---
334
+
335
+ ### Navigation
336
+
337
+ #### `Navbar`
338
+
339
+ Navigation bar primitive. Handles sticky positioning and border.
340
+
341
+ ```tsx
342
+ <Navbar sticky border paddingX="5">
343
+ <Logo />
344
+ <Stack direction="horizontal" gap="4">
345
+ <a href="/docs">Docs</a>
346
+ </Stack>
347
+ </Navbar>
348
+ ```
349
+
350
+ | Prop | Type | Default |
351
+ |------|------|---------|
352
+ | `sticky` | `boolean` | `false` |
353
+ | `border` | `boolean` | `true` |
354
+ | `height` | `string` | `'56px'` |
355
+ | `paddingX` | `'0'–'8'` | `'5'` |
356
+
357
+ ---
358
+
359
+ ### Experimental
360
+
361
+ These components are functional but not yet stabilized — APIs may change.
362
+
363
+ #### `EnergyRing`
364
+
365
+ Animated pulsing ring. Useful for loading states or ambient indicators.
366
+
367
+ ```tsx
368
+ <EnergyRing size={48} rate="slow" color="accent" />
369
+ ```
370
+
371
+ #### `Pulse`
372
+
373
+ Pulsing animation wrapper.
374
+
375
+ ```tsx
376
+ <Pulse rate="medium" color="success"><StatusDot /></Pulse>
377
+ ```
378
+
379
+ #### `FlowField`
380
+
381
+ Animated flow-field canvas visualization. Accepts energy intensity and color profile.
202
382
 
203
383
  ---
204
384
 
@@ -209,16 +389,55 @@ Use `injectCSS={false}` and inject the CSS string server-side:
209
389
  ```tsx
210
390
  import { createRengeTheme } from '@renge-ui/tokens';
211
391
 
212
- // Server
213
- const theme = createRengeTheme({ profile: 'ocean' });
392
+ // Server — generate CSS for all profiles you need
393
+ const theme = createRengeTheme({ profile: 'ocean', mode: 'light' });
214
394
  // Embed theme.css in your HTML <head>
215
395
 
216
- // Client
217
- <RengeProvider config={{ profile: 'ocean' }} injectCSS={false}>
396
+ // Client — match profile to avoid re-injecting
397
+ <RengeProvider config={{ profile: 'ocean', mode: 'light' }} injectCSS={false}>
218
398
  <App />
219
399
  </RengeProvider>
220
400
  ```
221
401
 
402
+ For multi-profile support (profile switcher), generate CSS for each profile using `data-profile` attribute selectors and inject all of them at build time. See `apps/lib/tokens.ts` in the monorepo for a reference implementation.
403
+
404
+ ---
405
+
406
+ ## Architecture
407
+
408
+ Components have no opinions about class names, CSS files, or external stylesheets. Every visual decision is an inline `style` referencing a `--renge-*` CSS custom property. This means:
409
+
410
+ - Components work anywhere the token CSS is injected — React, Remix, Next.js, plain HTML.
411
+ - Overrides are always via the `style` prop. No specificity battles.
412
+ - Bundle output contains no CSS. Zero style conflicts with your existing stylesheet.
413
+
414
+ The rendering chain is:
415
+
416
+ ```
417
+ createRengeTheme() → theme.css → injected into <head>
418
+
419
+ CSS custom properties on :root
420
+
421
+ Components read var(--renge-*) via inline styles
422
+ ```
423
+
424
+ ---
425
+
426
+ ## Trade-offs
427
+
428
+ **No pseudo-class support.** Inline styles cannot target `:hover`, `:focus-visible`, `:disabled`, etc. This is the main limitation of the inline-styles approach. Workarounds:
429
+
430
+ - Focus rings use `onFocus`/`onBlur` JS handlers (see `Input`).
431
+ - Hover states are not currently implemented in primitives — add them via the `style` prop + event handlers in your application layer, or override with a CSS class.
432
+
433
+ **No class names means no CSS-level overrides.** You can't write `.my-button { ... }` to target a Renge component in a stylesheet. Use the `style` prop or wrap the component and override via a parent selector.
434
+
435
+ **`Spinner` injects keyframes at module load** via a module-level DOM insertion. This happens once per import, regardless of how many `Spinner` instances are rendered. It is not part of the Renge theme CSS — it lives outside the token system because CSS custom properties cannot animate `transform`.
436
+
437
+ **Single provider scope.** `RengeProvider` sets one theme for the entire subtree. If you need different themes in different parts of the tree, nest multiple `RengeProvider` instances — but note that the outermost provider's `injectCSS` behavior will apply globally unless scoped via the `selector` config option.
438
+
439
+ **Experimental components have unstable APIs.** `EnergyRing`, `Pulse`, and `FlowField` are included but not covered by the stability guarantee. Their props will likely change before v1.
440
+
222
441
  ---
223
442
 
224
443
  ## Development
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@renge-ui/react",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "React component primitives for the Renge design system.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -12,23 +12,32 @@
12
12
  "require": "./dist/index.js"
13
13
  }
14
14
  },
15
- "files": ["dist"],
15
+ "files": [
16
+ "dist"
17
+ ],
16
18
  "scripts": {
17
19
  "build": "tsup",
18
20
  "dev": "tsup --watch",
19
21
  "typecheck": "tsc --noEmit"
20
22
  },
21
- "keywords": ["design-system", "react", "components", "golden-ratio"],
23
+ "keywords": [
24
+ "design-system",
25
+ "react",
26
+ "components",
27
+ "golden-ratio"
28
+ ],
22
29
  "author": "Vanessa Martin",
23
30
  "license": "MIT",
24
- "publishConfig": { "access": "public" },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
25
34
  "repository": {
26
35
  "type": "git",
27
36
  "url": "https://github.com/vsm1996/renge-ui"
28
37
  },
29
38
  "packageManager": "pnpm@10.30.2",
30
39
  "dependencies": {
31
- "@renge-ui/tokens": "workspace:*"
40
+ "@renge-ui/tokens": "^1.0.3"
32
41
  },
33
42
  "peerDependencies": {
34
43
  "react": "^18.0.0 || ^19.0.0",