@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,219 @@
1
+ /* textarea.css - Textarea (multiline composer): the .txa box over the .fld shell, auto-grow, char meter, over-limit highlight. */
2
+
3
+ .txa {
4
+ --txa-pad-y: var(--space-3);
5
+ --txa-pad-x: var(--space-4);
6
+ --txa-lh: var(--leading-body);
7
+ --txa-min-rows: 3;
8
+ --txa-max-rows: 10;
9
+ position: relative;
10
+ display: flex;
11
+ flex-direction: column;
12
+ background: var(--bg-inset);
13
+ border: var(--border-hairline) solid var(--border-default);
14
+ border-radius: var(--radius-lg);
15
+ transition: var(--transition-control);
16
+ }
17
+ .txa:hover:not(.is-disabled):not(.is-readonly):not(:focus-within) {
18
+ border-color: var(--border-strong);
19
+ }
20
+ .txa:focus-within {
21
+ border-color: var(--accent);
22
+ background: var(--bg-surface);
23
+ box-shadow: var(--ring-accent);
24
+ }
25
+ .txa.is-disabled {
26
+ background: var(--bg-muted);
27
+ }
28
+ .txa.is-readonly {
29
+ background: var(--bg-subtle);
30
+ }
31
+
32
+ /* The .txa__stack wrapper owns the height and is the only thing that animates; overflow:clip while growing (a clip box can't scroll to the caret - no jump), flipped to auto by JS only past max-rows. */
33
+ .txa__stack {
34
+ position: relative;
35
+ min-height: calc(var(--txa-min-rows) * var(--txa-lh) + 2 * var(--txa-pad-y));
36
+ max-height: calc(var(--txa-max-rows) * var(--txa-lh) + 2 * var(--txa-pad-y));
37
+ overflow: clip;
38
+ transition: height var(--duration-base) var(--ease-standard);
39
+ }
40
+ .txa__input,
41
+ .txa__mirror {
42
+ box-sizing: border-box;
43
+ width: 100%;
44
+ font: var(--type-body);
45
+ padding: var(--txa-pad-y) var(--txa-pad-x);
46
+ border-radius: inherit;
47
+ white-space: pre-wrap;
48
+ overflow-wrap: break-word;
49
+ word-break: break-word;
50
+ }
51
+ .txa__input {
52
+ position: relative;
53
+ z-index: 1;
54
+ display: block;
55
+ margin: 0;
56
+ border: 0;
57
+ background: transparent;
58
+ resize: none;
59
+ appearance: none;
60
+ -webkit-appearance: none;
61
+ outline: none;
62
+ color: var(--text-strong);
63
+ overflow: hidden;
64
+ }
65
+ .txa__input::placeholder {
66
+ color: var(--text-disabled);
67
+ }
68
+ .txa__input:disabled {
69
+ color: var(--text-disabled);
70
+ cursor: not-allowed;
71
+ }
72
+ .txa__input[readonly] {
73
+ color: var(--text-body);
74
+ }
75
+
76
+ /* over-limit highlight: faint tint + a danger underline on the excess chars */
77
+ .txa__mirror {
78
+ position: absolute;
79
+ top: 0;
80
+ left: 0;
81
+ right: 0;
82
+ z-index: 0;
83
+ color: transparent;
84
+ pointer-events: none;
85
+ user-select: none;
86
+ }
87
+ .txa__mirror mark {
88
+ color: transparent;
89
+ background: var(--danger-subtle);
90
+ border-radius: var(--radius-sm);
91
+ text-decoration: underline;
92
+ text-decoration-color: var(--danger);
93
+ text-decoration-thickness: 2px;
94
+ text-underline-offset: 3px;
95
+ -webkit-box-decoration-break: clone;
96
+ box-decoration-break: clone;
97
+ }
98
+
99
+ .txa__bar {
100
+ display: flex;
101
+ align-items: center;
102
+ gap: var(--space-3);
103
+ padding: var(--space-2) var(--space-3);
104
+ border-top: var(--border-hairline) solid var(--border-subtle);
105
+ transition: border-color var(--duration-fast) var(--ease-standard);
106
+ }
107
+ .txa:focus-within .txa__bar {
108
+ border-top-color: var(--border-default);
109
+ }
110
+ .txa__hint {
111
+ font: var(--type-micro);
112
+ color: var(--text-subtle);
113
+ display: inline-flex;
114
+ align-items: center;
115
+ gap: var(--space-1);
116
+ min-width: 0;
117
+ overflow: hidden;
118
+ text-overflow: ellipsis;
119
+ white-space: nowrap;
120
+ }
121
+ .txa__kbd {
122
+ font-family: var(--font-mono);
123
+ font-size: var(--size-micro);
124
+ line-height: 1;
125
+ color: var(--text-muted);
126
+ padding: var(--space-1);
127
+ border: var(--border-hairline) solid var(--border-default);
128
+ border-radius: var(--radius-sm);
129
+ background: var(--bg-surface);
130
+ }
131
+
132
+ .txa__meter {
133
+ margin-left: auto;
134
+ flex: none;
135
+ display: inline-flex;
136
+ align-items: center;
137
+ gap: var(--space-2);
138
+ }
139
+ .txa__count {
140
+ font-family: var(--font-mono);
141
+ font-size: var(--size-micro);
142
+ line-height: 1;
143
+ font-variant-numeric: tabular-nums;
144
+ color: var(--text-subtle);
145
+ white-space: nowrap;
146
+ transition: color var(--duration-fast) var(--ease-standard);
147
+ }
148
+ .txa__ring {
149
+ width: var(--icon-sm);
150
+ height: var(--icon-sm);
151
+ flex: none;
152
+ transform: rotate(-90deg);
153
+ }
154
+ .txa__ring circle {
155
+ fill: none;
156
+ stroke-width: 2;
157
+ }
158
+ .txa__ring .txa__ring-trk {
159
+ stroke: var(--border-default);
160
+ }
161
+ .txa__ring .txa__ring-prg {
162
+ stroke: var(--accent);
163
+ stroke-linecap: round;
164
+ stroke-dasharray: var(--txa-ring-c);
165
+ stroke-dashoffset: calc(var(--txa-ring-c) * (1 - var(--txa-ring-p, 0)));
166
+ transition:
167
+ stroke-dashoffset var(--duration-base) var(--ease-standard),
168
+ stroke var(--duration-fast) var(--ease-standard);
169
+ }
170
+ .txa__meter.is-near .txa__ring-prg {
171
+ stroke: var(--warning);
172
+ }
173
+ .txa__meter.is-near .txa__count {
174
+ color: var(--warning-text);
175
+ }
176
+ .txa__meter.is-over .txa__ring-prg {
177
+ stroke: var(--danger);
178
+ }
179
+ .txa__meter.is-over .txa__count {
180
+ color: var(--danger-text);
181
+ font-weight: var(--weight-medium);
182
+ }
183
+
184
+ .fld.is-error .txa {
185
+ border-color: var(--danger);
186
+ }
187
+ .fld.is-error .txa:hover:not(.is-disabled):not(.is-readonly):not(:focus-within) {
188
+ border-color: var(--danger-text);
189
+ }
190
+ .fld.is-error .txa:focus-within {
191
+ box-shadow: var(--ring-danger);
192
+ }
193
+ .fld.is-warning .txa {
194
+ border-color: var(--warning);
195
+ }
196
+ .fld.is-warning .txa:hover:not(.is-disabled):not(.is-readonly):not(:focus-within) {
197
+ border-color: var(--warning-text);
198
+ }
199
+ .fld.is-warning .txa:focus-within {
200
+ box-shadow: var(--ring-warning);
201
+ }
202
+ .fld.is-success .txa {
203
+ border-color: var(--success);
204
+ }
205
+ .fld.is-success .txa:hover:not(.is-disabled):not(.is-readonly):not(:focus-within) {
206
+ border-color: var(--success-text);
207
+ }
208
+ .fld.is-success .txa:focus-within {
209
+ box-shadow: var(--ring-success);
210
+ }
211
+
212
+ .fld--txa.fld--lg .txa {
213
+ --txa-pad-y: var(--space-4);
214
+ --txa-lh: var(--leading-body-lg);
215
+ }
216
+ .fld--txa.fld--lg .txa__input,
217
+ .fld--txa.fld--lg .txa__mirror {
218
+ font: var(--type-body-lg);
219
+ }
@@ -0,0 +1,105 @@
1
+ 'use client';
2
+
3
+ import './toggle.css';
4
+ import {
5
+ useState,
6
+ type ChangeEvent,
7
+ type ChangeEventHandler,
8
+ type CSSProperties,
9
+ type InputHTMLAttributes,
10
+ type ReactNode,
11
+ } from 'react';
12
+ import { useControllable } from '../../internal/hooks/use-controllable';
13
+ import type { DataAttributes } from '../../../dom-props';
14
+ import { cx } from '../../internal/utils/cx';
15
+
16
+ interface ToggleOwnProps {
17
+ /** Controlled checked state. Omit for uncontrolled (use `defaultChecked`). */
18
+ checked?: boolean;
19
+ /** Uncontrolled initial state. */
20
+ defaultChecked?: boolean;
21
+ /** Disabled - inert and de-emphasized (faded track, retains its position). */
22
+ disabled?: boolean;
23
+ /** Track size: `md` 36x20 - `sm` 28x16 for dense settings/table rows. @default 'md' */
24
+ size?: 'sm' | 'md';
25
+ /** Label text beside the track. */
26
+ label?: ReactNode;
27
+ /** Optional secondary line under the label (settings rows). */
28
+ description?: ReactNode;
29
+ /** Fires on flip - read `e.target.checked`. */
30
+ onChange?: ChangeEventHandler<HTMLInputElement>;
31
+ /** Extra class(es) merged onto the root label. */
32
+ className?: string;
33
+ /** Inline styles merged onto the root label. */
34
+ style?: CSSProperties;
35
+ }
36
+
37
+ export interface ToggleProps extends ToggleOwnProps {
38
+ /** Standard <input> attributes (name, value, required, aria-*, ...) forwarded to the switch input. */
39
+ htmlProps?: Omit<InputHTMLAttributes<HTMLInputElement>, keyof ToggleOwnProps | 'type'> & DataAttributes;
40
+ }
41
+
42
+ export function Toggle({
43
+ checked,
44
+ defaultChecked = false,
45
+ disabled = false,
46
+ size = 'md',
47
+ label,
48
+ description,
49
+ className = '',
50
+ style,
51
+ onChange,
52
+ htmlProps,
53
+ }: ToggleProps) {
54
+ const [isOn, setOn] = useControllable(checked, !!defaultChecked);
55
+
56
+ const cls = cx('sw', size === 'sm' && 'sw--sm', disabled && 'sw--disabled', className);
57
+
58
+ const [pressed, setPressed] = useState(false);
59
+ function press(on: boolean) {
60
+ return () => {
61
+ if (!disabled) setPressed(on);
62
+ };
63
+ }
64
+
65
+ function handleChange(e: ChangeEvent<HTMLInputElement>) {
66
+ setOn(e.target.checked);
67
+ if (onChange) onChange(e);
68
+ }
69
+
70
+ return (
71
+ <label
72
+ className={cls}
73
+ style={style}
74
+ onPointerDown={press(true)}
75
+ onPointerUp={press(false)}
76
+ onPointerLeave={press(false)}
77
+ onPointerCancel={press(false)}
78
+ >
79
+ <input
80
+ type="checkbox"
81
+ role="switch"
82
+ className="sw__input"
83
+ disabled={disabled}
84
+ checked={isOn}
85
+ onChange={handleChange}
86
+ {...htmlProps}
87
+ defaultChecked={undefined}
88
+ />
89
+ <span
90
+ className="sw__track"
91
+ data-on={isOn ? 'true' : undefined}
92
+ data-pressed={pressed ? 'true' : undefined}
93
+ aria-hidden="true"
94
+ >
95
+ <span className="sw__thumb"></span>
96
+ </span>
97
+ {label || description ? (
98
+ <span className="sw__text">
99
+ {label ? <span className="sw__label">{label}</span> : null}
100
+ {description ? <span className="sw__desc">{description}</span> : null}
101
+ </span>
102
+ ) : null}
103
+ </label>
104
+ );
105
+ }
@@ -0,0 +1,152 @@
1
+ /* Toggle (switch) - immediate on/off; native input hidden, track + thumb painted, thumb travels on a CSS spring. */
2
+
3
+ .sw {
4
+ --sw-on-fill: linear-gradient(180deg, var(--accent), var(--accent-hover));
5
+ --sw-off-fill: var(--bg-muted);
6
+ --sw-on-border: var(--accent);
7
+ --sw-off-border: var(--border-strong);
8
+ --sw-ring: var(--ring-accent);
9
+ /* track size is the only input; thumb + travel derive from it */
10
+ --sw-w: var(--control-height);
11
+ --sw-h: var(--control-switch);
12
+ --sw-inset: calc(2 * var(--space-px));
13
+ /* inner track height minus both borders and the gap each side, so the inset is uniform on all edges */
14
+ --sw-thumb: calc(var(--sw-h) - 2 * var(--border-hairline) - 2 * var(--sw-inset));
15
+ /* colors read this flag; the thumb is moved by transform, not by it */
16
+ --on: 0;
17
+
18
+ display: inline-flex;
19
+ align-items: flex-start;
20
+ gap: var(--space-3);
21
+ padding: var(--space-2);
22
+ border-radius: var(--radius-md);
23
+ cursor: pointer;
24
+ -webkit-tap-highlight-color: transparent;
25
+ transition: background-color var(--duration-fast) var(--ease-standard);
26
+
27
+ &:has(:checked) {
28
+ --on: 1;
29
+ }
30
+
31
+ &:not(.sw--disabled):hover {
32
+ background: var(--bg-muted);
33
+ }
34
+ &:not(.sw--disabled):hover:not(:has(:checked)) {
35
+ --sw-off-border: var(--text-subtle);
36
+ }
37
+ &:has(:focus-visible) .sw__track {
38
+ box-shadow: var(--sw-ring);
39
+ }
40
+ }
41
+
42
+ .sw__input {
43
+ position: absolute;
44
+ width: 1px;
45
+ height: 1px;
46
+ margin: 0;
47
+ padding: 0;
48
+ border: 0;
49
+ opacity: 0;
50
+ clip-path: inset(50%);
51
+ white-space: nowrap;
52
+ overflow: hidden;
53
+ }
54
+
55
+ .sw__track {
56
+ position: relative;
57
+ flex: none;
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: flex-start;
61
+ padding: var(--sw-inset);
62
+ width: var(--sw-w);
63
+ height: var(--sw-h);
64
+ margin-top: calc((var(--leading-body) - var(--sw-h)) / 2);
65
+ border: var(--border-hairline) solid
66
+ color-mix(in oklab, var(--sw-on-border) calc(var(--on) * 100%), var(--sw-off-border));
67
+ border-radius: var(--radius-full);
68
+ background: var(--sw-off-fill);
69
+ transform: scale(1);
70
+ transition:
71
+ border-color var(--duration-base) var(--ease-standard),
72
+ background-color var(--duration-base) var(--ease-standard),
73
+ box-shadow var(--duration-fast) var(--ease-standard),
74
+ transform var(--duration-fast) var(--ease-spring);
75
+
76
+ &::before {
77
+ content: '';
78
+ position: absolute;
79
+ inset: 0;
80
+ border-radius: inherit;
81
+ background: var(--sw-on-fill);
82
+ opacity: var(--on);
83
+ transition: opacity var(--duration-base) var(--ease-standard);
84
+ }
85
+ }
86
+ /* --tx (travel) + --sx (press-stretch) compose one transform, so press and travel never fight over it */
87
+ .sw__thumb {
88
+ --tx: 0px;
89
+ --sx: 1;
90
+ position: relative;
91
+ z-index: 1;
92
+ flex: none;
93
+ width: var(--sw-thumb);
94
+ height: var(--sw-thumb);
95
+ border-radius: var(--radius-full);
96
+ background: var(--bg-surface);
97
+ box-shadow: var(--shadow-sm);
98
+ transform: translateX(var(--tx)) scaleX(var(--sx));
99
+ transition: transform var(--duration-base) var(--ease-spring);
100
+ }
101
+ /* React-set [data-on] drives travel - no :has() twin to move the thumb out from under the spring and snap it */
102
+ .sw__track[data-on='true'] .sw__thumb {
103
+ --tx: calc(var(--sw-w) - var(--sw-h));
104
+ }
105
+ .sw__track[data-pressed='true'] {
106
+ transform: scale(0.96);
107
+ }
108
+ .sw__track[data-pressed='true'] .sw__thumb {
109
+ --sx: 1.16;
110
+ }
111
+
112
+ .sw__text {
113
+ display: grid;
114
+ gap: var(--space-1);
115
+ }
116
+ .sw__label {
117
+ font: var(--type-label);
118
+ color: var(--text-strong);
119
+ }
120
+ .sw__desc {
121
+ font: var(--type-caption);
122
+ color: var(--text-muted);
123
+ }
124
+
125
+ .sw--sm {
126
+ --sw-w: var(--control-height-sm);
127
+ --sw-h: var(--space-4);
128
+ & .sw__label {
129
+ font: var(--type-caption);
130
+ font-weight: var(--weight-medium);
131
+ color: var(--text-strong);
132
+ }
133
+ & .sw__track {
134
+ margin-top: calc((var(--leading-caption) - var(--sw-h)) / 2);
135
+ }
136
+ }
137
+
138
+ /* disabled leaves the flag untouched, so the thumb keeps its correct position */
139
+ .sw--disabled {
140
+ cursor: not-allowed;
141
+ --sw-on-fill: var(--accent-disabled);
142
+ --sw-off-fill: var(--bg-muted);
143
+ --sw-on-border: var(--accent-disabled);
144
+ --sw-off-border: var(--border-default);
145
+ & .sw__thumb {
146
+ box-shadow: var(--shadow-xs);
147
+ }
148
+ & .sw__label,
149
+ & .sw__desc {
150
+ color: var(--text-disabled);
151
+ }
152
+ }
package/src/css.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare module '*.css';
@@ -0,0 +1 @@
1
+ export type DataAttributes = { [key: `data-${string}`]: string | number | boolean | undefined };
@@ -0,0 +1,141 @@
1
+ import { resolveTiming, type Timing } from './ease';
2
+
3
+ export const clock = { scale: 1 };
4
+
5
+ export interface Playback {
6
+ stop(): void;
7
+ finished: Promise<void>;
8
+ }
9
+
10
+ type Vec = [number, number];
11
+ export type Size = number | 'auto';
12
+
13
+ export interface Layer {
14
+ x?: number[];
15
+ y?: number[];
16
+ scale?: number[] | Vec[];
17
+ opacity?: number[];
18
+ width?: Size[];
19
+ height?: Size[];
20
+ timing?: Timing;
21
+ composite?: CompositeOperation;
22
+ }
23
+
24
+ const RESET: Record<string, string> = { translate: '0px 0px', scale: '1' };
25
+ const AUTO_METRIC = { width: 'offsetWidth', height: 'offsetHeight' } as const;
26
+ const SIZES = ['width', 'height'] as const;
27
+
28
+ function measureAuto(el: HTMLElement, key: 'width' | 'height'): number {
29
+ const saved = el.style.getPropertyValue(key);
30
+ el.style.setProperty(key, 'auto');
31
+ const measured = el[AUTO_METRIC[key]];
32
+ if (saved) el.style.setProperty(key, saved);
33
+ else el.style.removeProperty(key);
34
+ return measured;
35
+ }
36
+
37
+ function held(list: number[], index: number): number {
38
+ return list[Math.min(index, list.length - 1)];
39
+ }
40
+
41
+ function currentValue(el: HTMLElement, key: string): string {
42
+ const raw = getComputedStyle(el).getPropertyValue(key).trim();
43
+ return raw && raw !== 'none' ? raw : RESET[key];
44
+ }
45
+
46
+ function compile(el: HTMLElement, layer: Layer) {
47
+ const frames: Record<string, string[]> = {};
48
+ const autoKeys: string[] = [];
49
+
50
+ if (layer.x?.length || layer.y?.length) {
51
+ const x = layer.x?.length ? layer.x : [0];
52
+ const y = layer.y?.length ? layer.y : [0];
53
+ const count = Math.max(x.length, y.length);
54
+ frames.translate = Array.from({ length: count }, (_, i) => `${held(x, i)}px ${held(y, i)}px`);
55
+ }
56
+ if (layer.scale) frames.scale = layer.scale.map((v) => (typeof v === 'number' ? String(v) : `${v[0]} ${v[1]}`));
57
+ if (layer.opacity) frames.opacity = layer.opacity.map(String);
58
+
59
+ for (const key of SIZES) {
60
+ const list = layer[key];
61
+ if (!list) continue;
62
+ if (list[list.length - 1] === 'auto') autoKeys.push(key);
63
+ const measured = list.includes('auto') ? measureAuto(el, key) : 0;
64
+ frames[key] = list.map((v) => `${v === 'auto' ? measured : v}px`);
65
+ }
66
+
67
+ for (const [key, list] of Object.entries(frames)) if (list.length === 1) list.unshift(currentValue(el, key));
68
+ return { frames, autoKeys };
69
+ }
70
+
71
+ const owned = new WeakMap<HTMLElement, Map<string, Animation>>();
72
+
73
+ function claim(el: HTMLElement, keys: string[], animation: Animation | null): void {
74
+ const map = owned.get(el) ?? new Map<string, Animation>();
75
+ owned.set(el, map);
76
+ for (const key of keys) {
77
+ const previous = map.get(key);
78
+ if (previous && previous !== animation) previous.cancel();
79
+ if (animation) map.set(key, animation);
80
+ else map.delete(key);
81
+ }
82
+ }
83
+
84
+ function play(el: HTMLElement, layer: Layer): Playback | null {
85
+ const { frames, autoKeys } = compile(el, layer);
86
+ const keys = Object.keys(frames);
87
+ if (!keys.length) return null;
88
+
89
+ const timing = layer.timing;
90
+ const resolved = resolveTiming(timing);
91
+
92
+ if (resolved.duration <= 0) {
93
+ claim(el, keys, null);
94
+ if (timing?.fill !== 'none') for (const key of keys) el.style.setProperty(key, frames[key][frames[key].length - 1]);
95
+ return null;
96
+ }
97
+
98
+ const shaped: PropertyIndexedKeyframes = { ...frames };
99
+ if (timing?.times) shaped.offset = timing.times;
100
+ if (resolved.segments) shaped.easing = resolved.segments;
101
+
102
+ const animation = el.animate(shaped, {
103
+ duration: resolved.duration,
104
+ delay: resolved.delay,
105
+ easing: resolved.easing,
106
+ fill: timing?.fill ?? 'both',
107
+ composite: layer.composite,
108
+ });
109
+ animation.playbackRate = 1 / clock.scale;
110
+ if (layer.composite !== 'add') claim(el, keys, animation);
111
+
112
+ const finished = animation.finished.then(
113
+ (): void => {
114
+ if (!autoKeys.length) return;
115
+ animation.commitStyles();
116
+ animation.cancel();
117
+ for (const key of autoKeys) el.style.setProperty(key, 'auto');
118
+ },
119
+ (): void => {},
120
+ );
121
+
122
+ return { stop: () => animation.cancel(), finished };
123
+ }
124
+
125
+ const settled: Playback = { stop() {}, finished: Promise.resolve() };
126
+
127
+ export function animate(el: HTMLElement, ...layers: Layer[]): Playback {
128
+ const plays: Playback[] = [];
129
+ for (const layer of layers) {
130
+ const started = play(el, layer);
131
+ if (started) plays.push(started);
132
+ }
133
+ if (!plays.length) return settled;
134
+ if (plays.length === 1) return plays[0];
135
+ return {
136
+ stop: () => {
137
+ for (const started of plays) started.stop();
138
+ },
139
+ finished: Promise.all(plays.map((started) => started.finished)).then((): void => {}),
140
+ };
141
+ }
@@ -0,0 +1,53 @@
1
+ interface Axes {
2
+ x: number;
3
+ y: number;
4
+ }
5
+
6
+ export interface PanInfo {
7
+ offset: Axes;
8
+ velocity: Axes;
9
+ }
10
+
11
+ interface DragHandlers {
12
+ onMove?: (info: PanInfo) => void;
13
+ onEnd?: (info: PanInfo) => void;
14
+ }
15
+
16
+ export function startDrag(origin: { clientX: number; clientY: number }, handlers: DragHandlers): () => void {
17
+ const startX = origin.clientX;
18
+ const startY = origin.clientY;
19
+ let lastX = startX;
20
+ let lastY = startY;
21
+ let lastTime = performance.now();
22
+ let velocity: Axes = { x: 0, y: 0 };
23
+
24
+ const info = (): PanInfo => ({ offset: { x: lastX - startX, y: lastY - startY }, velocity });
25
+
26
+ const move = (event: PointerEvent) => {
27
+ const now = performance.now();
28
+ const elapsed = now - lastTime;
29
+ if (elapsed > 0) {
30
+ velocity = { x: ((event.clientX - lastX) / elapsed) * 1000, y: ((event.clientY - lastY) / elapsed) * 1000 };
31
+ lastTime = now;
32
+ }
33
+ lastX = event.clientX;
34
+ lastY = event.clientY;
35
+ handlers.onMove?.(info());
36
+ };
37
+
38
+ const detach = () => {
39
+ window.removeEventListener('pointermove', move);
40
+ window.removeEventListener('pointerup', end);
41
+ window.removeEventListener('pointercancel', end);
42
+ };
43
+
44
+ const end = () => {
45
+ detach();
46
+ handlers.onEnd?.(info());
47
+ };
48
+
49
+ window.addEventListener('pointermove', move);
50
+ window.addEventListener('pointerup', end);
51
+ window.addEventListener('pointercancel', end);
52
+ return detach;
53
+ }