@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
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { Bezier, MotionTransition } from '../tokens/motion-tokens';
|
|
2
|
+
|
|
3
|
+
const NAMED: Record<string, Bezier> = {
|
|
4
|
+
linear: [0, 0, 1, 1],
|
|
5
|
+
easeIn: [0.42, 0, 1, 1],
|
|
6
|
+
easeOut: [0, 0, 0.58, 1],
|
|
7
|
+
easeInOut: [0.42, 0, 0.58, 1],
|
|
8
|
+
circIn: [0.55, 0, 1, 0.45],
|
|
9
|
+
circOut: [0, 0.55, 0.45, 1],
|
|
10
|
+
circInOut: [0.85, 0, 0.15, 1],
|
|
11
|
+
backIn: [0.36, 0, 0.66, -0.56],
|
|
12
|
+
backOut: [0.34, 1.56, 0.64, 1],
|
|
13
|
+
backInOut: [0.68, -0.6, 0.32, 1.6],
|
|
14
|
+
anticipate: [0.38, -0.4, 0.88, 1.08],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const bezierCurve = (a: number, b: number, t: number) => {
|
|
18
|
+
const u = 1 - t;
|
|
19
|
+
return 3 * a * u * u * t + 3 * b * u * t * t + t * t * t;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const bezierSlope = (a: number, b: number, t: number) => {
|
|
23
|
+
const u = 1 - t;
|
|
24
|
+
return 3 * a * (u * u - 2 * t * u) + 3 * b * (2 * t * u - t * t) + 3 * t * t;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function bezierAt(b: Bezier, x: number): number {
|
|
28
|
+
const [x1, y1, x2, y2] = b;
|
|
29
|
+
if (x1 === y1 && x2 === y2) return x;
|
|
30
|
+
if (x <= 0) return 0;
|
|
31
|
+
if (x >= 1) return 1;
|
|
32
|
+
let t = x;
|
|
33
|
+
for (let i = 0; i < 8; i++) {
|
|
34
|
+
const err = bezierCurve(x1, x2, t) - x;
|
|
35
|
+
if (err < 1e-4 && err > -1e-4) break;
|
|
36
|
+
const slope = bezierSlope(x1, x2, t);
|
|
37
|
+
if (slope < 1e-6 && slope > -1e-6) break;
|
|
38
|
+
t -= err / slope;
|
|
39
|
+
}
|
|
40
|
+
return bezierCurve(y1, y2, t);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type EaseValue = NonNullable<MotionTransition['ease']>;
|
|
44
|
+
|
|
45
|
+
export const toBezier = (ease: EaseValue): Bezier =>
|
|
46
|
+
typeof ease === 'string' ? (NAMED[ease] ?? NAMED.easeInOut) : ease;
|
|
47
|
+
|
|
48
|
+
export const cssBezier = (ease: EaseValue): string => `cubic-bezier(${toBezier(ease).join(',')})`;
|
|
49
|
+
|
|
50
|
+
export const isSegmented = (ease: unknown): ease is EaseValue[] =>
|
|
51
|
+
Array.isArray(ease) && (Array.isArray(ease[0]) || typeof ease[0] === 'string');
|
|
52
|
+
|
|
53
|
+
const ARRIVAL = 0.99;
|
|
54
|
+
const SETTLE_BAND = 0.002;
|
|
55
|
+
const MAX_SPRING_SECONDS = 4;
|
|
56
|
+
const SETTLE_LIMIT = 2.5;
|
|
57
|
+
const SETTLE_STEP = 1 / 240;
|
|
58
|
+
const OMEGA_PROBE = 0.1;
|
|
59
|
+
const OMEGA_CEILING = 1e5;
|
|
60
|
+
|
|
61
|
+
function springAt(zeta: number, omega: number, t: number): number {
|
|
62
|
+
const decay = Math.exp(-zeta * omega * t);
|
|
63
|
+
if (zeta < 1) {
|
|
64
|
+
const damped = omega * Math.sqrt(1 - zeta * zeta);
|
|
65
|
+
return 1 - decay * (Math.cos(damped * t) + ((zeta * omega) / damped) * Math.sin(damped * t));
|
|
66
|
+
}
|
|
67
|
+
return 1 - decay * (1 + omega * t);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function omegaForArrival(zeta: number, visual: number): number {
|
|
71
|
+
let hi = OMEGA_PROBE / visual;
|
|
72
|
+
while (hi < OMEGA_CEILING && springAt(zeta, hi, visual) < ARRIVAL) hi *= 2;
|
|
73
|
+
let lo = hi / 2;
|
|
74
|
+
for (let i = 0; i < 40; i++) {
|
|
75
|
+
const mid = (lo + hi) / 2;
|
|
76
|
+
if (springAt(zeta, mid, visual) < ARRIVAL) lo = mid;
|
|
77
|
+
else hi = mid;
|
|
78
|
+
}
|
|
79
|
+
return hi;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function settleSeconds(zeta: number, omega: number, visual: number): number {
|
|
83
|
+
let last = SETTLE_STEP;
|
|
84
|
+
for (let t = SETTLE_STEP; t < MAX_SPRING_SECONDS; t += SETTLE_STEP) {
|
|
85
|
+
if (Math.abs(springAt(zeta, omega, t) - 1) >= SETTLE_BAND) last = t;
|
|
86
|
+
}
|
|
87
|
+
return Math.min(MAX_SPRING_SECONDS, visual * SETTLE_LIMIT, last + SETTLE_STEP);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const SAMPLE_MS = 4;
|
|
91
|
+
const MIN_SAMPLES = 24;
|
|
92
|
+
const MAX_SAMPLES = 120;
|
|
93
|
+
|
|
94
|
+
const trim = (n: number) => Math.round(n * 1e4) / 1e4;
|
|
95
|
+
|
|
96
|
+
function toLinearEasing(at: (progress: number) => number, durationMs: number): string {
|
|
97
|
+
const steps = Math.min(MAX_SAMPLES, Math.max(MIN_SAMPLES, Math.round(durationMs / SAMPLE_MS)));
|
|
98
|
+
const points: string[] = [];
|
|
99
|
+
for (let i = 0; i <= steps; i++) points.push(String(trim(at(i / steps))));
|
|
100
|
+
return `linear(${points.join(',')})`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface SpringCurve {
|
|
104
|
+
duration: number;
|
|
105
|
+
easing: string;
|
|
106
|
+
at(progress: number): number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const springCache = new Map<string, SpringCurve>();
|
|
110
|
+
|
|
111
|
+
function springCurve(visual: number, bounce: number): SpringCurve {
|
|
112
|
+
const key = `${visual}:${bounce}`;
|
|
113
|
+
const cached = springCache.get(key);
|
|
114
|
+
if (cached) return cached;
|
|
115
|
+
const zeta = Math.min(1, Math.max(0.05, 1 - bounce));
|
|
116
|
+
const omega = omegaForArrival(zeta, visual);
|
|
117
|
+
const seconds = settleSeconds(zeta, omega, visual);
|
|
118
|
+
const duration = seconds * 1000;
|
|
119
|
+
const at = (progress: number) => (progress >= 1 ? 1 : springAt(zeta, omega, progress * seconds));
|
|
120
|
+
const curve: SpringCurve = { duration, easing: toLinearEasing(at, duration), at };
|
|
121
|
+
springCache.set(key, curve);
|
|
122
|
+
return curve;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface Timing extends Omit<MotionTransition, 'ease'> {
|
|
126
|
+
ease?: EaseValue | EaseValue[];
|
|
127
|
+
times?: number[];
|
|
128
|
+
fill?: FillMode;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface ResolvedTiming {
|
|
132
|
+
duration: number;
|
|
133
|
+
delay: number;
|
|
134
|
+
easing: string;
|
|
135
|
+
segments: string[] | null;
|
|
136
|
+
at(progress: number): number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function resolveTiming(timing: Timing = {}): ResolvedTiming {
|
|
140
|
+
const delay = (timing.delay ?? 0) * 1000;
|
|
141
|
+
const spring = timing.type === 'spring' || (timing.visualDuration != null && !timing.type);
|
|
142
|
+
if (spring) {
|
|
143
|
+
const visual = Math.max(timing.visualDuration ?? timing.duration ?? 0.3, 0.01);
|
|
144
|
+
const curve = springCurve(visual, timing.bounce ?? 0);
|
|
145
|
+
return { duration: curve.duration, delay, easing: curve.easing, segments: null, at: curve.at };
|
|
146
|
+
}
|
|
147
|
+
const duration = (timing.duration ?? 0.3) * 1000;
|
|
148
|
+
const ease = timing.ease;
|
|
149
|
+
if (isSegmented(ease)) {
|
|
150
|
+
const first = toBezier(ease[0]);
|
|
151
|
+
return { duration, delay, easing: 'linear', segments: ease.map(cssBezier), at: (p) => bezierAt(first, p) };
|
|
152
|
+
}
|
|
153
|
+
const bezier = ease === undefined ? NAMED.easeInOut : toBezier(ease);
|
|
154
|
+
return { duration, delay, easing: cssBezier(bezier), segments: null, at: (p) => bezierAt(bezier, p) };
|
|
155
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { animate, type Layer, type Playback } from './animate';
|
|
2
|
+
import type { Timing } from './ease';
|
|
3
|
+
|
|
4
|
+
export interface Box {
|
|
5
|
+
left: number;
|
|
6
|
+
top: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const MOVE_EPSILON = 0.5;
|
|
12
|
+
const SCALE_EPSILON = 0.01;
|
|
13
|
+
|
|
14
|
+
export function measure(el: HTMLElement): Box {
|
|
15
|
+
const rect = el.getBoundingClientRect();
|
|
16
|
+
return { left: rect.left + window.scrollX, top: rect.top + window.scrollY, width: rect.width, height: rect.height };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const shared = new Map<string, Box>();
|
|
20
|
+
|
|
21
|
+
export function keepShared(key: string, box: Box): void {
|
|
22
|
+
shared.set(key, box);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function readShared(key: string): Box | null {
|
|
26
|
+
return shared.get(key) ?? null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function dropShared(key: string): void {
|
|
30
|
+
shared.delete(key);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface FlipOptions {
|
|
34
|
+
scale?: boolean;
|
|
35
|
+
timing?: Timing;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function flip(el: HTMLElement, from: Box, options: FlipOptions = {}): Playback | null {
|
|
39
|
+
const to = measure(el);
|
|
40
|
+
if (!to.width || !to.height) return null;
|
|
41
|
+
|
|
42
|
+
const dx = from.left - to.left;
|
|
43
|
+
const dy = from.top - to.top;
|
|
44
|
+
const sx = options.scale === false ? 1 : from.width / to.width;
|
|
45
|
+
const sy = options.scale === false ? 1 : from.height / to.height;
|
|
46
|
+
|
|
47
|
+
const moved = Math.abs(dx) > MOVE_EPSILON || Math.abs(dy) > MOVE_EPSILON;
|
|
48
|
+
const scaled = Math.abs(sx - 1) > SCALE_EPSILON || Math.abs(sy - 1) > SCALE_EPSILON;
|
|
49
|
+
if (!moved && !scaled) return null;
|
|
50
|
+
|
|
51
|
+
const layer: Layer = { x: [dx, 0], y: [dy, 0], timing: { ...options.timing, fill: 'none' } };
|
|
52
|
+
if (scaled)
|
|
53
|
+
layer.scale = [
|
|
54
|
+
[sx, sy],
|
|
55
|
+
[1, 1],
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
return animate(el, layer);
|
|
59
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { animate, clock, type Layer, type Playback, type Size } from './animate';
|
|
2
|
+
export {
|
|
3
|
+
bezierAt,
|
|
4
|
+
cssBezier,
|
|
5
|
+
isSegmented,
|
|
6
|
+
resolveTiming,
|
|
7
|
+
toBezier,
|
|
8
|
+
type EaseValue,
|
|
9
|
+
type ResolvedTiming,
|
|
10
|
+
type Timing,
|
|
11
|
+
} from './ease';
|
|
12
|
+
export { flip, measure, keepShared, readShared, dropShared, type Box, type FlipOptions } from './flip';
|
|
13
|
+
export { startDrag, type PanInfo } from './drag';
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
function findRoot(): string {
|
|
6
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
for (;;) {
|
|
8
|
+
const pj = join(dir, 'package.json');
|
|
9
|
+
if (existsSync(pj)) {
|
|
10
|
+
try {
|
|
11
|
+
if (JSON.parse(readFileSync(pj, 'utf8')).name === '@zyncat/ui') return dir;
|
|
12
|
+
} catch {}
|
|
13
|
+
}
|
|
14
|
+
const parent = dirname(dir);
|
|
15
|
+
if (parent === dir) throw new Error('@zyncat/ui package root not found');
|
|
16
|
+
dir = parent;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface LlmsEntry {
|
|
21
|
+
title: string;
|
|
22
|
+
subpath: string;
|
|
23
|
+
section: string;
|
|
24
|
+
lines: string[];
|
|
25
|
+
}
|
|
26
|
+
interface LlmsSection {
|
|
27
|
+
title: string;
|
|
28
|
+
body: string[];
|
|
29
|
+
}
|
|
30
|
+
interface Db {
|
|
31
|
+
root: string;
|
|
32
|
+
version: string;
|
|
33
|
+
subpaths: string[];
|
|
34
|
+
preamble: string[];
|
|
35
|
+
sections: LlmsSection[];
|
|
36
|
+
entries: LlmsEntry[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const SECTION_RE = /^=+\s+(.+?)\s*$/;
|
|
40
|
+
const HEADING_RE = /^([A-Za-z][\w /]*?) - @zyncat\/ui\/([a-z][a-z0-9-]*)\b.*$/;
|
|
41
|
+
|
|
42
|
+
let cached: Db | null = null;
|
|
43
|
+
function db(): Db {
|
|
44
|
+
if (cached) return cached;
|
|
45
|
+
const root = findRoot();
|
|
46
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
47
|
+
const subpaths = Object.entries(pkg.exports ?? {})
|
|
48
|
+
.filter(([, v]) => typeof v === 'object' && v !== null && 'types' in (v as object))
|
|
49
|
+
.map(([k]) => k.replace(/^\.\//, ''));
|
|
50
|
+
|
|
51
|
+
const preamble: string[] = [];
|
|
52
|
+
const sections: LlmsSection[] = [];
|
|
53
|
+
const entries: LlmsEntry[] = [];
|
|
54
|
+
let section: LlmsSection | null = null;
|
|
55
|
+
let entry: LlmsEntry | null = null;
|
|
56
|
+
const close = (e: LlmsEntry | null) => {
|
|
57
|
+
if (!e) return;
|
|
58
|
+
while (e.lines.length && !e.lines[e.lines.length - 1].trim()) e.lines.pop();
|
|
59
|
+
entries.push(e);
|
|
60
|
+
};
|
|
61
|
+
for (const line of readFileSync(join(root, 'llms.txt'), 'utf8').split('\n')) {
|
|
62
|
+
const sec = line.match(SECTION_RE);
|
|
63
|
+
const head = line.match(HEADING_RE);
|
|
64
|
+
if (sec) {
|
|
65
|
+
close(entry);
|
|
66
|
+
entry = null;
|
|
67
|
+
section = { title: sec[1], body: [] };
|
|
68
|
+
sections.push(section);
|
|
69
|
+
} else if (head) {
|
|
70
|
+
close(entry);
|
|
71
|
+
entry = { title: head[1].trim(), subpath: head[2], section: section?.title ?? '', lines: [line] };
|
|
72
|
+
} else if (entry) {
|
|
73
|
+
entry.lines.push(line);
|
|
74
|
+
} else if (section) {
|
|
75
|
+
section.body.push(line);
|
|
76
|
+
} else {
|
|
77
|
+
preamble.push(line);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
close(entry);
|
|
81
|
+
for (const s of sections) {
|
|
82
|
+
while (s.body.length && !s.body[s.body.length - 1].trim()) s.body.pop();
|
|
83
|
+
while (s.body.length && !s.body[0].trim()) s.body.shift();
|
|
84
|
+
}
|
|
85
|
+
while (preamble.length && !preamble[preamble.length - 1].trim()) preamble.pop();
|
|
86
|
+
|
|
87
|
+
cached = { root, version: String(pkg.version ?? '0'), subpaths, preamble, sections, entries };
|
|
88
|
+
return cached;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const norm = (s: string) =>
|
|
92
|
+
s
|
|
93
|
+
.toLowerCase()
|
|
94
|
+
.replace(/^@zyncat\/ui\//, '')
|
|
95
|
+
.replace(/[^a-z0-9]/g, '');
|
|
96
|
+
|
|
97
|
+
function resolveSubpath(input: string): string {
|
|
98
|
+
const d = db();
|
|
99
|
+
const q = norm(input);
|
|
100
|
+
if (!q) throw new Error('Pass a component name or subpath, e.g. "Button" or "text-field".');
|
|
101
|
+
const candidates = new Map<string, string>();
|
|
102
|
+
for (const sub of d.subpaths) candidates.set(norm(sub), sub);
|
|
103
|
+
for (const e of d.entries) for (const part of e.title.split('/')) candidates.set(norm(part), e.subpath);
|
|
104
|
+
const exact = candidates.get(q);
|
|
105
|
+
if (exact) return exact;
|
|
106
|
+
const loose = new Set<string>();
|
|
107
|
+
for (const [name, sub] of candidates) if (name.includes(q) || q.includes(name)) loose.add(sub);
|
|
108
|
+
if (loose.size === 1) return [...loose][0];
|
|
109
|
+
const all = d.entries.map((e) => `${e.title} (${e.subpath})`).join(', ');
|
|
110
|
+
const extras = d.subpaths.filter((s) => !d.entries.some((e) => e.subpath === s)).join(', ');
|
|
111
|
+
throw new Error(
|
|
112
|
+
(loose.size ? `"${input}" is ambiguous: ${[...loose].join(', ')}. ` : `No component matches "${input}". `) +
|
|
113
|
+
`Available: ${all}${extras ? `; types-only modules: ${extras}` : ''}.`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readTypes(root: string, subpath: string): string {
|
|
118
|
+
const out: string[] = [];
|
|
119
|
+
const seen = new Set<string>();
|
|
120
|
+
const walk = (base: string) => {
|
|
121
|
+
if (seen.has(base)) return;
|
|
122
|
+
seen.add(base);
|
|
123
|
+
const p = join(root, 'dist', `${base}.d.ts`);
|
|
124
|
+
if (!existsSync(p)) return;
|
|
125
|
+
const text = readFileSync(p, 'utf8');
|
|
126
|
+
out.push(`--- dist/${base}.d.ts ---\n${text.trimEnd()}`);
|
|
127
|
+
for (const m of text.matchAll(/from\s+['"]\.\/([\w.-]+)\.js['"]/g)) walk(m[1]);
|
|
128
|
+
};
|
|
129
|
+
walk(subpath);
|
|
130
|
+
if (!out.length) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`dist/${subpath}.d.ts not found - the package is not built. In the zyncat-ui repo run "pnpm build"; in a consumer, reinstall the package.`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return out.join('\n\n');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const tokenFiles = (root: string): string[] =>
|
|
139
|
+
existsSync(join(root, 'src/tokens'))
|
|
140
|
+
? readdirSync(join(root, 'src/tokens'))
|
|
141
|
+
.filter((f) => f.endsWith('.css'))
|
|
142
|
+
.sort()
|
|
143
|
+
: [];
|
|
144
|
+
|
|
145
|
+
const CODE_LINE_RE = /^(?:[<{]|import\s|const\s|let\s|function\s)/;
|
|
146
|
+
|
|
147
|
+
function entryTeaser(e: LlmsEntry): string {
|
|
148
|
+
const parts: string[] = [];
|
|
149
|
+
for (let i = 1; i < e.lines.length; i++) {
|
|
150
|
+
const line = e.lines[i].trim();
|
|
151
|
+
if (!line) break;
|
|
152
|
+
if (i > 1 && CODE_LINE_RE.test(line)) break;
|
|
153
|
+
parts.push(line);
|
|
154
|
+
}
|
|
155
|
+
return parts.join(' ');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function listComponents(): string {
|
|
159
|
+
const d = db();
|
|
160
|
+
const out: string[] = [...d.preamble, ''];
|
|
161
|
+
for (const s of d.sections) {
|
|
162
|
+
const inSection = d.entries.filter((e) => e.section === s.title);
|
|
163
|
+
if (!inSection.length && !s.body.length) continue;
|
|
164
|
+
out.push(`== ${s.title} ==`);
|
|
165
|
+
out.push(...s.body);
|
|
166
|
+
for (const e of inSection) {
|
|
167
|
+
const first = entryTeaser(e);
|
|
168
|
+
out.push(`${e.title} - @zyncat/ui/${e.subpath}${first ? ` - ${first}` : ''}`);
|
|
169
|
+
}
|
|
170
|
+
out.push('');
|
|
171
|
+
}
|
|
172
|
+
const extras = d.subpaths.filter((sub) => !d.entries.some((e) => e.subpath === sub));
|
|
173
|
+
if (extras.length) {
|
|
174
|
+
out.push('== SUPPORTING MODULES (no llms.txt entry - full docs live in their types) ==');
|
|
175
|
+
for (const sub of extras) out.push(`@zyncat/ui/${sub} - call get_component("${sub}")`);
|
|
176
|
+
}
|
|
177
|
+
return out.join('\n').trim();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getComponent(input: string): string {
|
|
181
|
+
const d = db();
|
|
182
|
+
const sub = resolveSubpath(input);
|
|
183
|
+
const entry = d.entries.find((e) => e.subpath === sub);
|
|
184
|
+
|
|
185
|
+
const out: string[] = [`# ${entry ? entry.title : sub} - @zyncat/ui/${sub}`, ''];
|
|
186
|
+
if (entry) {
|
|
187
|
+
const note = d.sections.find((s) => s.title === entry.section)?.body ?? [];
|
|
188
|
+
if (note.length) out.push(`Applies to every ${entry.section} component:`, ...note, '');
|
|
189
|
+
out.push('## Usage (llms.txt)', ...entry.lines, '');
|
|
190
|
+
} else {
|
|
191
|
+
out.push('No llms.txt entry - this module is documented by its types below.', '');
|
|
192
|
+
}
|
|
193
|
+
out.push('## Types (entry .d.ts + the shared chunks it imports)', readTypes(d.root, sub));
|
|
194
|
+
return out.join('\n');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function searchApi(query: string): string {
|
|
198
|
+
const d = db();
|
|
199
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
200
|
+
if (!words.length) throw new Error('Pass one or more keywords, e.g. "searchable" or "drag dismiss".');
|
|
201
|
+
const hits = new Map<string, string[]>();
|
|
202
|
+
const scan = (label: string, text: string, lineLabel?: (line: string, i: number) => string) => {
|
|
203
|
+
text.split('\n').forEach((line, i) => {
|
|
204
|
+
const l = line.toLowerCase();
|
|
205
|
+
if (!words.every((w) => l.includes(w))) return;
|
|
206
|
+
const key = lineLabel ? lineLabel(line, i) : label;
|
|
207
|
+
if (!hits.has(key)) hits.set(key, []);
|
|
208
|
+
hits.get(key)!.push(` L${i + 1}: ${line.trim()}`);
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
{
|
|
213
|
+
const raw = readFileSync(join(d.root, 'llms.txt'), 'utf8');
|
|
214
|
+
const labels: string[] = [];
|
|
215
|
+
let current = 'llms.txt';
|
|
216
|
+
for (const line of raw.split('\n')) {
|
|
217
|
+
const sec = line.match(SECTION_RE);
|
|
218
|
+
const head = line.match(HEADING_RE);
|
|
219
|
+
if (sec) current = `llms.txt » ${sec[1]}`;
|
|
220
|
+
else if (head) current = `llms.txt » ${head[1].trim()}`;
|
|
221
|
+
labels.push(current);
|
|
222
|
+
}
|
|
223
|
+
scan('llms.txt', raw, (_line, i) => labels[i]);
|
|
224
|
+
}
|
|
225
|
+
const distDir = join(d.root, 'dist');
|
|
226
|
+
const dts = existsSync(distDir)
|
|
227
|
+
? readdirSync(distDir)
|
|
228
|
+
.filter((f) => f.endsWith('.d.ts'))
|
|
229
|
+
.sort()
|
|
230
|
+
: [];
|
|
231
|
+
for (const f of dts) scan(`dist/${f}`, readFileSync(join(distDir, f), 'utf8'));
|
|
232
|
+
for (const f of tokenFiles(d.root)) scan(`src/tokens/${f}`, readFileSync(join(d.root, 'src/tokens', f), 'utf8'));
|
|
233
|
+
|
|
234
|
+
const out: string[] = [];
|
|
235
|
+
let shown = 0;
|
|
236
|
+
let total = 0;
|
|
237
|
+
for (const [, lines] of hits) total += lines.length;
|
|
238
|
+
for (const [label, lines] of hits) {
|
|
239
|
+
if (shown >= 60) break;
|
|
240
|
+
out.push(`## ${label}`);
|
|
241
|
+
for (const l of lines) {
|
|
242
|
+
if (shown >= 60) break;
|
|
243
|
+
out.push(l);
|
|
244
|
+
shown++;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (!total) return `No matches for "${query}". Try a shorter keyword, or list_components for the full index.`;
|
|
248
|
+
if (total > shown) out.push(`... +${total - shown} more matching lines - narrow the query.`);
|
|
249
|
+
if (!dts.length) out.push('(note: dist/ is not built, so type definitions were not searched)');
|
|
250
|
+
out.push('', 'Follow up with get_component(<name>) for the full picture.');
|
|
251
|
+
return out.join('\n');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function getTokens(group?: string): string {
|
|
255
|
+
const d = db();
|
|
256
|
+
const files = tokenFiles(d.root);
|
|
257
|
+
if (!files.length) throw new Error('src/tokens/*.css not found in the installed package.');
|
|
258
|
+
let picked = files;
|
|
259
|
+
if (group) {
|
|
260
|
+
const q = norm(group);
|
|
261
|
+
picked = files.filter((f) => {
|
|
262
|
+
const name = norm(f.replace(/\.css$/, ''));
|
|
263
|
+
return name.includes(q) || q.includes(name);
|
|
264
|
+
});
|
|
265
|
+
if (!picked.length) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`No token group matches "${group}". Groups: ${files.map((f) => f.replace(/\.css$/, '')).join(', ')}.`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const out = picked.map(
|
|
272
|
+
(f) => `/* ==== src/tokens/${f} ==== */\n${readFileSync(join(d.root, 'src/tokens', f), 'utf8').trimEnd()}`,
|
|
273
|
+
);
|
|
274
|
+
const theming = d.sections.find((s) => s.title.includes('THEMING'));
|
|
275
|
+
if (theming?.body.length) out.push(['/* ==== theming ==== */', ...theming.body].join('\n'));
|
|
276
|
+
return out.join('\n\n');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const TOOLS = [
|
|
280
|
+
{
|
|
281
|
+
name: 'list_components',
|
|
282
|
+
description:
|
|
283
|
+
'Every Zyncat UI component: name, import subpath and one-line purpose, grouped by category, plus package setup and usage conventions. Start here instead of exploring node_modules.',
|
|
284
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'get_component',
|
|
288
|
+
description:
|
|
289
|
+
"Full API for one component: the maintainers' usage notes and example plus the complete TypeScript prop types with doc comments (shared type chunks inlined). Cheaper and more accurate than reading node_modules/@zyncat/ui yourself.",
|
|
290
|
+
inputSchema: {
|
|
291
|
+
type: 'object',
|
|
292
|
+
properties: {
|
|
293
|
+
component: {
|
|
294
|
+
type: 'string',
|
|
295
|
+
description: 'Component name or subpath - "Button", "TextField", "text-field", "@zyncat/ui/select".',
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
required: ['component'],
|
|
299
|
+
additionalProperties: false,
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'search_api',
|
|
304
|
+
description:
|
|
305
|
+
'Keyword search across all component docs, type definitions and design-token CSS. Finds which component owns a prop, type, token or behavior (e.g. "searchable", "DurationToken", "drag dismiss", "--accent"). Every word must match a line; follow up with get_component.',
|
|
306
|
+
inputSchema: {
|
|
307
|
+
type: 'object',
|
|
308
|
+
properties: { query: { type: 'string', description: 'One or more keywords.' } },
|
|
309
|
+
required: ['query'],
|
|
310
|
+
additionalProperties: false,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: 'get_tokens',
|
|
315
|
+
description:
|
|
316
|
+
'The design-token vocabulary as CSS custom properties with their real values - color, semantic, spacing, typography, radius, elevation, motion, icons, layers, glass, avatar, fonts. Optionally pass one group name; omit for all. Use for theming/overrides and for reading exact values.',
|
|
317
|
+
inputSchema: {
|
|
318
|
+
type: 'object',
|
|
319
|
+
properties: {
|
|
320
|
+
group: { type: 'string', description: 'One token group, e.g. "color" or "motion". Omit for all.' },
|
|
321
|
+
},
|
|
322
|
+
additionalProperties: false,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
const INSTRUCTIONS =
|
|
328
|
+
'Zyncat UI design-system API. Before writing Zyncat UI code, use these tools instead of reading ' +
|
|
329
|
+
'node_modules or guessing props: list_components (what exists + setup and conventions), get_component ' +
|
|
330
|
+
'(one component: usage docs + complete prop types), search_api (find the component/prop/token for a ' +
|
|
331
|
+
'keyword), get_tokens (CSS custom-property vocabulary with real values, for theming). Imports are ' +
|
|
332
|
+
'per-subpath (@zyncat/ui/button - no barrel); link @zyncat/ui/styles.css once at the root.';
|
|
333
|
+
|
|
334
|
+
function callTool(name: string, args: Record<string, unknown>): string {
|
|
335
|
+
const str = (k: string) => (typeof args[k] === 'string' ? (args[k] as string) : undefined);
|
|
336
|
+
switch (name) {
|
|
337
|
+
case 'list_components':
|
|
338
|
+
return listComponents();
|
|
339
|
+
case 'get_component': {
|
|
340
|
+
const component = str('component');
|
|
341
|
+
if (!component) throw new Error('Missing required argument "component".');
|
|
342
|
+
return getComponent(component);
|
|
343
|
+
}
|
|
344
|
+
case 'search_api': {
|
|
345
|
+
const query = str('query');
|
|
346
|
+
if (!query) throw new Error('Missing required argument "query".');
|
|
347
|
+
return searchApi(query);
|
|
348
|
+
}
|
|
349
|
+
case 'get_tokens':
|
|
350
|
+
return getTokens(str('group'));
|
|
351
|
+
default:
|
|
352
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
type RpcId = number | string | null;
|
|
357
|
+
function send(msg: object): void {
|
|
358
|
+
try {
|
|
359
|
+
process.stdout.write(`${JSON.stringify(msg)}\n`);
|
|
360
|
+
} catch {
|
|
361
|
+
process.exit(0);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const reply = (id: RpcId, result: object) => send({ jsonrpc: '2.0', id, result });
|
|
365
|
+
const replyError = (id: RpcId, code: number, message: string) => send({ jsonrpc: '2.0', id, error: { code, message } });
|
|
366
|
+
|
|
367
|
+
function handle(req: { id?: RpcId; method?: string; params?: Record<string, unknown> }): void {
|
|
368
|
+
const { id, method, params } = req;
|
|
369
|
+
const isNotification = id === undefined;
|
|
370
|
+
switch (method) {
|
|
371
|
+
case 'initialize': {
|
|
372
|
+
let version = '0.0.0';
|
|
373
|
+
try {
|
|
374
|
+
version = db().version;
|
|
375
|
+
} catch {}
|
|
376
|
+
return reply(id!, {
|
|
377
|
+
protocolVersion: typeof params?.protocolVersion === 'string' ? params.protocolVersion : '2025-06-18',
|
|
378
|
+
capabilities: { tools: {} },
|
|
379
|
+
serverInfo: { name: 'zyncat-ui', version },
|
|
380
|
+
instructions: INSTRUCTIONS,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
case 'ping':
|
|
384
|
+
return reply(id!, {});
|
|
385
|
+
case 'tools/list':
|
|
386
|
+
return reply(id!, { tools: TOOLS });
|
|
387
|
+
case 'tools/call': {
|
|
388
|
+
try {
|
|
389
|
+
const name = typeof params?.name === 'string' ? params.name : '';
|
|
390
|
+
const args = (params?.arguments ?? {}) as Record<string, unknown>;
|
|
391
|
+
const text = callTool(name, args);
|
|
392
|
+
return reply(id!, { content: [{ type: 'text', text }], isError: false });
|
|
393
|
+
} catch (e) {
|
|
394
|
+
const text = e instanceof Error ? e.message : String(e);
|
|
395
|
+
return reply(id!, { content: [{ type: 'text', text }], isError: true });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
default:
|
|
399
|
+
if (!isNotification) replyError(id ?? null, -32601, `Method not found: ${method}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let buffer = '';
|
|
404
|
+
process.stdin.setEncoding('utf8');
|
|
405
|
+
process.stdin.on('data', (chunk: string) => {
|
|
406
|
+
buffer += chunk;
|
|
407
|
+
let nl: number;
|
|
408
|
+
while ((nl = buffer.indexOf('\n')) !== -1) {
|
|
409
|
+
const line = buffer.slice(0, nl).trim();
|
|
410
|
+
buffer = buffer.slice(nl + 1);
|
|
411
|
+
if (!line) continue;
|
|
412
|
+
try {
|
|
413
|
+
handle(JSON.parse(line));
|
|
414
|
+
} catch {
|
|
415
|
+
replyError(null, -32700, 'Parse error');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
process.stdin.on('end', () => process.exit(0));
|
|
420
|
+
process.stdout.on('error', () => process.exit(0));
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createElement, useCallback, useRef, type ReactNode, type Ref } from 'react';
|
|
4
|
+
import { useSelfFlip, useSharedFlip, type FlipTuning } from './flip';
|
|
5
|
+
import { useMotion, type MotionSpecs } from './use-motion';
|
|
6
|
+
|
|
7
|
+
export interface MotionProps extends MotionSpecs {
|
|
8
|
+
/** Tag for the element this renders. @default 'div' */
|
|
9
|
+
as?: string;
|
|
10
|
+
/** FLIP this element from its previous box on every render; an object tunes the timing. */
|
|
11
|
+
layout?: boolean | FlipTuning;
|
|
12
|
+
/** FLIP from whatever element last held this id, so the box travels between nodes. */
|
|
13
|
+
layoutId?: string;
|
|
14
|
+
ref?: Ref<HTMLElement | null>;
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
[prop: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function Motion({
|
|
20
|
+
as = 'div',
|
|
21
|
+
animate,
|
|
22
|
+
exit,
|
|
23
|
+
initial,
|
|
24
|
+
deps,
|
|
25
|
+
layout,
|
|
26
|
+
layoutId,
|
|
27
|
+
ref,
|
|
28
|
+
children,
|
|
29
|
+
...rest
|
|
30
|
+
}: MotionProps) {
|
|
31
|
+
const tuning = typeof layout === 'object' ? layout : {};
|
|
32
|
+
const selfRef = useSelfFlip<HTMLElement>(tuning, !!layout && !layoutId);
|
|
33
|
+
const sharedRef = useSharedFlip<HTMLElement>(layoutId ?? '', tuning, !!layoutId);
|
|
34
|
+
const host = useRef<HTMLElement | null>(null);
|
|
35
|
+
const forwarded = useRef(ref);
|
|
36
|
+
forwarded.current = ref;
|
|
37
|
+
|
|
38
|
+
useMotion(host, { animate, exit, initial, deps });
|
|
39
|
+
|
|
40
|
+
const attach = useCallback(
|
|
41
|
+
(el: HTMLElement | null) => {
|
|
42
|
+
host.current = el;
|
|
43
|
+
selfRef.current = el;
|
|
44
|
+
sharedRef.current = el;
|
|
45
|
+
const outer = forwarded.current;
|
|
46
|
+
if (typeof outer === 'function') outer(el);
|
|
47
|
+
else if (outer) outer.current = el;
|
|
48
|
+
},
|
|
49
|
+
[selfRef, sharedRef],
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return createElement(as, { ...rest, ref: attach }, children);
|
|
53
|
+
}
|