@becket-ui/react 0.1.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/dist/components/Badge.d.ts +22 -0
- package/dist/components/Badge.d.ts.map +1 -0
- package/dist/components/Badge.js +39 -0
- package/dist/components/Badge.js.map +1 -0
- package/dist/components/Button.d.ts +24 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Button.js +46 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/Button.test.js +45 -0
- package/dist/components/Button.test.js.map +1 -0
- package/dist/components/Card.d.ts +37 -0
- package/dist/components/Card.d.ts.map +1 -0
- package/dist/components/Card.js +75 -0
- package/dist/components/Card.js.map +1 -0
- package/dist/components/Checkbox.d.ts +14 -0
- package/dist/components/Checkbox.d.ts.map +1 -0
- package/dist/components/Checkbox.js +46 -0
- package/dist/components/Checkbox.js.map +1 -0
- package/dist/components/Dialog.d.ts +41 -0
- package/dist/components/Dialog.d.ts.map +1 -0
- package/dist/components/Dialog.js +173 -0
- package/dist/components/Dialog.js.map +1 -0
- package/dist/components/Dialog.test.js +89 -0
- package/dist/components/Dialog.test.js.map +1 -0
- package/dist/components/Drawer.d.ts +38 -0
- package/dist/components/Drawer.d.ts.map +1 -0
- package/dist/components/Drawer.js +174 -0
- package/dist/components/Drawer.js.map +1 -0
- package/dist/components/Drawer.test.js +50 -0
- package/dist/components/Drawer.test.js.map +1 -0
- package/dist/components/Field.d.ts +37 -0
- package/dist/components/Field.d.ts.map +1 -0
- package/dist/components/Field.js +186 -0
- package/dist/components/Field.js.map +1 -0
- package/dist/components/Field.test.js +33 -0
- package/dist/components/Field.test.js.map +1 -0
- package/dist/components/Flex.d.ts +38 -0
- package/dist/components/Flex.d.ts.map +1 -0
- package/dist/components/Flex.js +85 -0
- package/dist/components/Flex.js.map +1 -0
- package/dist/components/Heading.d.ts +14 -0
- package/dist/components/Heading.d.ts.map +1 -0
- package/dist/components/Heading.js +33 -0
- package/dist/components/Heading.js.map +1 -0
- package/dist/components/Hide.d.ts +39 -0
- package/dist/components/Hide.d.ts.map +1 -0
- package/dist/components/Hide.js +51 -0
- package/dist/components/Hide.js.map +1 -0
- package/dist/components/Link.d.ts +14 -0
- package/dist/components/Link.d.ts.map +1 -0
- package/dist/components/Link.js +23 -0
- package/dist/components/Link.js.map +1 -0
- package/dist/components/Menu.d.ts +11 -0
- package/dist/components/Menu.d.ts.map +1 -0
- package/dist/components/Menu.js +132 -0
- package/dist/components/Menu.js.map +1 -0
- package/dist/components/Menu.test.js +69 -0
- package/dist/components/Menu.test.js.map +1 -0
- package/dist/components/Radio.d.ts +38 -0
- package/dist/components/Radio.d.ts.map +1 -0
- package/dist/components/Radio.js +144 -0
- package/dist/components/Radio.js.map +1 -0
- package/dist/components/Select.d.ts +17 -0
- package/dist/components/Select.d.ts.map +1 -0
- package/dist/components/Select.js +33 -0
- package/dist/components/Select.js.map +1 -0
- package/dist/components/Separator.d.ts +14 -0
- package/dist/components/Separator.d.ts.map +1 -0
- package/dist/components/Separator.js +25 -0
- package/dist/components/Separator.js.map +1 -0
- package/dist/components/SimpleGrid.d.ts +26 -0
- package/dist/components/SimpleGrid.d.ts.map +1 -0
- package/dist/components/SimpleGrid.js +98 -0
- package/dist/components/SimpleGrid.js.map +1 -0
- package/dist/components/Spinner.d.ts +10 -0
- package/dist/components/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner.js +25 -0
- package/dist/components/Spinner.js.map +1 -0
- package/dist/components/Stack.d.ts +32 -0
- package/dist/components/Stack.d.ts.map +1 -0
- package/dist/components/Stack.js +81 -0
- package/dist/components/Stack.js.map +1 -0
- package/dist/components/Switch.d.ts +20 -0
- package/dist/components/Switch.d.ts.map +1 -0
- package/dist/components/Switch.js +83 -0
- package/dist/components/Switch.js.map +1 -0
- package/dist/components/Switch.test.js +28 -0
- package/dist/components/Switch.test.js.map +1 -0
- package/dist/components/Table.d.ts +23 -0
- package/dist/components/Table.d.ts.map +1 -0
- package/dist/components/Table.js +90 -0
- package/dist/components/Table.js.map +1 -0
- package/dist/components/Tabs.d.ts +21 -0
- package/dist/components/Tabs.d.ts.map +1 -0
- package/dist/components/Tabs.js +140 -0
- package/dist/components/Tabs.js.map +1 -0
- package/dist/components/Tabs.test.js +41 -0
- package/dist/components/Tabs.test.js.map +1 -0
- package/dist/components/Tag.d.ts +22 -0
- package/dist/components/Tag.d.ts.map +1 -0
- package/dist/components/Tag.js +39 -0
- package/dist/components/Tag.js.map +1 -0
- package/dist/components/Text.d.ts +9 -0
- package/dist/components/Text.d.ts.map +1 -0
- package/dist/components/Text.js +18 -0
- package/dist/components/Text.js.map +1 -0
- package/dist/components/TextField.d.ts +19 -0
- package/dist/components/TextField.d.ts.map +1 -0
- package/dist/components/TextField.js +32 -0
- package/dist/components/TextField.js.map +1 -0
- package/dist/components/Textarea.d.ts +17 -0
- package/dist/components/Textarea.d.ts.map +1 -0
- package/dist/components/Textarea.js +34 -0
- package/dist/components/Textarea.js.map +1 -0
- package/dist/components/Tooltip.d.ts +17 -0
- package/dist/components/Tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip.js +217 -0
- package/dist/components/Tooltip.js.map +1 -0
- package/dist/components/Tooltip.test.js +58 -0
- package/dist/components/Tooltip.test.js.map +1 -0
- package/dist/helpers/gradientBorder.d.ts +5 -0
- package/dist/helpers/gradientBorder.d.ts.map +1 -0
- package/dist/helpers/gradientBorder.js +25 -0
- package/dist/helpers/gradientBorder.js.map +1 -0
- package/dist/helpers/labelColors.d.ts +17 -0
- package/dist/helpers/labelColors.d.ts.map +1 -0
- package/dist/helpers/labelColors.js +54 -0
- package/dist/helpers/labelColors.js.map +1 -0
- package/dist/helpers/nativeDialog.d.ts +23 -0
- package/dist/helpers/nativeDialog.d.ts.map +1 -0
- package/dist/helpers/nativeDialog.js +85 -0
- package/dist/helpers/nativeDialog.js.map +1 -0
- package/dist/helpers/responsive.d.ts +63 -0
- package/dist/helpers/responsive.d.ts.map +1 -0
- package/dist/helpers/responsive.js +78 -0
- package/dist/helpers/responsive.js.map +1 -0
- package/dist/helpers/storybookControls.d.ts +16 -0
- package/dist/helpers/storybookControls.d.ts.map +1 -0
- package/dist/helpers/storybookControls.js +40 -0
- package/dist/helpers/storybookControls.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +158 -0
- package/dist/index.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import {
|
|
4
|
+
Children,
|
|
5
|
+
cloneElement,
|
|
6
|
+
forwardRef,
|
|
7
|
+
isValidElement,
|
|
8
|
+
useCallback,
|
|
9
|
+
useEffect,
|
|
10
|
+
useId,
|
|
11
|
+
useLayoutEffect,
|
|
12
|
+
useRef,
|
|
13
|
+
useState
|
|
14
|
+
} from "react";
|
|
15
|
+
import { createPortal } from "react-dom";
|
|
16
|
+
import { tooltip as tooltipRecipe } from "@becket-ui/tokens/recipes";
|
|
17
|
+
const OFFSET_PX = 8;
|
|
18
|
+
const NATIVE_FOCUSABLE = /* @__PURE__ */ new Set(["a", "button", "input", "select", "summary", "textarea"]);
|
|
19
|
+
function mergeClassName(...classes) {
|
|
20
|
+
return classes.filter(Boolean).join(" ");
|
|
21
|
+
}
|
|
22
|
+
function assignRef(ref, value) {
|
|
23
|
+
if (typeof ref === "function") {
|
|
24
|
+
ref(value);
|
|
25
|
+
} else if (ref) {
|
|
26
|
+
ref.current = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function mergeRefs(...refs) {
|
|
30
|
+
return (node) => {
|
|
31
|
+
for (const ref of refs) {
|
|
32
|
+
assignRef(ref, node);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function chain(their, ours) {
|
|
37
|
+
return (event) => {
|
|
38
|
+
their?.(event);
|
|
39
|
+
ours?.(event);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function triggerNeedsTabIndex(element) {
|
|
43
|
+
if (typeof element.type !== "string") {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (NATIVE_FOCUSABLE.has(element.type)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (element.props.tabIndex != null) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
function computePosition(trigger, tip, placement) {
|
|
55
|
+
const cx = trigger.left + trigger.width / 2;
|
|
56
|
+
const cy = trigger.top + trigger.height / 2;
|
|
57
|
+
switch (placement) {
|
|
58
|
+
case "bottom":
|
|
59
|
+
return {
|
|
60
|
+
top: trigger.bottom + OFFSET_PX,
|
|
61
|
+
left: cx - tip.width / 2
|
|
62
|
+
};
|
|
63
|
+
case "left":
|
|
64
|
+
return {
|
|
65
|
+
top: cy - tip.height / 2,
|
|
66
|
+
left: trigger.left - tip.width - OFFSET_PX
|
|
67
|
+
};
|
|
68
|
+
case "right":
|
|
69
|
+
return {
|
|
70
|
+
top: cy - tip.height / 2,
|
|
71
|
+
left: trigger.right + OFFSET_PX
|
|
72
|
+
};
|
|
73
|
+
case "top":
|
|
74
|
+
default:
|
|
75
|
+
return {
|
|
76
|
+
top: trigger.top - tip.height - OFFSET_PX,
|
|
77
|
+
left: cx - tip.width / 2
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function clampToViewport(top, left, tip) {
|
|
82
|
+
const pad = 4;
|
|
83
|
+
const maxLeft = window.innerWidth - tip.width - pad;
|
|
84
|
+
const maxTop = window.innerHeight - tip.height - pad;
|
|
85
|
+
return {
|
|
86
|
+
top: Math.min(Math.max(pad, top), Math.max(pad, maxTop)),
|
|
87
|
+
left: Math.min(Math.max(pad, left), Math.max(pad, maxLeft))
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const Tooltip = forwardRef(
|
|
91
|
+
({
|
|
92
|
+
content,
|
|
93
|
+
children,
|
|
94
|
+
placement = "top",
|
|
95
|
+
delayMs = 200,
|
|
96
|
+
disabled = false
|
|
97
|
+
}, ref) => {
|
|
98
|
+
const tipId = useId();
|
|
99
|
+
const triggerRef = useRef(null);
|
|
100
|
+
const tipRef = useRef(null);
|
|
101
|
+
const showTimer = useRef(null);
|
|
102
|
+
const [open, setOpen] = useState(false);
|
|
103
|
+
const [coords, setCoords] = useState(null);
|
|
104
|
+
const setTriggerRef = useCallback(
|
|
105
|
+
(node) => {
|
|
106
|
+
triggerRef.current = node;
|
|
107
|
+
assignRef(ref, node);
|
|
108
|
+
},
|
|
109
|
+
[ref]
|
|
110
|
+
);
|
|
111
|
+
const clearShowTimer = useCallback(() => {
|
|
112
|
+
if (showTimer.current != null) {
|
|
113
|
+
clearTimeout(showTimer.current);
|
|
114
|
+
showTimer.current = null;
|
|
115
|
+
}
|
|
116
|
+
}, []);
|
|
117
|
+
const scheduleOpen = useCallback(() => {
|
|
118
|
+
if (disabled || content == null || content === "") return;
|
|
119
|
+
clearShowTimer();
|
|
120
|
+
showTimer.current = setTimeout(() => setOpen(true), delayMs);
|
|
121
|
+
}, [clearShowTimer, content, delayMs, disabled]);
|
|
122
|
+
const close = useCallback(() => {
|
|
123
|
+
clearShowTimer();
|
|
124
|
+
setOpen(false);
|
|
125
|
+
setCoords(null);
|
|
126
|
+
}, [clearShowTimer]);
|
|
127
|
+
useEffect(() => () => clearShowTimer(), [clearShowTimer]);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (!open) return;
|
|
130
|
+
const onKey = (event) => {
|
|
131
|
+
if (event.key === "Escape") close();
|
|
132
|
+
};
|
|
133
|
+
const onScroll = () => close();
|
|
134
|
+
window.addEventListener("keydown", onKey);
|
|
135
|
+
window.addEventListener("scroll", onScroll, true);
|
|
136
|
+
window.addEventListener("resize", onScroll);
|
|
137
|
+
return () => {
|
|
138
|
+
window.removeEventListener("keydown", onKey);
|
|
139
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
140
|
+
window.removeEventListener("resize", onScroll);
|
|
141
|
+
};
|
|
142
|
+
}, [close, open]);
|
|
143
|
+
useLayoutEffect(() => {
|
|
144
|
+
if (!open || !triggerRef.current || !tipRef.current) return;
|
|
145
|
+
const trigger2 = triggerRef.current.getBoundingClientRect();
|
|
146
|
+
const tip = tipRef.current.getBoundingClientRect();
|
|
147
|
+
const raw = computePosition(trigger2, tip, placement);
|
|
148
|
+
setCoords(clampToViewport(raw.top, raw.left, tip));
|
|
149
|
+
}, [open, placement, content]);
|
|
150
|
+
const tipStyle = {
|
|
151
|
+
top: coords?.top ?? -9999,
|
|
152
|
+
left: coords?.left ?? -9999,
|
|
153
|
+
visibility: coords ? "visible" : "hidden"
|
|
154
|
+
};
|
|
155
|
+
const describedByWhenOpen = open ? tipId : void 0;
|
|
156
|
+
const hoverHandlers = {
|
|
157
|
+
onMouseEnter: scheduleOpen,
|
|
158
|
+
onMouseLeave: close,
|
|
159
|
+
onFocus: scheduleOpen,
|
|
160
|
+
onBlur: close
|
|
161
|
+
};
|
|
162
|
+
const childArray = Children.toArray(children);
|
|
163
|
+
const onlyChild = childArray.length === 1 ? childArray[0] : void 0;
|
|
164
|
+
let trigger;
|
|
165
|
+
if (isValidElement(onlyChild)) {
|
|
166
|
+
const child = onlyChild;
|
|
167
|
+
const childRef = child.ref ?? child.props.ref;
|
|
168
|
+
const describedBy = [child.props["aria-describedby"], describedByWhenOpen].filter(Boolean).join(" ");
|
|
169
|
+
trigger = cloneElement(child, {
|
|
170
|
+
ref: mergeRefs(childRef, setTriggerRef),
|
|
171
|
+
onMouseEnter: chain(child.props.onMouseEnter, hoverHandlers.onMouseEnter),
|
|
172
|
+
onMouseLeave: chain(child.props.onMouseLeave, hoverHandlers.onMouseLeave),
|
|
173
|
+
onFocus: chain(child.props.onFocus, hoverHandlers.onFocus),
|
|
174
|
+
onBlur: chain(child.props.onBlur, hoverHandlers.onBlur),
|
|
175
|
+
"aria-describedby": describedBy || void 0,
|
|
176
|
+
...triggerNeedsTabIndex(child) ? { tabIndex: 0 } : {}
|
|
177
|
+
});
|
|
178
|
+
} else {
|
|
179
|
+
trigger = /* @__PURE__ */ jsx(
|
|
180
|
+
"span",
|
|
181
|
+
{
|
|
182
|
+
ref: setTriggerRef,
|
|
183
|
+
tabIndex: 0,
|
|
184
|
+
"aria-describedby": describedByWhenOpen,
|
|
185
|
+
onMouseEnter: hoverHandlers.onMouseEnter,
|
|
186
|
+
onMouseLeave: hoverHandlers.onMouseLeave,
|
|
187
|
+
onFocus: hoverHandlers.onFocus,
|
|
188
|
+
onBlur: hoverHandlers.onBlur,
|
|
189
|
+
style: { display: "inline-flex", maxWidth: "100%", verticalAlign: "middle" },
|
|
190
|
+
children
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
195
|
+
trigger,
|
|
196
|
+
open && typeof document !== "undefined" ? createPortal(
|
|
197
|
+
/* @__PURE__ */ jsx(
|
|
198
|
+
"div",
|
|
199
|
+
{
|
|
200
|
+
ref: tipRef,
|
|
201
|
+
id: tipId,
|
|
202
|
+
role: "tooltip",
|
|
203
|
+
className: mergeClassName(tooltipRecipe({ placement })),
|
|
204
|
+
style: tipStyle,
|
|
205
|
+
children: content
|
|
206
|
+
}
|
|
207
|
+
),
|
|
208
|
+
document.body
|
|
209
|
+
) : null
|
|
210
|
+
] });
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
Tooltip.displayName = "Tooltip";
|
|
214
|
+
export {
|
|
215
|
+
Tooltip
|
|
216
|
+
};
|
|
217
|
+
//# sourceMappingURL=Tooltip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Tooltip.tsx"],"sourcesContent":["'use client';\n\n/**\n * Hover / focus tooltip (generic DS primitive).\n * Default placement top, open delay 200ms.\n */\n\nimport {\n Children,\n cloneElement,\n forwardRef,\n isValidElement,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\n type FocusEventHandler,\n type MouseEventHandler,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { tooltip as tooltipRecipe } from '@becket-ui/tokens/recipes';\n\nexport type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';\n\nexport type TooltipProps = {\n /** Short glossary / hint text (or node). */\n content: ReactNode;\n children: ReactNode;\n placement?: TooltipPlacement;\n /** Delay before open on hover/focus (ms). */\n delayMs?: number;\n disabled?: boolean;\n};\n\nconst OFFSET_PX = 8;\n\nconst NATIVE_FOCUSABLE = new Set(['a', 'button', 'input', 'select', 'summary', 'textarea']);\n\ntype TriggerProps = {\n ref?: Ref<HTMLElement>;\n tabIndex?: number;\n href?: string;\n onMouseEnter?: MouseEventHandler<HTMLElement>;\n onMouseLeave?: MouseEventHandler<HTMLElement>;\n onFocus?: FocusEventHandler<HTMLElement>;\n onBlur?: FocusEventHandler<HTMLElement>;\n 'aria-describedby'?: string;\n};\n\nfunction mergeClassName(...classes: (string | undefined)[]): string {\n return classes.filter(Boolean).join(' ');\n}\n\nfunction assignRef<T>(ref: Ref<T> | undefined, value: T | null) {\n if (typeof ref === 'function') {\n ref(value);\n } else if (ref) {\n ref.current = value;\n }\n}\n\nfunction mergeRefs<T>(...refs: Array<Ref<T> | undefined>) {\n return (node: T | null) => {\n for (const ref of refs) {\n assignRef(ref, node);\n }\n };\n}\n\nfunction chain<E>(their?: (event: E) => void, ours?: (event: E) => void) {\n return (event: E) => {\n their?.(event);\n ours?.(event);\n };\n}\n\nfunction triggerNeedsTabIndex(element: ReactElement<TriggerProps>): boolean {\n if (typeof element.type !== 'string') {\n return false;\n }\n if (NATIVE_FOCUSABLE.has(element.type)) {\n return false;\n }\n if (element.props.tabIndex != null) {\n return false;\n }\n return true;\n}\n\nfunction computePosition(\n trigger: DOMRect,\n tip: DOMRect,\n placement: TooltipPlacement,\n): { top: number; left: number } {\n const cx = trigger.left + trigger.width / 2;\n const cy = trigger.top + trigger.height / 2;\n\n switch (placement) {\n case 'bottom':\n return {\n top: trigger.bottom + OFFSET_PX,\n left: cx - tip.width / 2,\n };\n case 'left':\n return {\n top: cy - tip.height / 2,\n left: trigger.left - tip.width - OFFSET_PX,\n };\n case 'right':\n return {\n top: cy - tip.height / 2,\n left: trigger.right + OFFSET_PX,\n };\n case 'top':\n default:\n return {\n top: trigger.top - tip.height - OFFSET_PX,\n left: cx - tip.width / 2,\n };\n }\n}\n\nfunction clampToViewport(top: number, left: number, tip: DOMRect): { top: number; left: number } {\n const pad = 4;\n const maxLeft = window.innerWidth - tip.width - pad;\n const maxTop = window.innerHeight - tip.height - pad;\n return {\n top: Math.min(Math.max(pad, top), Math.max(pad, maxTop)),\n left: Math.min(Math.max(pad, left), Math.max(pad, maxLeft)),\n };\n}\n\nexport const Tooltip = forwardRef<HTMLElement, TooltipProps>(\n (\n {\n content,\n children,\n placement = 'top',\n delayMs = 200,\n disabled = false,\n },\n ref,\n ) => {\n const tipId = useId();\n const triggerRef = useRef<HTMLElement | null>(null);\n const tipRef = useRef<HTMLDivElement | null>(null);\n const showTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const [open, setOpen] = useState(false);\n const [coords, setCoords] = useState<{ top: number; left: number } | null>(null);\n\n const setTriggerRef = useCallback(\n (node: HTMLElement | null) => {\n triggerRef.current = node;\n assignRef(ref, node);\n },\n [ref],\n );\n\n const clearShowTimer = useCallback(() => {\n if (showTimer.current != null) {\n clearTimeout(showTimer.current);\n showTimer.current = null;\n }\n }, []);\n\n const scheduleOpen = useCallback(() => {\n if (disabled || content == null || content === '') return;\n clearShowTimer();\n showTimer.current = setTimeout(() => setOpen(true), delayMs);\n }, [clearShowTimer, content, delayMs, disabled]);\n\n const close = useCallback(() => {\n clearShowTimer();\n setOpen(false);\n setCoords(null);\n }, [clearShowTimer]);\n\n useEffect(() => () => clearShowTimer(), [clearShowTimer]);\n\n useEffect(() => {\n if (!open) return;\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') close();\n };\n const onScroll = () => close();\n window.addEventListener('keydown', onKey);\n window.addEventListener('scroll', onScroll, true);\n window.addEventListener('resize', onScroll);\n return () => {\n window.removeEventListener('keydown', onKey);\n window.removeEventListener('scroll', onScroll, true);\n window.removeEventListener('resize', onScroll);\n };\n }, [close, open]);\n\n useLayoutEffect(() => {\n if (!open || !triggerRef.current || !tipRef.current) return;\n const trigger = triggerRef.current.getBoundingClientRect();\n const tip = tipRef.current.getBoundingClientRect();\n const raw = computePosition(trigger, tip, placement);\n setCoords(clampToViewport(raw.top, raw.left, tip));\n }, [open, placement, content]);\n\n const tipStyle: CSSProperties = {\n top: coords?.top ?? -9999,\n left: coords?.left ?? -9999,\n visibility: coords ? 'visible' : 'hidden',\n };\n\n const describedByWhenOpen = open ? tipId : undefined;\n const hoverHandlers = {\n onMouseEnter: scheduleOpen,\n onMouseLeave: close,\n onFocus: scheduleOpen,\n onBlur: close,\n };\n\n const childArray = Children.toArray(children);\n const onlyChild = childArray.length === 1 ? childArray[0] : undefined;\n let trigger: ReactNode;\n\n if (isValidElement(onlyChild)) {\n const child = onlyChild as ReactElement<TriggerProps>;\n const childRef = (child as { ref?: Ref<HTMLElement> }).ref ?? child.props.ref;\n const describedBy = [child.props['aria-describedby'], describedByWhenOpen]\n .filter(Boolean)\n .join(' ');\n trigger = cloneElement(child, {\n ref: mergeRefs(childRef, setTriggerRef),\n onMouseEnter: chain(child.props.onMouseEnter, hoverHandlers.onMouseEnter),\n onMouseLeave: chain(child.props.onMouseLeave, hoverHandlers.onMouseLeave),\n onFocus: chain(child.props.onFocus, hoverHandlers.onFocus),\n onBlur: chain(child.props.onBlur, hoverHandlers.onBlur),\n 'aria-describedby': describedBy || undefined,\n ...(triggerNeedsTabIndex(child) ? { tabIndex: 0 } : {}),\n });\n } else {\n trigger = (\n <span\n ref={setTriggerRef}\n tabIndex={0}\n aria-describedby={describedByWhenOpen}\n onMouseEnter={hoverHandlers.onMouseEnter}\n onMouseLeave={hoverHandlers.onMouseLeave}\n onFocus={hoverHandlers.onFocus}\n onBlur={hoverHandlers.onBlur}\n style={{ display: 'inline-flex', maxWidth: '100%', verticalAlign: 'middle' }}\n >\n {children}\n </span>\n );\n }\n\n return (\n <>\n {trigger}\n {open && typeof document !== 'undefined'\n ? createPortal(\n <div\n ref={tipRef}\n id={tipId}\n role=\"tooltip\"\n className={mergeClassName(tooltipRecipe({ placement }))}\n style={tipStyle}\n >\n {content}\n </div>,\n document.body,\n )\n : null}\n </>\n );\n },\n);\n\nTooltip.displayName = 'Tooltip';\n"],"mappings":"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","names":["trigger"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import { assertNoAxeViolations } from "@becket-ui/a11y";
|
|
6
|
+
import { Button } from "./Button";
|
|
7
|
+
import { Tooltip } from "./Tooltip";
|
|
8
|
+
describe("Tooltip", () => {
|
|
9
|
+
it("does not add an extra tab stop around Button", async () => {
|
|
10
|
+
const user = userEvent.setup();
|
|
11
|
+
render(
|
|
12
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
13
|
+
/* @__PURE__ */ jsx("button", { type: "button", children: "Before" }),
|
|
14
|
+
/* @__PURE__ */ jsx(Tooltip, { content: "Short glossary hint.", delayMs: 0, children: /* @__PURE__ */ jsx(Button, { children: "Hover me" }) }),
|
|
15
|
+
/* @__PURE__ */ jsx("button", { type: "button", children: "After" })
|
|
16
|
+
] })
|
|
17
|
+
);
|
|
18
|
+
const trigger = screen.getByRole("button", { name: "Hover me" });
|
|
19
|
+
expect(trigger.parentElement?.getAttribute("tabindex")).not.toBe("0");
|
|
20
|
+
await user.tab();
|
|
21
|
+
expect(document.activeElement).toBe(screen.getByRole("button", { name: "Before" }));
|
|
22
|
+
await user.tab();
|
|
23
|
+
expect(document.activeElement).toBe(trigger);
|
|
24
|
+
await user.tab();
|
|
25
|
+
expect(document.activeElement).toBe(screen.getByRole("button", { name: "After" }));
|
|
26
|
+
});
|
|
27
|
+
it("has no axe violations when wrapping a named Button", async () => {
|
|
28
|
+
const { container } = render(
|
|
29
|
+
/* @__PURE__ */ jsx(Tooltip, { content: "Short glossary hint.", delayMs: 0, children: /* @__PURE__ */ jsx(Button, { children: "Hover me" }) })
|
|
30
|
+
);
|
|
31
|
+
await assertNoAxeViolations(container);
|
|
32
|
+
});
|
|
33
|
+
it("sets aria-describedby on the Button while open and closes on Escape", async () => {
|
|
34
|
+
const user = userEvent.setup();
|
|
35
|
+
render(
|
|
36
|
+
/* @__PURE__ */ jsx(Tooltip, { content: "Short glossary hint.", delayMs: 0, children: /* @__PURE__ */ jsx(Button, { children: "Hover me" }) })
|
|
37
|
+
);
|
|
38
|
+
const trigger = screen.getByRole("button", { name: "Hover me" });
|
|
39
|
+
await user.hover(trigger);
|
|
40
|
+
const tip = await screen.findByRole("tooltip");
|
|
41
|
+
expect(trigger.getAttribute("aria-describedby")).toBe(tip.id);
|
|
42
|
+
await user.keyboard("{Escape}");
|
|
43
|
+
expect(screen.queryByRole("tooltip")).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
it("wraps plain text so it is keyboard reachable", async () => {
|
|
46
|
+
const user = userEvent.setup();
|
|
47
|
+
render(
|
|
48
|
+
/* @__PURE__ */ jsx(Tooltip, { content: "Hint", delayMs: 0, children: "glossary" })
|
|
49
|
+
);
|
|
50
|
+
const trigger = screen.getByText("glossary");
|
|
51
|
+
expect(trigger.tagName).toBe("SPAN");
|
|
52
|
+
expect(trigger.getAttribute("tabindex")).toBe("0");
|
|
53
|
+
trigger.focus();
|
|
54
|
+
await user.hover(trigger);
|
|
55
|
+
expect(await screen.findByRole("tooltip")).toBeTruthy();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=Tooltip.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Tooltip.test.tsx"],"sourcesContent":["import { describe, expect, it } from 'vitest';\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { assertNoAxeViolations } from '@becket-ui/a11y';\n\nimport { Button } from './Button';\nimport { Tooltip } from './Tooltip';\n\ndescribe('Tooltip', () => {\n it('does not add an extra tab stop around Button', async () => {\n const user = userEvent.setup();\n render(\n <>\n <button type=\"button\">Before</button>\n <Tooltip content=\"Short glossary hint.\" delayMs={0}>\n <Button>Hover me</Button>\n </Tooltip>\n <button type=\"button\">After</button>\n </>,\n );\n\n const trigger = screen.getByRole('button', { name: 'Hover me' });\n expect(trigger.parentElement?.getAttribute('tabindex')).not.toBe('0');\n\n await user.tab();\n expect(document.activeElement).toBe(screen.getByRole('button', { name: 'Before' }));\n await user.tab();\n expect(document.activeElement).toBe(trigger);\n await user.tab();\n expect(document.activeElement).toBe(screen.getByRole('button', { name: 'After' }));\n });\n\n it('has no axe violations when wrapping a named Button', async () => {\n const { container } = render(\n <Tooltip content=\"Short glossary hint.\" delayMs={0}>\n <Button>Hover me</Button>\n </Tooltip>,\n );\n await assertNoAxeViolations(container);\n });\n\n it('sets aria-describedby on the Button while open and closes on Escape', async () => {\n const user = userEvent.setup();\n render(\n <Tooltip content=\"Short glossary hint.\" delayMs={0}>\n <Button>Hover me</Button>\n </Tooltip>,\n );\n\n const trigger = screen.getByRole('button', { name: 'Hover me' });\n await user.hover(trigger);\n const tip = await screen.findByRole('tooltip');\n expect(trigger.getAttribute('aria-describedby')).toBe(tip.id);\n\n await user.keyboard('{Escape}');\n expect(screen.queryByRole('tooltip')).toBeNull();\n });\n\n it('wraps plain text so it is keyboard reachable', async () => {\n const user = userEvent.setup();\n render(\n <Tooltip content=\"Hint\" delayMs={0}>\n glossary\n </Tooltip>,\n );\n\n const trigger = screen.getByText('glossary');\n expect(trigger.tagName).toBe('SPAN');\n expect(trigger.getAttribute('tabindex')).toBe('0');\n\n trigger.focus();\n await user.hover(trigger);\n expect(await screen.findByRole('tooltip')).toBeTruthy();\n });\n});\n"],"mappings":"AAYM,mBACE,KADF;AAZN,SAAS,UAAU,QAAQ,UAAU;AACrC,SAAS,QAAQ,cAAc;AAC/B,OAAO,eAAe;AACtB,SAAS,6BAA6B;AAEtC,SAAS,cAAc;AACvB,SAAS,eAAe;AAExB,SAAS,WAAW,MAAM;AACxB,KAAG,gDAAgD,YAAY;AAC7D,UAAM,OAAO,UAAU,MAAM;AAC7B;AAAA,MACE,iCACE;AAAA,4BAAC,YAAO,MAAK,UAAS,oBAAM;AAAA,QAC5B,oBAAC,WAAQ,SAAQ,wBAAuB,SAAS,GAC/C,8BAAC,UAAO,sBAAQ,GAClB;AAAA,QACA,oBAAC,YAAO,MAAK,UAAS,mBAAK;AAAA,SAC7B;AAAA,IACF;AAEA,UAAM,UAAU,OAAO,UAAU,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,WAAO,QAAQ,eAAe,aAAa,UAAU,CAAC,EAAE,IAAI,KAAK,GAAG;AAEpE,UAAM,KAAK,IAAI;AACf,WAAO,SAAS,aAAa,EAAE,KAAK,OAAO,UAAU,UAAU,EAAE,MAAM,SAAS,CAAC,CAAC;AAClF,UAAM,KAAK,IAAI;AACf,WAAO,SAAS,aAAa,EAAE,KAAK,OAAO;AAC3C,UAAM,KAAK,IAAI;AACf,WAAO,SAAS,aAAa,EAAE,KAAK,OAAO,UAAU,UAAU,EAAE,MAAM,QAAQ,CAAC,CAAC;AAAA,EACnF,CAAC;AAED,KAAG,sDAAsD,YAAY;AACnE,UAAM,EAAE,UAAU,IAAI;AAAA,MACpB,oBAAC,WAAQ,SAAQ,wBAAuB,SAAS,GAC/C,8BAAC,UAAO,sBAAQ,GAClB;AAAA,IACF;AACA,UAAM,sBAAsB,SAAS;AAAA,EACvC,CAAC;AAED,KAAG,uEAAuE,YAAY;AACpF,UAAM,OAAO,UAAU,MAAM;AAC7B;AAAA,MACE,oBAAC,WAAQ,SAAQ,wBAAuB,SAAS,GAC/C,8BAAC,UAAO,sBAAQ,GAClB;AAAA,IACF;AAEA,UAAM,UAAU,OAAO,UAAU,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,UAAM,KAAK,MAAM,OAAO;AACxB,UAAM,MAAM,MAAM,OAAO,WAAW,SAAS;AAC7C,WAAO,QAAQ,aAAa,kBAAkB,CAAC,EAAE,KAAK,IAAI,EAAE;AAE5D,UAAM,KAAK,SAAS,UAAU;AAC9B,WAAO,OAAO,YAAY,SAAS,CAAC,EAAE,SAAS;AAAA,EACjD,CAAC;AAED,KAAG,gDAAgD,YAAY;AAC7D,UAAM,OAAO,UAAU,MAAM;AAC7B;AAAA,MACE,oBAAC,WAAQ,SAAQ,QAAO,SAAS,GAAG,sBAEpC;AAAA,IACF;AAEA,UAAM,UAAU,OAAO,UAAU,UAAU;AAC3C,WAAO,QAAQ,OAAO,EAAE,KAAK,MAAM;AACnC,WAAO,QAAQ,aAAa,UAAU,CAAC,EAAE,KAAK,GAAG;AAEjD,YAAQ,MAAM;AACd,UAAM,KAAK,MAAM,OAAO;AACxB,WAAO,MAAM,OAAO,WAAW,SAAS,CAAC,EAAE,WAAW;AAAA,EACxD,CAAC;AACH,CAAC;","names":[]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { GradientToken } from '@becket-ui/tokens/tokens';
|
|
2
|
+
export type GradientBorderToken = Exclude<GradientToken, 'primaryHover'>;
|
|
3
|
+
export declare function isGradientBorderToken(value: unknown): value is GradientBorderToken;
|
|
4
|
+
export declare function getGradientBorderClassName(token: GradientBorderToken): string;
|
|
5
|
+
//# sourceMappingURL=gradientBorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gradientBorder.d.ts","sourceRoot":"","sources":["../../src/helpers/gradientBorder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;AAmBzE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAElF;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CAE7E"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { css } from "@becket-ui/tokens";
|
|
2
|
+
const outlineGradientBorder = (image) => css({
|
|
3
|
+
borderWidth: "2px",
|
|
4
|
+
borderStyle: "solid",
|
|
5
|
+
borderColor: "transparent",
|
|
6
|
+
backgroundImage: `linear-gradient({colors.background}, {colors.background}), ${image}`,
|
|
7
|
+
backgroundOrigin: "border-box",
|
|
8
|
+
backgroundClip: "padding-box, border-box"
|
|
9
|
+
});
|
|
10
|
+
const gradientBorderClassMap = {
|
|
11
|
+
primary: outlineGradientBorder("{gradients.primary}"),
|
|
12
|
+
secondary: outlineGradientBorder("{gradients.secondary}"),
|
|
13
|
+
neutral: outlineGradientBorder("{gradients.neutral}")
|
|
14
|
+
};
|
|
15
|
+
function isGradientBorderToken(value) {
|
|
16
|
+
return typeof value === "string" && value in gradientBorderClassMap;
|
|
17
|
+
}
|
|
18
|
+
function getGradientBorderClassName(token) {
|
|
19
|
+
return gradientBorderClassMap[token];
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
getGradientBorderClassName,
|
|
23
|
+
isGradientBorderToken
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=gradientBorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/gradientBorder.ts"],"sourcesContent":["import { css } from '@becket-ui/tokens';\nimport type { GradientToken } from '@becket-ui/tokens/tokens';\n\nexport type GradientBorderToken = Exclude<GradientToken, 'primaryHover'>;\n\n/** Double-layer border-box fill so left/right caps match the radius (no ::before sliver). */\nconst outlineGradientBorder = (image: `{gradients.${GradientBorderToken}}`) =>\n css({\n borderWidth: '2px',\n borderStyle: 'solid',\n borderColor: 'transparent',\n backgroundImage: `linear-gradient({colors.background}, {colors.background}), ${image}`,\n backgroundOrigin: 'border-box',\n backgroundClip: 'padding-box, border-box',\n });\n\nconst gradientBorderClassMap: Record<GradientBorderToken, string> = {\n primary: outlineGradientBorder('{gradients.primary}'),\n secondary: outlineGradientBorder('{gradients.secondary}'),\n neutral: outlineGradientBorder('{gradients.neutral}'),\n};\n\nexport function isGradientBorderToken(value: unknown): value is GradientBorderToken {\n return typeof value === 'string' && value in gradientBorderClassMap;\n}\n\nexport function getGradientBorderClassName(token: GradientBorderToken): string {\n return gradientBorderClassMap[token];\n}\n"],"mappings":"AAAA,SAAS,WAAW;AAMpB,MAAM,wBAAwB,CAAC,UAC7B,IAAI;AAAA,EACF,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,iBAAiB,8DAA8D,KAAK;AAAA,EACpF,kBAAkB;AAAA,EAClB,gBAAgB;AAClB,CAAC;AAEH,MAAM,yBAA8D;AAAA,EAClE,SAAS,sBAAsB,qBAAqB;AAAA,EACpD,WAAW,sBAAsB,uBAAuB;AAAA,EACxD,SAAS,sBAAsB,qBAAqB;AACtD;AAEO,SAAS,sBAAsB,OAA8C;AAClF,SAAO,OAAO,UAAU,YAAY,SAAS;AAC/C;AAEO,SAAS,2BAA2B,OAAoC;AAC7E,SAAO,uBAAuB,KAAK;AACrC;","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
export declare const LABEL_COLOR_VARS: {
|
|
3
|
+
readonly background: '--mav-label-bg';
|
|
4
|
+
readonly border: '--mav-label-border';
|
|
5
|
+
readonly color: '--mav-label-color';
|
|
6
|
+
readonly gradient: '--mav-label-gradient';
|
|
7
|
+
};
|
|
8
|
+
/** Downstream apps pass CSS values (tokens, vars, or literals) for label surfaces. */
|
|
9
|
+
export type LabelCustomColors = {
|
|
10
|
+
background?: string;
|
|
11
|
+
border?: string;
|
|
12
|
+
color?: string;
|
|
13
|
+
gradient?: string;
|
|
14
|
+
};
|
|
15
|
+
export type LabelVisual = 'primary' | 'secondary' | 'neutral' | 'outline';
|
|
16
|
+
export declare function resolveLabelColorVars(visual: LabelVisual, withGradient: boolean, customColors?: LabelCustomColors): CSSProperties;
|
|
17
|
+
//# sourceMappingURL=labelColors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"labelColors.d.ts","sourceRoot":"","sources":["../../src/helpers/labelColors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,eAAO,MAAM,gBAAgB;aAC3B,UAAU,EAAE,gBAAgB;aAC5B,MAAM,EAAE,oBAAoB;aAC5B,KAAK,EAAE,mBAAmB;aAC1B,QAAQ,EAAE,sBAAsB;CACxB,CAAC;AAEX,sFAAsF;AACtF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;AAgC1E,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,WAAW,EACnB,YAAY,EAAE,OAAO,EACrB,YAAY,CAAC,EAAE,iBAAiB,GAC/B,aAAa,CAkBf"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const LABEL_COLOR_VARS = {
|
|
2
|
+
background: "--mav-label-bg",
|
|
3
|
+
border: "--mav-label-border",
|
|
4
|
+
color: "--mav-label-color",
|
|
5
|
+
gradient: "--mav-label-gradient"
|
|
6
|
+
};
|
|
7
|
+
const VISUAL_DEFAULTS = {
|
|
8
|
+
primary: {
|
|
9
|
+
background: "var(--beckui--colors-primary)",
|
|
10
|
+
color: "var(--beckui--colors-neutral-50)",
|
|
11
|
+
border: "transparent"
|
|
12
|
+
},
|
|
13
|
+
secondary: {
|
|
14
|
+
background: "var(--beckui--colors-secondary)",
|
|
15
|
+
color: "var(--beckui--colors-neutral-50)",
|
|
16
|
+
border: "transparent"
|
|
17
|
+
},
|
|
18
|
+
neutral: {
|
|
19
|
+
background: "var(--beckui--colors-neutral-800)",
|
|
20
|
+
color: "var(--beckui--colors-text)",
|
|
21
|
+
border: "transparent"
|
|
22
|
+
},
|
|
23
|
+
outline: {
|
|
24
|
+
background: "transparent",
|
|
25
|
+
color: "var(--beckui--colors-secondary)",
|
|
26
|
+
border: "var(--beckui--colors-secondary)"
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const GRADIENT_DEFAULTS = {
|
|
30
|
+
primary: "var(--beckui--gradients-primary)",
|
|
31
|
+
secondary: "var(--beckui--gradients-secondary)",
|
|
32
|
+
neutral: "var(--beckui--gradients-neutral)",
|
|
33
|
+
outline: "var(--beckui--gradients-primary)"
|
|
34
|
+
};
|
|
35
|
+
function resolveLabelColorVars(visual, withGradient, customColors) {
|
|
36
|
+
const defaults = VISUAL_DEFAULTS[visual];
|
|
37
|
+
const merged = { ...defaults, ...customColors };
|
|
38
|
+
const vars = {
|
|
39
|
+
[LABEL_COLOR_VARS.background]: merged.background,
|
|
40
|
+
[LABEL_COLOR_VARS.border]: merged.border,
|
|
41
|
+
[LABEL_COLOR_VARS.color]: merged.color
|
|
42
|
+
};
|
|
43
|
+
if (withGradient) {
|
|
44
|
+
vars[LABEL_COLOR_VARS.gradient] = customColors?.gradient ?? GRADIENT_DEFAULTS[visual];
|
|
45
|
+
}
|
|
46
|
+
return Object.fromEntries(
|
|
47
|
+
Object.entries(vars).filter((entry) => Boolean(entry[1]))
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
LABEL_COLOR_VARS,
|
|
52
|
+
resolveLabelColorVars
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=labelColors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/labelColors.ts"],"sourcesContent":["import type { CSSProperties } from 'react';\n\nexport const LABEL_COLOR_VARS = {\n background: '--mav-label-bg',\n border: '--mav-label-border',\n color: '--mav-label-color',\n gradient: '--mav-label-gradient',\n} as const;\n\n/** Downstream apps pass CSS values (tokens, vars, or literals) for label surfaces. */\nexport type LabelCustomColors = {\n background?: string;\n border?: string;\n color?: string;\n gradient?: string;\n};\n\nexport type LabelVisual = 'primary' | 'secondary' | 'neutral' | 'outline';\n\nconst VISUAL_DEFAULTS: Record<LabelVisual, LabelCustomColors> = {\n primary: {\n background: 'var(--beckui--colors-primary)',\n color: 'var(--beckui--colors-neutral-50)',\n border: 'transparent',\n },\n secondary: {\n background: 'var(--beckui--colors-secondary)',\n color: 'var(--beckui--colors-neutral-50)',\n border: 'transparent',\n },\n neutral: {\n background: 'var(--beckui--colors-neutral-800)',\n color: 'var(--beckui--colors-text)',\n border: 'transparent',\n },\n outline: {\n background: 'transparent',\n color: 'var(--beckui--colors-secondary)',\n border: 'var(--beckui--colors-secondary)',\n },\n};\n\nconst GRADIENT_DEFAULTS: Record<LabelVisual, string> = {\n primary: 'var(--beckui--gradients-primary)',\n secondary: 'var(--beckui--gradients-secondary)',\n neutral: 'var(--beckui--gradients-neutral)',\n outline: 'var(--beckui--gradients-primary)',\n};\n\nexport function resolveLabelColorVars(\n visual: LabelVisual,\n withGradient: boolean,\n customColors?: LabelCustomColors,\n): CSSProperties {\n const defaults = VISUAL_DEFAULTS[visual];\n const merged: LabelCustomColors = { ...defaults, ...customColors };\n\n const vars: Record<string, string | undefined> = {\n [LABEL_COLOR_VARS.background]: merged.background,\n [LABEL_COLOR_VARS.border]: merged.border,\n [LABEL_COLOR_VARS.color]: merged.color,\n };\n\n if (withGradient) {\n vars[LABEL_COLOR_VARS.gradient] =\n customColors?.gradient ?? GRADIENT_DEFAULTS[visual];\n }\n\n return Object.fromEntries(\n Object.entries(vars).filter((entry): entry is [string, string] => Boolean(entry[1])),\n ) as CSSProperties;\n}\n"],"mappings":"AAEO,MAAM,mBAAmB;AAAA,EAC9B,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,UAAU;AACZ;AAYA,MAAM,kBAA0D;AAAA,EAC9D,SAAS;AAAA,IACP,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,WAAW;AAAA,IACT,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,SAAS;AAAA,IACP,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,SAAS;AAAA,IACP,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AACF;AAEA,MAAM,oBAAiD;AAAA,EACrD,SAAS;AAAA,EACT,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AACX;AAEO,SAAS,sBACd,QACA,cACA,cACe;AACf,QAAM,WAAW,gBAAgB,MAAM;AACvC,QAAM,SAA4B,EAAE,GAAG,UAAU,GAAG,aAAa;AAEjE,QAAM,OAA2C;AAAA,IAC/C,CAAC,iBAAiB,UAAU,GAAG,OAAO;AAAA,IACtC,CAAC,iBAAiB,MAAM,GAAG,OAAO;AAAA,IAClC,CAAC,iBAAiB,KAAK,GAAG,OAAO;AAAA,EACnC;AAEA,MAAI,cAAc;AAChB,SAAK,iBAAiB,QAAQ,IAC5B,cAAc,YAAY,kBAAkB,MAAM;AAAA,EACtD;AAEA,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,IAAI,EAAE,OAAO,CAAC,UAAqC,QAAQ,MAAM,CAAC,CAAC,CAAC;AAAA,EACrF;AACF;","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Ref, type RefObject } from 'react';
|
|
2
|
+
type NativeDialogOptions = {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
defaultOpen?: boolean;
|
|
5
|
+
onOpenChange?: (open: boolean) => void;
|
|
6
|
+
/** Composed title is in the tree (skip waiting on DialogTitle mount). */
|
|
7
|
+
titlePresent?: boolean;
|
|
8
|
+
/** Composed body has content (skip waiting on DialogBody mount). */
|
|
9
|
+
descriptionPresent?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function useNativeDialog(dialogRef: RefObject<HTMLDialogElement | null>, { open, defaultOpen, onOpenChange, titlePresent, descriptionPresent, }: NativeDialogOptions): {
|
|
12
|
+
isOpen: boolean;
|
|
13
|
+
setOpen: (next: boolean) => void;
|
|
14
|
+
titleId: string;
|
|
15
|
+
descriptionId: string;
|
|
16
|
+
labelledBy: string | undefined;
|
|
17
|
+
describedBy: string | undefined;
|
|
18
|
+
registerTitle: (present: boolean) => void;
|
|
19
|
+
registerDescription: (present: boolean) => void;
|
|
20
|
+
};
|
|
21
|
+
export declare function assignRef<T>(ref: Ref<T> | undefined, value: T | null): void;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=nativeDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeDialog.d.ts","sourceRoot":"","sources":["../../src/helpers/nativeDialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,GAAG,EACR,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,KAAK,mBAAmB,GAAG;IACzB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,yEAAyE;IACzE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oEAAoE;IACpE,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,EAC9C,EACE,IAAI,EACJ,WAAmB,EACnB,YAAY,EACZ,YAAoB,EACpB,kBAA0B,GAC3B,EAAE,mBAAmB;;oBAkBb,OAAO;;;;;6BAT4B,OAAO;mCAID,OAAO;EAwD1D;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,QAQpE"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useId,
|
|
5
|
+
useState
|
|
6
|
+
} from "react";
|
|
7
|
+
function useNativeDialog(dialogRef, {
|
|
8
|
+
open,
|
|
9
|
+
defaultOpen = false,
|
|
10
|
+
onOpenChange,
|
|
11
|
+
titlePresent = false,
|
|
12
|
+
descriptionPresent = false
|
|
13
|
+
}) {
|
|
14
|
+
const isControlled = open !== void 0;
|
|
15
|
+
const [uncontrolled, setUncontrolled] = useState(defaultOpen);
|
|
16
|
+
const isOpen = isControlled ? Boolean(open) : uncontrolled;
|
|
17
|
+
const titleId = useId();
|
|
18
|
+
const descriptionId = useId();
|
|
19
|
+
const [slots, setSlots] = useState({ title: false, description: false });
|
|
20
|
+
const registerTitle = useCallback((present) => {
|
|
21
|
+
setSlots((prev) => prev.title === present ? prev : { ...prev, title: present });
|
|
22
|
+
}, []);
|
|
23
|
+
const registerDescription = useCallback((present) => {
|
|
24
|
+
setSlots((prev) => prev.description === present ? prev : { ...prev, description: present });
|
|
25
|
+
}, []);
|
|
26
|
+
const setOpen = useCallback(
|
|
27
|
+
(next) => {
|
|
28
|
+
if (!isControlled) setUncontrolled(next);
|
|
29
|
+
onOpenChange?.(next);
|
|
30
|
+
},
|
|
31
|
+
[isControlled, onOpenChange]
|
|
32
|
+
);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const el = dialogRef.current;
|
|
35
|
+
if (!el) return;
|
|
36
|
+
if (!isOpen) {
|
|
37
|
+
if (el.open) el.close();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!el.open) {
|
|
41
|
+
if (typeof el.showModal === "function") {
|
|
42
|
+
el.showModal();
|
|
43
|
+
} else {
|
|
44
|
+
el.setAttribute("open", "");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const focusInitial = () => {
|
|
48
|
+
if (!el.open && !el.hasAttribute("open")) return;
|
|
49
|
+
const marked = el.querySelector("[data-becket-initial-focus]");
|
|
50
|
+
const footerBtn = el.querySelector("[data-becket-dialog-footer] button:not([disabled])");
|
|
51
|
+
const target = marked ?? footerBtn ?? el;
|
|
52
|
+
target.focus();
|
|
53
|
+
};
|
|
54
|
+
const frame = requestAnimationFrame(() => {
|
|
55
|
+
requestAnimationFrame(focusInitial);
|
|
56
|
+
});
|
|
57
|
+
return () => cancelAnimationFrame(frame);
|
|
58
|
+
}, [dialogRef, isOpen]);
|
|
59
|
+
const labelledBy = titlePresent || slots.title ? titleId : void 0;
|
|
60
|
+
const describedBy = descriptionPresent || slots.description ? descriptionId : void 0;
|
|
61
|
+
return {
|
|
62
|
+
isOpen,
|
|
63
|
+
setOpen,
|
|
64
|
+
titleId,
|
|
65
|
+
descriptionId,
|
|
66
|
+
labelledBy,
|
|
67
|
+
describedBy,
|
|
68
|
+
registerTitle,
|
|
69
|
+
registerDescription
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function assignRef(ref, value) {
|
|
73
|
+
if (typeof ref === "function") {
|
|
74
|
+
ref(value);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (ref) {
|
|
78
|
+
ref.current = value;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
assignRef,
|
|
83
|
+
useNativeDialog
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=nativeDialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/nativeDialog.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useState,\n type MutableRefObject,\n type Ref,\n type RefObject,\n} from 'react';\n\ntype NativeDialogOptions = {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Composed title is in the tree (skip waiting on DialogTitle mount). */\n titlePresent?: boolean;\n /** Composed body has content (skip waiting on DialogBody mount). */\n descriptionPresent?: boolean;\n};\n\nexport function useNativeDialog(\n dialogRef: RefObject<HTMLDialogElement | null>,\n {\n open,\n defaultOpen = false,\n onOpenChange,\n titlePresent = false,\n descriptionPresent = false,\n }: NativeDialogOptions,\n) {\n const isControlled = open !== undefined;\n const [uncontrolled, setUncontrolled] = useState(defaultOpen);\n const isOpen = isControlled ? Boolean(open) : uncontrolled;\n const titleId = useId();\n const descriptionId = useId();\n const [slots, setSlots] = useState({ title: false, description: false });\n\n const registerTitle = useCallback((present: boolean) => {\n setSlots((prev) => (prev.title === present ? prev : { ...prev, title: present }));\n }, []);\n\n const registerDescription = useCallback((present: boolean) => {\n setSlots((prev) => (prev.description === present ? prev : { ...prev, description: present }));\n }, []);\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (!isControlled) setUncontrolled(next);\n onOpenChange?.(next);\n },\n [isControlled, onOpenChange],\n );\n\n useEffect(() => {\n const el = dialogRef.current;\n if (!el) return;\n\n if (!isOpen) {\n if (el.open) el.close();\n return;\n }\n\n if (!el.open) {\n if (typeof el.showModal === 'function') {\n el.showModal();\n } else {\n el.setAttribute('open', '');\n }\n }\n\n const focusInitial = () => {\n if (!el.open && !el.hasAttribute('open')) return;\n const marked = el.querySelector<HTMLElement>('[data-becket-initial-focus]');\n const footerBtn = el.querySelector<HTMLElement>('[data-becket-dialog-footer] button:not([disabled])');\n const target = marked ?? footerBtn ?? el;\n target.focus();\n };\n\n const frame = requestAnimationFrame(() => {\n requestAnimationFrame(focusInitial);\n });\n return () => cancelAnimationFrame(frame);\n }, [dialogRef, isOpen]);\n\n const labelledBy = titlePresent || slots.title ? titleId : undefined;\n const describedBy = descriptionPresent || slots.description ? descriptionId : undefined;\n\n return {\n isOpen,\n setOpen,\n titleId,\n descriptionId,\n labelledBy,\n describedBy,\n registerTitle,\n registerDescription,\n };\n}\n\nexport function assignRef<T>(ref: Ref<T> | undefined, value: T | null) {\n if (typeof ref === 'function') {\n ref(value);\n return;\n }\n if (ref) {\n (ref as MutableRefObject<T | null>).current = value;\n }\n}\n"],"mappings":"AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAYA,SAAS,gBACd,WACA;AAAA,EACE;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA,eAAe;AAAA,EACf,qBAAqB;AACvB,GACA;AACA,QAAM,eAAe,SAAS;AAC9B,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,WAAW;AAC5D,QAAM,SAAS,eAAe,QAAQ,IAAI,IAAI;AAC9C,QAAM,UAAU,MAAM;AACtB,QAAM,gBAAgB,MAAM;AAC5B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE,OAAO,OAAO,aAAa,MAAM,CAAC;AAEvE,QAAM,gBAAgB,YAAY,CAAC,YAAqB;AACtD,aAAS,CAAC,SAAU,KAAK,UAAU,UAAU,OAAO,EAAE,GAAG,MAAM,OAAO,QAAQ,CAAE;AAAA,EAClF,GAAG,CAAC,CAAC;AAEL,QAAM,sBAAsB,YAAY,CAAC,YAAqB;AAC5D,aAAS,CAAC,SAAU,KAAK,gBAAgB,UAAU,OAAO,EAAE,GAAG,MAAM,aAAa,QAAQ,CAAE;AAAA,EAC9F,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU;AAAA,IACd,CAAC,SAAkB;AACjB,UAAI,CAAC,aAAc,iBAAgB,IAAI;AACvC,qBAAe,IAAI;AAAA,IACrB;AAAA,IACA,CAAC,cAAc,YAAY;AAAA,EAC7B;AAEA,YAAU,MAAM;AACd,UAAM,KAAK,UAAU;AACrB,QAAI,CAAC,GAAI;AAET,QAAI,CAAC,QAAQ;AACX,UAAI,GAAG,KAAM,IAAG,MAAM;AACtB;AAAA,IACF;AAEA,QAAI,CAAC,GAAG,MAAM;AACZ,UAAI,OAAO,GAAG,cAAc,YAAY;AACtC,WAAG,UAAU;AAAA,MACf,OAAO;AACL,WAAG,aAAa,QAAQ,EAAE;AAAA,MAC5B;AAAA,IACF;AAEA,UAAM,eAAe,MAAM;AACzB,UAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,aAAa,MAAM,EAAG;AAC1C,YAAM,SAAS,GAAG,cAA2B,6BAA6B;AAC1E,YAAM,YAAY,GAAG,cAA2B,oDAAoD;AACpG,YAAM,SAAS,UAAU,aAAa;AACtC,aAAO,MAAM;AAAA,IACf;AAEA,UAAM,QAAQ,sBAAsB,MAAM;AACxC,4BAAsB,YAAY;AAAA,IACpC,CAAC;AACD,WAAO,MAAM,qBAAqB,KAAK;AAAA,EACzC,GAAG,CAAC,WAAW,MAAM,CAAC;AAEtB,QAAM,aAAa,gBAAgB,MAAM,QAAQ,UAAU;AAC3D,QAAM,cAAc,sBAAsB,MAAM,cAAc,gBAAgB;AAE9E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,UAAa,KAAyB,OAAiB;AACrE,MAAI,OAAO,QAAQ,YAAY;AAC7B,QAAI,KAAK;AACT;AAAA,EACF;AACA,MAAI,KAAK;AACP,IAAC,IAAmC,UAAU;AAAA,EAChD;AACF;","names":[]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ConditionalValue } from '@becket-ui/tokens/types';
|
|
2
|
+
/**
|
|
3
|
+
* Responsive props — Chakra-aligned, mobile-first (min-width).
|
|
4
|
+
*
|
|
5
|
+
* Prefer **object syntax** with a `base`/`mobile` value (Chakra docs + community
|
|
6
|
+
* best practice). Arrays are supported for parity but are harder to read.
|
|
7
|
+
*
|
|
8
|
+
* @see https://chakra-ui.com/docs/styling/responsive-design
|
|
9
|
+
*/
|
|
10
|
+
/** Token breakpoints (Panda / Chakra keys). */
|
|
11
|
+
export type Breakpoint = 'base' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
12
|
+
/**
|
|
13
|
+
* Semantic aliases for layout in consuming apps.
|
|
14
|
+
* Map onto token breakpoints — do not invent separate media queries.
|
|
15
|
+
*/
|
|
16
|
+
export type SemanticBreakpoint = 'mobile' | 'tablet' | 'desktop' | 'largeDesktop';
|
|
17
|
+
export type BreakpointAlias = Breakpoint | SemanticBreakpoint;
|
|
18
|
+
/** Breakpoint-only keys for responsive object props (token + semantic). */
|
|
19
|
+
export type BreakpointMap<T> = Partial<Record<BreakpointAlias, T>>;
|
|
20
|
+
/**
|
|
21
|
+
* Chakra-style responsive value: single value, per-breakpoint object, or array
|
|
22
|
+
* (index 0 = base, 1 = sm, 2 = md, …).
|
|
23
|
+
*/
|
|
24
|
+
export type ResponsiveValue<T> = ConditionalValue<T> | BreakpointMap<T>;
|
|
25
|
+
export declare const BREAKPOINTS: readonly ["base", "sm", "md", "lg", "xl", "2xl"];
|
|
26
|
+
export declare const SEMANTIC_BREAKPOINTS: readonly ["mobile", "tablet", "desktop", "largeDesktop"];
|
|
27
|
+
/** Semantic → token breakpoint (Chakra mobile-first scale). */
|
|
28
|
+
export declare const SEMANTIC_TO_BREAKPOINT: {
|
|
29
|
+
readonly mobile: 'base';
|
|
30
|
+
readonly tablet: 'md';
|
|
31
|
+
readonly desktop: 'lg';
|
|
32
|
+
readonly largeDesktop: 'xl';
|
|
33
|
+
};
|
|
34
|
+
/** Becket-authored spacing steps (Fibonacci + zero). */
|
|
35
|
+
export declare const SPACING_SCALE: readonly [0, 1, 2, 3, 5, 8, 13, 21, 34];
|
|
36
|
+
export type SpacingStep = (typeof SPACING_SCALE)[number];
|
|
37
|
+
/** Semantic aliases preferred in apps. */
|
|
38
|
+
export type SpacingSemantic = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xs';
|
|
39
|
+
/**
|
|
40
|
+
* Values that show up in IDE autocomplete for `gap` / spacing props.
|
|
41
|
+
* Numeric literals + string keys + semantic tokens.
|
|
42
|
+
* `(string & {})` / `(number & {})` keep IntelliSense while allowing escapes.
|
|
43
|
+
*/
|
|
44
|
+
export type SpacingScale = SpacingSemantic | SpacingStep | `${SpacingStep}` | (string & {}) | (number & {});
|
|
45
|
+
/** Gap prop: single value or responsive ConditionalValue. */
|
|
46
|
+
export type GapValue = ResponsiveValue<SpacingScale>;
|
|
47
|
+
export declare function isSemanticBreakpoint(key: string): key is SemanticBreakpoint;
|
|
48
|
+
export declare function isTokenBreakpoint(key: string): key is Breakpoint;
|
|
49
|
+
/** Resolve `desktop` → `lg`, leave `md` unchanged. */
|
|
50
|
+
export declare function resolveBreakpointAlias(key: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Expand semantic keys in a responsive object to token breakpoints.
|
|
53
|
+
* Token keys already present win (not overwritten by semantic aliases).
|
|
54
|
+
* Non-objects / arrays pass through.
|
|
55
|
+
*/
|
|
56
|
+
export declare function normalizeResponsiveValue<T>(value: T): T;
|
|
57
|
+
/**
|
|
58
|
+
* Coerce gap values so `gap={3}` and `gap="3"` both resolve to token key `"3"`.
|
|
59
|
+
* Walks responsive objects/arrays. Leaves semantic tokens unchanged.
|
|
60
|
+
* Expands semantic breakpoint keys (`mobile` → `base`, etc.).
|
|
61
|
+
*/
|
|
62
|
+
export declare function normalizeGapValue<T>(gap: T): T;
|
|
63
|
+
//# sourceMappingURL=responsive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responsive.d.ts","sourceRoot":"","sources":["../../src/helpers/responsive.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE;;;;;;;GAOG;AAEH,+CAA+C;AAC/C,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,CAAC;AAEpE;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,cAAc,CAAC;AAElF,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,kBAAkB,CAAC;AAE9D,2EAA2E;AAC3E,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,CAAC;AAEnE;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;AAExE,eAAO,MAAM,WAAW,kDAAmF,CAAC;AAE5G,eAAO,MAAM,oBAAoB,0DAKiB,CAAC;AAEnD,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB;qBACzB,MAAM;qBACN,IAAI;sBACH,IAAI;2BACC,IAAI;CACuC,CAAC;AAE5D,wDAAwD;AACxD,eAAO,MAAM,aAAa,YAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAU,CAAC;AAErE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,0CAA0C;AAC1C,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,CAAC;AAEvE;;;;GAIG;AACH,MAAM,MAAM,YAAY,GACpB,eAAe,GACf,WAAW,GACX,GAAG,WAAW,EAAE,GAChB,CAAC,MAAM,GAAG,EAAE,CAAC,GACb,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB,6DAA6D;AAC7D,MAAM,MAAM,QAAQ,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;AAErD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,kBAAkB,CAE3E;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,UAAU,CAEhE;AAED,sDAAsD;AACtD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CA4BvD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAgB9C"}
|