@usetheo/ui 0.12.0 → 0.13.0
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/CHANGELOG.md +94 -0
- package/README.md +20 -19
- package/dist/chunk-6ZQKEY54.js +149 -0
- package/dist/chunk-6ZQKEY54.js.map +1 -0
- package/dist/chunk-AVPHVQZS.js +73 -0
- package/dist/chunk-AVPHVQZS.js.map +1 -0
- package/dist/chunk-GXBFGWQN.js +81 -0
- package/dist/chunk-GXBFGWQN.js.map +1 -0
- package/dist/chunk-I32I36LW.js +113 -0
- package/dist/chunk-I32I36LW.js.map +1 -0
- package/dist/chunk-JPTPIZ5V.js +120 -0
- package/dist/chunk-JPTPIZ5V.js.map +1 -0
- package/dist/{chunk-TO3UAT6O.js → chunk-K7PYLTMP.js} +3 -3
- package/dist/{chunk-TO3UAT6O.js.map → chunk-K7PYLTMP.js.map} +1 -1
- package/dist/{chunk-R2PAGRDP.js → chunk-MYEHGDC2.js} +2 -2
- package/dist/{chunk-R2PAGRDP.js.map → chunk-MYEHGDC2.js.map} +1 -1
- package/dist/{chunk-IPEYGWA7.js → chunk-PTHRL242.js} +4 -4
- package/dist/{chunk-IPEYGWA7.js.map → chunk-PTHRL242.js.map} +1 -1
- package/dist/chunk-RC5XME4T.js +33 -0
- package/dist/chunk-RC5XME4T.js.map +1 -0
- package/dist/chunk-UK27KR35.js +73 -0
- package/dist/chunk-UK27KR35.js.map +1 -0
- package/dist/chunk-UOMQPIB4.js +48 -0
- package/dist/chunk-UOMQPIB4.js.map +1 -0
- package/dist/{chunk-TNBJ36XJ.js → chunk-XZKEGEPT.js} +4 -4
- package/dist/{chunk-TNBJ36XJ.js.map → chunk-XZKEGEPT.js.map} +1 -1
- package/dist/components.css +1 -1
- package/dist/composites/agent-editor/index.js +2 -2
- package/dist/composites/rule-editor/index.js +3 -3
- package/dist/composites/skill-editor/index.js +3 -3
- package/dist/composites/stability-bundle-viewer/index.js +4 -0
- package/dist/composites/stability-bundle-viewer/index.js.map +1 -0
- package/dist/index.d.ts +162 -1
- package/dist/index.js +44 -36
- package/dist/index.js.map +1 -1
- package/dist/primitives/branch-indicator/index.js +4 -0
- package/dist/primitives/branch-indicator/index.js.map +1 -0
- package/dist/primitives/channel-card/index.js +4 -0
- package/dist/primitives/channel-card/index.js.map +1 -0
- package/dist/primitives/export-chat-dialog/index.js +4 -0
- package/dist/primitives/export-chat-dialog/index.js.map +1 -0
- package/dist/primitives/gateway-status-indicator/index.js +4 -0
- package/dist/primitives/gateway-status-indicator/index.js.map +1 -0
- package/dist/primitives/pin-input/index.js +1 -1
- package/dist/primitives/run-status-pill/index.js +4 -0
- package/dist/primitives/run-status-pill/index.js.map +1 -0
- package/dist/primitives/thinking-level-selector/index.js +4 -0
- package/dist/primitives/thinking-level-selector/index.js.map +1 -0
- package/dist/primitives/update-banner/index.js +4 -0
- package/dist/primitives/update-banner/index.js.map +1 -0
- package/package.json +123 -99
- package/registry/r/pin-input.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usetheo/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Theo UI — framework-agnostic React component library with the Violet Forge design system. Focused on AI-agent interfaces, cloud dashboards, and developer-tooling surfaces.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,17 @@
|
|
|
12
12
|
"types": "./dist/index.d.ts",
|
|
13
13
|
"import": "./dist/index.js"
|
|
14
14
|
},
|
|
15
|
+
"./styles.css": "./dist/styles.css",
|
|
16
|
+
"./styles-v3-legacy.css": "./dist/styles-v3-legacy.css",
|
|
17
|
+
"./components.css": "./dist/components.css",
|
|
18
|
+
"./tokens.css": "./dist/tokens.css",
|
|
19
|
+
"./tokens-v4.css": "./dist/tokens-v4.css",
|
|
20
|
+
"./preset.css": "./dist/preset.css",
|
|
21
|
+
"./preset": "./dist/preset.css",
|
|
22
|
+
"./fonts.css": "./dist/fonts.css",
|
|
23
|
+
"./fonts-cdn.css": "./dist/fonts-cdn.css",
|
|
24
|
+
"./slide/themes/default.css": "./dist/slide/themes/default.css",
|
|
25
|
+
"./slide/themes/violet-forge.css": "./dist/slide/themes/violet-forge.css",
|
|
15
26
|
"./account-menu": {
|
|
16
27
|
"types": "./dist/index.d.ts",
|
|
17
28
|
"import": "./dist/composites/account-menu/index.js"
|
|
@@ -92,6 +103,10 @@
|
|
|
92
103
|
"types": "./dist/index.d.ts",
|
|
93
104
|
"import": "./dist/primitives/badge/index.js"
|
|
94
105
|
},
|
|
106
|
+
"./branch-indicator": {
|
|
107
|
+
"types": "./dist/index.d.ts",
|
|
108
|
+
"import": "./dist/primitives/branch-indicator/index.js"
|
|
109
|
+
},
|
|
95
110
|
"./browser-controls": {
|
|
96
111
|
"types": "./dist/index.d.ts",
|
|
97
112
|
"import": "./dist/primitives/browser-controls/index.js"
|
|
@@ -112,6 +127,10 @@
|
|
|
112
127
|
"types": "./dist/index.d.ts",
|
|
113
128
|
"import": "./dist/primitives/card/index.js"
|
|
114
129
|
},
|
|
130
|
+
"./channel-card": {
|
|
131
|
+
"types": "./dist/index.d.ts",
|
|
132
|
+
"import": "./dist/primitives/channel-card/index.js"
|
|
133
|
+
},
|
|
115
134
|
"./chat-composer": {
|
|
116
135
|
"types": "./dist/index.d.ts",
|
|
117
136
|
"import": "./dist/composites/chat-composer/index.js"
|
|
@@ -136,7 +155,6 @@
|
|
|
136
155
|
"types": "./dist/index.d.ts",
|
|
137
156
|
"import": "./dist/composites/command-palette/index.js"
|
|
138
157
|
},
|
|
139
|
-
"./components.css": "./dist/components.css",
|
|
140
158
|
"./confirm-dialog": {
|
|
141
159
|
"types": "./dist/index.d.ts",
|
|
142
160
|
"import": "./dist/composites/confirm-dialog/index.js"
|
|
@@ -205,6 +223,10 @@
|
|
|
205
223
|
"types": "./dist/index.d.ts",
|
|
206
224
|
"import": "./dist/composites/env-var-editor/index.js"
|
|
207
225
|
},
|
|
226
|
+
"./export-chat-dialog": {
|
|
227
|
+
"types": "./dist/index.d.ts",
|
|
228
|
+
"import": "./dist/primitives/export-chat-dialog/index.js"
|
|
229
|
+
},
|
|
208
230
|
"./folder-context-card": {
|
|
209
231
|
"types": "./dist/index.d.ts",
|
|
210
232
|
"import": "./dist/primitives/folder-context-card/index.js"
|
|
@@ -213,12 +235,14 @@
|
|
|
213
235
|
"types": "./dist/index.d.ts",
|
|
214
236
|
"import": "./dist/primitives/folder-selector/index.js"
|
|
215
237
|
},
|
|
216
|
-
"./fonts-cdn.css": "./dist/fonts-cdn.css",
|
|
217
|
-
"./fonts.css": "./dist/fonts.css",
|
|
218
238
|
"./form-field": {
|
|
219
239
|
"types": "./dist/index.d.ts",
|
|
220
240
|
"import": "./dist/primitives/form-field/index.js"
|
|
221
241
|
},
|
|
242
|
+
"./gateway-status-indicator": {
|
|
243
|
+
"types": "./dist/index.d.ts",
|
|
244
|
+
"import": "./dist/primitives/gateway-status-indicator/index.js"
|
|
245
|
+
},
|
|
222
246
|
"./hook-config": {
|
|
223
247
|
"types": "./dist/index.d.ts",
|
|
224
248
|
"import": "./dist/primitives/hook-config/index.js"
|
|
@@ -299,12 +323,6 @@
|
|
|
299
323
|
"types": "./dist/index.d.ts",
|
|
300
324
|
"import": "./dist/primitives/plan-badge/index.js"
|
|
301
325
|
},
|
|
302
|
-
"./preset": "./dist/preset.css",
|
|
303
|
-
"./preset-v3-legacy": {
|
|
304
|
-
"types": "./dist/preset-v3-legacy.d.ts",
|
|
305
|
-
"import": "./dist/preset-v3-legacy.js"
|
|
306
|
-
},
|
|
307
|
-
"./preset.css": "./dist/preset.css",
|
|
308
326
|
"./preview-env-card": {
|
|
309
327
|
"types": "./dist/index.d.ts",
|
|
310
328
|
"import": "./dist/composites/preview-env-card/index.js"
|
|
@@ -357,6 +375,10 @@
|
|
|
357
375
|
"types": "./dist/index.d.ts",
|
|
358
376
|
"import": "./dist/primitives/run-stats/index.js"
|
|
359
377
|
},
|
|
378
|
+
"./run-status-pill": {
|
|
379
|
+
"types": "./dist/index.d.ts",
|
|
380
|
+
"import": "./dist/primitives/run-status-pill/index.js"
|
|
381
|
+
},
|
|
360
382
|
"./running-tasks-panel": {
|
|
361
383
|
"types": "./dist/index.d.ts",
|
|
362
384
|
"import": "./dist/primitives/running-tasks-panel/index.js"
|
|
@@ -401,36 +423,14 @@
|
|
|
401
423
|
"types": "./dist/index.d.ts",
|
|
402
424
|
"import": "./dist/composites/skills-list/index.js"
|
|
403
425
|
},
|
|
404
|
-
"./slide": {
|
|
405
|
-
"types": "./dist/slide/index.d.ts",
|
|
406
|
-
"import": "./dist/slide/index.js"
|
|
407
|
-
},
|
|
408
|
-
"./slide-deck": {
|
|
409
|
-
"types": "./dist/slide-deck/index.d.ts",
|
|
410
|
-
"import": "./dist/slide-deck/index.js"
|
|
411
|
-
},
|
|
412
|
-
"./slide/plugins/emoji": {
|
|
413
|
-
"types": "./dist/slide/plugins/emoji/index.d.ts",
|
|
414
|
-
"import": "./dist/slide/plugins/emoji/index.js"
|
|
415
|
-
},
|
|
416
|
-
"./slide/plugins/math": {
|
|
417
|
-
"types": "./dist/slide/plugins/math/index.d.ts",
|
|
418
|
-
"import": "./dist/slide/plugins/math/index.js"
|
|
419
|
-
},
|
|
420
|
-
"./slide/plugins/mermaid": {
|
|
421
|
-
"types": "./dist/slide/plugins/mermaid/index.d.ts",
|
|
422
|
-
"import": "./dist/slide/plugins/mermaid/index.js"
|
|
423
|
-
},
|
|
424
|
-
"./slide/plugins/shiki": {
|
|
425
|
-
"types": "./dist/slide/plugins/shiki/index.d.ts",
|
|
426
|
-
"import": "./dist/slide/plugins/shiki/index.js"
|
|
427
|
-
},
|
|
428
|
-
"./slide/themes/default.css": "./dist/slide/themes/default.css",
|
|
429
|
-
"./slide/themes/violet-forge.css": "./dist/slide/themes/violet-forge.css",
|
|
430
426
|
"./social-auth-row": {
|
|
431
427
|
"types": "./dist/index.d.ts",
|
|
432
428
|
"import": "./dist/primitives/social-auth-row/index.js"
|
|
433
429
|
},
|
|
430
|
+
"./stability-bundle-viewer": {
|
|
431
|
+
"types": "./dist/index.d.ts",
|
|
432
|
+
"import": "./dist/composites/stability-bundle-viewer/index.js"
|
|
433
|
+
},
|
|
434
434
|
"./stat-tile": {
|
|
435
435
|
"types": "./dist/index.d.ts",
|
|
436
436
|
"import": "./dist/primitives/stat-tile/index.js"
|
|
@@ -443,8 +443,6 @@
|
|
|
443
443
|
"types": "./dist/index.d.ts",
|
|
444
444
|
"import": "./dist/primitives/steps-rail/index.js"
|
|
445
445
|
},
|
|
446
|
-
"./styles-v3-legacy.css": "./dist/styles-v3-legacy.css",
|
|
447
|
-
"./styles.css": "./dist/styles.css",
|
|
448
446
|
"./sub-agent-dispatch": {
|
|
449
447
|
"types": "./dist/index.d.ts",
|
|
450
448
|
"import": "./dist/primitives/sub-agent-dispatch/index.js"
|
|
@@ -481,6 +479,10 @@
|
|
|
481
479
|
"types": "./dist/index.d.ts",
|
|
482
480
|
"import": "./dist/primitives/textarea/index.js"
|
|
483
481
|
},
|
|
482
|
+
"./thinking-level-selector": {
|
|
483
|
+
"types": "./dist/index.d.ts",
|
|
484
|
+
"import": "./dist/primitives/thinking-level-selector/index.js"
|
|
485
|
+
},
|
|
484
486
|
"./timestamp": {
|
|
485
487
|
"types": "./dist/index.d.ts",
|
|
486
488
|
"import": "./dist/primitives/timestamp/index.js"
|
|
@@ -493,8 +495,6 @@
|
|
|
493
495
|
"types": "./dist/index.d.ts",
|
|
494
496
|
"import": "./dist/primitives/token-usage-chart/index.js"
|
|
495
497
|
},
|
|
496
|
-
"./tokens-v4.css": "./dist/tokens-v4.css",
|
|
497
|
-
"./tokens.css": "./dist/tokens.css",
|
|
498
498
|
"./tool-call": {
|
|
499
499
|
"types": "./dist/index.d.ts",
|
|
500
500
|
"import": "./dist/primitives/tool-call/index.js"
|
|
@@ -519,17 +519,49 @@
|
|
|
519
519
|
"types": "./dist/index.d.ts",
|
|
520
520
|
"import": "./dist/primitives/topnav/index.js"
|
|
521
521
|
},
|
|
522
|
+
"./update-banner": {
|
|
523
|
+
"types": "./dist/index.d.ts",
|
|
524
|
+
"import": "./dist/primitives/update-banner/index.js"
|
|
525
|
+
},
|
|
522
526
|
"./usage-meter": {
|
|
523
527
|
"types": "./dist/index.d.ts",
|
|
524
528
|
"import": "./dist/composites/usage-meter/index.js"
|
|
525
529
|
},
|
|
530
|
+
"./whiteboard": {
|
|
531
|
+
"types": "./dist/whiteboard/index.d.ts",
|
|
532
|
+
"import": "./dist/whiteboard/index.js"
|
|
533
|
+
},
|
|
534
|
+
"./slide": {
|
|
535
|
+
"types": "./dist/slide/index.d.ts",
|
|
536
|
+
"import": "./dist/slide/index.js"
|
|
537
|
+
},
|
|
538
|
+
"./slide/plugins/shiki": {
|
|
539
|
+
"types": "./dist/slide/plugins/shiki/index.d.ts",
|
|
540
|
+
"import": "./dist/slide/plugins/shiki/index.js"
|
|
541
|
+
},
|
|
542
|
+
"./slide/plugins/math": {
|
|
543
|
+
"types": "./dist/slide/plugins/math/index.d.ts",
|
|
544
|
+
"import": "./dist/slide/plugins/math/index.js"
|
|
545
|
+
},
|
|
546
|
+
"./slide/plugins/mermaid": {
|
|
547
|
+
"types": "./dist/slide/plugins/mermaid/index.d.ts",
|
|
548
|
+
"import": "./dist/slide/plugins/mermaid/index.js"
|
|
549
|
+
},
|
|
550
|
+
"./slide/plugins/emoji": {
|
|
551
|
+
"types": "./dist/slide/plugins/emoji/index.d.ts",
|
|
552
|
+
"import": "./dist/slide/plugins/emoji/index.js"
|
|
553
|
+
},
|
|
554
|
+
"./slide-deck": {
|
|
555
|
+
"types": "./dist/slide-deck/index.d.ts",
|
|
556
|
+
"import": "./dist/slide-deck/index.js"
|
|
557
|
+
},
|
|
526
558
|
"./vite-plugin": {
|
|
527
559
|
"types": "./dist/vite-plugin.d.ts",
|
|
528
560
|
"import": "./dist/vite-plugin.js"
|
|
529
561
|
},
|
|
530
|
-
"./
|
|
531
|
-
"types": "./dist/
|
|
532
|
-
"import": "./dist/
|
|
562
|
+
"./preset-v3-legacy": {
|
|
563
|
+
"types": "./dist/preset-v3-legacy.d.ts",
|
|
564
|
+
"import": "./dist/preset-v3-legacy.js"
|
|
533
565
|
}
|
|
534
566
|
},
|
|
535
567
|
"files": [
|
|
@@ -542,50 +574,6 @@
|
|
|
542
574
|
"llms.txt",
|
|
543
575
|
"DESIGN.md"
|
|
544
576
|
],
|
|
545
|
-
"scripts": {
|
|
546
|
-
"build": "tsup && tsx scripts/regen-subpath-exports.ts",
|
|
547
|
-
"dev": "ladle serve",
|
|
548
|
-
"ladle:build": "ladle build",
|
|
549
|
-
"ladle:preview": "ladle preview",
|
|
550
|
-
"playground": "vite --config playground/vite.config.ts",
|
|
551
|
-
"playground:build": "vite build --config playground/vite.config.ts",
|
|
552
|
-
"playground:preview": "vite preview --config playground/vite.config.ts",
|
|
553
|
-
"typecheck": "tsc --noEmit",
|
|
554
|
-
"lint": "biome check src",
|
|
555
|
-
"lint:ci": "biome ci src scripts .ladle playground",
|
|
556
|
-
"lint:fix": "biome check --write src",
|
|
557
|
-
"format": "biome format --write src scripts .ladle package.json tsconfig.json tailwind.config.ts vitest.config.ts tsup.config.ts biome.json",
|
|
558
|
-
"format:check": "biome format src scripts .ladle package.json tsconfig.json tailwind.config.ts vitest.config.ts tsup.config.ts biome.json",
|
|
559
|
-
"test": "vitest run",
|
|
560
|
-
"test:watch": "vitest",
|
|
561
|
-
"test:ui": "vitest --ui",
|
|
562
|
-
"test:contract": "vitest run tests/contract",
|
|
563
|
-
"prepublishOnly": "pnpm build && pnpm test:contract && node scripts/validate-exports.mjs",
|
|
564
|
-
"validate:exports": "node scripts/validate-exports.mjs",
|
|
565
|
-
"registry:build": "tsx scripts/build-registry.ts",
|
|
566
|
-
"registry:validate": "tsx scripts/validate-registry.ts",
|
|
567
|
-
"sync:readme": "tsx scripts/sync-readme.ts",
|
|
568
|
-
"sync:exports": "tsx scripts/sync-exports.ts",
|
|
569
|
-
"test:registry": "tsx scripts/test-registry-install.ts",
|
|
570
|
-
"test:coverage": "vitest run --coverage",
|
|
571
|
-
"quality:structure": "tsx scripts/validate-quality-gates.ts",
|
|
572
|
-
"quality:bundle": "tsx scripts/validate-bundle-size.ts",
|
|
573
|
-
"quality:bundle:update": "tsx scripts/validate-bundle-size.ts --update",
|
|
574
|
-
"quality:a11y": "vitest run src/test/ladle-axe.test.tsx",
|
|
575
|
-
"dogfood:whiteboard": "tsx scripts/dogfood-whiteboard.ts",
|
|
576
|
-
"dogfood:slide": "tsx scripts/dogfood-slide.ts",
|
|
577
|
-
"dogfood:slide-deck": "tsx scripts/dogfood-slide-deck.ts",
|
|
578
|
-
"dogfood:slide-rich": "tsx scripts/dogfood-slide-rich.ts",
|
|
579
|
-
"dogfood:v4-zero-config": "tsx scripts/dogfood-v4-zero-config.ts",
|
|
580
|
-
"dogfood:v4-real-build": "bash scripts/dogfood-v4-real-build.sh",
|
|
581
|
-
"dogfood:precompiled-utilities": "tsx scripts/dogfood-precompiled-utilities.ts",
|
|
582
|
-
"quality:gates": "pnpm format:check && pnpm lint:ci && pnpm typecheck && pnpm quality:knip && pnpm test && pnpm build && pnpm quality:publint && pnpm registry:build && pnpm registry:validate && pnpm quality:structure && pnpm quality:bundle && pnpm quality:a11y && pnpm ladle:build && pnpm dogfood:whiteboard && pnpm dogfood:slide && pnpm dogfood:slide-deck && pnpm dogfood:slide-rich && pnpm dogfood:v4-zero-config && pnpm dogfood:precompiled-utilities",
|
|
583
|
-
"quality:gates:fast": "pnpm format:check && pnpm lint:ci && pnpm typecheck && pnpm quality:knip && pnpm registry:build && pnpm registry:validate && pnpm quality:structure",
|
|
584
|
-
"quality:knip": "knip",
|
|
585
|
-
"quality:knip:fix": "knip --fix",
|
|
586
|
-
"quality:publint": "publint --strict",
|
|
587
|
-
"quality:attw": "attw --pack . --profile esm-only"
|
|
588
|
-
},
|
|
589
577
|
"peerDependencies": {
|
|
590
578
|
"@tailwindcss/vite": "^4.0.0",
|
|
591
579
|
"hast-util-from-html": "^2.0.0",
|
|
@@ -741,16 +729,6 @@
|
|
|
741
729
|
"engines": {
|
|
742
730
|
"node": ">=20"
|
|
743
731
|
},
|
|
744
|
-
"pnpm": {
|
|
745
|
-
"onlyBuiltDependencies": [
|
|
746
|
-
"@biomejs/biome",
|
|
747
|
-
"@swc/core",
|
|
748
|
-
"esbuild"
|
|
749
|
-
],
|
|
750
|
-
"overrides": {
|
|
751
|
-
"postcss": ">=8.5.10"
|
|
752
|
-
}
|
|
753
|
-
},
|
|
754
732
|
"keywords": [
|
|
755
733
|
"react",
|
|
756
734
|
"components",
|
|
@@ -766,5 +744,51 @@
|
|
|
766
744
|
"publishConfig": {
|
|
767
745
|
"access": "public"
|
|
768
746
|
},
|
|
769
|
-
"
|
|
770
|
-
|
|
747
|
+
"scripts": {
|
|
748
|
+
"build": "tsup && tsx scripts/regen-subpath-exports.ts",
|
|
749
|
+
"dev": "ladle serve",
|
|
750
|
+
"ladle:build": "ladle build",
|
|
751
|
+
"ladle:preview": "ladle preview",
|
|
752
|
+
"playground": "vite --config playground/vite.config.ts",
|
|
753
|
+
"playground:build": "vite build --config playground/vite.config.ts",
|
|
754
|
+
"playground:preview": "vite preview --config playground/vite.config.ts",
|
|
755
|
+
"typecheck": "tsc --noEmit",
|
|
756
|
+
"lint": "biome check src",
|
|
757
|
+
"lint:ci": "biome ci src scripts .ladle playground",
|
|
758
|
+
"inventory": "node scripts/inventory-components.mjs",
|
|
759
|
+
"stories:check": "node scripts/generate-missing-stories.mjs --check",
|
|
760
|
+
"stories:generate": "node scripts/generate-missing-stories.mjs --write",
|
|
761
|
+
"stories:test": "node scripts/__tests__/generate-missing-stories.test.mjs",
|
|
762
|
+
"lint:fix": "biome check --write src",
|
|
763
|
+
"format": "biome format --write src scripts .ladle package.json tsconfig.json tailwind.config.ts vitest.config.ts tsup.config.ts biome.json",
|
|
764
|
+
"format:check": "biome format src scripts .ladle package.json tsconfig.json tailwind.config.ts vitest.config.ts tsup.config.ts biome.json",
|
|
765
|
+
"test": "vitest run",
|
|
766
|
+
"test:watch": "vitest",
|
|
767
|
+
"test:ui": "vitest --ui",
|
|
768
|
+
"test:contract": "vitest run tests/contract",
|
|
769
|
+
"validate:exports": "node scripts/validate-exports.mjs",
|
|
770
|
+
"registry:build": "tsx scripts/build-registry.ts",
|
|
771
|
+
"registry:validate": "tsx scripts/validate-registry.ts",
|
|
772
|
+
"sync:readme": "tsx scripts/sync-readme.ts",
|
|
773
|
+
"sync:exports": "tsx scripts/sync-exports.ts",
|
|
774
|
+
"test:registry": "tsx scripts/test-registry-install.ts",
|
|
775
|
+
"test:coverage": "vitest run --coverage",
|
|
776
|
+
"quality:structure": "tsx scripts/validate-quality-gates.ts",
|
|
777
|
+
"quality:bundle": "tsx scripts/validate-bundle-size.ts",
|
|
778
|
+
"quality:bundle:update": "tsx scripts/validate-bundle-size.ts --update",
|
|
779
|
+
"quality:a11y": "vitest run src/test/ladle-axe.test.tsx",
|
|
780
|
+
"dogfood:whiteboard": "tsx scripts/dogfood-whiteboard.ts",
|
|
781
|
+
"dogfood:slide": "tsx scripts/dogfood-slide.ts",
|
|
782
|
+
"dogfood:slide-deck": "tsx scripts/dogfood-slide-deck.ts",
|
|
783
|
+
"dogfood:slide-rich": "tsx scripts/dogfood-slide-rich.ts",
|
|
784
|
+
"dogfood:v4-zero-config": "tsx scripts/dogfood-v4-zero-config.ts",
|
|
785
|
+
"dogfood:v4-real-build": "bash scripts/dogfood-v4-real-build.sh",
|
|
786
|
+
"dogfood:precompiled-utilities": "tsx scripts/dogfood-precompiled-utilities.ts",
|
|
787
|
+
"quality:gates": "pnpm format:check && pnpm lint:ci && pnpm typecheck && pnpm quality:knip && pnpm test && pnpm build && pnpm quality:publint && pnpm registry:build && pnpm registry:validate && pnpm quality:structure && pnpm quality:bundle && pnpm quality:a11y && pnpm ladle:build && pnpm dogfood:whiteboard && pnpm dogfood:slide && pnpm dogfood:slide-deck && pnpm dogfood:slide-rich && pnpm dogfood:v4-zero-config && pnpm dogfood:precompiled-utilities",
|
|
788
|
+
"quality:gates:fast": "pnpm format:check && pnpm lint:ci && pnpm typecheck && pnpm quality:knip && pnpm registry:build && pnpm registry:validate && pnpm quality:structure",
|
|
789
|
+
"quality:knip": "knip",
|
|
790
|
+
"quality:knip:fix": "knip --fix",
|
|
791
|
+
"quality:publint": "publint --strict",
|
|
792
|
+
"quality:attw": "attw --pack . --profile esm-only"
|
|
793
|
+
}
|
|
794
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"path": "components/primitives/pin-input/pin-input.tsx",
|
|
15
15
|
"type": "registry:ui",
|
|
16
16
|
"target": "components/ui/pin-input.tsx",
|
|
17
|
-
"content": "import { forwardRef, useEffect, useRef } from \"react\";\nimport type { ClipboardEvent, HTMLAttributes, KeyboardEvent } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * PinInput — multi-slot OTP / code input primitive.\n *\n * Renders N separate boxes (default 6) that auto-advance focus on\n * input. Paste handling fills all slots from clipboard (whitespace\n * stripped). Arrow keys navigate; backspace clears current slot\n * then moves focus back when empty.\n *\n * Industry-standard pattern for email verification codes (Apple,\n * Stripe, Clerk, Auth0, GitHub two-factor).\n *\n * @example\n * <PinInput\n * length={6}\n * value={code}\n * onChange={setCode}\n * onComplete={(v) => verify(v)}\n * inputMode=\"numeric\"\n * aria-label=\"Verification code\"\n * />\n *\n * Note: value is treated as controlled. If you pass a complete value\n * on mount, onComplete will NOT fire — onComplete fires only on\n * transitions from incomplete → complete.\n */\nexport interface PinInputProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\" | \"inputMode\"> {\n length?: number;\n value?: string;\n onChange?: (value: string) => void;\n onComplete?: (value: string) => void;\n inputMode?: \"numeric\" | \"alphanumeric\";\n size?: \"sm\" | \"md\" | \"lg\";\n disabled?: boolean;\n error?: boolean;\n \"aria-label\": string;\n autoFocus?: boolean;\n mask?: boolean;\n}\n\nconst SIZE_CLASS: Record<NonNullable<PinInputProps[\"size\"]>, string> = {\n sm: \"size-8 text-body-sm\",\n md: \"size-10 text-body-md\",\n lg: \"size-12 text-title-sm\",\n};\n\nfunction sanitize(raw: string, inputMode: \"numeric\" | \"alphanumeric\"): string {\n const noWhitespace = raw.replace(/\\s/g, \"\");\n if (inputMode === \"numeric\") {\n return noWhitespace.replace(/\\D/g, \"\");\n }\n return noWhitespace.toUpperCase().replace(/[^A-Z0-9]/g, \"\");\n}\n\nconst PinInput = forwardRef<HTMLDivElement, PinInputProps>(\n (\n {\n className,\n length = 6,\n value = \"\",\n onChange,\n onComplete,\n inputMode = \"numeric\",\n size = \"md\",\n disabled = false,\n error = false,\n autoFocus = false,\n mask = false,\n \"aria-label\": ariaLabel,\n ...props\n },\n ref,\n ) => {\n const inputRefs = useRef<Array<HTMLInputElement | null>>([]);\n const wasCompleteRef = useRef<boolean>(value.length === length);\n\n // Auto-focus first slot on mount (SSR-safe)\n useEffect(() => {\n if (!autoFocus) return;\n if (typeof window === \"undefined\") return;\n inputRefs.current[0]?.focus();\n }, [autoFocus]);\n\n // Fire onComplete on transitions from incomplete → complete\n useEffect(() => {\n const isComplete = value.length === length && value.length > 0;\n if (isComplete && !wasCompleteRef.current) {\n onComplete?.(value);\n }\n wasCompleteRef.current = isComplete;\n }, [value, length, onComplete]);\n\n function commit(next: string) {\n const sanitized = sanitize(next, inputMode).slice(0, length);\n onChange?.(sanitized);\n }\n\n function handleChange(slot: number, raw: string) {\n const sanitized = sanitize(raw, inputMode);\n if (sanitized.length === 0) {\n // Clear current slot\n const next = `${value.slice(0, slot)}${value.slice(slot + 1)}`;\n commit(next);\n return;\n }\n // Take the last character typed (handles browser autocomplete that fills multiple)\n const ch = sanitized[sanitized.length - 1] ?? \"\";\n const next = `${value.slice(0, slot)}${ch}${value.slice(slot + 1)}`;\n commit(next);\n // Advance focus\n if (slot < length - 1) {\n inputRefs.current[slot + 1]?.focus();\n }\n }\n\n function handleKeyDown(slot: number, e: KeyboardEvent<HTMLInputElement>) {\n if (disabled) return;\n const slotChar = value[slot] ?? \"\";\n\n if (e.key === \"Backspace\") {\n if (slotChar === \"\") {\n // Move focus back if current is empty\n if (slot > 0) {\n inputRefs.current[slot - 1]?.focus();\n }\n } else {\n // Clear current slot, stay focused\n const next = `${value.slice(0, slot)}${value.slice(slot + 1)}`;\n commit(next);\n }\n e.preventDefault();\n } else if (e.key === \"ArrowLeft\") {\n if (slot > 0) inputRefs.current[slot - 1]?.focus();\n e.preventDefault();\n } else if (e.key === \"ArrowRight\") {\n if (slot < length - 1) inputRefs.current[slot + 1]?.focus();\n e.preventDefault();\n }\n }\n\n function handlePaste(slot: number, e: ClipboardEvent<HTMLInputElement>) {\n if (disabled) return;\n e.preventDefault();\n const pasted = e.clipboardData.getData(\"text/plain\");\n const sanitized = sanitize(pasted, inputMode);\n if (sanitized.length === 0) return;\n // Build slot-indexed array, then overwrite from `slot` onwards.\n // Previous string-concat approach didn't pad when value was shorter\n // than `slot`, which made paste-from-middle-when-empty fill from 0.\n const slotArr: string[] = Array.from({ length }, (_, i) => value[i] ?? \"\");\n const remaining = length - slot;\n const filled = sanitized.slice(0, remaining);\n for (let i = 0; i < filled.length; i++) {\n slotArr[slot + i] = filled[i] ?? \"\";\n }\n const next = slotArr.join(\"\");\n commit(next);\n // Focus the slot after the last filled, or the last slot if completed\n const focusAt = Math.min(slot + filled.length, length - 1);\n requestAnimationFrame(() => inputRefs.current[focusAt]?.focus());\n }\n\n const slots = Array.from({ length }, (_, i) => i);\n\n return (\n <div\n ref={ref}\n // biome-ignore lint/a11y/useSemanticElements: <fieldset> would force a different visual layout (rectangular border by default) and is form-bound; we use a div with role=\"group\" + aria-label for grouping semantics.\n role=\"group\"\n aria-label={ariaLabel}\n className={cn(\"inline-flex items-center gap-2\", className)}\n {...props}\n >\n {slots.map((i) => {\n const ch = value[i] ?? \"\";\n const display = mask && ch !== \"\" ? \"•\" : ch;\n return (\n <input\n
|
|
17
|
+
"content": "import { forwardRef, useEffect, useRef } from \"react\";\nimport type { ClipboardEvent, HTMLAttributes, KeyboardEvent } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * PinInput — multi-slot OTP / code input primitive.\n *\n * Renders N separate boxes (default 6) that auto-advance focus on\n * input. Paste handling fills all slots from clipboard (whitespace\n * stripped). Arrow keys navigate; backspace clears current slot\n * then moves focus back when empty.\n *\n * Industry-standard pattern for email verification codes (Apple,\n * Stripe, Clerk, Auth0, GitHub two-factor).\n *\n * @example\n * <PinInput\n * length={6}\n * value={code}\n * onChange={setCode}\n * onComplete={(v) => verify(v)}\n * inputMode=\"numeric\"\n * aria-label=\"Verification code\"\n * />\n *\n * Note: value is treated as controlled. If you pass a complete value\n * on mount, onComplete will NOT fire — onComplete fires only on\n * transitions from incomplete → complete.\n */\nexport interface PinInputProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\" | \"inputMode\"> {\n length?: number;\n value?: string;\n onChange?: (value: string) => void;\n onComplete?: (value: string) => void;\n inputMode?: \"numeric\" | \"alphanumeric\";\n size?: \"sm\" | \"md\" | \"lg\";\n disabled?: boolean;\n error?: boolean;\n \"aria-label\": string;\n autoFocus?: boolean;\n mask?: boolean;\n}\n\nconst SIZE_CLASS: Record<NonNullable<PinInputProps[\"size\"]>, string> = {\n sm: \"size-8 text-body-sm\",\n md: \"size-10 text-body-md\",\n lg: \"size-12 text-title-sm\",\n};\n\nfunction sanitize(raw: string, inputMode: \"numeric\" | \"alphanumeric\"): string {\n const noWhitespace = raw.replace(/\\s/g, \"\");\n if (inputMode === \"numeric\") {\n return noWhitespace.replace(/\\D/g, \"\");\n }\n return noWhitespace.toUpperCase().replace(/[^A-Z0-9]/g, \"\");\n}\n\nconst PinInput = forwardRef<HTMLDivElement, PinInputProps>(\n (\n {\n className,\n length = 6,\n value = \"\",\n onChange,\n onComplete,\n inputMode = \"numeric\",\n size = \"md\",\n disabled = false,\n error = false,\n autoFocus = false,\n mask = false,\n \"aria-label\": ariaLabel,\n ...props\n },\n ref,\n ) => {\n const inputRefs = useRef<Array<HTMLInputElement | null>>([]);\n const wasCompleteRef = useRef<boolean>(value.length === length);\n\n // Auto-focus first slot on mount (SSR-safe)\n useEffect(() => {\n if (!autoFocus) return;\n if (typeof window === \"undefined\") return;\n inputRefs.current[0]?.focus();\n }, [autoFocus]);\n\n // Fire onComplete on transitions from incomplete → complete\n useEffect(() => {\n const isComplete = value.length === length && value.length > 0;\n if (isComplete && !wasCompleteRef.current) {\n onComplete?.(value);\n }\n wasCompleteRef.current = isComplete;\n }, [value, length, onComplete]);\n\n function commit(next: string) {\n const sanitized = sanitize(next, inputMode).slice(0, length);\n onChange?.(sanitized);\n }\n\n function handleChange(slot: number, raw: string) {\n const sanitized = sanitize(raw, inputMode);\n if (sanitized.length === 0) {\n // Clear current slot\n const next = `${value.slice(0, slot)}${value.slice(slot + 1)}`;\n commit(next);\n return;\n }\n // Take the last character typed (handles browser autocomplete that fills multiple)\n const ch = sanitized[sanitized.length - 1] ?? \"\";\n const next = `${value.slice(0, slot)}${ch}${value.slice(slot + 1)}`;\n commit(next);\n // Advance focus\n if (slot < length - 1) {\n inputRefs.current[slot + 1]?.focus();\n }\n }\n\n function handleKeyDown(slot: number, e: KeyboardEvent<HTMLInputElement>) {\n if (disabled) return;\n const slotChar = value[slot] ?? \"\";\n\n if (e.key === \"Backspace\") {\n if (slotChar === \"\") {\n // Move focus back if current is empty\n if (slot > 0) {\n inputRefs.current[slot - 1]?.focus();\n }\n } else {\n // Clear current slot, stay focused\n const next = `${value.slice(0, slot)}${value.slice(slot + 1)}`;\n commit(next);\n }\n e.preventDefault();\n } else if (e.key === \"ArrowLeft\") {\n if (slot > 0) inputRefs.current[slot - 1]?.focus();\n e.preventDefault();\n } else if (e.key === \"ArrowRight\") {\n if (slot < length - 1) inputRefs.current[slot + 1]?.focus();\n e.preventDefault();\n }\n }\n\n function handlePaste(slot: number, e: ClipboardEvent<HTMLInputElement>) {\n if (disabled) return;\n e.preventDefault();\n const pasted = e.clipboardData.getData(\"text/plain\");\n const sanitized = sanitize(pasted, inputMode);\n if (sanitized.length === 0) return;\n // Build slot-indexed array, then overwrite from `slot` onwards.\n // Previous string-concat approach didn't pad when value was shorter\n // than `slot`, which made paste-from-middle-when-empty fill from 0.\n const slotArr: string[] = Array.from({ length }, (_, i) => value[i] ?? \"\");\n const remaining = length - slot;\n const filled = sanitized.slice(0, remaining);\n for (let i = 0; i < filled.length; i++) {\n slotArr[slot + i] = filled[i] ?? \"\";\n }\n const next = slotArr.join(\"\");\n commit(next);\n // Focus the slot after the last filled, or the last slot if completed\n const focusAt = Math.min(slot + filled.length, length - 1);\n requestAnimationFrame(() => inputRefs.current[focusAt]?.focus());\n }\n\n const slots = Array.from({ length }, (_, i) => i);\n\n return (\n <div\n ref={ref}\n // biome-ignore lint/a11y/useSemanticElements: <fieldset> would force a different visual layout (rectangular border by default) and is form-bound; we use a div with role=\"group\" + aria-label for grouping semantics.\n role=\"group\"\n aria-label={ariaLabel}\n className={cn(\"inline-flex items-center gap-2\", className)}\n {...props}\n >\n {slots.map((i) => {\n const ch = value[i] ?? \"\";\n const display = mask && ch !== \"\" ? \"•\" : ch;\n return (\n <input\n key={i}\n ref={(el) => {\n inputRefs.current[i] = el;\n }}\n type=\"text\"\n inputMode={inputMode === \"numeric\" ? \"numeric\" : \"text\"}\n pattern={inputMode === \"numeric\" ? \"[0-9]*\" : undefined}\n maxLength={1}\n autoComplete={i === 0 ? \"one-time-code\" : \"off\"}\n disabled={disabled}\n value={display}\n onChange={(e) => handleChange(i, e.target.value)}\n onKeyDown={(e) => handleKeyDown(i, e)}\n onPaste={(e) => handlePaste(i, e)}\n aria-label={`Digit ${i + 1} of ${length}`}\n className={cn(\n \"rounded-md border bg-card text-center font-medium font-mono\",\n \"transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n SIZE_CLASS[size],\n error ? \"border-destructive\" : \"border-border/60 hover:border-border\",\n )}\n />\n );\n })}\n </div>\n );\n },\n);\nPinInput.displayName = \"PinInput\";\n\nexport { PinInput };\n"
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
20
|
}
|