@zyncat/ui 0.8.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 (334) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -0
  3. package/dist/alert.css +132 -0
  4. package/dist/alert.d.ts +46 -0
  5. package/dist/alert.js +80 -0
  6. package/dist/alert.js.map +1 -0
  7. package/dist/avatar-group.d.ts +24 -0
  8. package/dist/avatar-group.js +23 -0
  9. package/dist/avatar-group.js.map +1 -0
  10. package/dist/avatar.css +200 -0
  11. package/dist/avatar.d.ts +35 -0
  12. package/dist/avatar.js +67 -0
  13. package/dist/avatar.js.map +1 -0
  14. package/dist/badge.css +182 -0
  15. package/dist/badge.d.ts +34 -0
  16. package/dist/badge.js +4 -0
  17. package/dist/badge.js.map +1 -0
  18. package/dist/button.css +269 -0
  19. package/dist/button.d.ts +44 -0
  20. package/dist/button.js +4 -0
  21. package/dist/button.js.map +1 -0
  22. package/dist/checkbox.css +177 -0
  23. package/dist/checkbox.d.ts +35 -0
  24. package/dist/checkbox.js +5 -0
  25. package/dist/checkbox.js.map +1 -0
  26. package/dist/chunk-2ERBBFZS.js +12 -0
  27. package/dist/chunk-2ERBBFZS.js.map +1 -0
  28. package/dist/chunk-2HORSLXJ.js +191 -0
  29. package/dist/chunk-2HORSLXJ.js.map +1 -0
  30. package/dist/chunk-36WVAQKL.js +40 -0
  31. package/dist/chunk-36WVAQKL.js.map +1 -0
  32. package/dist/chunk-3SBCBYB7.js +575 -0
  33. package/dist/chunk-3SBCBYB7.js.map +1 -0
  34. package/dist/chunk-4R2GQO43.js +125 -0
  35. package/dist/chunk-4R2GQO43.js.map +1 -0
  36. package/dist/chunk-64UU4LGU.js +258 -0
  37. package/dist/chunk-64UU4LGU.js.map +1 -0
  38. package/dist/chunk-7SZCJ7WI.js +35 -0
  39. package/dist/chunk-7SZCJ7WI.js.map +1 -0
  40. package/dist/chunk-BIDVNLJO.js +38 -0
  41. package/dist/chunk-BIDVNLJO.js.map +1 -0
  42. package/dist/chunk-CONSWNIV.js +17 -0
  43. package/dist/chunk-CONSWNIV.js.map +1 -0
  44. package/dist/chunk-EQ32F2QJ.js +75 -0
  45. package/dist/chunk-EQ32F2QJ.js.map +1 -0
  46. package/dist/chunk-GY7JOPUQ.js +111 -0
  47. package/dist/chunk-GY7JOPUQ.js.map +1 -0
  48. package/dist/chunk-I4XQ4PV2.js +30 -0
  49. package/dist/chunk-I4XQ4PV2.js.map +1 -0
  50. package/dist/chunk-JSKXZMLH.js +67 -0
  51. package/dist/chunk-JSKXZMLH.js.map +1 -0
  52. package/dist/chunk-L3Q3VDIW.js +295 -0
  53. package/dist/chunk-L3Q3VDIW.js.map +1 -0
  54. package/dist/chunk-LDC3RIYA.js +62 -0
  55. package/dist/chunk-LDC3RIYA.js.map +1 -0
  56. package/dist/chunk-MKIQFK7S.js +43 -0
  57. package/dist/chunk-MKIQFK7S.js.map +1 -0
  58. package/dist/chunk-MMRIQNEN.js +20 -0
  59. package/dist/chunk-MMRIQNEN.js.map +1 -0
  60. package/dist/chunk-MXOPCFWR.js +59 -0
  61. package/dist/chunk-MXOPCFWR.js.map +1 -0
  62. package/dist/chunk-NEF5XAHG.js +17 -0
  63. package/dist/chunk-NEF5XAHG.js.map +1 -0
  64. package/dist/chunk-NNXJCLTV.js +22 -0
  65. package/dist/chunk-NNXJCLTV.js.map +1 -0
  66. package/dist/chunk-O4N2OBTR.js +94 -0
  67. package/dist/chunk-O4N2OBTR.js.map +1 -0
  68. package/dist/chunk-PMECHR3A.js +38 -0
  69. package/dist/chunk-PMECHR3A.js.map +1 -0
  70. package/dist/chunk-QDK66S4G.js +44 -0
  71. package/dist/chunk-QDK66S4G.js.map +1 -0
  72. package/dist/chunk-QGNC7LH5.js +8 -0
  73. package/dist/chunk-QGNC7LH5.js.map +1 -0
  74. package/dist/chunk-QMSJZCK3.js +398 -0
  75. package/dist/chunk-QMSJZCK3.js.map +1 -0
  76. package/dist/chunk-S5ZTZANS.js +57 -0
  77. package/dist/chunk-S5ZTZANS.js.map +1 -0
  78. package/dist/chunk-S7B34VGN.js +14 -0
  79. package/dist/chunk-S7B34VGN.js.map +1 -0
  80. package/dist/chunk-TCUCYFT7.js +25 -0
  81. package/dist/chunk-TCUCYFT7.js.map +1 -0
  82. package/dist/chunk-TYHR4VNM.js +193 -0
  83. package/dist/chunk-TYHR4VNM.js.map +1 -0
  84. package/dist/chunk-U5QQZC3U.js +56 -0
  85. package/dist/chunk-U5QQZC3U.js.map +1 -0
  86. package/dist/chunk-UE63OOVG.js +13 -0
  87. package/dist/chunk-UE63OOVG.js.map +1 -0
  88. package/dist/chunk-VTJTGVZZ.js +22 -0
  89. package/dist/chunk-VTJTGVZZ.js.map +1 -0
  90. package/dist/chunk-W3SHPKSB.js +49 -0
  91. package/dist/chunk-W3SHPKSB.js.map +1 -0
  92. package/dist/chunk-XZAEIQDR.js +188 -0
  93. package/dist/chunk-XZAEIQDR.js.map +1 -0
  94. package/dist/chunk-YGGCQNNU.js +92 -0
  95. package/dist/chunk-YGGCQNNU.js.map +1 -0
  96. package/dist/collapse.css +88 -0
  97. package/dist/collapse.d.ts +46 -0
  98. package/dist/collapse.js +5 -0
  99. package/dist/collapse.js.map +1 -0
  100. package/dist/count-badge.d.ts +13 -0
  101. package/dist/count-badge.js +13 -0
  102. package/dist/count-badge.js.map +1 -0
  103. package/dist/date-field.d.ts +25 -0
  104. package/dist/date-field.js +87 -0
  105. package/dist/date-field.js.map +1 -0
  106. package/dist/date-picker.css +595 -0
  107. package/dist/date-range-field.d.ts +33 -0
  108. package/dist/date-range-field.js +404 -0
  109. package/dist/date-range-field.js.map +1 -0
  110. package/dist/datetime-field.d.ts +29 -0
  111. package/dist/datetime-field.js +155 -0
  112. package/dist/datetime-field.js.map +1 -0
  113. package/dist/dialog.css +180 -0
  114. package/dist/dialog.d.ts +42 -0
  115. package/dist/dialog.js +128 -0
  116. package/dist/dialog.js.map +1 -0
  117. package/dist/dom-props-C0jrllBw.d.ts +5 -0
  118. package/dist/field-chrome-CMPOQSLs.d.ts +28 -0
  119. package/dist/field-shell-Cuda-feL.d.ts +24 -0
  120. package/dist/glide.d.ts +15 -0
  121. package/dist/glide.js +5 -0
  122. package/dist/glide.js.map +1 -0
  123. package/dist/icon.css +33 -0
  124. package/dist/input.css +333 -0
  125. package/dist/mcp.js +359 -0
  126. package/dist/motion-devtools.css +265 -0
  127. package/dist/motion-devtools.d.ts +51 -0
  128. package/dist/motion-devtools.js +354 -0
  129. package/dist/motion-devtools.js.map +1 -0
  130. package/dist/motion-scale-Canq4stZ.d.ts +18 -0
  131. package/dist/motion-tokens.d.ts +28 -0
  132. package/dist/motion-tokens.js +3 -0
  133. package/dist/motion-tokens.js.map +1 -0
  134. package/dist/multi-select.d.ts +44 -0
  135. package/dist/multi-select.js +109 -0
  136. package/dist/multi-select.js.map +1 -0
  137. package/dist/number-field.d.ts +36 -0
  138. package/dist/number-field.js +112 -0
  139. package/dist/number-field.js.map +1 -0
  140. package/dist/odometer.css +62 -0
  141. package/dist/otp-field.d.ts +31 -0
  142. package/dist/otp-field.js +98 -0
  143. package/dist/otp-field.js.map +1 -0
  144. package/dist/overlay.css +23 -0
  145. package/dist/pagination.css +43 -0
  146. package/dist/pagination.d.ts +29 -0
  147. package/dist/pagination.js +107 -0
  148. package/dist/pagination.js.map +1 -0
  149. package/dist/popover.css +69 -0
  150. package/dist/popover.d.ts +35 -0
  151. package/dist/popover.js +15 -0
  152. package/dist/popover.js.map +1 -0
  153. package/dist/radio-group.css +300 -0
  154. package/dist/radio-group.d.ts +52 -0
  155. package/dist/radio-group.js +115 -0
  156. package/dist/radio-group.js.map +1 -0
  157. package/dist/select.css +416 -0
  158. package/dist/select.d.ts +49 -0
  159. package/dist/select.js +99 -0
  160. package/dist/select.js.map +1 -0
  161. package/dist/sheet.css +26 -0
  162. package/dist/sheet.d.ts +31 -0
  163. package/dist/sheet.js +14 -0
  164. package/dist/sheet.js.map +1 -0
  165. package/dist/status-badge.d.ts +19 -0
  166. package/dist/status-badge.js +72 -0
  167. package/dist/status-badge.js.map +1 -0
  168. package/dist/table.css +348 -0
  169. package/dist/table.d.ts +75 -0
  170. package/dist/table.js +262 -0
  171. package/dist/table.js.map +1 -0
  172. package/dist/tabs.css +208 -0
  173. package/dist/tabs.d.ts +71 -0
  174. package/dist/tabs.js +181 -0
  175. package/dist/tabs.js.map +1 -0
  176. package/dist/tag.css +248 -0
  177. package/dist/tag.d.ts +52 -0
  178. package/dist/tag.js +72 -0
  179. package/dist/tag.js.map +1 -0
  180. package/dist/text-field.d.ts +28 -0
  181. package/dist/text-field.js +69 -0
  182. package/dist/text-field.js.map +1 -0
  183. package/dist/textarea.css +219 -0
  184. package/dist/textarea.d.ts +38 -0
  185. package/dist/textarea.js +144 -0
  186. package/dist/textarea.js.map +1 -0
  187. package/dist/time-field.d.ts +28 -0
  188. package/dist/time-field.js +66 -0
  189. package/dist/time-field.js.map +1 -0
  190. package/dist/timing-7JA_zQHP.d.ts +27 -0
  191. package/dist/toast-store.d.ts +86 -0
  192. package/dist/toast-store.js +3 -0
  193. package/dist/toast-store.js.map +1 -0
  194. package/dist/toast.css +257 -0
  195. package/dist/toast.d.ts +12 -0
  196. package/dist/toast.js +310 -0
  197. package/dist/toast.js.map +1 -0
  198. package/dist/toggle-tag.d.ts +34 -0
  199. package/dist/toggle-tag.js +57 -0
  200. package/dist/toggle-tag.js.map +1 -0
  201. package/dist/toggle.css +152 -0
  202. package/dist/toggle.d.ts +31 -0
  203. package/dist/toggle.js +75 -0
  204. package/dist/toggle.js.map +1 -0
  205. package/dist/tooltip.css +86 -0
  206. package/dist/tooltip.d.ts +30 -0
  207. package/dist/tooltip.js +268 -0
  208. package/dist/tooltip.js.map +1 -0
  209. package/dist/types-SGMShK7R.d.ts +22 -0
  210. package/llms.txt +196 -0
  211. package/next.d.mts +7 -0
  212. package/next.mjs +59 -0
  213. package/package.json +257 -0
  214. package/src/components/composites/alert/Alert.tsx +141 -0
  215. package/src/components/composites/alert/alert.css +132 -0
  216. package/src/components/composites/date-picker/DateField.tsx +80 -0
  217. package/src/components/composites/date-picker/DateRangeField.tsx +110 -0
  218. package/src/components/composites/date-picker/DateTimeField.tsx +162 -0
  219. package/src/components/composites/date-picker/TimeField.tsx +78 -0
  220. package/src/components/composites/date-picker/calendar-panel.tsx +216 -0
  221. package/src/components/composites/date-picker/date-picker.css +595 -0
  222. package/src/components/composites/date-picker/date-utils.ts +56 -0
  223. package/src/components/composites/date-picker/field-shell.tsx +85 -0
  224. package/src/components/composites/date-picker/range-panel.tsx +363 -0
  225. package/src/components/composites/date-picker/time-core.tsx +290 -0
  226. package/src/components/composites/date-picker/use-day-focus.ts +35 -0
  227. package/src/components/composites/dialog/Dialog.tsx +198 -0
  228. package/src/components/composites/dialog/dialog.css +180 -0
  229. package/src/components/composites/pagination/Pagination.tsx +131 -0
  230. package/src/components/composites/pagination/pagination.css +43 -0
  231. package/src/components/composites/popover/Popover.tsx +141 -0
  232. package/src/components/composites/popover/popover.css +69 -0
  233. package/src/components/composites/select/MultiSelect.tsx +133 -0
  234. package/src/components/composites/select/Select.tsx +122 -0
  235. package/src/components/composites/select/core/index.ts +4 -0
  236. package/src/components/composites/select/core/menu.tsx +100 -0
  237. package/src/components/composites/select/core/panel.tsx +154 -0
  238. package/src/components/composites/select/core/trigger.tsx +74 -0
  239. package/src/components/composites/select/core/types.ts +39 -0
  240. package/src/components/composites/select/core/use-listbox.ts +182 -0
  241. package/src/components/composites/select/select.css +416 -0
  242. package/src/components/composites/sheet/Sheet.tsx +135 -0
  243. package/src/components/composites/sheet/sheet.css +26 -0
  244. package/src/components/composites/sheet/use-sheet-drag.ts +126 -0
  245. package/src/components/composites/table/Table.tsx +377 -0
  246. package/src/components/composites/table/table.css +348 -0
  247. package/src/components/composites/tabs/Tabs.tsx +278 -0
  248. package/src/components/composites/tabs/tabs.css +208 -0
  249. package/src/components/composites/toast/Toast.tsx +380 -0
  250. package/src/components/composites/toast/toast-store.ts +295 -0
  251. package/src/components/composites/toast/toast.css +257 -0
  252. package/src/components/composites/tooltip/Tooltip.tsx +110 -0
  253. package/src/components/composites/tooltip/tooltip-host.tsx +184 -0
  254. package/src/components/composites/tooltip/tooltip-store.ts +81 -0
  255. package/src/components/composites/tooltip/tooltip.css +86 -0
  256. package/src/components/dev/MotionDevtools.tsx +200 -0
  257. package/src/components/dev/motion-devtools.css +265 -0
  258. package/src/components/dev/slowmo-engine.ts +230 -0
  259. package/src/components/internal/glass/glass.css +100 -0
  260. package/src/components/internal/glass/glint.ts +9 -0
  261. package/src/components/internal/hooks/use-controllable.ts +21 -0
  262. package/src/components/internal/hooks/use-scroll-edges.ts +46 -0
  263. package/src/components/internal/icon/Icon.tsx +66 -0
  264. package/src/components/internal/icon/IconSlot.tsx +10 -0
  265. package/src/components/internal/icon/icon.css +33 -0
  266. package/src/components/internal/overlay/focus.ts +68 -0
  267. package/src/components/internal/overlay/layer.tsx +149 -0
  268. package/src/components/internal/overlay/modal.tsx +141 -0
  269. package/src/components/internal/overlay/overlay.css +23 -0
  270. package/src/components/internal/overlay/position.ts +103 -0
  271. package/src/components/internal/utils/cx.ts +3 -0
  272. package/src/components/internal/utils/token-px.ts +8 -0
  273. package/src/components/primitives/avatar/Avatar.tsx +120 -0
  274. package/src/components/primitives/avatar/AvatarGroup.tsx +56 -0
  275. package/src/components/primitives/avatar/avatar.css +200 -0
  276. package/src/components/primitives/badge/Badge.tsx +75 -0
  277. package/src/components/primitives/badge/CountBadge.tsx +20 -0
  278. package/src/components/primitives/badge/StatusBadge.tsx +111 -0
  279. package/src/components/primitives/badge/badge.css +182 -0
  280. package/src/components/primitives/badge/odometer.css +62 -0
  281. package/src/components/primitives/badge/odometer.tsx +36 -0
  282. package/src/components/primitives/button/Button.tsx +102 -0
  283. package/src/components/primitives/button/button.css +269 -0
  284. package/src/components/primitives/checkbox/Checkbox.tsx +76 -0
  285. package/src/components/primitives/checkbox/check-glyph.tsx +28 -0
  286. package/src/components/primitives/checkbox/checkbox.css +177 -0
  287. package/src/components/primitives/collapse/Collapse.tsx +86 -0
  288. package/src/components/primitives/collapse/collapse.css +88 -0
  289. package/src/components/primitives/collapse/collapse.typecheck.tsx +29 -0
  290. package/src/components/primitives/input/NumberField.tsx +139 -0
  291. package/src/components/primitives/input/OtpField.tsx +134 -0
  292. package/src/components/primitives/input/TextField.tsx +110 -0
  293. package/src/components/primitives/input/field-chrome.tsx +83 -0
  294. package/src/components/primitives/input/input.css +333 -0
  295. package/src/components/primitives/radio-group/RadioGroup.tsx +183 -0
  296. package/src/components/primitives/radio-group/radio-group.css +300 -0
  297. package/src/components/primitives/tag/Tag.tsx +143 -0
  298. package/src/components/primitives/tag/ToggleTag.tsx +97 -0
  299. package/src/components/primitives/tag/tag.css +248 -0
  300. package/src/components/primitives/textarea/Textarea.tsx +190 -0
  301. package/src/components/primitives/textarea/textarea.css +219 -0
  302. package/src/components/primitives/toggle/Toggle.tsx +105 -0
  303. package/src/components/primitives/toggle/toggle.css +152 -0
  304. package/src/css.d.ts +1 -0
  305. package/src/dom-props.ts +1 -0
  306. package/src/engine/animate.ts +141 -0
  307. package/src/engine/drag.ts +53 -0
  308. package/src/engine/ease.ts +155 -0
  309. package/src/engine/flip.ts +59 -0
  310. package/src/engine/index.ts +13 -0
  311. package/src/mcp/server.ts +420 -0
  312. package/src/motion/element.tsx +53 -0
  313. package/src/motion/flip.tsx +87 -0
  314. package/src/motion/glide.tsx +47 -0
  315. package/src/motion/motion-timing.ts +33 -0
  316. package/src/motion/presence-context.ts +18 -0
  317. package/src/motion/presence.tsx +91 -0
  318. package/src/motion/timing.ts +49 -0
  319. package/src/motion/use-motion.ts +64 -0
  320. package/src/styles.css +26 -0
  321. package/src/tokens/avatar.css +20 -0
  322. package/src/tokens/color.css +58 -0
  323. package/src/tokens/elevation.css +22 -0
  324. package/src/tokens/fonts.css +3 -0
  325. package/src/tokens/glass.css +20 -0
  326. package/src/tokens/icons.css +7 -0
  327. package/src/tokens/layers.css +6 -0
  328. package/src/tokens/motion-scale.ts +17 -0
  329. package/src/tokens/motion-tokens.ts +92 -0
  330. package/src/tokens/motion.css +49 -0
  331. package/src/tokens/radius.css +11 -0
  332. package/src/tokens/semantic.css +65 -0
  333. package/src/tokens/spacing.css +26 -0
  334. package/src/tokens/typography.css +52 -0
@@ -0,0 +1,76 @@
1
+ 'use client';
2
+
3
+ import './checkbox.css';
4
+ import type { ChangeEventHandler, CSSProperties, InputHTMLAttributes, ReactNode } from 'react';
5
+ import { CheckGlyph } from './check-glyph';
6
+ import type { DataAttributes } from '../../../dom-props';
7
+ import { cx } from '../../internal/utils/cx';
8
+
9
+ interface CheckboxOwnProps {
10
+ /** Controlled checked state. Omit for uncontrolled (use `defaultChecked`). */
11
+ checked?: boolean;
12
+ /** Uncontrolled initial state. */
13
+ defaultChecked?: boolean;
14
+ /** Indeterminate - the "some, not all" select-all state; visually wins over `checked`. @default false */
15
+ indeterminate?: boolean;
16
+ /** Single error state for consent gates ("you must agree"); also sets `aria-invalid`. @default false */
17
+ error?: boolean;
18
+ /** Disabled - inert and de-emphasized (distinct fill when checked). */
19
+ disabled?: boolean;
20
+ /** Box size: `md` 18px - `sm` 16px for dense table rows. @default 'md' */
21
+ size?: 'sm' | 'md';
22
+ /** Label text beside the box. */
23
+ label?: ReactNode;
24
+ /** Optional secondary line under the label (settings rows). */
25
+ description?: ReactNode;
26
+ /** Fires on toggle - read `e.target.checked`. */
27
+ onChange?: ChangeEventHandler<HTMLInputElement>;
28
+ /** Extra class(es) merged onto the root label. */
29
+ className?: string;
30
+ /** Inline styles merged onto the root label. */
31
+ style?: CSSProperties;
32
+ }
33
+
34
+ export interface CheckboxProps extends CheckboxOwnProps {
35
+ /** Standard <input> attributes (name, value, required, aria-*, ...) forwarded to the checkbox input. */
36
+ htmlProps?: Omit<InputHTMLAttributes<HTMLInputElement>, keyof CheckboxOwnProps | 'type'> & DataAttributes;
37
+ }
38
+
39
+ export function Checkbox({
40
+ checked,
41
+ defaultChecked,
42
+ indeterminate = false,
43
+ error = false,
44
+ disabled = false,
45
+ size = 'md',
46
+ label,
47
+ description,
48
+ className = '',
49
+ style,
50
+ onChange,
51
+ htmlProps,
52
+ }: CheckboxProps) {
53
+ const cls = cx('cbx', size === 'sm' && 'cbx--sm', error && 'cbx--error', disabled && 'cbx--disabled', className);
54
+
55
+ const controlled = checked !== undefined;
56
+ const checkedProps = controlled ? { checked } : { defaultChecked };
57
+
58
+ return (
59
+ <label className={cls} style={style}>
60
+ <CheckGlyph
61
+ indeterminate={indeterminate}
62
+ disabled={disabled}
63
+ aria-invalid={error || undefined}
64
+ onChange={onChange}
65
+ {...checkedProps}
66
+ {...htmlProps}
67
+ />
68
+ {label || description ? (
69
+ <span className="cbx__text">
70
+ {label ? <span className="cbx__label">{label}</span> : null}
71
+ {description ? <span className="cbx__desc">{description}</span> : null}
72
+ </span>
73
+ ) : null}
74
+ </label>
75
+ );
76
+ }
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+
3
+ import './checkbox.css';
4
+ import { Fragment, useEffect, useRef, type InputHTMLAttributes } from 'react';
5
+
6
+ export interface CheckGlyphProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'size'> {
7
+ indeterminate?: boolean;
8
+ }
9
+
10
+ export function CheckGlyph({ indeterminate = false, ...inputProps }: CheckGlyphProps) {
11
+ const ref = useRef<HTMLInputElement>(null);
12
+
13
+ useEffect(() => {
14
+ if (ref.current) ref.current.indeterminate = indeterminate;
15
+ }, [indeterminate, inputProps.checked]);
16
+
17
+ return (
18
+ <Fragment>
19
+ <input ref={ref} type="checkbox" className="cbx__input" {...inputProps} />
20
+ <span className="cbx__box" aria-hidden="true">
21
+ <svg className="cbx__mark" viewBox="0 0 16 16" fill="none">
22
+ <path className="cbx__tick" d="M3.5 8.5 L6.75 11.5 L12.5 4.75" />
23
+ </svg>
24
+ <span className="cbx__dash" />
25
+ </span>
26
+ </Fragment>
27
+ );
28
+ }
@@ -0,0 +1,177 @@
1
+ /* Checkbox - on/off + indeterminate; native input hidden, box painted from :has()-set flags read via calc(). */
2
+
3
+ .cbx {
4
+ --cbx-fill: linear-gradient(180deg, var(--accent), var(--accent-hover));
5
+ --cbx-on: var(--accent);
6
+ --cbx-off: var(--border-strong);
7
+ --cbx-ring: var(--ring-accent);
8
+ --cbx-size: var(--control-box);
9
+ --cbx-radius: var(--radius-sm);
10
+ --tick-len: 14px; /* needs a unit so the calc() dashoffset is a valid <length>; ~ the polyline path length */
11
+ --fill: 0;
12
+ --tick: 0;
13
+ --dash: 0;
14
+ --press: 1;
15
+
16
+ display: inline-flex;
17
+ align-items: flex-start;
18
+ gap: var(--space-2);
19
+ padding: var(--space-2);
20
+ border-radius: var(--radius-md);
21
+ cursor: pointer;
22
+ -webkit-tap-highlight-color: transparent;
23
+ transition: background-color var(--duration-base) var(--ease-standard);
24
+
25
+ /* later rule wins, so indeterminate beats checked */
26
+ &:has(:checked) {
27
+ --fill: 1;
28
+ --tick: 1;
29
+ }
30
+ &:has(:indeterminate) {
31
+ --fill: 1;
32
+ --tick: 0;
33
+ --dash: 1;
34
+ }
35
+
36
+ &:not(.cbx--disabled):hover {
37
+ background: var(--neutral-wash);
38
+ }
39
+ /* accent the empty box on hover */
40
+ &:not(.cbx--disabled):hover:not(:has(:checked, :indeterminate)) {
41
+ --cbx-off: var(--cbx-on);
42
+ }
43
+ &:not(.cbx--disabled):active {
44
+ --press: 0.88;
45
+ }
46
+ &:has(:focus-visible) .cbx__box {
47
+ box-shadow: var(--cbx-ring);
48
+ }
49
+ }
50
+
51
+ .cbx__input {
52
+ position: absolute;
53
+ width: 1px;
54
+ height: 1px;
55
+ margin: 0;
56
+ padding: 0;
57
+ border: 0;
58
+ opacity: 0;
59
+ clip-path: inset(50%);
60
+ white-space: nowrap;
61
+ overflow: hidden;
62
+ }
63
+
64
+ .cbx__box {
65
+ position: relative;
66
+ flex: none;
67
+ display: grid;
68
+ place-items: center;
69
+ width: var(--cbx-size);
70
+ height: var(--cbx-size);
71
+ margin-top: calc((var(--leading-body) - var(--cbx-size)) / 2);
72
+ border: var(--border-hairline) solid color-mix(in oklab, var(--cbx-on) calc(var(--fill) * 100%), var(--cbx-off));
73
+ border-radius: var(--cbx-radius);
74
+ background: var(--bg-inset);
75
+ transform: scale(var(--press));
76
+ transition:
77
+ border-color var(--duration-fast) var(--ease-standard),
78
+ background-color var(--duration-fast) var(--ease-standard),
79
+ box-shadow var(--duration-fast) var(--ease-standard),
80
+ transform var(--duration-base) var(--ease-spring);
81
+
82
+ &::before {
83
+ content: '';
84
+ position: absolute;
85
+ inset: 0;
86
+
87
+ /* inner corner = outer radius - border, so the fill reaches the corners (no --bg-inset showing) */
88
+ border-radius: calc(var(--cbx-radius) - var(--border-hairline));
89
+ background: var(--cbx-fill);
90
+ opacity: var(--fill);
91
+ transform: scale(calc(0.4 + 0.6 * var(--fill)));
92
+ transition:
93
+ transform var(--duration-base) var(--ease-spring),
94
+ opacity var(--duration-fast) var(--ease-standard);
95
+ }
96
+ }
97
+
98
+ /* stagger lives in the delay: draw-on waits a beat (calc - --tick), retract fires immediately */
99
+ .cbx__mark {
100
+ position: relative;
101
+ display: block;
102
+ width: 100%;
103
+ height: 100%;
104
+ color: var(--text-on-accent);
105
+ pointer-events: none;
106
+ }
107
+ .cbx__tick {
108
+ fill: none;
109
+ stroke: currentColor;
110
+ stroke-width: 2;
111
+ stroke-linecap: round;
112
+ stroke-linejoin: round;
113
+ stroke-dasharray: var(--tick-len);
114
+ stroke-dashoffset: calc(var(--tick-len) * (1 - var(--tick)));
115
+ transition: stroke-dashoffset var(--duration-base) var(--ease-entrance) calc(var(--tick) * var(--duration-fast));
116
+ }
117
+
118
+ .cbx__dash {
119
+ position: absolute;
120
+ width: 56%;
121
+ height: 2px;
122
+ border-radius: var(--border-hairline);
123
+ background: var(--text-on-accent);
124
+ pointer-events: none;
125
+ opacity: var(--dash);
126
+ transform: scaleX(var(--dash));
127
+ transition:
128
+ transform var(--duration-base) var(--ease-spring),
129
+ opacity var(--duration-fast) var(--ease-standard);
130
+ }
131
+
132
+ .cbx__text {
133
+ display: grid;
134
+ gap: var(--space-1);
135
+ }
136
+ .cbx__label {
137
+ font: var(--type-label);
138
+ color: var(--text-strong);
139
+ }
140
+ .cbx__desc {
141
+ font: var(--type-caption);
142
+ color: var(--text-muted);
143
+ }
144
+
145
+ .cbx--sm {
146
+ --cbx-size: var(--space-4);
147
+ & .cbx__label {
148
+ font: var(--type-caption);
149
+ font-weight: var(--weight-medium);
150
+ color: var(--text-strong);
151
+ }
152
+ & .cbx__box {
153
+ margin-top: calc((var(--leading-caption) - var(--cbx-size)) / 2);
154
+ }
155
+ }
156
+
157
+ .cbx--error {
158
+ --cbx-fill: linear-gradient(180deg, var(--danger), var(--danger));
159
+ --cbx-on: var(--danger);
160
+ --cbx-off: var(--danger);
161
+ --cbx-ring: var(--ring-danger);
162
+ }
163
+
164
+ /* disabled leaves the state flags untouched, so the mark still matches checked state */
165
+ .cbx--disabled {
166
+ cursor: not-allowed;
167
+ --cbx-fill: var(--border-strong);
168
+ --cbx-on: var(--border-default);
169
+ --cbx-off: var(--border-subtle);
170
+ & .cbx__box {
171
+ background: var(--bg-muted);
172
+ }
173
+ & .cbx__label,
174
+ & .cbx__desc {
175
+ color: var(--text-disabled);
176
+ }
177
+ }
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+
3
+ import './collapse.css';
4
+ import type { CSSProperties, HTMLAttributes, JSX, ReactNode } from 'react';
5
+ import type { DataAttributes } from '../../../dom-props';
6
+ import type { DurationToken, EaseToken } from '../../../tokens/motion-scale';
7
+ import {
8
+ timingVars,
9
+ type AnimationTiming,
10
+ type DirectionalToken,
11
+ type DisableableAnimation,
12
+ type TimingProps,
13
+ } from '../../../motion/timing';
14
+ import { cx } from '../../internal/utils/cx';
15
+
16
+ /** Motion-scale duration tokens - the only values Collapse timing accepts. */
17
+ export type CollapseDuration = DurationToken;
18
+ /** Motion-scale ease tokens - the only values Collapse timing accepts. */
19
+ export type CollapseEase = EaseToken;
20
+ /** One token for both directions, or split per direction; an omitted direction keeps its default. */
21
+ export type CollapseTiming<Token extends string> = DirectionalToken<Token>;
22
+ /** Grouped `{ duration, ease }` timing for the size/fade transition. */
23
+ export type CollapseAnimation = AnimationTiming;
24
+
25
+ interface CollapseOwnProps extends TimingProps {
26
+ /** Open/closed state. Closed content is also removed from the tab order, the accessibility
27
+ * tree and hit-testing once the exit transition completes; reopening restores it before
28
+ * the entrance runs. */
29
+ open?: boolean;
30
+ /** Axis to animate. @default 'height' */
31
+ axis?: 'height' | 'width';
32
+ /** Also cross-fade contents while resizing; follows `duration` / `ease`. */
33
+ fade?: boolean;
34
+ /** Class applied to the inner measured wrapper - the clipping element (overflow: hidden).
35
+ * Content whose focus ring sits flush to its edge needs padding there, or the ring clips. */
36
+ innerClassName?: string;
37
+ /** Size (and fade) transition timing - motion tokens only, each field one token or
38
+ * `{ open, close }`, e.g. `{ duration: { close: 'fast' }, ease: { close: 'exit' } }`.
39
+ * `null` disables the transition (open/close snap instantly).
40
+ * @default duration 'slow' + ease 'entrance' (fade-out: 'base' + 'standard') */
41
+ animation?: DisableableAnimation;
42
+ /** Tag to render as. @default 'div' */
43
+ as?: keyof JSX.IntrinsicElements;
44
+ /** Extra class(es) merged onto the root. */
45
+ className?: string;
46
+ /** Inline styles merged onto the root (composed with the timing custom properties). */
47
+ style?: CSSProperties;
48
+ /** The collapsing content. */
49
+ children?: ReactNode;
50
+ }
51
+
52
+ export interface CollapseProps extends CollapseOwnProps {
53
+ /** Standard <div> attributes (aria-*, data-*, ...) forwarded to the root. */
54
+ htmlProps?: Omit<HTMLAttributes<HTMLDivElement>, keyof CollapseOwnProps> & DataAttributes;
55
+ }
56
+
57
+ export function Collapse({
58
+ open = false,
59
+ axis = 'height',
60
+ fade = false,
61
+ animation,
62
+ className = '',
63
+ innerClassName = '',
64
+ style,
65
+ children,
66
+ as: As = 'div',
67
+ htmlProps,
68
+ }: CollapseProps) {
69
+ const cls = cx('collapse', fade && 'collapse--fade', className);
70
+
71
+ const vars = timingVars('collapse', animation);
72
+
73
+ return (
74
+ <div
75
+ className={cls}
76
+ data-open={open ? 'true' : 'false'}
77
+ data-axis={axis}
78
+ style={vars || style ? ({ ...style, ...vars } as CSSProperties) : undefined}
79
+ {...htmlProps}
80
+ >
81
+ <As className={cx('collapse__inner', innerClassName)} style={vars}>
82
+ {children}
83
+ </As>
84
+ </div>
85
+ );
86
+ }
@@ -0,0 +1,88 @@
1
+ /* Collapse - animates open/closed via a grid track interpolating 0fr and 1fr (never `auto`).
2
+
3
+ Closed content leaves the page semantics, not just the paint: the root turns
4
+ visibility:hidden, dropping children out of the tab order, the accessibility tree and
5
+ hit-testing. The flip is directional by construction. Closing lists visibility with a
6
+ delay equal to the size duration, so removal lands only after the exit finishes and
7
+ nothing pops. Opening omits visibility from its transition list entirely, so content is
8
+ focusable the same frame it opens, before the entrance runs. The delay rides the duration
9
+ tokens, so the global reduced-motion collapse (~1ms) applies to it as well. The open state
10
+ deliberately declares no `visibility: visible` - it inherits instead, so a Collapse nested
11
+ inside a closed ancestor cannot poke a focusable hole through it. */
12
+
13
+ /* Timing overrides - set inline by Collapse.tsx from the token-typed duration/ease props.
14
+ Registered non-inheriting so one Collapse's timing can never leak into a Collapse nested
15
+ inside it; where unset they resolve invalid and the var() fallbacks supply the defaults. */
16
+ @property --collapse-dur-open {
17
+ syntax: '*';
18
+ inherits: false;
19
+ }
20
+ @property --collapse-dur-close {
21
+ syntax: '*';
22
+ inherits: false;
23
+ }
24
+ @property --collapse-ease-open {
25
+ syntax: '*';
26
+ inherits: false;
27
+ }
28
+ @property --collapse-ease-close {
29
+ syntax: '*';
30
+ inherits: false;
31
+ }
32
+
33
+ .collapse {
34
+ display: grid;
35
+ grid-template-rows: 0fr;
36
+ transition:
37
+ grid-template-rows var(--collapse-dur-close, var(--duration-slow)) var(--collapse-ease-close, var(--ease-entrance)),
38
+ grid-template-columns var(--collapse-dur-close, var(--duration-slow))
39
+ var(--collapse-ease-close, var(--ease-entrance)),
40
+ visibility 0s linear var(--collapse-dur-close, var(--duration-slow));
41
+ }
42
+ .collapse[data-open='false'] {
43
+ visibility: hidden;
44
+ }
45
+ .collapse[data-open='true'] {
46
+ grid-template-rows: 1fr;
47
+ transition:
48
+ grid-template-rows var(--collapse-dur-open, var(--duration-slow)) var(--collapse-ease-open, var(--ease-entrance)),
49
+ grid-template-columns var(--collapse-dur-open, var(--duration-slow)) var(--collapse-ease-open, var(--ease-entrance));
50
+ }
51
+
52
+ .collapse[data-axis='width'] {
53
+ grid-template-rows: 1fr;
54
+ grid-template-columns: 0fr;
55
+ }
56
+ .collapse[data-axis='width'][data-open='true'] {
57
+ grid-template-columns: 1fr;
58
+ }
59
+
60
+ /* min-size:0 is what lets the grid track collapse to 0. */
61
+ .collapse__inner {
62
+ overflow: hidden;
63
+ min-height: 0;
64
+ min-width: 0;
65
+ }
66
+
67
+ .collapse--fade .collapse__inner {
68
+ opacity: 0;
69
+ transition: opacity var(--collapse-dur-close, var(--duration-base)) var(--collapse-ease-close, var(--ease-standard));
70
+ }
71
+ .collapse--fade[data-open='true'] .collapse__inner {
72
+ opacity: 1;
73
+ /* fade in slightly after the size opens */
74
+ transition: opacity var(--collapse-dur-open, var(--duration-slow)) var(--collapse-ease-open, var(--ease-entrance))
75
+ 60ms;
76
+ }
77
+
78
+ @media (prefers-reduced-motion: reduce) {
79
+ .collapse,
80
+ .collapse--fade .collapse__inner {
81
+ transition-duration: 1ms;
82
+ }
83
+ /* drop the fade-in stagger so content is visible immediately; the visibility delay needs
84
+ no override - it rides the duration tokens, which this media query collapses to 1ms */
85
+ .collapse--fade .collapse__inner {
86
+ transition-delay: 0ms;
87
+ }
88
+ }
@@ -0,0 +1,29 @@
1
+ import { Collapse } from './Collapse';
2
+
3
+ export const ok = (
4
+ <>
5
+ <Collapse open animation={{ duration: 'fast', ease: 'exit' }} />
6
+ <Collapse
7
+ open
8
+ animation={{ duration: { open: 'slower', close: 'fast' }, ease: { open: 'entrance', close: 'exit' } }}
9
+ />
10
+ <Collapse open animation={{ duration: { close: 'fast' } }} />
11
+ <Collapse open animation={null} />
12
+ <Collapse open className="x" htmlProps={{ 'aria-label': 'panel', 'data-open-state': 'yes', onClick: () => {} }} />
13
+ </>
14
+ );
15
+
16
+ export const bad = (
17
+ <>
18
+ {/* @ts-expect-error - raw CSS strings must not type-check */}
19
+ <Collapse open animation={{ duration: '200ms' }} />
20
+ {/* @ts-expect-error - numbers must not type-check */}
21
+ <Collapse open animation={{ duration: 200 }} />
22
+ {/* @ts-expect-error - free-string eases must not type-check */}
23
+ <Collapse open animation={{ ease: 'ease-in-out' }} />
24
+ {/* @ts-expect-error - unknown directions must not type-check */}
25
+ <Collapse open animation={{ duration: { enter: 'fast' } }} />
26
+ {/* @ts-expect-error - raw DOM attributes no longer live at the top level */}
27
+ <Collapse open onClick={() => {}} />
28
+ </>
29
+ );
@@ -0,0 +1,139 @@
1
+ 'use client';
2
+
3
+ import './input.css';
4
+ import { useState } from 'react';
5
+ import type { CSSProperties, InputHTMLAttributes } from 'react';
6
+ import { Icon } from '../../internal/icon/Icon';
7
+ import { FieldLabel, FieldMessage, type FieldCoreMessagingProps, type FieldIdProps } from './field-chrome';
8
+ import { useControllable } from '../../internal/hooks/use-controllable';
9
+ import type { DataAttributes } from '../../../dom-props';
10
+ import { cx } from '../../internal/utils/cx';
11
+
12
+ interface NumberFieldOwnProps extends FieldIdProps, FieldCoreMessagingProps {
13
+ /** Unit suffix shown inside the field (e.g. "days", "%"). */
14
+ unit?: string;
15
+ /** Minimum value - steps and committed typing clamp up to this; the decrease stepper disables here. @default 0 */
16
+ min?: number;
17
+ /** Maximum value - steps and committed typing clamp down to this; the increase stepper disables here. @default Infinity */
18
+ max?: number;
19
+ /** Amount added/removed per ArrowUp/ArrowDown press and per caret stepper click. Decimals work. @default 1 */
20
+ step?: number;
21
+ /** Controlled numeric value. Omit for uncontrolled (use `defaultValue`). */
22
+ value?: number | string;
23
+ /** Uncontrolled initial value. Use instead of `value`. @default 0 */
24
+ defaultValue?: number;
25
+ /** Called with the next clamped number. */
26
+ onChange?: (value: number) => void;
27
+ /** Disabled - the input and both steppers go inert. */
28
+ disabled?: boolean;
29
+ /** Control height: sm - md (default) - lg. */
30
+ size?: 'sm' | 'md' | 'lg';
31
+ /** Extra class(es) merged onto the field root. */
32
+ className?: string;
33
+ /** Inline styles merged onto the field root. */
34
+ style?: CSSProperties;
35
+ }
36
+
37
+ export interface NumberFieldProps extends NumberFieldOwnProps {
38
+ /** Standard <input> attributes (name, aria-*, ...) forwarded to the input. */
39
+ htmlProps?: Omit<
40
+ InputHTMLAttributes<HTMLInputElement>,
41
+ keyof NumberFieldOwnProps | 'size' | 'value' | 'onChange' | 'min' | 'max' | 'step'
42
+ > &
43
+ DataAttributes;
44
+ }
45
+
46
+ export function NumberField({
47
+ id,
48
+ label,
49
+ helper,
50
+ error,
51
+ unit,
52
+ min = 0,
53
+ max = Infinity,
54
+ step = 1,
55
+ value,
56
+ defaultValue = 0,
57
+ onChange,
58
+ disabled,
59
+ size,
60
+ className = '',
61
+ style,
62
+ htmlProps,
63
+ }: NumberFieldProps) {
64
+ const resolvedValue = value === undefined ? undefined : typeof value === 'number' ? value : parseFloat(value) || 0;
65
+ const [num, setNum] = useControllable<number>(resolvedValue, defaultValue, onChange);
66
+ const [draft, setDraft] = useState<string | null>(null);
67
+ const v = draft !== null ? parseFloat(draft) || 0 : num;
68
+ const snap = (n: number) => parseFloat(n.toFixed(10));
69
+ const clamp = (n: number) => Math.min(max, Math.max(min, snap(n)));
70
+ const commit = (n: number) => {
71
+ setDraft(null);
72
+ setNum(clamp(n));
73
+ };
74
+ const cls = cx(
75
+ 'fld',
76
+ 'numf',
77
+ unit && 'numf--unit',
78
+ size === 'sm' ? 'fld--sm' : size === 'lg' ? 'fld--lg' : '',
79
+ error && 'is-error',
80
+ className,
81
+ );
82
+
83
+ return (
84
+ <div className={cls} style={style}>
85
+ <FieldLabel id={id} label={label} />
86
+ <div className="fld__control">
87
+ <input
88
+ id={id}
89
+ className="fld__input"
90
+ type="text"
91
+ inputMode="decimal"
92
+ disabled={disabled}
93
+ aria-invalid={error ? true : undefined}
94
+ value={draft !== null ? draft : String(num)}
95
+ onChange={(e) => {
96
+ const text = e.target.value.replace(/[^\d.-]/g, '');
97
+ setDraft(text);
98
+ setNum(clamp(parseFloat(text) || 0));
99
+ }}
100
+ onBlur={() => commit(v)}
101
+ onKeyDown={(e) => {
102
+ if (e.key === 'ArrowUp') {
103
+ e.preventDefault();
104
+ commit(v + step);
105
+ }
106
+ if (e.key === 'ArrowDown') {
107
+ e.preventDefault();
108
+ commit(v - step);
109
+ }
110
+ if (e.key === 'Enter') commit(v);
111
+ }}
112
+ {...htmlProps}
113
+ />
114
+ {unit && <span className="numf__unit">{unit}</span>}
115
+ <div className="numf__steppers">
116
+ <button
117
+ type="button"
118
+ className="numf__step"
119
+ aria-label="Increase"
120
+ disabled={disabled || v >= max}
121
+ onClick={() => commit(v + step)}
122
+ >
123
+ <Icon name="caret-up" size="sm" />
124
+ </button>
125
+ <button
126
+ type="button"
127
+ className="numf__step"
128
+ aria-label="Decrease"
129
+ disabled={disabled || v <= min}
130
+ onClick={() => commit(v - step)}
131
+ >
132
+ <Icon name="caret-down" size="sm" />
133
+ </button>
134
+ </div>
135
+ </div>
136
+ <FieldMessage message={error || helper} icon={error ? 'warning-circle' : null} />
137
+ </div>
138
+ );
139
+ }