@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,268 @@
1
+ 'use client';import { tokenPx } from './chunk-UE63OOVG.js';
2
+ import { Presence } from './chunk-MXOPCFWR.js';
3
+ import { useMotion } from './chunk-W3SHPKSB.js';
4
+ import { animate } from './chunk-L3Q3VDIW.js';
5
+ import { UIMotion } from './chunk-S5ZTZANS.js';
6
+ import { cx } from './chunk-QGNC7LH5.js';
7
+ import './tooltip.css';
8
+ import { useRef, useId, useEffect, Fragment, useSyncExternalStore, useState, useLayoutEffect } from 'react';
9
+ import { createPortal } from 'react-dom';
10
+ import { jsxs, jsx } from 'react/jsx-runtime';
11
+
12
+ var OPEN_DELAY = 350;
13
+ var CLOSE_GRACE = 140;
14
+ var TOOLTIP_DOM_ID = "pds-tooltip";
15
+ var WARM_WINDOW = 300;
16
+ var store = {
17
+ active: null,
18
+ closeTimer: 0,
19
+ warmUntil: 0,
20
+ listeners: /* @__PURE__ */ new Set(),
21
+ get: () => store.active,
22
+ subscribe(l) {
23
+ store.listeners.add(l);
24
+ return () => store.listeners.delete(l);
25
+ },
26
+ emit() {
27
+ store.listeners.forEach((l) => l());
28
+ },
29
+ isWarm: () => store.active !== null || performance.now() < store.warmUntil,
30
+ open(payload) {
31
+ clearTimeout(store.closeTimer);
32
+ store.active = payload;
33
+ store.emit();
34
+ },
35
+ close(id, grace = CLOSE_GRACE) {
36
+ clearTimeout(store.closeTimer);
37
+ store.closeTimer = setTimeout(() => {
38
+ if (store.active && store.active.id === id) store.closeNow();
39
+ }, grace);
40
+ },
41
+ closeNow() {
42
+ clearTimeout(store.closeTimer);
43
+ if (!store.active) return;
44
+ store.warmUntil = performance.now() + WARM_WINDOW;
45
+ store.active = null;
46
+ store.emit();
47
+ }
48
+ };
49
+ var hostReg = {
50
+ keys: [],
51
+ listeners: /* @__PURE__ */ new Set(),
52
+ subscribe(l) {
53
+ hostReg.listeners.add(l);
54
+ return () => hostReg.listeners.delete(l);
55
+ },
56
+ register(k) {
57
+ hostReg.keys.push(k);
58
+ hostReg.listeners.forEach((l) => l());
59
+ return () => {
60
+ const i = hostReg.keys.indexOf(k);
61
+ if (i >= 0) hostReg.keys.splice(i, 1);
62
+ hostReg.listeners.forEach((l) => l());
63
+ };
64
+ }
65
+ };
66
+ function useHostElection() {
67
+ const keyRef = useRef(null);
68
+ if (!keyRef.current) keyRef.current = /* @__PURE__ */ Symbol("tip-host");
69
+ useEffect(() => hostReg.register(keyRef.current), []);
70
+ return useSyncExternalStore(
71
+ hostReg.subscribe,
72
+ () => hostReg.keys[0] === keyRef.current,
73
+ () => false
74
+ );
75
+ }
76
+ var SM = UIMotion;
77
+ function targetBox(size, t, want) {
78
+ const vw = window.innerWidth, vh = window.innerHeight, TIP_GAP = tokenPx("--space-2", 8) || 8, M = TIP_GAP;
79
+ let p = want;
80
+ if (p === "top" && t.top - size.h - TIP_GAP < M) p = "bottom";
81
+ else if (p === "bottom" && t.bottom + size.h + TIP_GAP > vh - M) p = "top";
82
+ else if (p === "left" && t.left - size.w - TIP_GAP < M) p = "right";
83
+ else if (p === "right" && t.right + size.w + TIP_GAP > vw - M) p = "left";
84
+ let x, y;
85
+ if (p === "top") {
86
+ x = t.left + t.width / 2 - size.w / 2;
87
+ y = t.top - size.h - TIP_GAP;
88
+ } else if (p === "bottom") {
89
+ x = t.left + t.width / 2 - size.w / 2;
90
+ y = t.bottom + TIP_GAP;
91
+ } else if (p === "left") {
92
+ x = t.left - size.w - TIP_GAP;
93
+ y = t.top + t.height / 2 - size.h / 2;
94
+ } else {
95
+ x = t.right + TIP_GAP;
96
+ y = t.top + t.height / 2 - size.h / 2;
97
+ }
98
+ return {
99
+ x: Math.round(Math.min(Math.max(x, M), vw - size.w - M)),
100
+ y: Math.round(Math.min(Math.max(y, M), vh - size.h - M)),
101
+ w: size.w,
102
+ h: size.h,
103
+ placement: p
104
+ };
105
+ }
106
+ var fromEdge = (p) => ({
107
+ x: p === "left" ? 4 : p === "right" ? -4 : 0,
108
+ y: p === "top" ? 4 : p === "bottom" ? -4 : 0
109
+ });
110
+ function Body({ a, width }) {
111
+ return /* @__PURE__ */ jsx("span", { className: "tooltip__body", style: width ? { width } : void 0, children: a.shortcut ? /* @__PURE__ */ jsxs("span", { className: "tooltip__row", children: [
112
+ /* @__PURE__ */ jsx("span", { children: a.content }),
113
+ /* @__PURE__ */ jsx("kbd", { className: "tooltip__shortcut", children: a.shortcut })
114
+ ] }) : a.content });
115
+ }
116
+ function TipSurface({ a, box, animate: animateSpec, exit }) {
117
+ const ref = useRef(null);
118
+ const bodyRef = useRef(null);
119
+ const settled = useRef(false);
120
+ useMotion(ref, { animate: animateSpec, exit });
121
+ useLayoutEffect(() => {
122
+ const el = ref.current;
123
+ if (!el) return;
124
+ if (!settled.current) {
125
+ settled.current = true;
126
+ return;
127
+ }
128
+ animate(el, { x: [box.x], y: [box.y], width: [box.w], height: [box.h], timing: SM.t.layout });
129
+ }, [box.x, box.y, box.w, box.h]);
130
+ useLayoutEffect(() => {
131
+ if (bodyRef.current) animate(bodyRef.current, { opacity: [0, 1], timing: SM.t.enter });
132
+ }, [a.id]);
133
+ return /* @__PURE__ */ jsx("div", { ref, className: "tooltip", id: TOOLTIP_DOM_ID, role: "tooltip", "data-placement": box.placement, children: /* @__PURE__ */ jsx("span", { ref: bodyRef, children: /* @__PURE__ */ jsx(Body, { a, width: box.bodyW }) }) });
134
+ }
135
+ function TooltipHost() {
136
+ const active = useSyncExternalStore(store.subscribe, store.get);
137
+ const measureRef = useRef(null);
138
+ const [box, setBox] = useState(null);
139
+ useLayoutEffect(() => {
140
+ if (!active) {
141
+ setBox(null);
142
+ return;
143
+ }
144
+ const r = measureRef.current.getBoundingClientRect();
145
+ const b = measureRef.current.firstChild.getBoundingClientRect();
146
+ setBox({
147
+ ...targetBox({ w: Math.ceil(r.width), h: Math.ceil(r.height) }, active.rect(), active.placement),
148
+ bodyW: Math.ceil(b.width)
149
+ });
150
+ }, [active]);
151
+ useEffect(() => {
152
+ if (!active) return void 0;
153
+ const onKey = (e) => {
154
+ if (e.key === "Escape") store.closeNow();
155
+ };
156
+ const onScroll = () => store.closeNow();
157
+ document.addEventListener("keydown", onKey);
158
+ window.addEventListener("scroll", onScroll, true);
159
+ return () => {
160
+ document.removeEventListener("keydown", onKey);
161
+ window.removeEventListener("scroll", onScroll, true);
162
+ };
163
+ }, [active]);
164
+ const shown = useRef(null);
165
+ if (box) shown.current = { box, off: fromEdge(box.placement) };
166
+ return createPortal(
167
+ /* @__PURE__ */ jsxs(Fragment, { children: [
168
+ active && /* @__PURE__ */ jsx("div", { className: "tooltip tooltip--measure", ref: measureRef, "aria-hidden": "true", children: /* @__PURE__ */ jsx(Body, { a: active }) }),
169
+ /* @__PURE__ */ jsx(Presence, { children: active && box && /* @__PURE__ */ jsx(
170
+ TipSurface,
171
+ {
172
+ a: active,
173
+ box,
174
+ animate: (el) => {
175
+ const at = shown.current;
176
+ if (!at) return null;
177
+ return animate(
178
+ el,
179
+ { width: [at.box.w], height: [at.box.h], timing: { duration: 0 } },
180
+ { x: [at.box.x + at.off.x, at.box.x], y: [at.box.y + at.off.y, at.box.y], timing: SM.t.layout },
181
+ { opacity: [0, 1], scale: [0.96, 1], timing: SM.t.enter }
182
+ );
183
+ },
184
+ exit: (el) => {
185
+ const at = shown.current;
186
+ if (!at) return null;
187
+ return animate(el, {
188
+ x: [at.box.x + at.off.x],
189
+ y: [at.box.y + at.off.y],
190
+ opacity: [0],
191
+ scale: [0.96],
192
+ timing: SM.t.exit
193
+ });
194
+ }
195
+ },
196
+ "tip"
197
+ ) })
198
+ ] }),
199
+ document.body
200
+ );
201
+ }
202
+ function Tooltip({
203
+ content,
204
+ shortcut = null,
205
+ placement = "top",
206
+ disabled = false,
207
+ openDelay = OPEN_DELAY,
208
+ closeDelay = CLOSE_GRACE,
209
+ id,
210
+ children,
211
+ htmlProps
212
+ }) {
213
+ const wrapRef = useRef(null);
214
+ const openTimer = useRef(0);
215
+ const myId = id || "tip-" + useId();
216
+ const isHost = useHostElection();
217
+ const anchorEl = () => wrapRef.current?.firstElementChild ?? null;
218
+ useEffect(
219
+ () => () => {
220
+ clearTimeout(openTimer.current);
221
+ store.close(myId, 0);
222
+ },
223
+ []
224
+ );
225
+ function show(immediate) {
226
+ if (disabled) return;
227
+ clearTimeout(openTimer.current);
228
+ const el = anchorEl();
229
+ if (!el) return;
230
+ el.setAttribute("aria-describedby", TOOLTIP_DOM_ID);
231
+ const open = () => store.open({ id: myId, content, shortcut, placement, rect: () => anchorEl().getBoundingClientRect() });
232
+ if (immediate || store.isWarm()) open();
233
+ else openTimer.current = setTimeout(open, openDelay);
234
+ }
235
+ function hide() {
236
+ clearTimeout(openTimer.current);
237
+ const el = anchorEl();
238
+ if (el) el.removeAttribute("aria-describedby");
239
+ store.close(myId, closeDelay);
240
+ }
241
+ const onEnter = (e) => {
242
+ if (e.pointerType !== "touch") show(false);
243
+ };
244
+ const onFocusIn = (e) => {
245
+ if (e.target.matches(":focus-visible")) show(true);
246
+ };
247
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
248
+ /* @__PURE__ */ jsx(
249
+ "span",
250
+ {
251
+ ...htmlProps,
252
+ ref: wrapRef,
253
+ className: cx("tooltip-anchor", htmlProps?.className),
254
+ onPointerEnter: onEnter,
255
+ onPointerLeave: hide,
256
+ onPointerDown: hide,
257
+ onFocus: onFocusIn,
258
+ onBlur: hide,
259
+ children
260
+ }
261
+ ),
262
+ isHost && /* @__PURE__ */ jsx(TooltipHost, {})
263
+ ] });
264
+ }
265
+
266
+ export { Tooltip };
267
+ //# sourceMappingURL=tooltip.js.map
268
+ //# sourceMappingURL=tooltip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/composites/tooltip/tooltip-store.ts","../src/components/composites/tooltip/tooltip-host.tsx","../src/components/composites/tooltip/Tooltip.tsx"],"names":["useRef","useSyncExternalStore","useEffect","jsxs","Fragment","jsx"],"mappings":"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","file":"tooltip.js","sourcesContent":["'use client';\n\nimport { useEffect, useRef, useSyncExternalStore, type ReactNode } from 'react';\n\nexport type Placement = 'top' | 'bottom' | 'left' | 'right';\n\nexport interface ActivePayload {\n id: string;\n content: ReactNode;\n shortcut?: string | null;\n placement: Placement;\n rect: () => DOMRect;\n}\n\nexport const OPEN_DELAY = 350;\nexport const CLOSE_GRACE = 140;\nexport const TOOLTIP_DOM_ID = 'pds-tooltip';\nconst WARM_WINDOW = 300;\n\nexport const store = {\n active: null as ActivePayload | null,\n closeTimer: 0 as ReturnType<typeof setTimeout> | 0,\n warmUntil: 0,\n listeners: new Set<() => void>(),\n get: () => store.active,\n subscribe(l: () => void) {\n store.listeners.add(l);\n return () => store.listeners.delete(l);\n },\n emit() {\n store.listeners.forEach((l) => l());\n },\n isWarm: () => store.active !== null || performance.now() < store.warmUntil,\n open(payload: ActivePayload) {\n clearTimeout(store.closeTimer);\n store.active = payload;\n store.emit();\n },\n close(id: string, grace = CLOSE_GRACE) {\n clearTimeout(store.closeTimer);\n store.closeTimer = setTimeout(() => {\n if (store.active && store.active.id === id) store.closeNow();\n }, grace);\n },\n closeNow() {\n clearTimeout(store.closeTimer);\n if (!store.active) return;\n store.warmUntil = performance.now() + WARM_WINDOW;\n store.active = null;\n store.emit();\n },\n};\n\nconst hostReg = {\n keys: [] as symbol[],\n listeners: new Set<() => void>(),\n subscribe(l: () => void) {\n hostReg.listeners.add(l);\n return () => hostReg.listeners.delete(l);\n },\n register(k: symbol) {\n hostReg.keys.push(k);\n hostReg.listeners.forEach((l) => l());\n return () => {\n const i = hostReg.keys.indexOf(k);\n if (i >= 0) hostReg.keys.splice(i, 1);\n hostReg.listeners.forEach((l) => l());\n };\n },\n};\n\nexport function useHostElection(): boolean {\n const keyRef = useRef<symbol | null>(null);\n if (!keyRef.current) keyRef.current = Symbol('tip-host');\n useEffect(() => hostReg.register(keyRef.current!), []);\n return useSyncExternalStore(\n hostReg.subscribe,\n () => hostReg.keys[0] === keyRef.current,\n () => false,\n );\n}\n","'use client';\n\nimport { Fragment, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport { createPortal } from 'react-dom';\nimport { animate } from '../../../engine';\nimport { Presence } from '../../../motion/presence';\nimport { useMotion, type MotionSpecs } from '../../../motion/use-motion';\nimport { UIMotion } from '../../../tokens/motion-tokens';\nimport { tokenPx } from '../../internal/utils/token-px';\nimport { store, TOOLTIP_DOM_ID, type ActivePayload, type Placement } from './tooltip-store';\n\nconst SM = UIMotion;\n\ninterface Size {\n w: number;\n h: number;\n}\ninterface TargetBox extends Size {\n x: number;\n y: number;\n placement: Placement;\n}\ninterface RenderedBox extends TargetBox {\n bodyW: number;\n}\n\nfunction targetBox(size: Size, t: DOMRect, want: Placement): TargetBox {\n const vw = window.innerWidth,\n vh = window.innerHeight,\n TIP_GAP = tokenPx('--space-2', 8) || 8,\n M = TIP_GAP;\n let p = want;\n if (p === 'top' && t.top - size.h - TIP_GAP < M) p = 'bottom';\n else if (p === 'bottom' && t.bottom + size.h + TIP_GAP > vh - M) p = 'top';\n else if (p === 'left' && t.left - size.w - TIP_GAP < M) p = 'right';\n else if (p === 'right' && t.right + size.w + TIP_GAP > vw - M) p = 'left';\n let x, y;\n if (p === 'top') {\n x = t.left + t.width / 2 - size.w / 2;\n y = t.top - size.h - TIP_GAP;\n } else if (p === 'bottom') {\n x = t.left + t.width / 2 - size.w / 2;\n y = t.bottom + TIP_GAP;\n } else if (p === 'left') {\n x = t.left - size.w - TIP_GAP;\n y = t.top + t.height / 2 - size.h / 2;\n } else {\n x = t.right + TIP_GAP;\n y = t.top + t.height / 2 - size.h / 2;\n }\n return {\n x: Math.round(Math.min(Math.max(x, M), vw - size.w - M)),\n y: Math.round(Math.min(Math.max(y, M), vh - size.h - M)),\n w: size.w,\n h: size.h,\n placement: p,\n };\n}\n\nconst fromEdge = (p: Placement) => ({\n x: p === 'left' ? 4 : p === 'right' ? -4 : 0,\n y: p === 'top' ? 4 : p === 'bottom' ? -4 : 0,\n});\n\nfunction Body({ a, width }: { a: ActivePayload; width?: number }) {\n return (\n <span className=\"tooltip__body\" style={width ? { width } : undefined}>\n {a.shortcut ? (\n <span className=\"tooltip__row\">\n <span>{a.content}</span>\n <kbd className=\"tooltip__shortcut\">{a.shortcut}</kbd>\n </span>\n ) : (\n a.content\n )}\n </span>\n );\n}\n\nfunction TipSurface({ a, box, animate: animateSpec, exit }: { a: ActivePayload; box: RenderedBox } & MotionSpecs) {\n const ref = useRef<HTMLDivElement>(null);\n const bodyRef = useRef<HTMLSpanElement>(null);\n const settled = useRef(false);\n useMotion(ref, { animate: animateSpec, exit });\n\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) return;\n if (!settled.current) {\n settled.current = true;\n return;\n }\n animate(el, { x: [box.x], y: [box.y], width: [box.w], height: [box.h], timing: SM.t.layout });\n }, [box.x, box.y, box.w, box.h]);\n\n useLayoutEffect(() => {\n if (bodyRef.current) animate(bodyRef.current, { opacity: [0, 1], timing: SM.t.enter });\n }, [a.id]);\n\n return (\n <div ref={ref} className=\"tooltip\" id={TOOLTIP_DOM_ID} role=\"tooltip\" data-placement={box.placement}>\n <span ref={bodyRef}>\n <Body a={a} width={box.bodyW} />\n </span>\n </div>\n );\n}\n\nexport function TooltipHost() {\n const active = useSyncExternalStore(store.subscribe, store.get);\n const measureRef = useRef<HTMLDivElement>(null);\n const [box, setBox] = useState<RenderedBox | null>(null);\n\n useLayoutEffect(() => {\n if (!active) {\n setBox(null);\n return;\n }\n const r = measureRef.current.getBoundingClientRect();\n const b = (measureRef.current.firstChild as HTMLElement).getBoundingClientRect();\n setBox({\n ...targetBox({ w: Math.ceil(r.width), h: Math.ceil(r.height) }, active.rect(), active.placement),\n bodyW: Math.ceil(b.width),\n });\n }, [active]);\n\n useEffect(() => {\n if (!active) return undefined;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') store.closeNow();\n };\n const onScroll = () => store.closeNow();\n document.addEventListener('keydown', onKey);\n window.addEventListener('scroll', onScroll, true);\n return () => {\n document.removeEventListener('keydown', onKey);\n window.removeEventListener('scroll', onScroll, true);\n };\n }, [active]);\n\n const shown = useRef<{ box: RenderedBox; off: { x: number; y: number } } | null>(null);\n if (box) shown.current = { box, off: fromEdge(box.placement) };\n\n return createPortal(\n <Fragment>\n {active && (\n <div className=\"tooltip tooltip--measure\" ref={measureRef} aria-hidden=\"true\">\n <Body a={active} />\n </div>\n )}\n <Presence>\n {active && box && (\n <TipSurface\n key=\"tip\"\n a={active}\n box={box}\n animate={(el) => {\n const at = shown.current;\n if (!at) return null;\n return animate(\n el,\n { width: [at.box.w], height: [at.box.h], timing: { duration: 0 } },\n { x: [at.box.x + at.off.x, at.box.x], y: [at.box.y + at.off.y, at.box.y], timing: SM.t.layout },\n { opacity: [0, 1], scale: [0.96, 1], timing: SM.t.enter },\n );\n }}\n exit={(el) => {\n const at = shown.current;\n if (!at) return null;\n return animate(el, {\n x: [at.box.x + at.off.x],\n y: [at.box.y + at.off.y],\n opacity: [0],\n scale: [0.96],\n timing: SM.t.exit,\n });\n }}\n />\n )}\n </Presence>\n </Fragment>,\n document.body,\n );\n}\n","'use client';\n\nimport './tooltip.css';\nimport {\n Fragment,\n useEffect,\n useId,\n useRef,\n type FocusEvent,\n type HTMLAttributes,\n type PointerEvent,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { store, useHostElection, OPEN_DELAY, CLOSE_GRACE, TOOLTIP_DOM_ID, type Placement } from './tooltip-store';\nimport { TooltipHost } from './tooltip-host';\nimport type { DataAttributes } from '../../../dom-props';\nimport { cx } from '../../internal/utils/cx';\n\nexport interface TooltipProps {\n /** The hint - a string or small node; never interactive content. */\n content: ReactNode;\n /** Optional keyboard hint, rendered as mono metadata (\"⌘↩\", \"S\"). */\n shortcut?: string | null;\n /** Preferred side; flips automatically when out of viewport room. */\n placement?: Placement;\n /** Suppress the tooltip entirely (trigger renders untouched). */\n disabled?: boolean;\n /** ms before a cold hover shows; warm hovers + focus are instant. Default 350. */\n openDelay?: number;\n /** ms the bubble lingers after leaving (bridges moving to a neighbour). Default 140. */\n closeDelay?: number;\n /** Stable id for this trigger's store entry - lets overlapping triggers race safely under\n * the one shared tooltip host. Not rendered to the DOM. Auto-generated when omitted. */\n id?: string;\n /** Exactly one element. */\n children: ReactElement;\n /** Standard <span> attributes forwarded to the anchor wrapper. */\n htmlProps?: HTMLAttributes<HTMLSpanElement> & DataAttributes;\n}\n\nexport function Tooltip({\n content,\n shortcut = null,\n placement = 'top',\n disabled = false,\n openDelay = OPEN_DELAY,\n closeDelay = CLOSE_GRACE,\n id,\n children,\n htmlProps,\n}: TooltipProps) {\n const wrapRef = useRef<HTMLSpanElement | null>(null);\n const openTimer = useRef<ReturnType<typeof setTimeout> | 0>(0);\n const myId = id || 'tip-' + useId();\n const isHost = useHostElection();\n\n const anchorEl = (): HTMLElement | null => (wrapRef.current?.firstElementChild as HTMLElement) ?? null;\n\n useEffect(\n () => () => {\n clearTimeout(openTimer.current);\n store.close(myId, 0);\n },\n [],\n ); // eslint-disable-line react-hooks/exhaustive-deps\n\n function show(immediate: boolean) {\n if (disabled) return;\n clearTimeout(openTimer.current);\n const el = anchorEl();\n if (!el) return;\n el.setAttribute('aria-describedby', TOOLTIP_DOM_ID);\n const open = () =>\n store.open({ id: myId, content, shortcut, placement, rect: () => anchorEl()!.getBoundingClientRect() });\n if (immediate || store.isWarm()) open();\n else openTimer.current = setTimeout(open, openDelay);\n }\n function hide() {\n clearTimeout(openTimer.current);\n const el = anchorEl();\n if (el) el.removeAttribute('aria-describedby');\n store.close(myId, closeDelay);\n }\n\n const onEnter = (e: PointerEvent) => {\n if (e.pointerType !== 'touch') show(false);\n };\n const onFocusIn = (e: FocusEvent) => {\n if ((e.target as HTMLElement).matches(':focus-visible')) show(true);\n };\n\n return (\n <Fragment>\n <span\n {...htmlProps}\n ref={wrapRef}\n className={cx('tooltip-anchor', htmlProps?.className)}\n onPointerEnter={onEnter}\n onPointerLeave={hide}\n onPointerDown={hide}\n onFocus={onFocusIn}\n onBlur={hide}\n >\n {children}\n </span>\n {isHost && <TooltipHost />}\n </Fragment>\n );\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ interface SelectOption {
4
+ /** The stored value - what `onChange` returns and `value` matches; must be unique. */
5
+ value: string;
6
+ /** Primary row text, and the trigger label once selected; also matched by `searchable`. */
7
+ label: ReactNode;
8
+ /** Optional secondary line under the label; also matched by `searchable`. */
9
+ description?: string;
10
+ /** Your own icon node shown before the label. */
11
+ icon?: ReactNode;
12
+ /** Not selectable - skipped by keyboard nav and typeahead, and marked `aria-disabled`. @default false */
13
+ disabled?: boolean;
14
+ }
15
+ interface SelectGroup {
16
+ /** Section heading rendered above the options; omit for an unlabeled group. */
17
+ label?: string;
18
+ /** The options in this section. */
19
+ options: SelectOption[];
20
+ }
21
+
22
+ export type { SelectOption as S, SelectGroup as a };
package/llms.txt ADDED
@@ -0,0 +1,196 @@
1
+ # Zyncat UI - LLM usage guide
2
+
3
+ Premium React 19 design system, shipped as built ESM + types. This is a compact per-component
4
+ index; full prop types (with docs) live in node_modules/@zyncat/ui/dist/*.d.ts.
5
+
6
+ Setup:
7
+ import { Button } from '@zyncat/ui/button'; // one subpath per component - there is no barrel entry
8
+ import '@zyncat/ui/styles.css'; // base layer (fonts+tokens) - link ONCE; each component lazy-loads its own CSS
9
+ Peers: react, react-dom. (The curated Phosphor glyphs used internally are bundled in - not a peer.)
10
+ Animation is built in - a ~2.5 kB WAAPI engine, no animation library to install.
11
+ No bundler/transpile config - built ESM keeps 'use client' intact for the Next.js App Router.
12
+
13
+ Conventions:
14
+ - Sentence case everywhere; no emoji or exclamation marks in UI. One primary <Button> per view.
15
+ - Numbers, times, IDs, status read mono + tabular. Status words: Scheduled, Draft, Published, Failed.
16
+ - Status hues (info/success/warning/danger) only for genuine status; hierarchy from the neutral ramp.
17
+ - Icons: Zyncat UI renders its own glyphs from a bundled curated Phosphor set, but does NOT
18
+ export an Icon component. Where an example below shows <Icon name="..." />, that is YOUR OWN icon
19
+ node - any ReactNode you pass into the prop (e.g. an @phosphor-icons/react element).
20
+
21
+ Every named export is also a subpath: @zyncat/ui/<kebab-name> (e.g. @zyncat/ui/status-badge).
22
+
23
+ Agents: prefer the bundled MCP server over scanning this file or node_modules - register
24
+ { "mcpServers": { "zyncat-ui": { "command": "node", "args": ["./node_modules/@zyncat/ui/dist/mcp.js"] } } }
25
+ then use list_components / get_component / search_api / get_tokens.
26
+
27
+ ============================================================ PRIMITIVES
28
+
29
+ Button - @zyncat/ui/button
30
+ One control for every click; exactly one primary per view. variant primary|secondary|ghost|danger|link, size sm|md|lg, iconLeft/iconRight (ReactNode), loading, disabled.
31
+ <Button variant="primary" iconLeft={<Icon name="plus" size="sm" />}>Schedule post</Button>
32
+
33
+ Collapse - @zyncat/ui/collapse
34
+ Layout-transition primitive; eases height open/closed, never teleports. Closed content also
35
+ leaves the tab order, accessibility tree and hit-testing once the exit completes; reopening is
36
+ focusable immediately. animation takes motion tokens ONLY - { duration, ease }, each one token
37
+ (fast|base|slow|slower|slowest / standard|entrance|exit|spring|glide) or per-direction { open, close }.
38
+ <Collapse open={open} animation={{ duration: { close: 'fast' }, ease: { close: 'exit' } }}><div>...revealed content...</div></Collapse>
39
+
40
+ Glide / GlidePill - @zyncat/ui/glide
41
+ A persistent background pill that glides smoothly between hovered/active elements. Container must be `position: relative`.
42
+ const glide = useGlide(containerRef);
43
+ <nav ref={containerRef} onPointerLeave={glide.leave}>
44
+ <GlidePill rect={glide.rect} active={glide.active} className="my-pill" />
45
+ <button onPointerEnter={(e) => glide.enter(e.currentTarget)}>Item 1</button>
46
+ </nav>
47
+
48
+ Badge - @zyncat/ui/badge
49
+ Glass or outline chip. tone neutral|info|success|warning|danger, dot, live, variant="outline", pill, size="sm".
50
+ <Badge tone="success" pill>Published</Badge>
51
+
52
+ StatusBadge - @zyncat/ui/status-badge
53
+ Canonical post status - tone + one-word label. status draft|scheduled|processing|published|failed; add `morph` to animate in place as status changes.
54
+ <StatusBadge status="published" />
55
+ <StatusBadge status={s} morph />
56
+
57
+ CountBadge - @zyncat/ui/count-badge
58
+ Mono tabular count; `roll` animates digits like an odometer. value: number | string.
59
+ <CountBadge value="7 / 10" />
60
+ <CountBadge value={count} roll tone="info" />
61
+
62
+ ============================================================ FORMS
63
+ (all take label, helper/error/warning/success, size sm|md|lg, disabled)
64
+
65
+ TextField - @zyncat/ui/text-field
66
+ Base text input; states disclose via Collapse. leadingIcon, clearable, optional/required.
67
+ <TextField id="ws" label="Workspace name" required value={v} onChange={(e) => set(e.target.value)} />
68
+ <TextField id="h" label="Username" error="Must be at least 4 characters." value={v} onChange={...} />
69
+
70
+ NumberField - @zyncat/ui/number-field
71
+ Tabular figures, caret steppers, unit suffix, clamp to [min,max]. onChange(value: number).
72
+ <NumberField id="seats" label="Seats" unit="users" min={1} max={50} value={n} onChange={setN} />
73
+
74
+ OtpField - @zyncat/ui/otp-field
75
+ Segmented one-time code; auto-advance, paste-to-fill. length, group, error, size.
76
+ <OtpField length={6} group={3} value={code} onChange={setCode} />
77
+
78
+ Textarea - @zyncat/ui/textarea
79
+ Auto-grow, char meter (max), ⌘/Ctrl+↵ submit (onSubmit). minRows/maxRows.
80
+ <Textarea id="bio" label="Bio" max={280} minRows={4} value={v} onChange={(e) => set(e.target.value)} onSubmit={save} />
81
+
82
+ Checkbox - @zyncat/ui/checkbox
83
+ Stages a choice. label, description, indeterminate, invalid, required, disabled. Standard checked/onChange.
84
+ <Checkbox label="Email me about product updates" checked={on} onChange={(e) => set(e.target.checked)} />
85
+
86
+ Toggle - @zyncat/ui/toggle
87
+ Actuates a setting on the spot. label, description; checked/onChange like a checkbox.
88
+ <Toggle label="Auto-save drafts" checked={on} onChange={(e) => set(e.target.checked)} />
89
+
90
+ RadioGroup - @zyncat/ui/radio-group
91
+ Pick exactly one. options: RadioOption[] ({ value, label, description?, icon?, disabled? }); variant rows|cards, orientation; value/onChange(value).
92
+ <RadioGroup name="role" label="Member role" value={role} onChange={setRole} options={ROLES} />
93
+
94
+ Select - @zyncat/ui/select
95
+ Single-select listbox. options: SelectOption[]; value: string|null, onChange(value); searchable, leadingIcon, placeholder, invalid, loading, ariaLabel.
96
+ <Select options={TIMEZONES} value={tz} onChange={setTz} searchable placeholder="Choose a time zone" ariaLabel="Time zone" />
97
+
98
+ MultiSelect - @zyncat/ui/multi-select
99
+ Many-of listbox; stays open while toggling, trigger summarises as first +N. value: string[], onChange(string[]); searchable.
100
+ <MultiSelect options={PEOPLE} value={ids} onChange={setIds} placeholder="Choose members" ariaLabel="Members" />
101
+
102
+ ============================================================ DATA DISPLAY
103
+
104
+ Avatar - @zyncat/ui/avatar
105
+ Identity mark: image, initials, or silhouette. name, src, size xs|sm|md|lg|xl, status online|away|busy|offline, shape, paletteIndex, icon.
106
+ <Avatar name="Ana Ng" src="..." status="online" />
107
+
108
+ AvatarGroup - @zyncat/ui/avatar-group
109
+ Overlapping avatars with +N overflow. max, size; children are <Avatar>s.
110
+ <AvatarGroup max={4} size="sm"><Avatar name="Ana Ng" />...</AvatarGroup>
111
+
112
+ Tag / TagGroup - @zyncat/ui/tag
113
+ User-owned label (a control, not a status). icon, size, disabled, onRemove + removeLabel (removable). TagGroup wraps a labelled set.
114
+ <Tag icon={<Icon name="hash" />} onRemove={fn} removeLabel="Remove design">design</Tag>
115
+
116
+ ToggleTag - @zyncat/ui/toggle-tag
117
+ Many-of-many filter chip. selected/onChange(next) or defaultSelected; icon, count.
118
+ <ToggleTag selected={on} onChange={setOn} icon={<Icon name="star" />} count={48}>Starred</ToggleTag>
119
+
120
+ Table - @zyncat/ui/table
121
+ Declare columns + rows; owns sort, selection, stickiness, overflow. columns: TableColumn<T>[] ({ key, label, render?, sortable?, sortBy?, align?, mono?, strong?, grow? }); selectable, defaultSort, footer.
122
+ <Table<Invoice> label="Invoices" columns={COLUMNS} rows={INVOICES} selectable defaultSort={{ key: 'amount', dir: 'desc' }} />
123
+
124
+ Pagination - @zyncat/ui/pagination
125
+ Cursor strip: mono range readout + prev/next pair. range: [from,to], total?, hasPrev/hasNext, onPrev/onNext.
126
+ <Pagination label="Rows" range={[from, to]} total={312} hasPrev hasNext onPrev={...} onNext={...} />
127
+
128
+ ============================================================ DATE, TIME & TABS
129
+ (value is a string; onChange(value | null); timezone, min/max, disabled)
130
+
131
+ DateField - @zyncat/ui/date-field
132
+ Month calendar in a popover; value 'YYYY-MM-DD'.
133
+ <DateField label="Start date" value={d} onChange={setD} timezone="Europe/Riga" min="2026-06-01" />
134
+
135
+ DateTimeField - @zyncat/ui/datetime-field
136
+ Calendar + segmented HH:MM; value 'YYYY-MM-DDTHH:mm'. format 12h|24h, minuteStep.
137
+ <DateTimeField label="Due at" value={v} onChange={setV} format="12h" minuteStep={15} />
138
+
139
+ DateRangeField - @zyncat/ui/date-range-field
140
+ Two-tap auto-ordering window; value DateRange { start, end } | null; commits when both ends exist.
141
+ <DateRangeField label="Reporting period" value={r} onChange={setR} />
142
+
143
+ TimeField - @zyncat/ui/time-field
144
+ Standalone segmented time; value 'HH:mm'; bounds saturate. format, minuteStep.
145
+ <TimeField label="Send at" value={t} onChange={setT} format="12h" minuteStep={15} />
146
+
147
+ Tabs / TabPanel - @zyncat/ui/tabs
148
+ Line tabs; the ink reaches then releases; panels enter from the side you moved toward. items: TabItem[]; value/onChange(value, dir). Pair with <TabPanel name tab dir>.
149
+ <Tabs name="views" label="Workspace views" value={view} onChange={(v, d) => { setView(v); setDir(d); }} items={items} />
150
+ <TabPanel name="views" tab={view} dir={dir}>...</TabPanel>
151
+
152
+ ============================================================ OVERLAYS & FEEDBACK
153
+
154
+ Alert - @zyncat/ui/alert
155
+ Persistent, in-flow status. tone, title, children (body), action { label, onClick }, dismissible, banner, open/onDismiss (controlled).
156
+ <Alert tone="warning" title="Your trial ends in 5 days" action={{ label: 'Upgrade', onClick: fn }}>Add a plan...</Alert>
157
+
158
+ Toast - @zyncat/ui/toast (imperative; mount <Toaster /> once at the app root, then call toast() from anywhere)
159
+ <Toaster /> props: position ('bottom-right' default), duration, visibleToasts, gap, offset, expand. Without it, toast() no-ops.
160
+ toast(msg) - toast.success/error/warning/info(msg, { description, action }) - toast.promise(p, { loading, success, error }) - toast.loading - toast.dismiss().
161
+ import { Toaster, toast } from '@zyncat/ui/toast';
162
+ // once at the root: <Toaster position="bottom-right" />
163
+ toast.success('Changes saved', { description: 'Synced to the cloud' });
164
+ toast.promise(save(), { loading: 'Saving...', success: (v) => `Saved - ${v.count}`, error: (e) => `Failed - ${e.message}` });
165
+
166
+ Tooltip - @zyncat/ui/tooltip
167
+ Transient hint on hover/focus; wraps any child (no ref wiring needed). content, shortcut, placement top|bottom|left|right.
168
+ <Tooltip content="Save changes" shortcut="⌘S"><Button>Save</Button></Tooltip>
169
+
170
+ Dialog - @zyncat/ui/dialog
171
+ Styled modal: scrim, focus trap, scroll lock. open/onOpenChange, tone, icon, title, description, footer: (close) => node.
172
+ <Dialog open={o} onOpenChange={setO} tone="danger" title="Delete this project?" footer={(close) => <>...</>}>...</Dialog>
173
+
174
+ Popover - @zyncat/ui/popover
175
+ Headless anchored panel, non-modal. side, align, arrow, trigger (node), asChild; children: ({ close }) => node.
176
+ <Popover side="bottom" align="start" trigger={<Button>Actions</Button>}>{({ close }) => <menu>...</menu>}</Popover>
177
+
178
+ Sheet - @zyncat/ui/sheet
179
+ Edge-docked modal panel, drag-to-dismiss. side right|bottom, trigger (node), asChild; children: ({ close }) => node.
180
+ <Sheet side="right" trigger={<Button>Filters</Button>}>{({ close }) => <div role="dialog">...</div>}</Sheet>
181
+
182
+ ============================================================ DEV TOOLS
183
+
184
+ MotionDevtools - @zyncat/ui/motion-devtools
185
+ Dev-only floating panel that slows or freezes EVERY animation at once (CSS and WAAPI)
186
+ for motion debugging. Mount once at the app root - no provider, touches no other component.
187
+ Gate behind a dev check; keep it out of production.
188
+ placement top-left|top-right|bottom-left|bottom-right, offset, presets, maxFactor, defaultFactor,
189
+ scaleTimers, persist, defaultOpen. Also exports motionSlowmo for console/hotkey control.
190
+ {import.meta.env.DEV && <MotionDevtools placement="bottom-right" />}
191
+
192
+ ============================================================ THEMING
193
+ Re-skin by overriding the CSS custom properties (tokens) in your own stylesheet, loaded after
194
+ @zyncat/ui/styles.css - never fork the source. Token groups: spacing --space-*, type --type-*,
195
+ color (--bg-*, --text-*, --border-*, --accent), --radius-*, elevation --shadow-*, motion
196
+ (--duration-*, --ease-*).
package/next.d.mts ADDED
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from 'next';
2
+
3
+ /**
4
+ * Next.js plugin that configures Turbopack to resolve @zyncat/ui
5
+ * imports to TypeScript source, enabling live HMR without a build step.
6
+ */
7
+ export declare function withZyncatUI(nextConfig?: NextConfig): NextConfig;
package/next.mjs ADDED
@@ -0,0 +1,59 @@
1
+ // @ts-check
2
+ import { readFileSync } from 'node:fs';
3
+ import { dirname, relative, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ /**
7
+ * Next.js plugin that configures Turbopack to resolve @zyncat/ui
8
+ * component imports to TypeScript source, enabling live HMR without
9
+ * a build step.
10
+ *
11
+ * Sets `turbopack.root` to the common ancestor of the consuming project
12
+ * and the @zyncat/ui package so Turbopack's filesystem scope includes
13
+ * the source tree (which may live outside the project when linked).
14
+ *
15
+ * @param {import('next').NextConfig} nextConfig
16
+ * @returns {import('next').NextConfig}
17
+ */
18
+ export function withZyncatUI(nextConfig = {}) {
19
+ const pkgDir = dirname(fileURLToPath(import.meta.url));
20
+ const pkg = JSON.parse(readFileSync(resolve(pkgDir, 'package.json'), 'utf-8'));
21
+
22
+ const projectDir = process.cwd();
23
+ const root = commonAncestor(projectDir, pkgDir);
24
+
25
+ /** @type {Record<string, string>} */
26
+ const aliases = {};
27
+
28
+ for (const [subpath, conditions] of Object.entries(pkg.exports)) {
29
+ if (typeof conditions === 'object' && conditions !== null && 'source' in conditions) {
30
+ const bare = subpath.slice(2);
31
+ const abs = resolve(pkgDir, conditions.source);
32
+ const rel = relative(projectDir, abs);
33
+ aliases[`@zyncat/ui/${bare}`] = rel.startsWith('..') ? rel : `./${rel}`;
34
+ }
35
+ }
36
+
37
+ return {
38
+ ...nextConfig,
39
+ transpilePackages: Array.from(new Set([...(nextConfig.transpilePackages ?? []), '@zyncat/ui'])),
40
+ turbopack: { ...nextConfig.turbopack, root, resolveAlias: { ...nextConfig.turbopack?.resolveAlias, ...aliases } },
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Returns the deepest directory that is an ancestor of both paths.
46
+ * @param {string} a
47
+ * @param {string} b
48
+ * @returns {string}
49
+ */
50
+ function commonAncestor(a, b) {
51
+ const aParts = a.split('/');
52
+ const bParts = b.split('/');
53
+ const common = [];
54
+ for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) {
55
+ if (aParts[i] === bParts[i]) common.push(aParts[i]);
56
+ else break;
57
+ }
58
+ return common.join('/') || '/';
59
+ }