@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.
- package/LICENSE +21 -0
- package/README.md +133 -0
- package/dist/alert.css +132 -0
- package/dist/alert.d.ts +46 -0
- package/dist/alert.js +80 -0
- package/dist/alert.js.map +1 -0
- package/dist/avatar-group.d.ts +24 -0
- package/dist/avatar-group.js +23 -0
- package/dist/avatar-group.js.map +1 -0
- package/dist/avatar.css +200 -0
- package/dist/avatar.d.ts +35 -0
- package/dist/avatar.js +67 -0
- package/dist/avatar.js.map +1 -0
- package/dist/badge.css +182 -0
- package/dist/badge.d.ts +34 -0
- package/dist/badge.js +4 -0
- package/dist/badge.js.map +1 -0
- package/dist/button.css +269 -0
- package/dist/button.d.ts +44 -0
- package/dist/button.js +4 -0
- package/dist/button.js.map +1 -0
- package/dist/checkbox.css +177 -0
- package/dist/checkbox.d.ts +35 -0
- package/dist/checkbox.js +5 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/chunk-2ERBBFZS.js +12 -0
- package/dist/chunk-2ERBBFZS.js.map +1 -0
- package/dist/chunk-2HORSLXJ.js +191 -0
- package/dist/chunk-2HORSLXJ.js.map +1 -0
- package/dist/chunk-36WVAQKL.js +40 -0
- package/dist/chunk-36WVAQKL.js.map +1 -0
- package/dist/chunk-3SBCBYB7.js +575 -0
- package/dist/chunk-3SBCBYB7.js.map +1 -0
- package/dist/chunk-4R2GQO43.js +125 -0
- package/dist/chunk-4R2GQO43.js.map +1 -0
- package/dist/chunk-64UU4LGU.js +258 -0
- package/dist/chunk-64UU4LGU.js.map +1 -0
- package/dist/chunk-7SZCJ7WI.js +35 -0
- package/dist/chunk-7SZCJ7WI.js.map +1 -0
- package/dist/chunk-BIDVNLJO.js +38 -0
- package/dist/chunk-BIDVNLJO.js.map +1 -0
- package/dist/chunk-CONSWNIV.js +17 -0
- package/dist/chunk-CONSWNIV.js.map +1 -0
- package/dist/chunk-EQ32F2QJ.js +75 -0
- package/dist/chunk-EQ32F2QJ.js.map +1 -0
- package/dist/chunk-GY7JOPUQ.js +111 -0
- package/dist/chunk-GY7JOPUQ.js.map +1 -0
- package/dist/chunk-I4XQ4PV2.js +30 -0
- package/dist/chunk-I4XQ4PV2.js.map +1 -0
- package/dist/chunk-JSKXZMLH.js +67 -0
- package/dist/chunk-JSKXZMLH.js.map +1 -0
- package/dist/chunk-L3Q3VDIW.js +295 -0
- package/dist/chunk-L3Q3VDIW.js.map +1 -0
- package/dist/chunk-LDC3RIYA.js +62 -0
- package/dist/chunk-LDC3RIYA.js.map +1 -0
- package/dist/chunk-MKIQFK7S.js +43 -0
- package/dist/chunk-MKIQFK7S.js.map +1 -0
- package/dist/chunk-MMRIQNEN.js +20 -0
- package/dist/chunk-MMRIQNEN.js.map +1 -0
- package/dist/chunk-MXOPCFWR.js +59 -0
- package/dist/chunk-MXOPCFWR.js.map +1 -0
- package/dist/chunk-NEF5XAHG.js +17 -0
- package/dist/chunk-NEF5XAHG.js.map +1 -0
- package/dist/chunk-NNXJCLTV.js +22 -0
- package/dist/chunk-NNXJCLTV.js.map +1 -0
- package/dist/chunk-O4N2OBTR.js +94 -0
- package/dist/chunk-O4N2OBTR.js.map +1 -0
- package/dist/chunk-PMECHR3A.js +38 -0
- package/dist/chunk-PMECHR3A.js.map +1 -0
- package/dist/chunk-QDK66S4G.js +44 -0
- package/dist/chunk-QDK66S4G.js.map +1 -0
- package/dist/chunk-QGNC7LH5.js +8 -0
- package/dist/chunk-QGNC7LH5.js.map +1 -0
- package/dist/chunk-QMSJZCK3.js +398 -0
- package/dist/chunk-QMSJZCK3.js.map +1 -0
- package/dist/chunk-S5ZTZANS.js +57 -0
- package/dist/chunk-S5ZTZANS.js.map +1 -0
- package/dist/chunk-S7B34VGN.js +14 -0
- package/dist/chunk-S7B34VGN.js.map +1 -0
- package/dist/chunk-TCUCYFT7.js +25 -0
- package/dist/chunk-TCUCYFT7.js.map +1 -0
- package/dist/chunk-TYHR4VNM.js +193 -0
- package/dist/chunk-TYHR4VNM.js.map +1 -0
- package/dist/chunk-U5QQZC3U.js +56 -0
- package/dist/chunk-U5QQZC3U.js.map +1 -0
- package/dist/chunk-UE63OOVG.js +13 -0
- package/dist/chunk-UE63OOVG.js.map +1 -0
- package/dist/chunk-VTJTGVZZ.js +22 -0
- package/dist/chunk-VTJTGVZZ.js.map +1 -0
- package/dist/chunk-W3SHPKSB.js +49 -0
- package/dist/chunk-W3SHPKSB.js.map +1 -0
- package/dist/chunk-XZAEIQDR.js +188 -0
- package/dist/chunk-XZAEIQDR.js.map +1 -0
- package/dist/chunk-YGGCQNNU.js +92 -0
- package/dist/chunk-YGGCQNNU.js.map +1 -0
- package/dist/collapse.css +88 -0
- package/dist/collapse.d.ts +46 -0
- package/dist/collapse.js +5 -0
- package/dist/collapse.js.map +1 -0
- package/dist/count-badge.d.ts +13 -0
- package/dist/count-badge.js +13 -0
- package/dist/count-badge.js.map +1 -0
- package/dist/date-field.d.ts +25 -0
- package/dist/date-field.js +87 -0
- package/dist/date-field.js.map +1 -0
- package/dist/date-picker.css +595 -0
- package/dist/date-range-field.d.ts +33 -0
- package/dist/date-range-field.js +404 -0
- package/dist/date-range-field.js.map +1 -0
- package/dist/datetime-field.d.ts +29 -0
- package/dist/datetime-field.js +155 -0
- package/dist/datetime-field.js.map +1 -0
- package/dist/dialog.css +180 -0
- package/dist/dialog.d.ts +42 -0
- package/dist/dialog.js +128 -0
- package/dist/dialog.js.map +1 -0
- package/dist/dom-props-C0jrllBw.d.ts +5 -0
- package/dist/field-chrome-CMPOQSLs.d.ts +28 -0
- package/dist/field-shell-Cuda-feL.d.ts +24 -0
- package/dist/glide.d.ts +15 -0
- package/dist/glide.js +5 -0
- package/dist/glide.js.map +1 -0
- package/dist/icon.css +33 -0
- package/dist/input.css +333 -0
- package/dist/mcp.js +359 -0
- package/dist/motion-devtools.css +265 -0
- package/dist/motion-devtools.d.ts +51 -0
- package/dist/motion-devtools.js +354 -0
- package/dist/motion-devtools.js.map +1 -0
- package/dist/motion-scale-Canq4stZ.d.ts +18 -0
- package/dist/motion-tokens.d.ts +28 -0
- package/dist/motion-tokens.js +3 -0
- package/dist/motion-tokens.js.map +1 -0
- package/dist/multi-select.d.ts +44 -0
- package/dist/multi-select.js +109 -0
- package/dist/multi-select.js.map +1 -0
- package/dist/number-field.d.ts +36 -0
- package/dist/number-field.js +112 -0
- package/dist/number-field.js.map +1 -0
- package/dist/odometer.css +62 -0
- package/dist/otp-field.d.ts +31 -0
- package/dist/otp-field.js +98 -0
- package/dist/otp-field.js.map +1 -0
- package/dist/overlay.css +23 -0
- package/dist/pagination.css +43 -0
- package/dist/pagination.d.ts +29 -0
- package/dist/pagination.js +107 -0
- package/dist/pagination.js.map +1 -0
- package/dist/popover.css +69 -0
- package/dist/popover.d.ts +35 -0
- package/dist/popover.js +15 -0
- package/dist/popover.js.map +1 -0
- package/dist/radio-group.css +300 -0
- package/dist/radio-group.d.ts +52 -0
- package/dist/radio-group.js +115 -0
- package/dist/radio-group.js.map +1 -0
- package/dist/select.css +416 -0
- package/dist/select.d.ts +49 -0
- package/dist/select.js +99 -0
- package/dist/select.js.map +1 -0
- package/dist/sheet.css +26 -0
- package/dist/sheet.d.ts +31 -0
- package/dist/sheet.js +14 -0
- package/dist/sheet.js.map +1 -0
- package/dist/status-badge.d.ts +19 -0
- package/dist/status-badge.js +72 -0
- package/dist/status-badge.js.map +1 -0
- package/dist/table.css +348 -0
- package/dist/table.d.ts +75 -0
- package/dist/table.js +262 -0
- package/dist/table.js.map +1 -0
- package/dist/tabs.css +208 -0
- package/dist/tabs.d.ts +71 -0
- package/dist/tabs.js +181 -0
- package/dist/tabs.js.map +1 -0
- package/dist/tag.css +248 -0
- package/dist/tag.d.ts +52 -0
- package/dist/tag.js +72 -0
- package/dist/tag.js.map +1 -0
- package/dist/text-field.d.ts +28 -0
- package/dist/text-field.js +69 -0
- package/dist/text-field.js.map +1 -0
- package/dist/textarea.css +219 -0
- package/dist/textarea.d.ts +38 -0
- package/dist/textarea.js +144 -0
- package/dist/textarea.js.map +1 -0
- package/dist/time-field.d.ts +28 -0
- package/dist/time-field.js +66 -0
- package/dist/time-field.js.map +1 -0
- package/dist/timing-7JA_zQHP.d.ts +27 -0
- package/dist/toast-store.d.ts +86 -0
- package/dist/toast-store.js +3 -0
- package/dist/toast-store.js.map +1 -0
- package/dist/toast.css +257 -0
- package/dist/toast.d.ts +12 -0
- package/dist/toast.js +310 -0
- package/dist/toast.js.map +1 -0
- package/dist/toggle-tag.d.ts +34 -0
- package/dist/toggle-tag.js +57 -0
- package/dist/toggle-tag.js.map +1 -0
- package/dist/toggle.css +152 -0
- package/dist/toggle.d.ts +31 -0
- package/dist/toggle.js +75 -0
- package/dist/toggle.js.map +1 -0
- package/dist/tooltip.css +86 -0
- package/dist/tooltip.d.ts +30 -0
- package/dist/tooltip.js +268 -0
- package/dist/tooltip.js.map +1 -0
- package/dist/types-SGMShK7R.d.ts +22 -0
- package/llms.txt +196 -0
- package/next.d.mts +7 -0
- package/next.mjs +59 -0
- package/package.json +257 -0
- package/src/components/composites/alert/Alert.tsx +141 -0
- package/src/components/composites/alert/alert.css +132 -0
- package/src/components/composites/date-picker/DateField.tsx +80 -0
- package/src/components/composites/date-picker/DateRangeField.tsx +110 -0
- package/src/components/composites/date-picker/DateTimeField.tsx +162 -0
- package/src/components/composites/date-picker/TimeField.tsx +78 -0
- package/src/components/composites/date-picker/calendar-panel.tsx +216 -0
- package/src/components/composites/date-picker/date-picker.css +595 -0
- package/src/components/composites/date-picker/date-utils.ts +56 -0
- package/src/components/composites/date-picker/field-shell.tsx +85 -0
- package/src/components/composites/date-picker/range-panel.tsx +363 -0
- package/src/components/composites/date-picker/time-core.tsx +290 -0
- package/src/components/composites/date-picker/use-day-focus.ts +35 -0
- package/src/components/composites/dialog/Dialog.tsx +198 -0
- package/src/components/composites/dialog/dialog.css +180 -0
- package/src/components/composites/pagination/Pagination.tsx +131 -0
- package/src/components/composites/pagination/pagination.css +43 -0
- package/src/components/composites/popover/Popover.tsx +141 -0
- package/src/components/composites/popover/popover.css +69 -0
- package/src/components/composites/select/MultiSelect.tsx +133 -0
- package/src/components/composites/select/Select.tsx +122 -0
- package/src/components/composites/select/core/index.ts +4 -0
- package/src/components/composites/select/core/menu.tsx +100 -0
- package/src/components/composites/select/core/panel.tsx +154 -0
- package/src/components/composites/select/core/trigger.tsx +74 -0
- package/src/components/composites/select/core/types.ts +39 -0
- package/src/components/composites/select/core/use-listbox.ts +182 -0
- package/src/components/composites/select/select.css +416 -0
- package/src/components/composites/sheet/Sheet.tsx +135 -0
- package/src/components/composites/sheet/sheet.css +26 -0
- package/src/components/composites/sheet/use-sheet-drag.ts +126 -0
- package/src/components/composites/table/Table.tsx +377 -0
- package/src/components/composites/table/table.css +348 -0
- package/src/components/composites/tabs/Tabs.tsx +278 -0
- package/src/components/composites/tabs/tabs.css +208 -0
- package/src/components/composites/toast/Toast.tsx +380 -0
- package/src/components/composites/toast/toast-store.ts +295 -0
- package/src/components/composites/toast/toast.css +257 -0
- package/src/components/composites/tooltip/Tooltip.tsx +110 -0
- package/src/components/composites/tooltip/tooltip-host.tsx +184 -0
- package/src/components/composites/tooltip/tooltip-store.ts +81 -0
- package/src/components/composites/tooltip/tooltip.css +86 -0
- package/src/components/dev/MotionDevtools.tsx +200 -0
- package/src/components/dev/motion-devtools.css +265 -0
- package/src/components/dev/slowmo-engine.ts +230 -0
- package/src/components/internal/glass/glass.css +100 -0
- package/src/components/internal/glass/glint.ts +9 -0
- package/src/components/internal/hooks/use-controllable.ts +21 -0
- package/src/components/internal/hooks/use-scroll-edges.ts +46 -0
- package/src/components/internal/icon/Icon.tsx +66 -0
- package/src/components/internal/icon/IconSlot.tsx +10 -0
- package/src/components/internal/icon/icon.css +33 -0
- package/src/components/internal/overlay/focus.ts +68 -0
- package/src/components/internal/overlay/layer.tsx +149 -0
- package/src/components/internal/overlay/modal.tsx +141 -0
- package/src/components/internal/overlay/overlay.css +23 -0
- package/src/components/internal/overlay/position.ts +103 -0
- package/src/components/internal/utils/cx.ts +3 -0
- package/src/components/internal/utils/token-px.ts +8 -0
- package/src/components/primitives/avatar/Avatar.tsx +120 -0
- package/src/components/primitives/avatar/AvatarGroup.tsx +56 -0
- package/src/components/primitives/avatar/avatar.css +200 -0
- package/src/components/primitives/badge/Badge.tsx +75 -0
- package/src/components/primitives/badge/CountBadge.tsx +20 -0
- package/src/components/primitives/badge/StatusBadge.tsx +111 -0
- package/src/components/primitives/badge/badge.css +182 -0
- package/src/components/primitives/badge/odometer.css +62 -0
- package/src/components/primitives/badge/odometer.tsx +36 -0
- package/src/components/primitives/button/Button.tsx +102 -0
- package/src/components/primitives/button/button.css +269 -0
- package/src/components/primitives/checkbox/Checkbox.tsx +76 -0
- package/src/components/primitives/checkbox/check-glyph.tsx +28 -0
- package/src/components/primitives/checkbox/checkbox.css +177 -0
- package/src/components/primitives/collapse/Collapse.tsx +86 -0
- package/src/components/primitives/collapse/collapse.css +88 -0
- package/src/components/primitives/collapse/collapse.typecheck.tsx +29 -0
- package/src/components/primitives/input/NumberField.tsx +139 -0
- package/src/components/primitives/input/OtpField.tsx +134 -0
- package/src/components/primitives/input/TextField.tsx +110 -0
- package/src/components/primitives/input/field-chrome.tsx +83 -0
- package/src/components/primitives/input/input.css +333 -0
- package/src/components/primitives/radio-group/RadioGroup.tsx +183 -0
- package/src/components/primitives/radio-group/radio-group.css +300 -0
- package/src/components/primitives/tag/Tag.tsx +143 -0
- package/src/components/primitives/tag/ToggleTag.tsx +97 -0
- package/src/components/primitives/tag/tag.css +248 -0
- package/src/components/primitives/textarea/Textarea.tsx +190 -0
- package/src/components/primitives/textarea/textarea.css +219 -0
- package/src/components/primitives/toggle/Toggle.tsx +105 -0
- package/src/components/primitives/toggle/toggle.css +152 -0
- package/src/css.d.ts +1 -0
- package/src/dom-props.ts +1 -0
- package/src/engine/animate.ts +141 -0
- package/src/engine/drag.ts +53 -0
- package/src/engine/ease.ts +155 -0
- package/src/engine/flip.ts +59 -0
- package/src/engine/index.ts +13 -0
- package/src/mcp/server.ts +420 -0
- package/src/motion/element.tsx +53 -0
- package/src/motion/flip.tsx +87 -0
- package/src/motion/glide.tsx +47 -0
- package/src/motion/motion-timing.ts +33 -0
- package/src/motion/presence-context.ts +18 -0
- package/src/motion/presence.tsx +91 -0
- package/src/motion/timing.ts +49 -0
- package/src/motion/use-motion.ts +64 -0
- package/src/styles.css +26 -0
- package/src/tokens/avatar.css +20 -0
- package/src/tokens/color.css +58 -0
- package/src/tokens/elevation.css +22 -0
- package/src/tokens/fonts.css +3 -0
- package/src/tokens/glass.css +20 -0
- package/src/tokens/icons.css +7 -0
- package/src/tokens/layers.css +6 -0
- package/src/tokens/motion-scale.ts +17 -0
- package/src/tokens/motion-tokens.ts +92 -0
- package/src/tokens/motion.css +49 -0
- package/src/tokens/radius.css +11 -0
- package/src/tokens/semantic.css +65 -0
- package/src/tokens/spacing.css +26 -0
- package/src/tokens/typography.css +52 -0
package/dist/tooltip.js
ADDED
|
@@ -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
|
+
}
|