premium-ds 0.1.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.
Files changed (257) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -0
  3. package/dist/alert.d.ts +31 -0
  4. package/dist/alert.js +6 -0
  5. package/dist/alert.js.map +1 -0
  6. package/dist/avatar-group.d.ts +13 -0
  7. package/dist/avatar-group.js +3 -0
  8. package/dist/avatar-group.js.map +1 -0
  9. package/dist/avatar.d.ts +25 -0
  10. package/dist/avatar.js +3 -0
  11. package/dist/avatar.js.map +1 -0
  12. package/dist/badge.d.ts +23 -0
  13. package/dist/badge.js +3 -0
  14. package/dist/badge.js.map +1 -0
  15. package/dist/button.d.ts +20 -0
  16. package/dist/button.js +3 -0
  17. package/dist/button.js.map +1 -0
  18. package/dist/checkbox.d.ts +25 -0
  19. package/dist/checkbox.js +3 -0
  20. package/dist/checkbox.js.map +1 -0
  21. package/dist/chunk-2OWHZ4JT.js +36 -0
  22. package/dist/chunk-2OWHZ4JT.js.map +1 -0
  23. package/dist/chunk-34SIXSYL.js +64 -0
  24. package/dist/chunk-34SIXSYL.js.map +1 -0
  25. package/dist/chunk-37O2ZXD6.js +55 -0
  26. package/dist/chunk-37O2ZXD6.js.map +1 -0
  27. package/dist/chunk-4AZL76UJ.js +89 -0
  28. package/dist/chunk-4AZL76UJ.js.map +1 -0
  29. package/dist/chunk-4HSCN5TZ.js +86 -0
  30. package/dist/chunk-4HSCN5TZ.js.map +1 -0
  31. package/dist/chunk-5DDOOT33.js +258 -0
  32. package/dist/chunk-5DDOOT33.js.map +1 -0
  33. package/dist/chunk-5FVHWIMY.js +117 -0
  34. package/dist/chunk-5FVHWIMY.js.map +1 -0
  35. package/dist/chunk-5K6KRJGX.js +147 -0
  36. package/dist/chunk-5K6KRJGX.js.map +1 -0
  37. package/dist/chunk-5PQMQBQC.js +74 -0
  38. package/dist/chunk-5PQMQBQC.js.map +1 -0
  39. package/dist/chunk-7OCTVQ7C.js +95 -0
  40. package/dist/chunk-7OCTVQ7C.js.map +1 -0
  41. package/dist/chunk-7OPMOET7.js +39 -0
  42. package/dist/chunk-7OPMOET7.js.map +1 -0
  43. package/dist/chunk-BXXS7YRC.js +270 -0
  44. package/dist/chunk-BXXS7YRC.js.map +1 -0
  45. package/dist/chunk-CV2Q4YXX.js +272 -0
  46. package/dist/chunk-CV2Q4YXX.js.map +1 -0
  47. package/dist/chunk-EIMMDWIW.js +282 -0
  48. package/dist/chunk-EIMMDWIW.js.map +1 -0
  49. package/dist/chunk-EZ2CWTBE.js +230 -0
  50. package/dist/chunk-EZ2CWTBE.js.map +1 -0
  51. package/dist/chunk-FGHDG3Y4.js +89 -0
  52. package/dist/chunk-FGHDG3Y4.js.map +1 -0
  53. package/dist/chunk-FPP2XLKX.js +127 -0
  54. package/dist/chunk-FPP2XLKX.js.map +1 -0
  55. package/dist/chunk-G6OY35DI.js +295 -0
  56. package/dist/chunk-G6OY35DI.js.map +1 -0
  57. package/dist/chunk-H6KWJNOE.js +65 -0
  58. package/dist/chunk-H6KWJNOE.js.map +1 -0
  59. package/dist/chunk-HGILYGY3.js +45 -0
  60. package/dist/chunk-HGILYGY3.js.map +1 -0
  61. package/dist/chunk-I3BCB4Z5.js +88 -0
  62. package/dist/chunk-I3BCB4Z5.js.map +1 -0
  63. package/dist/chunk-KBWNUUWM.js +582 -0
  64. package/dist/chunk-KBWNUUWM.js.map +1 -0
  65. package/dist/chunk-KN7JFAZ6.js +113 -0
  66. package/dist/chunk-KN7JFAZ6.js.map +1 -0
  67. package/dist/chunk-MEF7PI6U.js +16 -0
  68. package/dist/chunk-MEF7PI6U.js.map +1 -0
  69. package/dist/chunk-NKGMQL6I.js +310 -0
  70. package/dist/chunk-NKGMQL6I.js.map +1 -0
  71. package/dist/chunk-NMFQRGLL.js +127 -0
  72. package/dist/chunk-NMFQRGLL.js.map +1 -0
  73. package/dist/chunk-OUBWD6CX.js +433 -0
  74. package/dist/chunk-OUBWD6CX.js.map +1 -0
  75. package/dist/chunk-PFNXVBLU.js +96 -0
  76. package/dist/chunk-PFNXVBLU.js.map +1 -0
  77. package/dist/chunk-PUPZ4HME.js +165 -0
  78. package/dist/chunk-PUPZ4HME.js.map +1 -0
  79. package/dist/chunk-QFS52OK5.js +690 -0
  80. package/dist/chunk-QFS52OK5.js.map +1 -0
  81. package/dist/chunk-QNC6O3PG.js +45 -0
  82. package/dist/chunk-QNC6O3PG.js.map +1 -0
  83. package/dist/chunk-QUHOXWBK.js +82 -0
  84. package/dist/chunk-QUHOXWBK.js.map +1 -0
  85. package/dist/chunk-UIQGSTBJ.js +106 -0
  86. package/dist/chunk-UIQGSTBJ.js.map +1 -0
  87. package/dist/chunk-UJQKVP6V.js +193 -0
  88. package/dist/chunk-UJQKVP6V.js.map +1 -0
  89. package/dist/chunk-VVPGEAC6.js +11 -0
  90. package/dist/chunk-VVPGEAC6.js.map +1 -0
  91. package/dist/chunk-XA3T5KWA.js +58 -0
  92. package/dist/chunk-XA3T5KWA.js.map +1 -0
  93. package/dist/chunk-YSHJHSJM.js +19 -0
  94. package/dist/chunk-YSHJHSJM.js.map +1 -0
  95. package/dist/chunk-YVHOAVSM.js +182 -0
  96. package/dist/chunk-YVHOAVSM.js.map +1 -0
  97. package/dist/collapse.d.ts +16 -0
  98. package/dist/collapse.js +3 -0
  99. package/dist/collapse.js.map +1 -0
  100. package/dist/count-badge.d.ts +11 -0
  101. package/dist/count-badge.js +4 -0
  102. package/dist/count-badge.js.map +1 -0
  103. package/dist/date-field.d.ts +39 -0
  104. package/dist/date-field.js +8 -0
  105. package/dist/date-field.js.map +1 -0
  106. package/dist/date-range-field.d.ts +30 -0
  107. package/dist/date-range-field.js +8 -0
  108. package/dist/date-range-field.js.map +1 -0
  109. package/dist/datetime-field.d.ts +28 -0
  110. package/dist/datetime-field.js +10 -0
  111. package/dist/datetime-field.js.map +1 -0
  112. package/dist/dialog.d.ts +26 -0
  113. package/dist/dialog.js +7 -0
  114. package/dist/dialog.js.map +1 -0
  115. package/dist/index.d.ts +35 -0
  116. package/dist/index.js +40 -0
  117. package/dist/index.js.map +1 -0
  118. package/dist/motion-tokens.d.ts +29 -0
  119. package/dist/motion-tokens.js +3 -0
  120. package/dist/motion-tokens.js.map +1 -0
  121. package/dist/multi-select.d.ts +25 -0
  122. package/dist/multi-select.js +7 -0
  123. package/dist/multi-select.js.map +1 -0
  124. package/dist/number-field.d.ts +24 -0
  125. package/dist/number-field.js +4 -0
  126. package/dist/number-field.js.map +1 -0
  127. package/dist/otp-field.d.ts +20 -0
  128. package/dist/otp-field.js +3 -0
  129. package/dist/otp-field.js.map +1 -0
  130. package/dist/overlay.d.ts +31 -0
  131. package/dist/overlay.js +4 -0
  132. package/dist/overlay.js.map +1 -0
  133. package/dist/pagination.d.ts +24 -0
  134. package/dist/pagination.js +5 -0
  135. package/dist/pagination.js.map +1 -0
  136. package/dist/radio-group.d.ts +46 -0
  137. package/dist/radio-group.js +6 -0
  138. package/dist/radio-group.js.map +1 -0
  139. package/dist/select-core-SAyS-8w0.d.ts +16 -0
  140. package/dist/select.d.ts +27 -0
  141. package/dist/select.js +7 -0
  142. package/dist/select.js.map +1 -0
  143. package/dist/status-badge.d.ts +17 -0
  144. package/dist/status-badge.js +5 -0
  145. package/dist/status-badge.js.map +1 -0
  146. package/dist/table.d.ts +65 -0
  147. package/dist/table.js +5 -0
  148. package/dist/table.js.map +1 -0
  149. package/dist/tabs.d.ts +44 -0
  150. package/dist/tabs.js +5 -0
  151. package/dist/tabs.js.map +1 -0
  152. package/dist/tag.d.ts +28 -0
  153. package/dist/tag.js +5 -0
  154. package/dist/tag.js.map +1 -0
  155. package/dist/text-field.d.ts +30 -0
  156. package/dist/text-field.js +6 -0
  157. package/dist/text-field.js.map +1 -0
  158. package/dist/textarea.d.ts +33 -0
  159. package/dist/textarea.js +5 -0
  160. package/dist/textarea.js.map +1 -0
  161. package/dist/time-field.d.ts +27 -0
  162. package/dist/time-field.js +6 -0
  163. package/dist/time-field.js.map +1 -0
  164. package/dist/toast-store.d.ts +75 -0
  165. package/dist/toast-store.js +3 -0
  166. package/dist/toast-store.js.map +1 -0
  167. package/dist/toast.d.ts +3 -0
  168. package/dist/toast.js +6 -0
  169. package/dist/toast.js.map +1 -0
  170. package/dist/toggle-tag.d.ts +24 -0
  171. package/dist/toggle-tag.js +4 -0
  172. package/dist/toggle-tag.js.map +1 -0
  173. package/dist/toggle.d.ts +21 -0
  174. package/dist/toggle.js +3 -0
  175. package/dist/toggle.js.map +1 -0
  176. package/dist/tooltip.d.ts +27 -0
  177. package/dist/tooltip.js +4 -0
  178. package/dist/tooltip.js.map +1 -0
  179. package/llms.txt +165 -0
  180. package/package.json +205 -0
  181. package/src/components/alert/Alert.tsx +118 -0
  182. package/src/components/alert/alert.css +136 -0
  183. package/src/components/avatar/Avatar.tsx +128 -0
  184. package/src/components/avatar/AvatarGroup.tsx +50 -0
  185. package/src/components/avatar/avatar.css +200 -0
  186. package/src/components/badge/Badge.tsx +66 -0
  187. package/src/components/badge/CountBadge.tsx +46 -0
  188. package/src/components/badge/StatusBadge.tsx +132 -0
  189. package/src/components/badge/badge.css +243 -0
  190. package/src/components/button/Button.tsx +68 -0
  191. package/src/components/button/button.css +222 -0
  192. package/src/components/checkbox/Checkbox.tsx +90 -0
  193. package/src/components/checkbox/checkbox.css +179 -0
  194. package/src/components/date-picker/DateField.tsx +362 -0
  195. package/src/components/date-picker/DateRangeField.tsx +533 -0
  196. package/src/components/date-picker/DateTimeField.tsx +177 -0
  197. package/src/components/date-picker/TimeField.tsx +100 -0
  198. package/src/components/date-picker/date-picker.css +591 -0
  199. package/src/components/date-picker/date-utils.ts +55 -0
  200. package/src/components/date-picker/field-shell.tsx +78 -0
  201. package/src/components/date-picker/glide-pill.tsx +81 -0
  202. package/src/components/date-picker/time-core.tsx +305 -0
  203. package/src/components/dialog/Dialog.tsx +181 -0
  204. package/src/components/dialog/dialog.css +170 -0
  205. package/src/components/glass/glass.css +100 -0
  206. package/src/components/icon/Icon.tsx +76 -0
  207. package/src/components/icon/IconSlot.tsx +11 -0
  208. package/src/components/icon/icon.css +33 -0
  209. package/src/components/input/NumberField.tsx +117 -0
  210. package/src/components/input/OtpField.tsx +118 -0
  211. package/src/components/input/TextField.tsx +123 -0
  212. package/src/components/input/input.css +335 -0
  213. package/src/components/motion/Collapse.tsx +33 -0
  214. package/src/components/motion/collapse.css +41 -0
  215. package/src/components/overlay/Overlay.tsx +239 -0
  216. package/src/components/overlay/overlay-core.tsx +565 -0
  217. package/src/components/overlay/overlay.css +119 -0
  218. package/src/components/overlay/sheet-drag.tsx +146 -0
  219. package/src/components/pagination/Pagination.tsx +140 -0
  220. package/src/components/pagination/pagination.css +48 -0
  221. package/src/components/radio-group/RadioGroup.tsx +182 -0
  222. package/src/components/radio-group/radio-group.css +277 -0
  223. package/src/components/select/MultiSelect.tsx +251 -0
  224. package/src/components/select/Select.tsx +235 -0
  225. package/src/components/select/select-core.tsx +417 -0
  226. package/src/components/select/select.css +386 -0
  227. package/src/components/table/Table.tsx +433 -0
  228. package/src/components/table/table.css +348 -0
  229. package/src/components/tabs/Tabs.tsx +371 -0
  230. package/src/components/tabs/tabs.css +228 -0
  231. package/src/components/tag/Tag.tsx +145 -0
  232. package/src/components/tag/ToggleTag.tsx +125 -0
  233. package/src/components/tag/tag.css +248 -0
  234. package/src/components/textarea/Textarea.tsx +197 -0
  235. package/src/components/textarea/textarea.css +219 -0
  236. package/src/components/toast/Toast.tsx +349 -0
  237. package/src/components/toast/toast-store.ts +266 -0
  238. package/src/components/toast/toast.css +233 -0
  239. package/src/components/toggle/Toggle.tsx +94 -0
  240. package/src/components/toggle/toggle.css +152 -0
  241. package/src/components/tooltip/Tooltip.tsx +365 -0
  242. package/src/components/tooltip/tooltip.css +86 -0
  243. package/src/index.ts +42 -0
  244. package/src/styles.css +39 -0
  245. package/src/tokens/avatar.css +20 -0
  246. package/src/tokens/color.css +56 -0
  247. package/src/tokens/elevation.css +20 -0
  248. package/src/tokens/fonts.css +3 -0
  249. package/src/tokens/glass.css +21 -0
  250. package/src/tokens/icons.css +7 -0
  251. package/src/tokens/layers.css +6 -0
  252. package/src/tokens/motion-tokens.ts +72 -0
  253. package/src/tokens/motion.css +49 -0
  254. package/src/tokens/radius.css +11 -0
  255. package/src/tokens/semantic.css +75 -0
  256. package/src/tokens/spacing.css +26 -0
  257. package/src/tokens/typography.css +54 -0
@@ -0,0 +1,170 @@
1
+ /* dialog.css - the styled dialog surface (Overlay owns modality + motion). */
2
+
3
+ /* The panel - flex column so the body scrolls between a pinned header + footer; padding lives on the parts. */
4
+ .dialog {
5
+ --dialog-w: 32rem;
6
+ position: relative;
7
+ margin: 0;
8
+ display: flex;
9
+ flex-direction: column;
10
+ min-height: 0;
11
+ width: var(--dialog-w);
12
+ max-width: 100%;
13
+ max-height: min(85dvh, 44rem);
14
+ padding: 0;
15
+ overflow: hidden;
16
+ background: var(--bg-surface);
17
+ border: var(--border-hairline) solid var(--border-default);
18
+ border-radius: var(--radius-xl);
19
+ box-shadow: var(--shadow-xl);
20
+ color: var(--text-body);
21
+ font-family: var(--font-sans);
22
+ }
23
+
24
+ .dialog[data-size='sm'] {
25
+ --dialog-w: 25rem;
26
+ }
27
+ .dialog[data-size='lg'] {
28
+ --dialog-w: 40rem;
29
+ }
30
+
31
+ /* Header - icon - heading - close; flex-start keeps a tall heading top-aligned with the badge. */
32
+ .dialog__header {
33
+ flex: 0 0 auto;
34
+ display: flex;
35
+ align-items: flex-start;
36
+ gap: var(--space-3);
37
+ padding: var(--space-5) var(--space-5) var(--space-4);
38
+ transition:
39
+ border-color var(--duration-base) var(--ease-standard),
40
+ box-shadow var(--duration-base) var(--ease-standard);
41
+ border-bottom: var(--border-hairline) solid transparent;
42
+ }
43
+ /* divider + faint lift appears only once the body is scrolled under it */
44
+ .dialog:has(.dialog__body[data-scroll-top]) .dialog__header {
45
+ border-bottom-color: var(--border-subtle);
46
+ box-shadow: var(--shadow-xs);
47
+ }
48
+
49
+ .dialog__icon {
50
+ flex: 0 0 auto;
51
+ display: grid;
52
+ place-items: center;
53
+ width: 2.25rem;
54
+ height: 2.25rem;
55
+ border-radius: var(--radius-md);
56
+ background: var(--accent-subtle);
57
+ color: var(--text-accent);
58
+ font-size: var(--icon-md);
59
+ }
60
+ .dialog[data-tone='danger'] .dialog__icon {
61
+ background: var(--danger-subtle);
62
+ color: var(--danger-text);
63
+ }
64
+
65
+ .dialog__heading {
66
+ flex: 1 1 auto;
67
+ min-width: 0;
68
+ display: flex;
69
+ flex-direction: column;
70
+ gap: var(--space-1);
71
+ /* nudge text to optical-center against the 36px icon badge when present */
72
+ padding-top: var(--space-px);
73
+ }
74
+ .dialog__title {
75
+ margin: 0;
76
+ color: var(--text-strong);
77
+ font: var(--weight-semibold) var(--size-heading)/var(--leading-heading) var(--font-sans);
78
+ letter-spacing: var(--tracking-tight);
79
+ text-wrap: pretty;
80
+ }
81
+ .dialog__desc {
82
+ margin: 0;
83
+ color: var(--text-muted);
84
+ font: var(--type-body);
85
+ text-wrap: pretty;
86
+ }
87
+
88
+ /* close - a quiet ghost icon button; the hit box is bigger than the glyph */
89
+ .dialog__close {
90
+ flex: 0 0 auto;
91
+ appearance: none;
92
+ margin: calc(var(--space-1) * -1) calc(var(--space-1) * -1) 0 0;
93
+ display: grid;
94
+ place-items: center;
95
+ width: 1.75rem;
96
+ height: 1.75rem;
97
+ padding: 0;
98
+ border: none;
99
+ border-radius: var(--radius-md);
100
+ background: transparent;
101
+ color: var(--text-subtle);
102
+ cursor: pointer;
103
+ font-size: var(--icon-sm);
104
+ transition: var(--transition-control);
105
+ }
106
+ .dialog__close:hover {
107
+ background: var(--bg-subtle);
108
+ color: var(--text-strong);
109
+ }
110
+ .dialog__close:active {
111
+ background: var(--bg-muted);
112
+ }
113
+ .dialog__close:focus-visible {
114
+ outline: none;
115
+ color: var(--text-strong);
116
+ box-shadow: var(--ring-accent);
117
+ }
118
+
119
+ /* Body - the scroll region; min-height:0 lets it shrink so header/footer stay pinned. */
120
+ .dialog__body {
121
+ flex: 1 1 auto;
122
+ min-height: 0;
123
+ overflow-y: auto;
124
+ overscroll-behavior: contain;
125
+ padding: 0 var(--space-5) var(--space-5);
126
+ color: var(--text-body);
127
+ font: var(--type-body);
128
+ }
129
+ .dialog:has(.dialog__icon) .dialog__body {
130
+ padding-left: calc(var(--space-5) + 2.25rem + var(--space-3));
131
+ }
132
+ .dialog:has(.dialog__footer) .dialog__body {
133
+ padding-bottom: var(--space-4);
134
+ }
135
+ .dialog__body > :first-child {
136
+ margin-top: 0;
137
+ }
138
+ .dialog__body > :last-child {
139
+ margin-bottom: 0;
140
+ }
141
+
142
+ .dialog__body {
143
+ scrollbar-width: thin;
144
+ scrollbar-color: var(--border-strong) transparent;
145
+ }
146
+ .dialog__body::-webkit-scrollbar {
147
+ width: 10px;
148
+ }
149
+ .dialog__body::-webkit-scrollbar-thumb {
150
+ background: var(--border-strong);
151
+ border-radius: var(--radius-full);
152
+ border: 3px solid var(--bg-surface);
153
+ background-clip: padding-box;
154
+ }
155
+
156
+ /* Footer - right-aligned actions; gains a top divider only when the body scrolls under it. */
157
+ .dialog__footer {
158
+ flex: 0 0 auto;
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: flex-end;
162
+ gap: var(--space-2);
163
+ flex-wrap: wrap;
164
+ padding: var(--space-4) var(--space-5) var(--space-5);
165
+ border-top: var(--border-hairline) solid transparent;
166
+ transition: border-color var(--duration-base) var(--ease-standard);
167
+ }
168
+ .dialog:has(.dialog__body[data-scroll-bottom]) .dialog__footer {
169
+ border-top-color: var(--border-subtle);
170
+ }
@@ -0,0 +1,100 @@
1
+ /* Glass surface - translucent tint + backdrop blur + a sheen layer; consumer sets --glass-tint and --glass-fg. */
2
+
3
+ .glass {
4
+ --glass-fg: var(--text-body);
5
+ --glass-tint: var(--glass-tint-neutral);
6
+
7
+ position: relative;
8
+ isolation: isolate; /* local stacking for the sheen */
9
+ color: var(--glass-fg);
10
+
11
+ background: var(--glass-tint);
12
+ -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
13
+ backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
14
+ box-shadow:
15
+ var(--glass-highlight),
16
+ inset 0 0 0 var(--border-hairline) color-mix(in oklab, var(--glass-fg) 22%, transparent),
17
+ var(--glass-shadow);
18
+
19
+ transition:
20
+ transform var(--duration-base) var(--ease-standard),
21
+ box-shadow var(--duration-base) var(--ease-standard),
22
+ background-color var(--duration-base) var(--ease-standard);
23
+ }
24
+
25
+ /* Sheen on its own layer so hover animates opacity/transform - gradients can't transition directly. */
26
+ .glass::before {
27
+ content: '';
28
+ position: absolute;
29
+ inset: 0;
30
+ z-index: 0;
31
+ border-radius: inherit;
32
+ background: var(--glass-sheen);
33
+ opacity: var(--glass-sheen-rest);
34
+ pointer-events: none;
35
+ transition:
36
+ opacity var(--duration-base) var(--ease-standard),
37
+ transform var(--duration-slow) var(--ease-entrance);
38
+ }
39
+ .glass > * {
40
+ position: relative;
41
+ z-index: 1;
42
+ } /* content rides above the sheen */
43
+
44
+ .glass--strong {
45
+ --glass-blur: var(--glass-blur-strong);
46
+ }
47
+
48
+ .glass--interactive:hover {
49
+ transform: translateY(calc(var(--space-px) * -1));
50
+ box-shadow:
51
+ var(--glass-highlight),
52
+ inset 0 0 0 var(--border-hairline) color-mix(in oklab, var(--glass-fg) 26%, transparent),
53
+ var(--glass-shadow-hover);
54
+ }
55
+ .glass--interactive:hover::before {
56
+ opacity: var(--glass-sheen-hover);
57
+ transform: translateY(-8%);
58
+ }
59
+
60
+ /* Glint - one-shot light sweep; add .glass-glint to fire, remove after it ends (sits above content, z-index 2). */
61
+ .glass-glint::after {
62
+ content: '';
63
+ position: absolute;
64
+ inset: 0;
65
+ z-index: 2;
66
+ border-radius: inherit;
67
+ pointer-events: none;
68
+ opacity: 0; /* resting state is invisible - no mid-sweep flash */
69
+ background: linear-gradient(118deg, transparent 36%, rgb(255 255 255 / 0.7) 50%, transparent 64%);
70
+ animation: glass-glint var(--duration-slow) var(--ease-standard) 1 forwards;
71
+ }
72
+ @keyframes glass-glint {
73
+ 0% {
74
+ opacity: 0;
75
+ transform: translateX(-28%);
76
+ }
77
+ 40% {
78
+ opacity: 1;
79
+ }
80
+ 100% {
81
+ opacity: 0;
82
+ transform: translateX(28%);
83
+ }
84
+ }
85
+
86
+ @media (prefers-reduced-motion: reduce) {
87
+ .glass,
88
+ .glass::before {
89
+ transition: none;
90
+ }
91
+ .glass--interactive:hover {
92
+ transform: none;
93
+ }
94
+ .glass--interactive:hover::before {
95
+ transform: none;
96
+ }
97
+ .glass-glint::after {
98
+ animation: none;
99
+ }
100
+ }
@@ -0,0 +1,76 @@
1
+ 'use client';
2
+
3
+ // Icon - internal, curated glyph set; statically imported so the bundle ships only these (tree-shaking).
4
+
5
+ import {
6
+ ArrowUp,
7
+ Calendar,
8
+ CalendarDot,
9
+ CaretDown,
10
+ CaretLeft,
11
+ CaretRight,
12
+ CaretUp,
13
+ Check,
14
+ CheckCircle,
15
+ Clock,
16
+ Globe,
17
+ Info,
18
+ MagnifyingGlass,
19
+ Warning,
20
+ WarningCircle,
21
+ X,
22
+ } from '@phosphor-icons/react';
23
+ import type { IconWeight } from '@phosphor-icons/react';
24
+
25
+ type Glyph = typeof X;
26
+
27
+ const REGISTRY = {
28
+ x: X,
29
+ close: X,
30
+ check: Check,
31
+ 'arrow-up': ArrowUp,
32
+ 'caret-up': CaretUp,
33
+ 'caret-down': CaretDown,
34
+ 'caret-left': CaretLeft,
35
+ 'caret-right': CaretRight,
36
+ 'magnifying-glass': MagnifyingGlass,
37
+ calendar: Calendar,
38
+ 'calendar-dot': CalendarDot,
39
+ clock: Clock,
40
+ globe: Globe,
41
+ info: Info,
42
+ warning: Warning,
43
+ 'warning-circle': WarningCircle,
44
+ 'check-circle': CheckCircle,
45
+ } satisfies Record<string, Glyph>;
46
+
47
+ export type IconName = keyof typeof REGISTRY;
48
+ export type IconSize = 'sm' | 'md' | 'lg';
49
+ export type { IconWeight };
50
+
51
+ export interface IconProps {
52
+ /** A curated glyph name (see the registry in this file). */
53
+ name: IconName;
54
+ /** Token size: sm 16 - md 20 (default) - lg 24. */
55
+ size?: IconSize;
56
+ /** `fill` marks active/selected; Regular otherwise. */
57
+ weight?: IconWeight;
58
+ /** If set, the icon is meaningful and exposed to AT; omit for decorative. */
59
+ label?: string;
60
+ className?: string;
61
+ }
62
+
63
+ const SIZE_PX: Record<IconSize, number> = { sm: 16, md: 20, lg: 24 };
64
+
65
+ export function Icon({ name, size = 'md', weight = 'regular', label, className = '' }: IconProps) {
66
+ const Glyph = REGISTRY[name];
67
+ const a11y = label ? { role: 'img', 'aria-label': label } : { 'aria-hidden': true };
68
+ return (
69
+ <Glyph
70
+ size={SIZE_PX[size]}
71
+ weight={weight}
72
+ className={('icon ' + className).trim()}
73
+ {...a11y}
74
+ />
75
+ );
76
+ }
@@ -0,0 +1,11 @@
1
+ 'use client';
2
+
3
+ // IconSlot - sizes a consumer-supplied icon node to the --icon-* tokens and currentColor.
4
+
5
+ import type { ReactNode } from 'react';
6
+ import type { IconSize } from './Icon';
7
+
8
+ export function IconSlot({ size = 'md', children }: { size?: IconSize; children?: ReactNode }) {
9
+ if (!children) return null;
10
+ return <span className={`icon-slot icon-slot--${size}`}>{children}</span>;
11
+ }
@@ -0,0 +1,33 @@
1
+ /* Icon - base layout (inline/flex seating); size and color come from props/tokens. */
2
+
3
+ .icon {
4
+ flex: none;
5
+ vertical-align: middle;
6
+ }
7
+
8
+ /* icon-slot - locks a consumer icon node to the token size and currentColor. */
9
+ .icon-slot {
10
+ display: inline-flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ flex: none;
14
+ color: currentColor;
15
+ }
16
+ .icon-slot--sm {
17
+ width: var(--icon-sm);
18
+ height: var(--icon-sm);
19
+ }
20
+ .icon-slot--md {
21
+ width: var(--icon-md);
22
+ height: var(--icon-md);
23
+ }
24
+ .icon-slot--lg {
25
+ width: var(--icon-lg);
26
+ height: var(--icon-lg);
27
+ }
28
+ .icon-slot > svg,
29
+ .icon-slot > img {
30
+ width: 100%;
31
+ height: 100%;
32
+ display: block;
33
+ }
@@ -0,0 +1,117 @@
1
+ 'use client';
2
+
3
+ // NumberField.tsx - numeric input: tabular figures, caret steppers, unit suffix, min/max clamp, arrow stepping.
4
+
5
+ import type { ChangeEvent, InputHTMLAttributes, KeyboardEvent, ReactNode } from 'react';
6
+ import { Icon } from '../icon/Icon';
7
+
8
+ export interface NumberFieldProps extends Omit<
9
+ InputHTMLAttributes<HTMLInputElement>,
10
+ 'size' | 'value' | 'onChange' | 'min' | 'max' | 'step'
11
+ > {
12
+ id?: string;
13
+ label?: ReactNode;
14
+ helper?: ReactNode;
15
+ /** Error message + error state. */
16
+ error?: ReactNode;
17
+ /** Unit suffix shown inside the field (e.g. "days", "%"). */
18
+ unit?: string;
19
+ /** Bounds + step. Steppers disable at min/max; value clamps on blur. */
20
+ min?: number;
21
+ max?: number;
22
+ step?: number;
23
+ /** Controlled numeric value. */
24
+ value?: number | string;
25
+ /** Called with the next clamped number. */
26
+ onChange?: (value: number) => void;
27
+ size?: 'sm' | 'md' | 'lg';
28
+ }
29
+
30
+ export function NumberField({
31
+ id,
32
+ label,
33
+ helper,
34
+ error,
35
+ unit,
36
+ min = 0,
37
+ max = Infinity,
38
+ step = 1,
39
+ value,
40
+ onChange,
41
+ size,
42
+ className = '',
43
+ ...rest
44
+ }: NumberFieldProps) {
45
+ const v = parseInt((value ?? 0) as any, 10) || 0;
46
+ const set = (n: number) => onChange && onChange(Math.min(max, Math.max(min, n)));
47
+ const cls = [
48
+ 'fld',
49
+ 'numf',
50
+ unit ? 'numf--unit' : '',
51
+ size === 'sm' ? 'fld--sm' : size === 'lg' ? 'fld--lg' : '',
52
+ error ? 'is-error' : '',
53
+ className,
54
+ ]
55
+ .filter(Boolean)
56
+ .join(' ');
57
+
58
+ return (
59
+ <div className={cls}>
60
+ {label && (
61
+ <label className="fld__label" htmlFor={id}>
62
+ {label}
63
+ </label>
64
+ )}
65
+ <div className="fld__control">
66
+ <input
67
+ id={id}
68
+ className="fld__input"
69
+ type="text"
70
+ inputMode="numeric"
71
+ value={value}
72
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
73
+ set(parseInt(e.target.value.replace(/[^\d]/g, '') || '0', 10))
74
+ }
75
+ onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
76
+ if (e.key === 'ArrowUp') {
77
+ e.preventDefault();
78
+ set(v + step);
79
+ }
80
+ if (e.key === 'ArrowDown') {
81
+ e.preventDefault();
82
+ set(v - step);
83
+ }
84
+ }}
85
+ {...rest}
86
+ />
87
+ {unit && <span className="numf__unit">{unit}</span>}
88
+ <div className="numf__steppers">
89
+ <button
90
+ type="button"
91
+ className="numf__step"
92
+ aria-label="Increase"
93
+ disabled={v >= max}
94
+ onClick={() => set(v + step)}
95
+ >
96
+ <Icon name="caret-up" size="sm" />
97
+ </button>
98
+ <button
99
+ type="button"
100
+ className="numf__step"
101
+ aria-label="Decrease"
102
+ disabled={v <= min}
103
+ onClick={() => set(v - step)}
104
+ >
105
+ <Icon name="caret-down" size="sm" />
106
+ </button>
107
+ </div>
108
+ </div>
109
+ {(error || helper) && (
110
+ <div className="fld__msg">
111
+ {error && <Icon name="warning-circle" size="sm" weight="fill" />}
112
+ {error || helper}
113
+ </div>
114
+ )}
115
+ </div>
116
+ );
117
+ }
@@ -0,0 +1,118 @@
1
+ 'use client';
2
+
3
+ // OtpField.tsx - segmented one-time-code input: mono slots, auto-advance, arrow nav, paste-to-fill, grouping.
4
+
5
+ import { useRef } from 'react';
6
+ import type {
7
+ ChangeEvent,
8
+ ClipboardEvent,
9
+ FocusEvent,
10
+ KeyboardEvent,
11
+ MouseEvent,
12
+ ReactNode,
13
+ } from 'react';
14
+
15
+ export interface OtpFieldProps {
16
+ /** Number of slots. @default 6 */
17
+ length?: number;
18
+ /** Controlled value (digit string). */
19
+ value?: string;
20
+ /** Called with the next value string. */
21
+ onChange?: (value: string) => void;
22
+ /** Insert a separator every N slots (e.g. 3 - "-------"). */
23
+ group?: number;
24
+ /** Error state (red slots + ring). */
25
+ error?: boolean;
26
+ disabled?: boolean;
27
+ size?: 'sm';
28
+ className?: string;
29
+ }
30
+
31
+ export function OtpField({
32
+ length = 6,
33
+ value = '',
34
+ onChange,
35
+ group,
36
+ error,
37
+ disabled,
38
+ size,
39
+ className = '',
40
+ }: OtpFieldProps) {
41
+ const refs = useRef<(HTMLInputElement | null)[]>([]);
42
+ const chars = String(value).slice(0, length).padEnd(length).split('');
43
+ const emit = (s: string) => onChange && onChange(s.replace(/ +$/, '').slice(0, length));
44
+ const setAt = (i: number, ch: string) => {
45
+ const a = chars.slice();
46
+ a[i] = ch || ' ';
47
+ emit(a.join(''));
48
+ };
49
+ // place caret at the end (no selection highlight); replacement handled by slice(-1)
50
+ const caretEnd = (el: HTMLInputElement | null) => {
51
+ if (!el) return;
52
+ const n = el.value.length;
53
+ try {
54
+ el.setSelectionRange(n, n);
55
+ } catch {}
56
+ };
57
+ const go = (j: number) => {
58
+ const el = refs.current[j];
59
+ if (el) {
60
+ el.focus();
61
+ caretEnd(el);
62
+ }
63
+ };
64
+
65
+ const cls = ['otp', size === 'sm' ? 'otp--sm' : '', error ? 'is-error' : '', className]
66
+ .filter(Boolean)
67
+ .join(' ');
68
+
69
+ const cells: ReactNode[] = [];
70
+ for (let i = 0; i < length; i++) {
71
+ if (group && i > 0 && i % group === 0)
72
+ cells.push(<span key={`sep${i}`} className="otp__sep" />);
73
+ cells.push(
74
+ <input
75
+ key={i}
76
+ ref={(el) => {
77
+ refs.current[i] = el;
78
+ }}
79
+ className="otp__slot"
80
+ inputMode="numeric"
81
+ autoComplete="one-time-code"
82
+ disabled={disabled}
83
+ value={chars[i].trim()}
84
+ data-filled={chars[i].trim() ? 'true' : 'false'}
85
+ onFocus={(e: FocusEvent<HTMLInputElement>) => caretEnd(e.target)}
86
+ onMouseDown={(e: MouseEvent<HTMLInputElement>) => {
87
+ e.preventDefault();
88
+ go(i);
89
+ }}
90
+ onChange={(e: ChangeEvent<HTMLInputElement>) => {
91
+ const ch = e.target.value.replace(/[^\d]/g, '').slice(-1);
92
+ setAt(i, ch);
93
+ if (ch && i < length - 1) go(i + 1);
94
+ }}
95
+ onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
96
+ if (e.key === 'Backspace' && !chars[i].trim() && i > 0) {
97
+ e.preventDefault();
98
+ setAt(i - 1, '');
99
+ go(i - 1);
100
+ } else if (e.key === 'ArrowLeft' && i > 0) {
101
+ e.preventDefault();
102
+ go(i - 1);
103
+ } else if (e.key === 'ArrowRight' && i < length - 1) {
104
+ e.preventDefault();
105
+ go(i + 1);
106
+ }
107
+ }}
108
+ onPaste={(e: ClipboardEvent<HTMLInputElement>) => {
109
+ e.preventDefault();
110
+ const d = (e.clipboardData.getData('text') || '').replace(/[^\d]/g, '');
111
+ emit(chars.slice(0, i).join('') + d);
112
+ refs.current[Math.min(i + d.length, length - 1)]?.focus();
113
+ }}
114
+ />,
115
+ );
116
+ }
117
+ return <div className={cls}>{cells}</div>;
118
+ }