@financedistrict/apps-ui 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 +19 -0
- package/README.md +119 -0
- package/dist/Radio-BYWz1n7p.d.cts +32 -0
- package/dist/Radio-BYWz1n7p.d.ts +32 -0
- package/dist/SelectTrigger-By0iPBI3.d.cts +78 -0
- package/dist/SelectTrigger-By0iPBI3.d.ts +78 -0
- package/dist/accordion.cjs +322 -0
- package/dist/accordion.d.cts +156 -0
- package/dist/accordion.d.ts +156 -0
- package/dist/accordion.js +2 -0
- package/dist/apps-ui.umd.js +1 -0
- package/dist/button.cjs +366 -0
- package/dist/button.d.cts +52 -0
- package/dist/button.d.ts +52 -0
- package/dist/button.js +3 -0
- package/dist/checkbox.cjs +74 -0
- package/dist/checkbox.d.cts +63 -0
- package/dist/checkbox.d.ts +63 -0
- package/dist/checkbox.js +2 -0
- package/dist/chunk-3RU7JMFE.js +27 -0
- package/dist/chunk-5TWRK4NW.js +783 -0
- package/dist/chunk-7EOSDFS3.js +9 -0
- package/dist/chunk-AIQ4UOGK.js +142 -0
- package/dist/chunk-B3MMGTPV.js +200 -0
- package/dist/chunk-DAHQYLUI.js +146 -0
- package/dist/chunk-DGBXADSS.js +1 -0
- package/dist/chunk-E6KPNTWW.js +295 -0
- package/dist/chunk-EOAB6G2D.js +149 -0
- package/dist/chunk-FAOVKDVM.js +32 -0
- package/dist/chunk-FV76Z4Z2.js +78 -0
- package/dist/chunk-GLTLBUZ7.js +285 -0
- package/dist/chunk-GTZFV43T.js +154 -0
- package/dist/chunk-HRGZNL4A.js +88 -0
- package/dist/chunk-IEVN3J2F.js +98 -0
- package/dist/chunk-IRS3T6LF.js +352 -0
- package/dist/chunk-IYYHCQXG.js +146 -0
- package/dist/chunk-JIXW3455.js +89 -0
- package/dist/chunk-N4VAP2GB.js +77 -0
- package/dist/chunk-SJ2ZRYVV.js +167 -0
- package/dist/chunk-SNU4YXOR.js +66 -0
- package/dist/chunk-TT6JQATV.js +99 -0
- package/dist/chunk-UPQ3ZPCO.js +66 -0
- package/dist/chunk-UVUSVFOH.js +264 -0
- package/dist/chunk-WJFPGGJB.js +255 -0
- package/dist/chunk-WXEBQA7A.js +371 -0
- package/dist/chunk-Y2O4YYZQ.js +184 -0
- package/dist/cut-corner.cjs +111 -0
- package/dist/cut-corner.d.cts +49 -0
- package/dist/cut-corner.d.ts +49 -0
- package/dist/cut-corner.js +1 -0
- package/dist/dialog.cjs +441 -0
- package/dist/dialog.d.cts +240 -0
- package/dist/dialog.d.ts +240 -0
- package/dist/dialog.js +3 -0
- package/dist/feature-card.cjs +182 -0
- package/dist/feature-card.d.cts +54 -0
- package/dist/feature-card.d.ts +54 -0
- package/dist/feature-card.js +2 -0
- package/dist/filter.cjs +873 -0
- package/dist/filter.d.cts +129 -0
- package/dist/filter.d.ts +129 -0
- package/dist/filter.js +6 -0
- package/dist/index.cjs +4583 -0
- package/dist/index.d.cts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +27 -0
- package/dist/inline-select.cjs +856 -0
- package/dist/inline-select.d.cts +92 -0
- package/dist/inline-select.d.ts +92 -0
- package/dist/inline-select.js +6 -0
- package/dist/input.cjs +263 -0
- package/dist/input.d.cts +157 -0
- package/dist/input.d.ts +157 -0
- package/dist/input.js +4 -0
- package/dist/panel.cjs +185 -0
- package/dist/panel.d.cts +103 -0
- package/dist/panel.d.ts +103 -0
- package/dist/panel.js +2 -0
- package/dist/property-grid.cjs +110 -0
- package/dist/property-grid.d.cts +56 -0
- package/dist/property-grid.d.ts +56 -0
- package/dist/property-grid.js +2 -0
- package/dist/radio.cjs +74 -0
- package/dist/radio.d.cts +39 -0
- package/dist/radio.d.ts +39 -0
- package/dist/radio.js +2 -0
- package/dist/scrollbar.cjs +34 -0
- package/dist/scrollbar.d.cts +35 -0
- package/dist/scrollbar.d.ts +35 -0
- package/dist/scrollbar.js +1 -0
- package/dist/search-bar.cjs +408 -0
- package/dist/search-bar.d.cts +42 -0
- package/dist/search-bar.d.ts +42 -0
- package/dist/search-bar.js +4 -0
- package/dist/select.cjs +998 -0
- package/dist/select.d.cts +357 -0
- package/dist/select.d.ts +357 -0
- package/dist/select.js +5 -0
- package/dist/selectable-feature-card.cjs +159 -0
- package/dist/selectable-feature-card.d.cts +74 -0
- package/dist/selectable-feature-card.d.ts +74 -0
- package/dist/selectable-feature-card.js +3 -0
- package/dist/sidebar-item.cjs +175 -0
- package/dist/sidebar-item.d.cts +75 -0
- package/dist/sidebar-item.d.ts +75 -0
- package/dist/sidebar-item.js +2 -0
- package/dist/switch.cjs +86 -0
- package/dist/switch.d.cts +72 -0
- package/dist/switch.d.ts +72 -0
- package/dist/switch.js +2 -0
- package/dist/table-cell.cjs +196 -0
- package/dist/table-cell.d.cts +76 -0
- package/dist/table-cell.d.ts +76 -0
- package/dist/table-cell.js +2 -0
- package/dist/table-title.cjs +34 -0
- package/dist/table-title.d.cts +16 -0
- package/dist/table-title.d.ts +16 -0
- package/dist/table-title.js +2 -0
- package/dist/tabs.cjs +299 -0
- package/dist/tabs.d.cts +116 -0
- package/dist/tabs.d.ts +116 -0
- package/dist/tabs.js +2 -0
- package/dist/toast.cjs +87 -0
- package/dist/toast.d.cts +48 -0
- package/dist/toast.d.ts +48 -0
- package/dist/toast.js +2 -0
- package/dist/token-input.cjs +415 -0
- package/dist/token-input.d.cts +90 -0
- package/dist/token-input.d.ts +90 -0
- package/dist/token-input.js +3 -0
- package/dist/tooltip.cjs +384 -0
- package/dist/tooltip.d.cts +142 -0
- package/dist/tooltip.d.ts +142 -0
- package/dist/tooltip.js +2 -0
- package/package.json +200 -0
- package/src/fonts.css +33 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,4583 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React21 = require('react');
|
|
4
|
+
var react = require('@phosphor-icons/react');
|
|
5
|
+
var clsx = require('clsx');
|
|
6
|
+
var tailwindMerge = require('tailwind-merge');
|
|
7
|
+
var classVarianceAuthority = require('class-variance-authority');
|
|
8
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
var reactDom = require('react-dom');
|
|
10
|
+
|
|
11
|
+
function _interopNamespace(e) {
|
|
12
|
+
if (e && e.__esModule) return e;
|
|
13
|
+
var n = Object.create(null);
|
|
14
|
+
if (e) {
|
|
15
|
+
Object.keys(e).forEach(function (k) {
|
|
16
|
+
if (k !== 'default') {
|
|
17
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
18
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () { return e[k]; }
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
n.default = e;
|
|
26
|
+
return Object.freeze(n);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var React21__namespace = /*#__PURE__*/_interopNamespace(React21);
|
|
30
|
+
|
|
31
|
+
// src/accordion/Accordion.tsx
|
|
32
|
+
function cn(...inputs) {
|
|
33
|
+
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
34
|
+
}
|
|
35
|
+
var accordionRootVariants = classVarianceAuthority.cva(["flex flex-col"]);
|
|
36
|
+
var accordionItemVariants = classVarianceAuthority.cva(["border-b border-card-border"], {
|
|
37
|
+
variants: {
|
|
38
|
+
disabled: {
|
|
39
|
+
true: "opacity-60",
|
|
40
|
+
false: ""
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
defaultVariants: {
|
|
44
|
+
disabled: false
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
var accordionTriggerVariants = classVarianceAuthority.cva(
|
|
48
|
+
[
|
|
49
|
+
"group flex w-full cursor-pointer items-start gap-4 pt-3 text-left",
|
|
50
|
+
"outline-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus",
|
|
51
|
+
"disabled:cursor-not-allowed"
|
|
52
|
+
],
|
|
53
|
+
{
|
|
54
|
+
variants: {
|
|
55
|
+
open: {
|
|
56
|
+
true: "pb-2",
|
|
57
|
+
false: "pb-3"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
defaultVariants: {
|
|
61
|
+
open: false
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
var accordionTriggerTitleVariants = classVarianceAuthority.cva([
|
|
66
|
+
"body-02-medium min-w-0 flex-1 break-words text-card-foreground"
|
|
67
|
+
]);
|
|
68
|
+
var accordionTriggerIconVariants = classVarianceAuthority.cva([
|
|
69
|
+
"flex shrink-0 items-center text-card-foreground-muted transition-colors",
|
|
70
|
+
"group-hover:text-card-foreground",
|
|
71
|
+
"[&_svg]:size-4 [&_svg]:shrink-0"
|
|
72
|
+
]);
|
|
73
|
+
var accordionContentVariants = classVarianceAuthority.cva([
|
|
74
|
+
"body-03 pb-3 text-card-foreground-muted"
|
|
75
|
+
]);
|
|
76
|
+
var AccordionContext = React21__namespace.createContext(
|
|
77
|
+
null
|
|
78
|
+
);
|
|
79
|
+
function useAccordionContext(part) {
|
|
80
|
+
const context = React21__namespace.useContext(AccordionContext);
|
|
81
|
+
if (context === null) {
|
|
82
|
+
throw new Error(`${part} must be used within <Accordion.Root>`);
|
|
83
|
+
}
|
|
84
|
+
return context;
|
|
85
|
+
}
|
|
86
|
+
var AccordionItemContext = React21__namespace.createContext(null);
|
|
87
|
+
function useAccordionItemContext(part) {
|
|
88
|
+
const context = React21__namespace.useContext(AccordionItemContext);
|
|
89
|
+
if (context === null) {
|
|
90
|
+
throw new Error(`${part} must be used within <Accordion.Item>`);
|
|
91
|
+
}
|
|
92
|
+
return context;
|
|
93
|
+
}
|
|
94
|
+
function setRef(ref, node) {
|
|
95
|
+
if (typeof ref === "function") ref(node);
|
|
96
|
+
else if (ref) ref.current = node;
|
|
97
|
+
}
|
|
98
|
+
function AccordionRoot({
|
|
99
|
+
type = "single",
|
|
100
|
+
value: valueProp,
|
|
101
|
+
defaultValue,
|
|
102
|
+
onValueChange,
|
|
103
|
+
collapsible = true,
|
|
104
|
+
className,
|
|
105
|
+
children,
|
|
106
|
+
ref,
|
|
107
|
+
...props
|
|
108
|
+
}) {
|
|
109
|
+
const isMultiple = type === "multiple";
|
|
110
|
+
const isControlled = valueProp !== void 0;
|
|
111
|
+
const [uncontrolledValue, setUncontrolledValue] = React21__namespace.useState(
|
|
112
|
+
() => defaultValue !== void 0 ? defaultValue : isMultiple ? [] : null
|
|
113
|
+
);
|
|
114
|
+
const openModel = isControlled ? valueProp : uncontrolledValue;
|
|
115
|
+
const commit = (next) => {
|
|
116
|
+
if (!isControlled) setUncontrolledValue(next);
|
|
117
|
+
onValueChange?.(next);
|
|
118
|
+
};
|
|
119
|
+
const isOpen = (itemValue) => isMultiple ? Array.isArray(openModel) && openModel.includes(itemValue) : openModel === itemValue;
|
|
120
|
+
const toggle = (itemValue) => {
|
|
121
|
+
if (isMultiple) {
|
|
122
|
+
const current2 = Array.isArray(openModel) ? openModel : [];
|
|
123
|
+
commit(
|
|
124
|
+
current2.includes(itemValue) ? current2.filter((v) => v !== itemValue) : [...current2, itemValue]
|
|
125
|
+
);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const current = Array.isArray(openModel) ? null : openModel;
|
|
129
|
+
if (current === itemValue) {
|
|
130
|
+
commit(collapsible ? null : itemValue);
|
|
131
|
+
} else {
|
|
132
|
+
commit(itemValue);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const triggersRef = React21__namespace.useRef(/* @__PURE__ */ new Set());
|
|
136
|
+
const registerTrigger = React21__namespace.useCallback((node) => {
|
|
137
|
+
triggersRef.current.add(node);
|
|
138
|
+
}, []);
|
|
139
|
+
const unregisterTrigger = React21__namespace.useCallback((node) => {
|
|
140
|
+
triggersRef.current.delete(node);
|
|
141
|
+
}, []);
|
|
142
|
+
const onTriggerKeyDown = (event) => {
|
|
143
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
|
|
144
|
+
const triggers = Array.from(triggersRef.current).filter((el) => !el.disabled).sort(
|
|
145
|
+
(a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1
|
|
146
|
+
);
|
|
147
|
+
if (triggers.length === 0) return;
|
|
148
|
+
const currentIndex = triggers.indexOf(event.currentTarget);
|
|
149
|
+
if (currentIndex === -1) return;
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
let nextIndex = currentIndex;
|
|
152
|
+
switch (event.key) {
|
|
153
|
+
case "ArrowDown":
|
|
154
|
+
nextIndex = (currentIndex + 1) % triggers.length;
|
|
155
|
+
break;
|
|
156
|
+
case "ArrowUp":
|
|
157
|
+
nextIndex = (currentIndex - 1 + triggers.length) % triggers.length;
|
|
158
|
+
break;
|
|
159
|
+
case "Home":
|
|
160
|
+
nextIndex = 0;
|
|
161
|
+
break;
|
|
162
|
+
case "End":
|
|
163
|
+
nextIndex = triggers.length - 1;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
triggers[nextIndex]?.focus();
|
|
167
|
+
};
|
|
168
|
+
const contextValue = {
|
|
169
|
+
type,
|
|
170
|
+
isOpen,
|
|
171
|
+
toggle,
|
|
172
|
+
registerTrigger,
|
|
173
|
+
unregisterTrigger,
|
|
174
|
+
onTriggerKeyDown
|
|
175
|
+
};
|
|
176
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AccordionContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
177
|
+
"div",
|
|
178
|
+
{
|
|
179
|
+
ref,
|
|
180
|
+
"data-slot": "accordion",
|
|
181
|
+
className: cn(accordionRootVariants(), className),
|
|
182
|
+
...props,
|
|
183
|
+
children
|
|
184
|
+
}
|
|
185
|
+
) });
|
|
186
|
+
}
|
|
187
|
+
function AccordionItem({
|
|
188
|
+
value,
|
|
189
|
+
disabled = false,
|
|
190
|
+
className,
|
|
191
|
+
children,
|
|
192
|
+
ref,
|
|
193
|
+
...props
|
|
194
|
+
}) {
|
|
195
|
+
const { isOpen } = useAccordionContext("Accordion.Item");
|
|
196
|
+
const open = isOpen(value);
|
|
197
|
+
const reactId = React21__namespace.useId();
|
|
198
|
+
const triggerId = `${reactId}-trigger`;
|
|
199
|
+
const contentId = `${reactId}-content`;
|
|
200
|
+
const itemContext = {
|
|
201
|
+
value,
|
|
202
|
+
open,
|
|
203
|
+
disabled,
|
|
204
|
+
triggerId,
|
|
205
|
+
contentId
|
|
206
|
+
};
|
|
207
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AccordionItemContext.Provider, { value: itemContext, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
208
|
+
"div",
|
|
209
|
+
{
|
|
210
|
+
ref,
|
|
211
|
+
"data-slot": "accordion-item",
|
|
212
|
+
"data-state": open ? "open" : "closed",
|
|
213
|
+
"data-disabled": disabled || void 0,
|
|
214
|
+
className: cn(accordionItemVariants({ disabled }), className),
|
|
215
|
+
...props,
|
|
216
|
+
children
|
|
217
|
+
}
|
|
218
|
+
) });
|
|
219
|
+
}
|
|
220
|
+
function AccordionTrigger({
|
|
221
|
+
className,
|
|
222
|
+
children,
|
|
223
|
+
onClick,
|
|
224
|
+
onKeyDown,
|
|
225
|
+
ref,
|
|
226
|
+
...props
|
|
227
|
+
}) {
|
|
228
|
+
const { toggle, registerTrigger, unregisterTrigger, onTriggerKeyDown } = useAccordionContext("Accordion.Trigger");
|
|
229
|
+
const { value, open, disabled, triggerId, contentId } = useAccordionItemContext("Accordion.Trigger");
|
|
230
|
+
const buttonRef = React21__namespace.useRef(null);
|
|
231
|
+
const setButtonRef = (node) => {
|
|
232
|
+
buttonRef.current = node;
|
|
233
|
+
setRef(ref, node);
|
|
234
|
+
};
|
|
235
|
+
React21__namespace.useEffect(() => {
|
|
236
|
+
const node = buttonRef.current;
|
|
237
|
+
if (!node) return;
|
|
238
|
+
registerTrigger(node);
|
|
239
|
+
return () => unregisterTrigger(node);
|
|
240
|
+
}, [registerTrigger, unregisterTrigger]);
|
|
241
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
242
|
+
"button",
|
|
243
|
+
{
|
|
244
|
+
...props,
|
|
245
|
+
ref: setButtonRef,
|
|
246
|
+
type: "button",
|
|
247
|
+
id: triggerId,
|
|
248
|
+
"data-slot": "accordion-trigger",
|
|
249
|
+
"aria-expanded": open,
|
|
250
|
+
"aria-controls": open ? contentId : void 0,
|
|
251
|
+
disabled,
|
|
252
|
+
className: cn(accordionTriggerVariants({ open }), className),
|
|
253
|
+
onClick: (event) => {
|
|
254
|
+
onClick?.(event);
|
|
255
|
+
if (!event.defaultPrevented) toggle(value);
|
|
256
|
+
},
|
|
257
|
+
onKeyDown: (event) => {
|
|
258
|
+
onKeyDown?.(event);
|
|
259
|
+
if (!event.defaultPrevented) onTriggerKeyDown(event);
|
|
260
|
+
},
|
|
261
|
+
children: [
|
|
262
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
263
|
+
"span",
|
|
264
|
+
{
|
|
265
|
+
"data-slot": "accordion-trigger-title",
|
|
266
|
+
className: accordionTriggerTitleVariants(),
|
|
267
|
+
children
|
|
268
|
+
}
|
|
269
|
+
),
|
|
270
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
271
|
+
"span",
|
|
272
|
+
{
|
|
273
|
+
"data-slot": "accordion-trigger-icon",
|
|
274
|
+
"aria-hidden": "true",
|
|
275
|
+
className: accordionTriggerIconVariants(),
|
|
276
|
+
children: open ? /* @__PURE__ */ jsxRuntime.jsx(react.MinusIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(react.PlusIcon, {})
|
|
277
|
+
}
|
|
278
|
+
)
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
function AccordionContent({
|
|
284
|
+
className,
|
|
285
|
+
children,
|
|
286
|
+
ref,
|
|
287
|
+
...props
|
|
288
|
+
}) {
|
|
289
|
+
const { open, triggerId, contentId } = useAccordionItemContext("Accordion.Content");
|
|
290
|
+
if (!open) return null;
|
|
291
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
292
|
+
"div",
|
|
293
|
+
{
|
|
294
|
+
ref,
|
|
295
|
+
role: "region",
|
|
296
|
+
id: contentId,
|
|
297
|
+
"aria-labelledby": triggerId,
|
|
298
|
+
"data-slot": "accordion-content",
|
|
299
|
+
"data-state": "open",
|
|
300
|
+
className: cn(accordionContentVariants(), className),
|
|
301
|
+
...props,
|
|
302
|
+
children
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
var Accordion = {
|
|
307
|
+
Root: AccordionRoot,
|
|
308
|
+
Item: AccordionItem,
|
|
309
|
+
Trigger: AccordionTrigger,
|
|
310
|
+
Content: AccordionContent
|
|
311
|
+
};
|
|
312
|
+
function buildCutPath({
|
|
313
|
+
W,
|
|
314
|
+
H,
|
|
315
|
+
cut,
|
|
316
|
+
rTR,
|
|
317
|
+
rBL,
|
|
318
|
+
rCut,
|
|
319
|
+
ox = 0,
|
|
320
|
+
oy = 0
|
|
321
|
+
}) {
|
|
322
|
+
const rCutSq = rCut / Math.SQRT2;
|
|
323
|
+
const x = (v) => v + ox;
|
|
324
|
+
const y = (v) => v + oy;
|
|
325
|
+
return [
|
|
326
|
+
`M ${x(cut + rCut)} ${y(0)}`,
|
|
327
|
+
`L ${x(W - rTR)} ${y(0)}`,
|
|
328
|
+
`Q ${x(W)} ${y(0)} ${x(W)} ${y(rTR)}`,
|
|
329
|
+
`L ${x(W)} ${y(H - cut - rCut)}`,
|
|
330
|
+
`Q ${x(W)} ${y(H - cut)} ${x(W - rCutSq)} ${y(H - cut + rCutSq)}`,
|
|
331
|
+
`L ${x(W - cut + rCutSq)} ${y(H - rCutSq)}`,
|
|
332
|
+
`Q ${x(W - cut)} ${y(H)} ${x(W - cut - rCut)} ${y(H)}`,
|
|
333
|
+
`L ${x(rBL)} ${y(H)}`,
|
|
334
|
+
`Q ${x(0)} ${y(H)} ${x(0)} ${y(H - rBL)}`,
|
|
335
|
+
`L ${x(0)} ${y(cut + rCut)}`,
|
|
336
|
+
`Q ${x(0)} ${y(cut)} ${x(rCutSq)} ${y(cut - rCutSq)}`,
|
|
337
|
+
`L ${x(cut - rCutSq)} ${y(rCutSq)}`,
|
|
338
|
+
`Q ${x(cut)} ${y(0)} ${x(cut + rCut)} ${y(0)}`,
|
|
339
|
+
`Z`
|
|
340
|
+
].join(" ");
|
|
341
|
+
}
|
|
342
|
+
function useCutCornerClipPath(cut, {
|
|
343
|
+
radius = 2,
|
|
344
|
+
radiusCuts,
|
|
345
|
+
radiusTopRight,
|
|
346
|
+
radiusBottomLeft,
|
|
347
|
+
outset
|
|
348
|
+
} = {}) {
|
|
349
|
+
const ref = React21__namespace.useRef(null);
|
|
350
|
+
const [clipPath, setClipPath] = React21__namespace.useState();
|
|
351
|
+
const [pathD, setPathD] = React21__namespace.useState();
|
|
352
|
+
const [outsetPathD, setOutsetPathD] = React21__namespace.useState();
|
|
353
|
+
React21__namespace.useLayoutEffect(() => {
|
|
354
|
+
const el = ref.current;
|
|
355
|
+
if (!el) return;
|
|
356
|
+
const rTR = radiusTopRight ?? radius;
|
|
357
|
+
const rBL = radiusBottomLeft ?? radius;
|
|
358
|
+
const rCut = radiusCuts ?? radius;
|
|
359
|
+
function compute(W, H) {
|
|
360
|
+
const d = buildCutPath({ W, H, cut, rTR, rBL, rCut });
|
|
361
|
+
setClipPath(`path('${d}')`);
|
|
362
|
+
setPathD(d);
|
|
363
|
+
if (outset != null) {
|
|
364
|
+
const g = outset;
|
|
365
|
+
setOutsetPathD(
|
|
366
|
+
buildCutPath({
|
|
367
|
+
W: W + 2 * g,
|
|
368
|
+
H: H + 2 * g,
|
|
369
|
+
cut: cut + (2 - Math.SQRT2) * g,
|
|
370
|
+
rTR: rTR + g,
|
|
371
|
+
rBL: rBL + g,
|
|
372
|
+
rCut: rCut + g,
|
|
373
|
+
ox: -g,
|
|
374
|
+
oy: -g
|
|
375
|
+
})
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const initial = el.getBoundingClientRect();
|
|
380
|
+
compute(initial.width, initial.height);
|
|
381
|
+
const ro = new ResizeObserver((entries) => {
|
|
382
|
+
const box = entries[entries.length - 1]?.borderBoxSize?.[0];
|
|
383
|
+
if (box) {
|
|
384
|
+
compute(box.inlineSize, box.blockSize);
|
|
385
|
+
} else {
|
|
386
|
+
const rect = el.getBoundingClientRect();
|
|
387
|
+
compute(rect.width, rect.height);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
ro.observe(el);
|
|
391
|
+
return () => ro.disconnect();
|
|
392
|
+
}, [cut, radius, radiusCuts, radiusTopRight, radiusBottomLeft, outset]);
|
|
393
|
+
return { ref, clipPath, pathD, outsetPathD };
|
|
394
|
+
}
|
|
395
|
+
var BUTTON_CUT = { cut: 8, radius: 2, radiusCuts: 0 };
|
|
396
|
+
var buttonVariants = classVarianceAuthority.cva(
|
|
397
|
+
[
|
|
398
|
+
// Layout
|
|
399
|
+
"inline-flex shrink-0 items-center justify-center gap-1",
|
|
400
|
+
// Type — the Figma `button-01` text style (Archivo Medium 14/20)
|
|
401
|
+
"button-01",
|
|
402
|
+
// Shape — cut corners are applied as a clip-path in <Button>; keep the box square
|
|
403
|
+
"rounded-none",
|
|
404
|
+
// Transition
|
|
405
|
+
"transition-colors cursor-pointer",
|
|
406
|
+
// Suppress the native ring. The Figma focus ring is drawn OUTSIDE the
|
|
407
|
+
// clip-path (which would swallow an outline here), so cut-corner variants
|
|
408
|
+
// render it on the wrapper in <Button>; ghost renders it directly (below).
|
|
409
|
+
"outline-none",
|
|
410
|
+
// Disabled
|
|
411
|
+
"disabled:pointer-events-none disabled:cursor-not-allowed",
|
|
412
|
+
// Icons
|
|
413
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0"
|
|
414
|
+
],
|
|
415
|
+
{
|
|
416
|
+
variants: {
|
|
417
|
+
variation: {
|
|
418
|
+
primary: [
|
|
419
|
+
"bg-button-primary-background text-button-primary-foreground",
|
|
420
|
+
"hover:bg-button-primary-accent active:bg-button-primary-accent",
|
|
421
|
+
"disabled:bg-button-primary-muted disabled:text-button-primary-foreground-muted"
|
|
422
|
+
],
|
|
423
|
+
brand: [
|
|
424
|
+
"bg-button-brand-primary-background text-button-brand-primary-foreground",
|
|
425
|
+
"hover:bg-button-brand-primary-accent active:bg-button-brand-primary-accent",
|
|
426
|
+
"disabled:bg-button-primary-muted disabled:text-button-primary-foreground-muted"
|
|
427
|
+
],
|
|
428
|
+
destructive: [
|
|
429
|
+
"bg-button-destructive-background text-button-destructive-primary-foreground",
|
|
430
|
+
"hover:bg-button-destructive-accent active:bg-button-destructive-accent",
|
|
431
|
+
"disabled:bg-button-primary-muted disabled:text-button-primary-foreground-muted"
|
|
432
|
+
],
|
|
433
|
+
// Outline — background fill + an SVG stroke border drawn in <Button>.
|
|
434
|
+
secondary: [
|
|
435
|
+
"bg-button-secondary-background text-button-secondary-foreground",
|
|
436
|
+
"hover:text-button-secondary-foreground-accent active:text-button-secondary-foreground-accent",
|
|
437
|
+
"disabled:text-button-secondary-foreground-muted"
|
|
438
|
+
],
|
|
439
|
+
// No background, no cut corner — so no wrapper; ring on the element.
|
|
440
|
+
// `outline-solid` restores the style that base `outline-none` zeroed.
|
|
441
|
+
ghost: [
|
|
442
|
+
"bg-transparent text-button-secondary-foreground",
|
|
443
|
+
"hover:text-button-secondary-foreground-accent active:text-button-secondary-foreground-accent",
|
|
444
|
+
"disabled:text-button-secondary-foreground-muted",
|
|
445
|
+
// `rounded-xs` (2px) keeps the ghost focus ring as sharp as the other
|
|
446
|
+
// variants' corners (which now use sharp chamfers + 2px square corners).
|
|
447
|
+
"rounded-xs focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus"
|
|
448
|
+
]
|
|
449
|
+
},
|
|
450
|
+
size: {
|
|
451
|
+
sm: "h-8 px-2.5 [&_svg:not([class*='size-'])]:size-4",
|
|
452
|
+
md: "h-10 px-2.5 [&_svg:not([class*='size-'])]:size-4",
|
|
453
|
+
lg: "h-12 px-3 [&_svg:not([class*='size-'])]:size-4"
|
|
454
|
+
},
|
|
455
|
+
iconOnly: {
|
|
456
|
+
true: "",
|
|
457
|
+
false: ""
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
compoundVariants: [
|
|
461
|
+
// Icon-only: square footprint, no horizontal padding.
|
|
462
|
+
{ iconOnly: true, size: "sm", className: "w-8 px-0" },
|
|
463
|
+
{ iconOnly: true, size: "md", className: "w-10 px-0" },
|
|
464
|
+
{ iconOnly: true, size: "lg", className: "w-12 px-0" },
|
|
465
|
+
// Ghost has no chamfer/background, so it sits flush with no inset padding.
|
|
466
|
+
{ variation: "ghost", iconOnly: false, className: "px-0" }
|
|
467
|
+
],
|
|
468
|
+
defaultVariants: {
|
|
469
|
+
variation: "primary",
|
|
470
|
+
size: "md",
|
|
471
|
+
iconOnly: false
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
);
|
|
475
|
+
var FOCUS_RING_GAP = 3;
|
|
476
|
+
var FOCUS_RING_WIDTH = 2;
|
|
477
|
+
function setRef2(ref, node) {
|
|
478
|
+
if (typeof ref === "function") ref(node);
|
|
479
|
+
else if (ref) ref.current = node;
|
|
480
|
+
}
|
|
481
|
+
function Button({
|
|
482
|
+
className,
|
|
483
|
+
variation = "primary",
|
|
484
|
+
size = "md",
|
|
485
|
+
iconOnly = false,
|
|
486
|
+
asChild = false,
|
|
487
|
+
loading = false,
|
|
488
|
+
leftSlot,
|
|
489
|
+
rightSlot,
|
|
490
|
+
wrapperClassName,
|
|
491
|
+
disabled,
|
|
492
|
+
style,
|
|
493
|
+
children,
|
|
494
|
+
onClick,
|
|
495
|
+
ref,
|
|
496
|
+
...props
|
|
497
|
+
}) {
|
|
498
|
+
const {
|
|
499
|
+
ref: cutRef,
|
|
500
|
+
clipPath,
|
|
501
|
+
pathD,
|
|
502
|
+
outsetPathD
|
|
503
|
+
} = useCutCornerClipPath(BUTTON_CUT.cut, {
|
|
504
|
+
radius: BUTTON_CUT.radius,
|
|
505
|
+
radiusCuts: BUTTON_CUT.radiusCuts,
|
|
506
|
+
outset: FOCUS_RING_GAP
|
|
507
|
+
});
|
|
508
|
+
const setRootRef = React21__namespace.useCallback(
|
|
509
|
+
(node) => {
|
|
510
|
+
cutRef.current = node;
|
|
511
|
+
setRef2(ref, node);
|
|
512
|
+
},
|
|
513
|
+
[cutRef, ref]
|
|
514
|
+
);
|
|
515
|
+
const isDisabled = disabled;
|
|
516
|
+
const hasCutCorner = variation !== "ghost";
|
|
517
|
+
const hasSvgBorder = variation === "secondary";
|
|
518
|
+
const handleClick = loading ? (event) => {
|
|
519
|
+
event.preventDefault();
|
|
520
|
+
event.stopPropagation();
|
|
521
|
+
} : onClick;
|
|
522
|
+
const rootClassName = cn(
|
|
523
|
+
buttonVariants({ variation, size, iconOnly }),
|
|
524
|
+
hasSvgBorder && "border-0",
|
|
525
|
+
loading && "pointer-events-none",
|
|
526
|
+
className
|
|
527
|
+
);
|
|
528
|
+
const rootStyle = hasCutCorner ? { ...style, clipPath } : style;
|
|
529
|
+
const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
530
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx(react.CircleNotchIcon, { className: "animate-spin", "aria-hidden": "true" }) : leftSlot,
|
|
531
|
+
!iconOnly && children,
|
|
532
|
+
iconOnly && !loading && children,
|
|
533
|
+
!iconOnly && rightSlot
|
|
534
|
+
] });
|
|
535
|
+
let rootEl;
|
|
536
|
+
if (asChild) {
|
|
537
|
+
if (!React21__namespace.isValidElement(children)) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
"Button: `asChild` requires a single React element child"
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
const child = children;
|
|
543
|
+
const childProps = child.props;
|
|
544
|
+
const childOnClick = childProps.onClick;
|
|
545
|
+
rootEl = React21__namespace.cloneElement(
|
|
546
|
+
child,
|
|
547
|
+
{
|
|
548
|
+
...props,
|
|
549
|
+
...childProps,
|
|
550
|
+
"data-slot": "button",
|
|
551
|
+
"data-loading": loading || void 0,
|
|
552
|
+
"aria-busy": loading || void 0,
|
|
553
|
+
"aria-disabled": loading || void 0,
|
|
554
|
+
// Block activation while loading; otherwise run both handlers.
|
|
555
|
+
onClick: loading ? (event) => {
|
|
556
|
+
event.preventDefault();
|
|
557
|
+
event.stopPropagation();
|
|
558
|
+
} : (event) => {
|
|
559
|
+
onClick?.(event);
|
|
560
|
+
childOnClick?.(event);
|
|
561
|
+
},
|
|
562
|
+
ref: (node) => {
|
|
563
|
+
setRootRef(node);
|
|
564
|
+
setRef2(childProps.ref, node);
|
|
565
|
+
},
|
|
566
|
+
className: cn(
|
|
567
|
+
rootClassName,
|
|
568
|
+
childProps.className
|
|
569
|
+
),
|
|
570
|
+
style: {
|
|
571
|
+
...rootStyle,
|
|
572
|
+
...childProps.style
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx(react.CircleNotchIcon, { className: "animate-spin", "aria-hidden": "true" }) : null,
|
|
576
|
+
childProps.children
|
|
577
|
+
);
|
|
578
|
+
} else {
|
|
579
|
+
rootEl = /* @__PURE__ */ jsxRuntime.jsx(
|
|
580
|
+
"button",
|
|
581
|
+
{
|
|
582
|
+
"data-slot": "button",
|
|
583
|
+
"data-loading": loading || void 0,
|
|
584
|
+
"aria-busy": loading || void 0,
|
|
585
|
+
"aria-disabled": loading || void 0,
|
|
586
|
+
className: rootClassName,
|
|
587
|
+
style: rootStyle,
|
|
588
|
+
disabled: isDisabled,
|
|
589
|
+
...props,
|
|
590
|
+
onClick: handleClick,
|
|
591
|
+
ref: setRootRef,
|
|
592
|
+
children: content
|
|
593
|
+
}
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
if (!hasCutCorner) return rootEl;
|
|
597
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("group relative inline-block", wrapperClassName), children: [
|
|
598
|
+
rootEl,
|
|
599
|
+
outsetPathD && /* @__PURE__ */ jsxRuntime.jsx(
|
|
600
|
+
"svg",
|
|
601
|
+
{
|
|
602
|
+
className: "pointer-events-none absolute inset-0 h-full w-full overflow-visible opacity-0 group-has-[:focus-visible]:opacity-100",
|
|
603
|
+
"aria-hidden": "true",
|
|
604
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
605
|
+
"path",
|
|
606
|
+
{
|
|
607
|
+
d: outsetPathD,
|
|
608
|
+
fill: "none",
|
|
609
|
+
strokeWidth: FOCUS_RING_WIDTH,
|
|
610
|
+
className: "stroke-focus"
|
|
611
|
+
}
|
|
612
|
+
)
|
|
613
|
+
}
|
|
614
|
+
),
|
|
615
|
+
hasSvgBorder && pathD && /* @__PURE__ */ jsxRuntime.jsx(
|
|
616
|
+
"svg",
|
|
617
|
+
{
|
|
618
|
+
className: "pointer-events-none absolute inset-0 h-full w-full overflow-visible",
|
|
619
|
+
"aria-hidden": "true",
|
|
620
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
621
|
+
"path",
|
|
622
|
+
{
|
|
623
|
+
d: pathD,
|
|
624
|
+
fill: "none",
|
|
625
|
+
strokeWidth: 1,
|
|
626
|
+
className: cn(
|
|
627
|
+
// Disabled locks the muted stroke; the hover/active accent
|
|
628
|
+
// classes must not be present at all (a `group-hover:` variant
|
|
629
|
+
// out-specifies the plain muted class, so it would still
|
|
630
|
+
// recolor the border on hover of a disabled button).
|
|
631
|
+
isDisabled ? "stroke-button-secondary-border-muted" : [
|
|
632
|
+
"stroke-button-secondary-border",
|
|
633
|
+
"group-hover:stroke-button-secondary-border-accent group-active:stroke-button-secondary-border-accent"
|
|
634
|
+
]
|
|
635
|
+
)
|
|
636
|
+
}
|
|
637
|
+
)
|
|
638
|
+
}
|
|
639
|
+
)
|
|
640
|
+
] });
|
|
641
|
+
}
|
|
642
|
+
var checkboxVariants = classVarianceAuthority.cva([
|
|
643
|
+
// Box — 20×20 (`size-5`), 2px radius (Figma `radius/xs`), 1px border.
|
|
644
|
+
// `peer` lets the sibling indicator react to :checked / :disabled.
|
|
645
|
+
"peer size-5 shrink-0 appearance-none rounded-xs border",
|
|
646
|
+
"cursor-pointer transition-colors",
|
|
647
|
+
// Default
|
|
648
|
+
"bg-background border-border",
|
|
649
|
+
// Hover (unselected) — focus keeps the DEFAULT border; only the ring signals
|
|
650
|
+
// focus (so focus doesn't visually read as hover).
|
|
651
|
+
"hover:border-border-accent",
|
|
652
|
+
// Focus ring — suppress the native ring, draw the shared `--focus` ring the
|
|
653
|
+
// same way ghost Button does: 2px stroke, 2px offset (`outline-solid`
|
|
654
|
+
// restores what `outline-none` zeroed).
|
|
655
|
+
"outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus",
|
|
656
|
+
// Selected
|
|
657
|
+
"checked:bg-brand-primary-background checked:border-brand-primary-border",
|
|
658
|
+
"checked:hover:bg-brand-primary-accent checked:hover:border-brand-primary-border",
|
|
659
|
+
"checked:focus-visible:bg-brand-primary-accent checked:focus-visible:border-brand-primary-border",
|
|
660
|
+
// Disabled — shared neutral-muted look whether selected or not (per Figma)
|
|
661
|
+
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:border-border-muted",
|
|
662
|
+
"checked:disabled:bg-background checked:disabled:border-border-muted"
|
|
663
|
+
]);
|
|
664
|
+
function Checkbox({
|
|
665
|
+
className,
|
|
666
|
+
wrapperClassName,
|
|
667
|
+
onChange,
|
|
668
|
+
onCheckedChange,
|
|
669
|
+
...props
|
|
670
|
+
}) {
|
|
671
|
+
const handleChange = (event) => {
|
|
672
|
+
onChange?.(event);
|
|
673
|
+
onCheckedChange?.(event.currentTarget.checked);
|
|
674
|
+
};
|
|
675
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("relative inline-flex shrink-0", wrapperClassName), children: [
|
|
676
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
677
|
+
"input",
|
|
678
|
+
{
|
|
679
|
+
type: "checkbox",
|
|
680
|
+
"data-slot": "checkbox",
|
|
681
|
+
className: cn(checkboxVariants(), className),
|
|
682
|
+
...props,
|
|
683
|
+
onChange: handleChange
|
|
684
|
+
}
|
|
685
|
+
),
|
|
686
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
687
|
+
react.CheckIcon,
|
|
688
|
+
{
|
|
689
|
+
"aria-hidden": "true",
|
|
690
|
+
weight: "regular",
|
|
691
|
+
"data-slot": "checkbox-indicator",
|
|
692
|
+
className: cn(
|
|
693
|
+
"pointer-events-none invisible absolute inset-0 m-auto size-4",
|
|
694
|
+
"text-brand-primary-foreground",
|
|
695
|
+
"peer-checked:visible peer-disabled:text-foreground-muted"
|
|
696
|
+
)
|
|
697
|
+
}
|
|
698
|
+
)
|
|
699
|
+
] });
|
|
700
|
+
}
|
|
701
|
+
var scrollbarVariants = classVarianceAuthority.cva(
|
|
702
|
+
[
|
|
703
|
+
// Firefox (approximate): thin themed scrollbar over a transparent track.
|
|
704
|
+
"[scrollbar-width:thin]",
|
|
705
|
+
"[scrollbar-color:var(--color-card-foreground-muted)_transparent]",
|
|
706
|
+
// WebKit gutter — 12px, both axes so the utility is axis-agnostic.
|
|
707
|
+
"[&::-webkit-scrollbar]:w-3 [&::-webkit-scrollbar]:h-3",
|
|
708
|
+
// WebKit thumb — 4px pill: a 4px transparent border + content-box clip
|
|
709
|
+
// leaves a 4px-wide fill centred in the 12px gutter.
|
|
710
|
+
"[&::-webkit-scrollbar-thumb]:rounded-full",
|
|
711
|
+
"[&::-webkit-scrollbar-thumb]:border-4 [&::-webkit-scrollbar-thumb]:border-solid [&::-webkit-scrollbar-thumb]:border-transparent",
|
|
712
|
+
"[&::-webkit-scrollbar-thumb]:bg-clip-content",
|
|
713
|
+
"[&::-webkit-scrollbar-thumb]:bg-card-foreground-muted",
|
|
714
|
+
// WebKit track — transparent by default (stroke is the `stroke` variant).
|
|
715
|
+
"[&::-webkit-scrollbar-track]:bg-transparent"
|
|
716
|
+
],
|
|
717
|
+
{
|
|
718
|
+
variants: {
|
|
719
|
+
stroke: {
|
|
720
|
+
// 1px leading-edge track stroke (Figma `stroke`, on by default).
|
|
721
|
+
true: "[&::-webkit-scrollbar-track]:border-l [&::-webkit-scrollbar-track]:border-card-border",
|
|
722
|
+
false: ""
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
defaultVariants: { stroke: true }
|
|
726
|
+
}
|
|
727
|
+
);
|
|
728
|
+
var FOCUSABLE_SELECTOR = [
|
|
729
|
+
"a[href]",
|
|
730
|
+
"button:not([disabled])",
|
|
731
|
+
"textarea:not([disabled])",
|
|
732
|
+
"input:not([disabled])",
|
|
733
|
+
"select:not([disabled])",
|
|
734
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
735
|
+
].join(",");
|
|
736
|
+
function getFocusableElements(container) {
|
|
737
|
+
return Array.from(
|
|
738
|
+
container.querySelectorAll(FOCUSABLE_SELECTOR)
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
function useFocusTrap({
|
|
742
|
+
active,
|
|
743
|
+
containerRef
|
|
744
|
+
}) {
|
|
745
|
+
React21__namespace.useEffect(() => {
|
|
746
|
+
if (!active) return;
|
|
747
|
+
const container = containerRef.current;
|
|
748
|
+
if (!container) return;
|
|
749
|
+
const previouslyFocused = document.activeElement;
|
|
750
|
+
const initial = getFocusableElements(container)[0] ?? container;
|
|
751
|
+
initial.focus();
|
|
752
|
+
const body = document.body;
|
|
753
|
+
const previousOverflow = body.style.overflow;
|
|
754
|
+
body.style.overflow = "hidden";
|
|
755
|
+
const handleKeyDown = (event) => {
|
|
756
|
+
if (event.key !== "Tab") return;
|
|
757
|
+
const focusables = getFocusableElements(container);
|
|
758
|
+
if (focusables.length === 0) {
|
|
759
|
+
event.preventDefault();
|
|
760
|
+
container.focus();
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
const first = focusables[0];
|
|
764
|
+
const last = focusables[focusables.length - 1];
|
|
765
|
+
const activeEl = document.activeElement;
|
|
766
|
+
if (event.shiftKey) {
|
|
767
|
+
if (activeEl === first || !container.contains(activeEl)) {
|
|
768
|
+
event.preventDefault();
|
|
769
|
+
last.focus();
|
|
770
|
+
}
|
|
771
|
+
} else if (activeEl === last || !container.contains(activeEl)) {
|
|
772
|
+
event.preventDefault();
|
|
773
|
+
first.focus();
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
document.addEventListener("keydown", handleKeyDown, true);
|
|
777
|
+
return () => {
|
|
778
|
+
document.removeEventListener("keydown", handleKeyDown, true);
|
|
779
|
+
body.style.overflow = previousOverflow;
|
|
780
|
+
previouslyFocused?.focus?.();
|
|
781
|
+
};
|
|
782
|
+
}, [active, containerRef]);
|
|
783
|
+
}
|
|
784
|
+
var dialogOverlayVariants = classVarianceAuthority.cva([
|
|
785
|
+
"fixed inset-0 z-50 flex items-center justify-center p-4",
|
|
786
|
+
"bg-overlay"
|
|
787
|
+
]);
|
|
788
|
+
var dialogContentVariants = classVarianceAuthority.cva([
|
|
789
|
+
"relative flex w-[438px] max-w-[calc(100vw-2rem)] max-h-[calc(100vh-2rem)] flex-col gap-6",
|
|
790
|
+
"rounded-md border border-border bg-card-background p-6 shadow-s",
|
|
791
|
+
// Programmatic-focus fallback target (no visible ring when focused as a last
|
|
792
|
+
// resort by the focus trap — the card is not an interactive control).
|
|
793
|
+
"outline-none"
|
|
794
|
+
]);
|
|
795
|
+
var dialogHeaderVariants = classVarianceAuthority.cva([
|
|
796
|
+
"flex shrink-0 flex-col items-start gap-4"
|
|
797
|
+
]);
|
|
798
|
+
var dialogHeaderIconVariants = classVarianceAuthority.cva([
|
|
799
|
+
"flex items-center text-foreground [&_svg]:size-8 [&_svg]:shrink-0"
|
|
800
|
+
]);
|
|
801
|
+
var dialogHeaderTextVariants = classVarianceAuthority.cva(["flex w-full flex-col gap-2"]);
|
|
802
|
+
var dialogCloseButtonVariants = classVarianceAuthority.cva([
|
|
803
|
+
"absolute right-6 top-6 flex cursor-pointer items-center justify-center rounded-xs",
|
|
804
|
+
"text-foreground-muted transition-colors hover:text-foreground",
|
|
805
|
+
"[&_svg]:size-4 [&_svg]:shrink-0",
|
|
806
|
+
// TW4 needs `outline-solid` to render a solid ring (a bare `outline` won't).
|
|
807
|
+
"outline-none focus-visible:text-foreground focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus"
|
|
808
|
+
]);
|
|
809
|
+
var dialogBodyVariants = classVarianceAuthority.cva([
|
|
810
|
+
"min-h-0 flex-1 overflow-y-auto text-foreground"
|
|
811
|
+
]);
|
|
812
|
+
var dialogFooterVariants = classVarianceAuthority.cva([
|
|
813
|
+
"flex w-full shrink-0 gap-2 [&>*]:flex-1 [&_button]:w-full"
|
|
814
|
+
]);
|
|
815
|
+
var DialogContext = React21__namespace.createContext(null);
|
|
816
|
+
function useDialogContext(part) {
|
|
817
|
+
const context = React21__namespace.useContext(DialogContext);
|
|
818
|
+
if (context === null) {
|
|
819
|
+
throw new Error(`${part} must be used within <Dialog.Root>`);
|
|
820
|
+
}
|
|
821
|
+
return context;
|
|
822
|
+
}
|
|
823
|
+
function DialogRoot({
|
|
824
|
+
open: openProp,
|
|
825
|
+
defaultOpen = false,
|
|
826
|
+
onOpenChange,
|
|
827
|
+
dismissable = true,
|
|
828
|
+
children
|
|
829
|
+
}) {
|
|
830
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React21__namespace.useState(defaultOpen);
|
|
831
|
+
const [hasDescription, setHasDescription] = React21__namespace.useState(false);
|
|
832
|
+
const isControlled = openProp !== void 0;
|
|
833
|
+
const open = isControlled ? openProp : uncontrolledOpen;
|
|
834
|
+
const reactId = React21__namespace.useId();
|
|
835
|
+
const titleId = `${reactId}-title`;
|
|
836
|
+
const descriptionId = `${reactId}-description`;
|
|
837
|
+
const setOpen = React21__namespace.useCallback(
|
|
838
|
+
(next) => {
|
|
839
|
+
if (!isControlled) setUncontrolledOpen(next);
|
|
840
|
+
onOpenChange?.(next);
|
|
841
|
+
},
|
|
842
|
+
[isControlled, onOpenChange]
|
|
843
|
+
);
|
|
844
|
+
const value = {
|
|
845
|
+
open,
|
|
846
|
+
setOpen,
|
|
847
|
+
dismissable,
|
|
848
|
+
titleId,
|
|
849
|
+
descriptionId,
|
|
850
|
+
hasDescription,
|
|
851
|
+
setHasDescription
|
|
852
|
+
};
|
|
853
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DialogContext.Provider, { value, children });
|
|
854
|
+
}
|
|
855
|
+
function setRef3(ref, node) {
|
|
856
|
+
if (typeof ref === "function") ref(node);
|
|
857
|
+
else if (ref) ref.current = node;
|
|
858
|
+
}
|
|
859
|
+
function TriggerLike({
|
|
860
|
+
asChild = false,
|
|
861
|
+
onActivate,
|
|
862
|
+
dataSlot,
|
|
863
|
+
children,
|
|
864
|
+
className,
|
|
865
|
+
type,
|
|
866
|
+
...props
|
|
867
|
+
}) {
|
|
868
|
+
if (asChild) {
|
|
869
|
+
if (!React21__namespace.isValidElement(children)) {
|
|
870
|
+
throw new Error(
|
|
871
|
+
`${dataSlot}: \`asChild\` requires a single React element child`
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
const child = children;
|
|
875
|
+
const childProps = child.props;
|
|
876
|
+
const childOnClick = childProps.onClick;
|
|
877
|
+
return React21__namespace.cloneElement(child, {
|
|
878
|
+
...props,
|
|
879
|
+
...childProps,
|
|
880
|
+
"data-slot": dataSlot,
|
|
881
|
+
onClick: (event) => {
|
|
882
|
+
childOnClick?.(event);
|
|
883
|
+
if (!event.defaultPrevented) onActivate();
|
|
884
|
+
},
|
|
885
|
+
className: cn(className, childProps.className),
|
|
886
|
+
ref: (node) => {
|
|
887
|
+
setRef3(childProps.ref, node);
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
892
|
+
"button",
|
|
893
|
+
{
|
|
894
|
+
type: type ?? "button",
|
|
895
|
+
"data-slot": dataSlot,
|
|
896
|
+
className,
|
|
897
|
+
onClick: () => onActivate(),
|
|
898
|
+
...props,
|
|
899
|
+
children
|
|
900
|
+
}
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
function DialogTrigger({ asChild = false, ...props }) {
|
|
904
|
+
const { open, setOpen } = useDialogContext("Dialog.Trigger");
|
|
905
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
906
|
+
TriggerLike,
|
|
907
|
+
{
|
|
908
|
+
asChild,
|
|
909
|
+
dataSlot: "dialog-trigger",
|
|
910
|
+
onActivate: () => setOpen(true),
|
|
911
|
+
"aria-haspopup": "dialog",
|
|
912
|
+
"aria-expanded": open,
|
|
913
|
+
...props
|
|
914
|
+
}
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
function DialogClose({ asChild = false, ...props }) {
|
|
918
|
+
const { setOpen } = useDialogContext("Dialog.Close");
|
|
919
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
920
|
+
TriggerLike,
|
|
921
|
+
{
|
|
922
|
+
asChild,
|
|
923
|
+
dataSlot: "dialog-close",
|
|
924
|
+
onActivate: () => setOpen(false),
|
|
925
|
+
...props
|
|
926
|
+
}
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
function DialogContent({
|
|
930
|
+
className,
|
|
931
|
+
children,
|
|
932
|
+
ref,
|
|
933
|
+
onKeyDown,
|
|
934
|
+
...props
|
|
935
|
+
}) {
|
|
936
|
+
const { open, setOpen, dismissable, titleId, descriptionId, hasDescription } = useDialogContext("Dialog.Content");
|
|
937
|
+
const cardRef = React21__namespace.useRef(null);
|
|
938
|
+
useFocusTrap({ active: open, containerRef: cardRef });
|
|
939
|
+
React21__namespace.useEffect(() => {
|
|
940
|
+
if (!open || !dismissable) return;
|
|
941
|
+
const handleEscape = (event) => {
|
|
942
|
+
if (event.key === "Escape") {
|
|
943
|
+
event.stopPropagation();
|
|
944
|
+
setOpen(false);
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
document.addEventListener("keydown", handleEscape);
|
|
948
|
+
return () => document.removeEventListener("keydown", handleEscape);
|
|
949
|
+
}, [open, dismissable, setOpen]);
|
|
950
|
+
if (!open) return null;
|
|
951
|
+
const setCardRef = (node) => {
|
|
952
|
+
cardRef.current = node;
|
|
953
|
+
setRef3(ref, node);
|
|
954
|
+
};
|
|
955
|
+
return reactDom.createPortal(
|
|
956
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
957
|
+
"div",
|
|
958
|
+
{
|
|
959
|
+
"data-slot": "dialog-overlay",
|
|
960
|
+
className: dialogOverlayVariants(),
|
|
961
|
+
onClick: (event) => {
|
|
962
|
+
if (dismissable && event.target === event.currentTarget) {
|
|
963
|
+
setOpen(false);
|
|
964
|
+
}
|
|
965
|
+
},
|
|
966
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
967
|
+
"div",
|
|
968
|
+
{
|
|
969
|
+
ref: setCardRef,
|
|
970
|
+
role: "dialog",
|
|
971
|
+
"aria-modal": "true",
|
|
972
|
+
"aria-labelledby": titleId,
|
|
973
|
+
"aria-describedby": hasDescription ? descriptionId : void 0,
|
|
974
|
+
tabIndex: -1,
|
|
975
|
+
"data-slot": "dialog-content",
|
|
976
|
+
className: cn(dialogContentVariants(), className),
|
|
977
|
+
onKeyDown,
|
|
978
|
+
...props,
|
|
979
|
+
children
|
|
980
|
+
}
|
|
981
|
+
)
|
|
982
|
+
}
|
|
983
|
+
),
|
|
984
|
+
document.body
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
function DialogHeader({
|
|
988
|
+
icon,
|
|
989
|
+
title,
|
|
990
|
+
description,
|
|
991
|
+
showClose = true,
|
|
992
|
+
className,
|
|
993
|
+
ref,
|
|
994
|
+
...props
|
|
995
|
+
}) {
|
|
996
|
+
const { titleId, descriptionId, setHasDescription, setOpen } = useDialogContext("Dialog.Header");
|
|
997
|
+
const hasDescription = description != null;
|
|
998
|
+
React21__namespace.useEffect(() => {
|
|
999
|
+
if (!hasDescription) return;
|
|
1000
|
+
setHasDescription(true);
|
|
1001
|
+
return () => setHasDescription(false);
|
|
1002
|
+
}, [hasDescription, setHasDescription]);
|
|
1003
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1004
|
+
"div",
|
|
1005
|
+
{
|
|
1006
|
+
ref,
|
|
1007
|
+
"data-slot": "dialog-header",
|
|
1008
|
+
className: cn(dialogHeaderVariants(), className),
|
|
1009
|
+
...props,
|
|
1010
|
+
children: [
|
|
1011
|
+
icon != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1012
|
+
"span",
|
|
1013
|
+
{
|
|
1014
|
+
"data-slot": "dialog-header-icon",
|
|
1015
|
+
"aria-hidden": "true",
|
|
1016
|
+
className: dialogHeaderIconVariants(),
|
|
1017
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.IconContext.Provider, { value: { weight: "fill" }, children: icon })
|
|
1018
|
+
}
|
|
1019
|
+
),
|
|
1020
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1021
|
+
"div",
|
|
1022
|
+
{
|
|
1023
|
+
className: cn(
|
|
1024
|
+
dialogHeaderTextVariants(),
|
|
1025
|
+
// Reserve right space for the corner X only when it would collide: an
|
|
1026
|
+
// icon-less header puts the title on the X's row. With an icon the
|
|
1027
|
+
// title sits below the X, so no reservation is needed.
|
|
1028
|
+
showClose && icon == null && "pr-8"
|
|
1029
|
+
),
|
|
1030
|
+
children: [
|
|
1031
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, className: "display-04 text-foreground", children: title }),
|
|
1032
|
+
hasDescription && /* @__PURE__ */ jsxRuntime.jsx("p", { id: descriptionId, className: "body-03 text-foreground-muted", children: description })
|
|
1033
|
+
]
|
|
1034
|
+
}
|
|
1035
|
+
),
|
|
1036
|
+
showClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1037
|
+
"button",
|
|
1038
|
+
{
|
|
1039
|
+
type: "button",
|
|
1040
|
+
"data-slot": "dialog-header-close",
|
|
1041
|
+
"aria-label": "Close",
|
|
1042
|
+
className: dialogCloseButtonVariants(),
|
|
1043
|
+
onClick: () => setOpen(false),
|
|
1044
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.XIcon, { weight: "regular" })
|
|
1045
|
+
}
|
|
1046
|
+
)
|
|
1047
|
+
]
|
|
1048
|
+
}
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
function DialogBody({ className, children, ref, ...props }) {
|
|
1052
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1053
|
+
"div",
|
|
1054
|
+
{
|
|
1055
|
+
ref,
|
|
1056
|
+
"data-slot": "dialog-body",
|
|
1057
|
+
className: cn(dialogBodyVariants(), scrollbarVariants(), className),
|
|
1058
|
+
...props,
|
|
1059
|
+
children
|
|
1060
|
+
}
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
function DialogFooter({
|
|
1064
|
+
className,
|
|
1065
|
+
children,
|
|
1066
|
+
ref,
|
|
1067
|
+
...props
|
|
1068
|
+
}) {
|
|
1069
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1070
|
+
"div",
|
|
1071
|
+
{
|
|
1072
|
+
ref,
|
|
1073
|
+
"data-slot": "dialog-footer",
|
|
1074
|
+
className: cn(dialogFooterVariants(), className),
|
|
1075
|
+
...props,
|
|
1076
|
+
children
|
|
1077
|
+
}
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
var Dialog = {
|
|
1081
|
+
Root: DialogRoot,
|
|
1082
|
+
Trigger: DialogTrigger,
|
|
1083
|
+
Content: DialogContent,
|
|
1084
|
+
Header: DialogHeader,
|
|
1085
|
+
Body: DialogBody,
|
|
1086
|
+
Footer: DialogFooter,
|
|
1087
|
+
Close: DialogClose
|
|
1088
|
+
};
|
|
1089
|
+
var featureCardVariants = classVarianceAuthority.cva(
|
|
1090
|
+
"flex h-16 items-center gap-3 px-4 py-3 bg-card-background no-underline",
|
|
1091
|
+
{
|
|
1092
|
+
variants: {
|
|
1093
|
+
interactive: {
|
|
1094
|
+
true: [
|
|
1095
|
+
"cursor-pointer transition-colors hover:bg-card-accent",
|
|
1096
|
+
"outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus"
|
|
1097
|
+
],
|
|
1098
|
+
false: ""
|
|
1099
|
+
}
|
|
1100
|
+
},
|
|
1101
|
+
defaultVariants: { interactive: false }
|
|
1102
|
+
}
|
|
1103
|
+
);
|
|
1104
|
+
var featureCardLeadingVariants = classVarianceAuthority.cva(
|
|
1105
|
+
"flex shrink-0 items-center text-card-foreground [&_svg]:size-6 [&_svg]:shrink-0"
|
|
1106
|
+
);
|
|
1107
|
+
var featureCardTrailingVariants = classVarianceAuthority.cva(
|
|
1108
|
+
"flex shrink-0 items-center text-card-foreground-muted [&_svg]:size-6 [&_svg]:shrink-0"
|
|
1109
|
+
);
|
|
1110
|
+
var featureCardTextVariants = classVarianceAuthority.cva("flex min-w-0 flex-1 flex-col");
|
|
1111
|
+
var featureCardTitleRowVariants = classVarianceAuthority.cva(
|
|
1112
|
+
"flex min-w-0 items-center gap-0.5"
|
|
1113
|
+
);
|
|
1114
|
+
var featureCardCaretVariants = classVarianceAuthority.cva(
|
|
1115
|
+
"flex shrink-0 items-center text-card-foreground-muted [&_svg]:size-4 [&_svg]:shrink-0"
|
|
1116
|
+
);
|
|
1117
|
+
function setRef4(ref, node) {
|
|
1118
|
+
if (typeof ref === "function") ref(node);
|
|
1119
|
+
else if (ref) ref.current = node;
|
|
1120
|
+
}
|
|
1121
|
+
function FeatureCard({
|
|
1122
|
+
title,
|
|
1123
|
+
subtitle,
|
|
1124
|
+
leading,
|
|
1125
|
+
trailing,
|
|
1126
|
+
caret = true,
|
|
1127
|
+
asChild = false,
|
|
1128
|
+
href,
|
|
1129
|
+
className,
|
|
1130
|
+
children,
|
|
1131
|
+
ref,
|
|
1132
|
+
...props
|
|
1133
|
+
}) {
|
|
1134
|
+
const interactive = asChild || href != null;
|
|
1135
|
+
const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1136
|
+
leading != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1137
|
+
"span",
|
|
1138
|
+
{
|
|
1139
|
+
"data-slot": "feature-card-leading",
|
|
1140
|
+
"aria-hidden": "true",
|
|
1141
|
+
className: featureCardLeadingVariants(),
|
|
1142
|
+
children: leading
|
|
1143
|
+
}
|
|
1144
|
+
),
|
|
1145
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { "data-slot": "feature-card-text", className: featureCardTextVariants(), children: [
|
|
1146
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: featureCardTitleRowVariants(), children: [
|
|
1147
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "body-02-medium truncate text-card-foreground", children: title }),
|
|
1148
|
+
caret && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1149
|
+
"span",
|
|
1150
|
+
{
|
|
1151
|
+
"data-slot": "feature-card-caret",
|
|
1152
|
+
"aria-hidden": "true",
|
|
1153
|
+
className: featureCardCaretVariants(),
|
|
1154
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.CaretRightIcon, {})
|
|
1155
|
+
}
|
|
1156
|
+
)
|
|
1157
|
+
] }),
|
|
1158
|
+
subtitle != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "body-03 truncate text-card-foreground-muted", children: subtitle })
|
|
1159
|
+
] }),
|
|
1160
|
+
trailing != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1161
|
+
"span",
|
|
1162
|
+
{
|
|
1163
|
+
"data-slot": "feature-card-trailing",
|
|
1164
|
+
"aria-hidden": "true",
|
|
1165
|
+
className: featureCardTrailingVariants(),
|
|
1166
|
+
children: trailing
|
|
1167
|
+
}
|
|
1168
|
+
)
|
|
1169
|
+
] });
|
|
1170
|
+
const rootClassName = cn(featureCardVariants({ interactive }), className);
|
|
1171
|
+
if (asChild) {
|
|
1172
|
+
if (!React21__namespace.isValidElement(children)) {
|
|
1173
|
+
throw new Error(
|
|
1174
|
+
"FeatureCard: `asChild` requires a single React element child"
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
const child = children;
|
|
1178
|
+
const childProps = child.props;
|
|
1179
|
+
return React21__namespace.cloneElement(
|
|
1180
|
+
child,
|
|
1181
|
+
{
|
|
1182
|
+
...props,
|
|
1183
|
+
...childProps,
|
|
1184
|
+
"data-slot": "feature-card",
|
|
1185
|
+
ref: (node) => {
|
|
1186
|
+
setRef4(ref, node);
|
|
1187
|
+
setRef4(childProps.ref, node);
|
|
1188
|
+
},
|
|
1189
|
+
className: cn(
|
|
1190
|
+
rootClassName,
|
|
1191
|
+
childProps.className
|
|
1192
|
+
),
|
|
1193
|
+
onClick: (event) => {
|
|
1194
|
+
props.onClick?.(event);
|
|
1195
|
+
childProps.onClick?.(event);
|
|
1196
|
+
},
|
|
1197
|
+
style: {
|
|
1198
|
+
...props.style,
|
|
1199
|
+
...childProps.style
|
|
1200
|
+
}
|
|
1201
|
+
},
|
|
1202
|
+
content,
|
|
1203
|
+
childProps.children
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
if (href != null) {
|
|
1207
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1208
|
+
"a",
|
|
1209
|
+
{
|
|
1210
|
+
"data-slot": "feature-card",
|
|
1211
|
+
href,
|
|
1212
|
+
className: rootClassName,
|
|
1213
|
+
ref,
|
|
1214
|
+
...props,
|
|
1215
|
+
children: content
|
|
1216
|
+
}
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1220
|
+
"div",
|
|
1221
|
+
{
|
|
1222
|
+
"data-slot": "feature-card",
|
|
1223
|
+
className: rootClassName,
|
|
1224
|
+
ref,
|
|
1225
|
+
...props,
|
|
1226
|
+
children: content
|
|
1227
|
+
}
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
var selectListboxVariants = classVarianceAuthority.cva([
|
|
1231
|
+
"flex flex-col",
|
|
1232
|
+
"bg-card-background border border-card-border rounded-sm shadow-s",
|
|
1233
|
+
// Small vertical inset so the first/last option isn't flush to the border.
|
|
1234
|
+
"py-1",
|
|
1235
|
+
// Scroll when the list outgrows its max-height (set inline from the rect).
|
|
1236
|
+
"overflow-y-auto",
|
|
1237
|
+
// The panel is not itself focusable; focus stays on the trigger.
|
|
1238
|
+
"outline-none"
|
|
1239
|
+
]);
|
|
1240
|
+
var selectListboxPanelVariants = classVarianceAuthority.cva([
|
|
1241
|
+
"flex flex-col min-h-0",
|
|
1242
|
+
"bg-card-background border border-card-border rounded-sm shadow-s",
|
|
1243
|
+
"overflow-hidden",
|
|
1244
|
+
"outline-none"
|
|
1245
|
+
]);
|
|
1246
|
+
var selectListboxScrollVariants = classVarianceAuthority.cva([
|
|
1247
|
+
"flex flex-col min-h-0 flex-1",
|
|
1248
|
+
"py-1",
|
|
1249
|
+
"overflow-y-auto",
|
|
1250
|
+
"outline-none"
|
|
1251
|
+
]);
|
|
1252
|
+
var selectListboxFooterVariants = classVarianceAuthority.cva([
|
|
1253
|
+
"shrink-0",
|
|
1254
|
+
"border-t border-t-card-border",
|
|
1255
|
+
"px-3 py-1"
|
|
1256
|
+
]);
|
|
1257
|
+
var selectOptionVariants = classVarianceAuthority.cva(
|
|
1258
|
+
[
|
|
1259
|
+
"body-03",
|
|
1260
|
+
"flex w-full items-center gap-2",
|
|
1261
|
+
"px-3 py-2",
|
|
1262
|
+
"cursor-pointer select-none",
|
|
1263
|
+
"transition-colors",
|
|
1264
|
+
// Reserve the 3px left accent bar on EVERY row (transparent by default,
|
|
1265
|
+
// recoloured only when single-selected). Keeping the border-box constant
|
|
1266
|
+
// across rows means selecting one never shifts its text right — same
|
|
1267
|
+
// no-layout-shift trick Input uses for its `showBottomLine` bottom line.
|
|
1268
|
+
"border-l-3",
|
|
1269
|
+
// Mirror the 3px on the right (always transparent) so content is inset
|
|
1270
|
+
// equally on both sides — the left accent bar would otherwise make the left
|
|
1271
|
+
// padding read 3px larger than the right.
|
|
1272
|
+
"border-r-3 border-r-transparent"
|
|
1273
|
+
// NOTE: no text-/border-colour in the base. cva concatenates class strings
|
|
1274
|
+
// without resolving conflicting Tailwind utilities, so a base colour + a
|
|
1275
|
+
// compound colour would both survive and CSS source-order (not our intent)
|
|
1276
|
+
// would pick the winner. Every state below sets exactly ONE text colour and
|
|
1277
|
+
// ONE left-border colour via compound variants.
|
|
1278
|
+
],
|
|
1279
|
+
{
|
|
1280
|
+
variants: {
|
|
1281
|
+
active: { true: "", false: "" },
|
|
1282
|
+
selected: { true: "", false: "" },
|
|
1283
|
+
multiple: { true: "", false: "" },
|
|
1284
|
+
disabled: { true: "cursor-not-allowed", false: "" }
|
|
1285
|
+
},
|
|
1286
|
+
compoundVariants: [
|
|
1287
|
+
// ── Text colour (exactly one matches per state) ──
|
|
1288
|
+
{ disabled: true, className: "text-foreground-muted" },
|
|
1289
|
+
{ disabled: false, selected: false, className: "text-foreground" },
|
|
1290
|
+
// Multi keeps the normal foreground when selected (the checkbox marks it).
|
|
1291
|
+
{
|
|
1292
|
+
disabled: false,
|
|
1293
|
+
selected: true,
|
|
1294
|
+
multiple: true,
|
|
1295
|
+
className: "text-foreground"
|
|
1296
|
+
},
|
|
1297
|
+
// Single-select selected → brand foreground (Figma node 4921:8203).
|
|
1298
|
+
{
|
|
1299
|
+
disabled: false,
|
|
1300
|
+
selected: true,
|
|
1301
|
+
multiple: false,
|
|
1302
|
+
className: "text-card-brand-foreground"
|
|
1303
|
+
},
|
|
1304
|
+
// ── Left accent bar colour (exactly one matches per state) ──
|
|
1305
|
+
// Invisible on every row except the single-select selected one, where it
|
|
1306
|
+
// becomes the brand accent. Width is reserved in the base, so this is a
|
|
1307
|
+
// pure colour change — no content shift.
|
|
1308
|
+
{ selected: false, className: "border-l-transparent" },
|
|
1309
|
+
{ selected: true, multiple: true, className: "border-l-transparent" },
|
|
1310
|
+
{
|
|
1311
|
+
selected: true,
|
|
1312
|
+
multiple: false,
|
|
1313
|
+
className: "border-l-card-brand-border-accent"
|
|
1314
|
+
},
|
|
1315
|
+
// ── Background ──
|
|
1316
|
+
// Neutral hover/keyboard highlight (Figma card/neutral/normal/accent) —
|
|
1317
|
+
// every row EXCEPT a single-select selected one (which stays branded).
|
|
1318
|
+
{ active: true, selected: false, className: "bg-card-accent" },
|
|
1319
|
+
{
|
|
1320
|
+
active: true,
|
|
1321
|
+
selected: true,
|
|
1322
|
+
multiple: true,
|
|
1323
|
+
className: "bg-card-accent"
|
|
1324
|
+
},
|
|
1325
|
+
// Single-select selected: brand-tinted row. No neutral `active` bg matches
|
|
1326
|
+
// this combo, so it stays branded on hover.
|
|
1327
|
+
{ selected: true, multiple: false, className: "bg-card-brand-accent" }
|
|
1328
|
+
],
|
|
1329
|
+
defaultVariants: {
|
|
1330
|
+
active: false,
|
|
1331
|
+
selected: false,
|
|
1332
|
+
multiple: false,
|
|
1333
|
+
disabled: false
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
);
|
|
1337
|
+
var LISTBOX_OFFSET = 4;
|
|
1338
|
+
var LISTBOX_MARGIN = 8;
|
|
1339
|
+
var LISTBOX_MAX_HEIGHT = 208;
|
|
1340
|
+
function computeListboxPosition(anchor, listboxHeight, viewportHeight) {
|
|
1341
|
+
const flipsAbove = anchor.bottom + listboxHeight > viewportHeight && anchor.top > listboxHeight;
|
|
1342
|
+
return {
|
|
1343
|
+
left: anchor.left,
|
|
1344
|
+
width: anchor.width,
|
|
1345
|
+
top: flipsAbove ? anchor.top - listboxHeight : anchor.bottom,
|
|
1346
|
+
placement: flipsAbove ? "top" : "bottom"
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
function computeListboxHorizontal(anchor, widthMode, measuredWidth, viewportWidth, margin) {
|
|
1350
|
+
if (widthMode === "trigger") {
|
|
1351
|
+
return { left: anchor.left, width: anchor.width };
|
|
1352
|
+
}
|
|
1353
|
+
const maxWidth = viewportWidth - 2 * margin;
|
|
1354
|
+
const upperBound = viewportWidth - margin - measuredWidth;
|
|
1355
|
+
const left = Math.max(margin, Math.min(anchor.left, upperBound));
|
|
1356
|
+
return { left, minWidth: anchor.width, maxWidth };
|
|
1357
|
+
}
|
|
1358
|
+
function SelectListbox({
|
|
1359
|
+
id,
|
|
1360
|
+
options,
|
|
1361
|
+
selected,
|
|
1362
|
+
multiple,
|
|
1363
|
+
activeIndex,
|
|
1364
|
+
anchorRect,
|
|
1365
|
+
widthMode = "trigger",
|
|
1366
|
+
onSelect,
|
|
1367
|
+
onActiveChange,
|
|
1368
|
+
footer,
|
|
1369
|
+
"aria-label": ariaLabel,
|
|
1370
|
+
"aria-labelledby": ariaLabelledby
|
|
1371
|
+
}) {
|
|
1372
|
+
const listRef = React21__namespace.useRef(null);
|
|
1373
|
+
const panelRef = React21__namespace.useRef(null);
|
|
1374
|
+
const hasFooter = footer != null;
|
|
1375
|
+
const [style, setStyle] = React21__namespace.useState(null);
|
|
1376
|
+
React21__namespace.useLayoutEffect(() => {
|
|
1377
|
+
if (anchorRect == null) {
|
|
1378
|
+
setStyle(null);
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
const measureEl = hasFooter ? panelRef.current : listRef.current;
|
|
1382
|
+
const measured = measureEl ? measureEl.offsetHeight : 0;
|
|
1383
|
+
const height = Math.min(measured || LISTBOX_MAX_HEIGHT, LISTBOX_MAX_HEIGHT);
|
|
1384
|
+
const pos = computeListboxPosition(anchorRect, height, window.innerHeight);
|
|
1385
|
+
const measuredWidth = measureEl ? measureEl.offsetWidth : 0;
|
|
1386
|
+
const horizontal = computeListboxHorizontal(
|
|
1387
|
+
anchorRect,
|
|
1388
|
+
widthMode,
|
|
1389
|
+
measuredWidth,
|
|
1390
|
+
window.innerWidth,
|
|
1391
|
+
LISTBOX_MARGIN
|
|
1392
|
+
);
|
|
1393
|
+
setStyle({
|
|
1394
|
+
position: "absolute",
|
|
1395
|
+
top: pos.top + window.scrollY + (pos.placement === "bottom" ? LISTBOX_OFFSET : -LISTBOX_OFFSET),
|
|
1396
|
+
left: horizontal.left + window.scrollX,
|
|
1397
|
+
// `"trigger"`: fixed pixel width = trigger width (Select, unchanged).
|
|
1398
|
+
// `"auto"`: content-sized via `max-content`, bounded by min/max width.
|
|
1399
|
+
...widthMode === "trigger" ? { width: horizontal.width } : {
|
|
1400
|
+
width: "max-content",
|
|
1401
|
+
minWidth: horizontal.minWidth,
|
|
1402
|
+
maxWidth: horizontal.maxWidth
|
|
1403
|
+
},
|
|
1404
|
+
// Bound to the design token (208px). `overflow-y-auto` on the panel
|
|
1405
|
+
// scrolls the list past this cap.
|
|
1406
|
+
maxHeight: "var(--container-4xs)",
|
|
1407
|
+
zIndex: 50
|
|
1408
|
+
});
|
|
1409
|
+
}, [anchorRect, widthMode, hasFooter]);
|
|
1410
|
+
React21__namespace.useLayoutEffect(() => {
|
|
1411
|
+
if (activeIndex < 0) return;
|
|
1412
|
+
const el = listRef.current?.querySelector(
|
|
1413
|
+
`#${CSS.escape(`${id}-opt-${activeIndex}`)}`
|
|
1414
|
+
);
|
|
1415
|
+
el?.scrollIntoView?.({ block: "nearest" });
|
|
1416
|
+
}, [activeIndex, id]);
|
|
1417
|
+
const positionStyle = style ?? (widthMode === "auto" ? { position: "absolute", visibility: "hidden", width: "max-content" } : { position: "absolute", visibility: "hidden" });
|
|
1418
|
+
const optionItems = /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: options.map((option, index) => {
|
|
1419
|
+
const isSelected = selected.includes(option.value);
|
|
1420
|
+
const isActive = index === activeIndex;
|
|
1421
|
+
const isDisabled = option.disabled === true;
|
|
1422
|
+
const iconColorClass = isSelected && !multiple ? "text-card-brand-foreground" : "text-foreground-muted";
|
|
1423
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1424
|
+
"li",
|
|
1425
|
+
{
|
|
1426
|
+
id: `${id}-opt-${index}`,
|
|
1427
|
+
role: "option",
|
|
1428
|
+
"aria-selected": isSelected,
|
|
1429
|
+
"aria-disabled": isDisabled || void 0,
|
|
1430
|
+
"data-slot": "select-option",
|
|
1431
|
+
"data-active": isActive || void 0,
|
|
1432
|
+
className: selectOptionVariants({
|
|
1433
|
+
active: isActive,
|
|
1434
|
+
selected: isSelected,
|
|
1435
|
+
multiple,
|
|
1436
|
+
disabled: isDisabled
|
|
1437
|
+
}),
|
|
1438
|
+
onMouseEnter: () => {
|
|
1439
|
+
if (!isDisabled) onActiveChange(index);
|
|
1440
|
+
},
|
|
1441
|
+
onMouseDown: (event) => {
|
|
1442
|
+
event.preventDefault();
|
|
1443
|
+
if (!isDisabled) onSelect(option.value);
|
|
1444
|
+
},
|
|
1445
|
+
children: [
|
|
1446
|
+
multiple && // Visual mirror of the row's selection — the `option` carries the
|
|
1447
|
+
// semantics, so the checkbox is hidden from AT and out of the tab
|
|
1448
|
+
// order. `readOnly` because the <li> owns the toggle.
|
|
1449
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1450
|
+
Checkbox,
|
|
1451
|
+
{
|
|
1452
|
+
checked: isSelected,
|
|
1453
|
+
disabled: true,
|
|
1454
|
+
tabIndex: -1,
|
|
1455
|
+
"aria-hidden": "true",
|
|
1456
|
+
"data-slot": "select-option-checkbox",
|
|
1457
|
+
className: cn(
|
|
1458
|
+
"pointer-events-none disabled:border-border",
|
|
1459
|
+
"checked:disabled:bg-brand-primary-background checked:disabled:border-brand-primary-border"
|
|
1460
|
+
),
|
|
1461
|
+
wrapperClassName: "[&_[data-slot=checkbox-indicator]]:text-brand-primary-foreground"
|
|
1462
|
+
}
|
|
1463
|
+
),
|
|
1464
|
+
option.leadingSlot != null && // After the checkbox in multi mode. Decorative; muted foreground
|
|
1465
|
+
// except on the single-select selected row (brand), per iconColorClass.
|
|
1466
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1467
|
+
"span",
|
|
1468
|
+
{
|
|
1469
|
+
"data-slot": "select-option-leading",
|
|
1470
|
+
"aria-hidden": "true",
|
|
1471
|
+
className: cn(
|
|
1472
|
+
"flex shrink-0 items-center [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
1473
|
+
iconColorClass
|
|
1474
|
+
),
|
|
1475
|
+
children: option.leadingSlot
|
|
1476
|
+
}
|
|
1477
|
+
),
|
|
1478
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: option.label }),
|
|
1479
|
+
option.trailingSlot != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1480
|
+
"span",
|
|
1481
|
+
{
|
|
1482
|
+
"data-slot": "select-option-trailing",
|
|
1483
|
+
"aria-hidden": "true",
|
|
1484
|
+
className: cn(
|
|
1485
|
+
"flex shrink-0 items-center [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
1486
|
+
iconColorClass
|
|
1487
|
+
),
|
|
1488
|
+
children: option.trailingSlot
|
|
1489
|
+
}
|
|
1490
|
+
)
|
|
1491
|
+
]
|
|
1492
|
+
},
|
|
1493
|
+
option.value
|
|
1494
|
+
);
|
|
1495
|
+
}) });
|
|
1496
|
+
if (!hasFooter) {
|
|
1497
|
+
return reactDom.createPortal(
|
|
1498
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1499
|
+
"ul",
|
|
1500
|
+
{
|
|
1501
|
+
ref: listRef,
|
|
1502
|
+
id,
|
|
1503
|
+
role: "listbox",
|
|
1504
|
+
"aria-multiselectable": multiple || void 0,
|
|
1505
|
+
"aria-label": ariaLabel,
|
|
1506
|
+
"aria-labelledby": ariaLabelledby,
|
|
1507
|
+
"data-slot": "select-listbox",
|
|
1508
|
+
className: cn(selectListboxVariants(), scrollbarVariants()),
|
|
1509
|
+
style: positionStyle,
|
|
1510
|
+
children: optionItems
|
|
1511
|
+
}
|
|
1512
|
+
),
|
|
1513
|
+
document.body
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
return reactDom.createPortal(
|
|
1517
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1518
|
+
"div",
|
|
1519
|
+
{
|
|
1520
|
+
ref: panelRef,
|
|
1521
|
+
"data-slot": "select-listbox-panel",
|
|
1522
|
+
className: selectListboxPanelVariants(),
|
|
1523
|
+
style: positionStyle,
|
|
1524
|
+
children: [
|
|
1525
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1526
|
+
"ul",
|
|
1527
|
+
{
|
|
1528
|
+
ref: listRef,
|
|
1529
|
+
id,
|
|
1530
|
+
role: "listbox",
|
|
1531
|
+
"aria-multiselectable": multiple || void 0,
|
|
1532
|
+
"aria-label": ariaLabel,
|
|
1533
|
+
"aria-labelledby": ariaLabelledby,
|
|
1534
|
+
"data-slot": "select-listbox",
|
|
1535
|
+
className: cn(selectListboxScrollVariants(), scrollbarVariants()),
|
|
1536
|
+
children: optionItems
|
|
1537
|
+
}
|
|
1538
|
+
),
|
|
1539
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1540
|
+
"div",
|
|
1541
|
+
{
|
|
1542
|
+
"data-slot": "select-listbox-footer",
|
|
1543
|
+
className: selectListboxFooterVariants(),
|
|
1544
|
+
children: footer
|
|
1545
|
+
}
|
|
1546
|
+
)
|
|
1547
|
+
]
|
|
1548
|
+
}
|
|
1549
|
+
),
|
|
1550
|
+
document.body
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
var inputVariants = classVarianceAuthority.cva(
|
|
1554
|
+
[
|
|
1555
|
+
// Field row layout — icons + input on one line, vertically centred.
|
|
1556
|
+
"flex w-full items-center",
|
|
1557
|
+
// Colour transitions for the focus border change.
|
|
1558
|
+
"transition-colors",
|
|
1559
|
+
// I-beam text cursor over the WHOLE field row (not just the inner input),
|
|
1560
|
+
// so clicking anywhere in the padding/icon area reads as "type here". This
|
|
1561
|
+
// is the only hover affordance — hover intentionally does NOT recolour the
|
|
1562
|
+
// border (user-authorized deviation from the Figma). `cursor-text` sits in
|
|
1563
|
+
// the base; `has-[:disabled]:cursor-not-allowed` below is declared AFTER it
|
|
1564
|
+
// so it wins on a disabled field (same-specificity utilities, later class
|
|
1565
|
+
// takes precedence — confirmed by tailwind-merge ordering).
|
|
1566
|
+
"cursor-text",
|
|
1567
|
+
// Disabled: not-allowed cursor + block pointer interaction on the whole
|
|
1568
|
+
// field (the native `disabled` on the input already stops typing; icons are
|
|
1569
|
+
// dimmed separately by the `disabled`-derived `iconStateClass` in <Input>).
|
|
1570
|
+
// Declared after `cursor-text` so it overrides the I-beam when disabled.
|
|
1571
|
+
"has-[:disabled]:cursor-not-allowed"
|
|
1572
|
+
],
|
|
1573
|
+
{
|
|
1574
|
+
variants: {
|
|
1575
|
+
variation: {
|
|
1576
|
+
field: [
|
|
1577
|
+
"bg-input-background border border-input-border rounded-sm",
|
|
1578
|
+
// Focus (mouse or keyboard) strengthens the whole border to the
|
|
1579
|
+
// accent. Hover does NOT change colour (cursor-only affordance).
|
|
1580
|
+
"focus-within:border-input-border-accent"
|
|
1581
|
+
],
|
|
1582
|
+
line: [
|
|
1583
|
+
"border-b border-input-border",
|
|
1584
|
+
// Focus (mouse or keyboard) strengthens the bottom line to the
|
|
1585
|
+
// accent. Hover does NOT change colour (cursor-only affordance).
|
|
1586
|
+
"focus-within:border-input-border-accent"
|
|
1587
|
+
]
|
|
1588
|
+
},
|
|
1589
|
+
size: {
|
|
1590
|
+
// sm/lg heights reuse the Button scale; gap follows Figma per size.
|
|
1591
|
+
sm: "h-8 gap-1",
|
|
1592
|
+
lg: "h-10 gap-2"
|
|
1593
|
+
},
|
|
1594
|
+
error: {
|
|
1595
|
+
// Error out-ranks focus: declare the destructive border for the resting
|
|
1596
|
+
// state AND its `focus-within:` form so an errored field never recolours
|
|
1597
|
+
// to the accent when focused (a bare `border-…` alone would lose to the
|
|
1598
|
+
// variation's `focus-within:` accent utility above). No `hover:` form is
|
|
1599
|
+
// needed — hover no longer changes colour on any variation. The `error`
|
|
1600
|
+
// variant's classes are emitted after the variation's, so at equal
|
|
1601
|
+
// specificity the destructive `focus-within:` wins by order.
|
|
1602
|
+
true: "border-input-destructive-foreground-accent focus-within:border-input-destructive-foreground-accent",
|
|
1603
|
+
false: ""
|
|
1604
|
+
},
|
|
1605
|
+
// Bottom-line visibility toggle. LINE ONLY — the hide effect is applied
|
|
1606
|
+
// via a compound variant below (scoped to `variation:line`), so `field`
|
|
1607
|
+
// is unaffected. Boolean → `true`/`false`; `true` (default) keeps today's
|
|
1608
|
+
// behaviour. No classes here: `true` is the resting `border-input-border`
|
|
1609
|
+
// from the variation, and `false`'s hide is the compound variant.
|
|
1610
|
+
showBottomLine: {
|
|
1611
|
+
true: "",
|
|
1612
|
+
false: ""
|
|
1613
|
+
}
|
|
1614
|
+
},
|
|
1615
|
+
compoundVariants: [
|
|
1616
|
+
// field padding — Figma field-lg = px-3 (12px) / py-2 (8px).
|
|
1617
|
+
{ variation: "field", size: "lg", className: "px-3 py-2" },
|
|
1618
|
+
// field-sm: DERIVED. px-2 (8px = spacing/2) steps down from field-lg's
|
|
1619
|
+
// px-3 (12px) and equals py-2 for a uniform spacing/2 inset all around.
|
|
1620
|
+
{ variation: "field", size: "sm", className: "px-2 py-2" },
|
|
1621
|
+
// line — no horizontal padding (Figma px-0); vertical padding per size.
|
|
1622
|
+
{ variation: "line", size: "lg", className: "py-2" },
|
|
1623
|
+
{ variation: "line", size: "sm", className: "py-2.5" },
|
|
1624
|
+
// Hide the resting bottom line (LINE ONLY): recolour to transparent while
|
|
1625
|
+
// keeping `border-b` for a stable 1px width (no layout shift). Gated on
|
|
1626
|
+
// `error:false` so an errored field keeps its destructive rest border
|
|
1627
|
+
// (transparent would otherwise last-win over it). Focus still wins:
|
|
1628
|
+
// `focus-within:border-input-border-accent` out-ranks `border-transparent`
|
|
1629
|
+
// (later in class order), so the line reappears on focus. `field` is
|
|
1630
|
+
// untouched — this compound only matches `variation:line`.
|
|
1631
|
+
{
|
|
1632
|
+
variation: "line",
|
|
1633
|
+
showBottomLine: false,
|
|
1634
|
+
error: false,
|
|
1635
|
+
className: "border-transparent"
|
|
1636
|
+
}
|
|
1637
|
+
],
|
|
1638
|
+
defaultVariants: {
|
|
1639
|
+
variation: "field",
|
|
1640
|
+
size: "lg",
|
|
1641
|
+
error: false,
|
|
1642
|
+
showBottomLine: true
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
);
|
|
1646
|
+
function selectTriggerLabel(options, selected, placeholder) {
|
|
1647
|
+
if (selected.length === 0) {
|
|
1648
|
+
return { text: placeholder, isPlaceholder: true };
|
|
1649
|
+
}
|
|
1650
|
+
if (selected.length === 1) {
|
|
1651
|
+
const match = options.find((o) => o.value === selected[0]);
|
|
1652
|
+
return { text: match?.label ?? placeholder, isPlaceholder: match == null };
|
|
1653
|
+
}
|
|
1654
|
+
return { text: `${selected.length} selected`, isPlaceholder: false };
|
|
1655
|
+
}
|
|
1656
|
+
function SelectTrigger({
|
|
1657
|
+
variation = "field",
|
|
1658
|
+
error = false,
|
|
1659
|
+
showBottomLine = true,
|
|
1660
|
+
open = false,
|
|
1661
|
+
displayText,
|
|
1662
|
+
isPlaceholder,
|
|
1663
|
+
label,
|
|
1664
|
+
hint,
|
|
1665
|
+
leadingSlot,
|
|
1666
|
+
id,
|
|
1667
|
+
hintId,
|
|
1668
|
+
disabled,
|
|
1669
|
+
className,
|
|
1670
|
+
wrapperClassName,
|
|
1671
|
+
fieldClassName,
|
|
1672
|
+
ref,
|
|
1673
|
+
...props
|
|
1674
|
+
}) {
|
|
1675
|
+
const iconStateClass = cn(
|
|
1676
|
+
error ? "text-input-destructive-foreground-accent" : "text-input-foreground",
|
|
1677
|
+
disabled && "text-input-foreground-muted"
|
|
1678
|
+
);
|
|
1679
|
+
const HintIcon = error ? react.WarningIcon : react.InfoIcon;
|
|
1680
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-1.5", wrapperClassName), children: [
|
|
1681
|
+
label != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1682
|
+
"label",
|
|
1683
|
+
{
|
|
1684
|
+
htmlFor: id,
|
|
1685
|
+
"data-slot": "select-label",
|
|
1686
|
+
className: cn(
|
|
1687
|
+
"caption-03-medium uppercase",
|
|
1688
|
+
error ? "text-input-destructive-foreground-accent" : "text-input-foreground",
|
|
1689
|
+
disabled && "text-input-foreground-muted"
|
|
1690
|
+
),
|
|
1691
|
+
children: label
|
|
1692
|
+
}
|
|
1693
|
+
),
|
|
1694
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1695
|
+
"button",
|
|
1696
|
+
{
|
|
1697
|
+
type: "button",
|
|
1698
|
+
id,
|
|
1699
|
+
ref,
|
|
1700
|
+
role: "combobox",
|
|
1701
|
+
"data-slot": "select-trigger",
|
|
1702
|
+
"data-variation": variation,
|
|
1703
|
+
"data-error": error || void 0,
|
|
1704
|
+
disabled,
|
|
1705
|
+
"aria-invalid": error || void 0,
|
|
1706
|
+
"aria-describedby": hintId,
|
|
1707
|
+
className: cn(
|
|
1708
|
+
// Reuse the Input field-row styling (field/line + focus/error border).
|
|
1709
|
+
inputVariants({ variation, size: "lg", error, showBottomLine }),
|
|
1710
|
+
// The field is a pointer target, not a text field: pointer cursor,
|
|
1711
|
+
// and not-allowed when disabled (overrides `inputVariants`' I-beam).
|
|
1712
|
+
"cursor-pointer disabled:cursor-not-allowed",
|
|
1713
|
+
// No system focus ring: like Input (ADR-0011), this field-shaped
|
|
1714
|
+
// control signals focus purely by recolouring its 1px border to the
|
|
1715
|
+
// accent (via `inputVariants`' `focus-within:`). The focusable element
|
|
1716
|
+
// here is the field itself, so kill the browser's default outline —
|
|
1717
|
+
// otherwise it layers ~1.5px on top of the border and reads as 2px.
|
|
1718
|
+
"outline-none",
|
|
1719
|
+
// Left-align content; the value grows, the caret stays trailing.
|
|
1720
|
+
"text-left",
|
|
1721
|
+
// Icon slots inherit the shared state colour; 16px default size for
|
|
1722
|
+
// any nested svg that hasn't set its own size (mirrors Input).
|
|
1723
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
1724
|
+
iconStateClass,
|
|
1725
|
+
fieldClassName,
|
|
1726
|
+
className
|
|
1727
|
+
),
|
|
1728
|
+
...props,
|
|
1729
|
+
children: [
|
|
1730
|
+
leadingSlot != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1731
|
+
"span",
|
|
1732
|
+
{
|
|
1733
|
+
"data-slot": "select-leading",
|
|
1734
|
+
"aria-hidden": "true",
|
|
1735
|
+
className: "flex items-center",
|
|
1736
|
+
children: leadingSlot
|
|
1737
|
+
}
|
|
1738
|
+
),
|
|
1739
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1740
|
+
"span",
|
|
1741
|
+
{
|
|
1742
|
+
"data-slot": "select-value",
|
|
1743
|
+
className: cn(
|
|
1744
|
+
"body-03 w-full min-w-0 truncate",
|
|
1745
|
+
isPlaceholder ? "text-input-foreground-muted" : "text-input-foreground-accent",
|
|
1746
|
+
disabled && "text-input-foreground-muted"
|
|
1747
|
+
),
|
|
1748
|
+
children: displayText
|
|
1749
|
+
}
|
|
1750
|
+
),
|
|
1751
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1752
|
+
"span",
|
|
1753
|
+
{
|
|
1754
|
+
"data-slot": "select-trailing",
|
|
1755
|
+
"aria-hidden": "true",
|
|
1756
|
+
className: "flex items-center",
|
|
1757
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1758
|
+
react.CaretDownIcon,
|
|
1759
|
+
{
|
|
1760
|
+
weight: "fill",
|
|
1761
|
+
className: cn("size-3 transition-transform", open && "rotate-180")
|
|
1762
|
+
}
|
|
1763
|
+
)
|
|
1764
|
+
}
|
|
1765
|
+
)
|
|
1766
|
+
]
|
|
1767
|
+
}
|
|
1768
|
+
),
|
|
1769
|
+
hint != null && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1770
|
+
"p",
|
|
1771
|
+
{
|
|
1772
|
+
id: hintId,
|
|
1773
|
+
"data-slot": "select-hint",
|
|
1774
|
+
className: cn(
|
|
1775
|
+
"body-04 flex items-center gap-1",
|
|
1776
|
+
error ? "text-input-destructive-foreground-accent" : "text-input-foreground",
|
|
1777
|
+
disabled && "text-input-foreground-muted"
|
|
1778
|
+
),
|
|
1779
|
+
children: [
|
|
1780
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1781
|
+
HintIcon,
|
|
1782
|
+
{
|
|
1783
|
+
"aria-hidden": "true",
|
|
1784
|
+
weight: "regular",
|
|
1785
|
+
className: "size-4 shrink-0"
|
|
1786
|
+
}
|
|
1787
|
+
),
|
|
1788
|
+
hint
|
|
1789
|
+
]
|
|
1790
|
+
}
|
|
1791
|
+
)
|
|
1792
|
+
] });
|
|
1793
|
+
}
|
|
1794
|
+
var TYPEAHEAD_RESET_MS = 500;
|
|
1795
|
+
function nextEnabledIndex(options, from, dir) {
|
|
1796
|
+
for (let i = from + dir; i >= 0 && i < options.length; i += dir) {
|
|
1797
|
+
if (!options[i].disabled) return i;
|
|
1798
|
+
}
|
|
1799
|
+
return from;
|
|
1800
|
+
}
|
|
1801
|
+
function firstEnabledIndex(options) {
|
|
1802
|
+
const i = options.findIndex((o) => !o.disabled);
|
|
1803
|
+
return i;
|
|
1804
|
+
}
|
|
1805
|
+
function toArray(value) {
|
|
1806
|
+
if (value == null) return [];
|
|
1807
|
+
return Array.isArray(value) ? value : [value];
|
|
1808
|
+
}
|
|
1809
|
+
function useSelectListbox(options) {
|
|
1810
|
+
const {
|
|
1811
|
+
options: opts,
|
|
1812
|
+
value: valueProp,
|
|
1813
|
+
defaultValue,
|
|
1814
|
+
onValueChange,
|
|
1815
|
+
multiple = false,
|
|
1816
|
+
disabled = false,
|
|
1817
|
+
closeOnSelect = true,
|
|
1818
|
+
id,
|
|
1819
|
+
ref,
|
|
1820
|
+
placeholder = "Select"
|
|
1821
|
+
} = options;
|
|
1822
|
+
const listboxId = `${id}-listbox`;
|
|
1823
|
+
const isControlled = valueProp !== void 0;
|
|
1824
|
+
const [uncontrolled, setUncontrolled] = React21__namespace.useState(
|
|
1825
|
+
() => toArray(defaultValue)
|
|
1826
|
+
);
|
|
1827
|
+
const selected = isControlled ? toArray(valueProp) : uncontrolled;
|
|
1828
|
+
const [open, setOpen] = React21__namespace.useState(false);
|
|
1829
|
+
const [activeIndex, setActiveIndex] = React21__namespace.useState(-1);
|
|
1830
|
+
const [anchorRect, setAnchorRect] = React21__namespace.useState(null);
|
|
1831
|
+
const triggerRef = React21__namespace.useRef(null);
|
|
1832
|
+
const setTriggerRef = React21__namespace.useCallback(
|
|
1833
|
+
(node) => {
|
|
1834
|
+
triggerRef.current = node;
|
|
1835
|
+
if (typeof ref === "function") ref(node);
|
|
1836
|
+
else if (ref != null) ref.current = node;
|
|
1837
|
+
},
|
|
1838
|
+
[ref]
|
|
1839
|
+
);
|
|
1840
|
+
const typeahead = React21__namespace.useRef({ buffer: "", at: 0 });
|
|
1841
|
+
const commit = (next) => {
|
|
1842
|
+
if (!isControlled) setUncontrolled(next);
|
|
1843
|
+
onValueChange?.(multiple ? next : next[0] ?? "");
|
|
1844
|
+
};
|
|
1845
|
+
const measure = React21__namespace.useCallback(() => {
|
|
1846
|
+
const node = triggerRef.current;
|
|
1847
|
+
if (node) setAnchorRect(node.getBoundingClientRect());
|
|
1848
|
+
}, []);
|
|
1849
|
+
const openListbox = React21__namespace.useCallback(() => {
|
|
1850
|
+
measure();
|
|
1851
|
+
const selectedIndex = opts.findIndex((o) => selected.includes(o.value));
|
|
1852
|
+
setActiveIndex(
|
|
1853
|
+
selectedIndex >= 0 ? selectedIndex : firstEnabledIndex(opts)
|
|
1854
|
+
);
|
|
1855
|
+
setOpen(true);
|
|
1856
|
+
}, [measure, opts, selected]);
|
|
1857
|
+
const closeListbox = React21__namespace.useCallback((restoreFocus) => {
|
|
1858
|
+
setOpen(false);
|
|
1859
|
+
setActiveIndex(-1);
|
|
1860
|
+
setAnchorRect(null);
|
|
1861
|
+
if (restoreFocus) triggerRef.current?.focus();
|
|
1862
|
+
}, []);
|
|
1863
|
+
const handleSelect = (optionValue) => {
|
|
1864
|
+
if (multiple) {
|
|
1865
|
+
const next = selected.includes(optionValue) ? selected.filter((v) => v !== optionValue) : [...selected, optionValue];
|
|
1866
|
+
commit(next);
|
|
1867
|
+
const idx = opts.findIndex((o) => o.value === optionValue);
|
|
1868
|
+
if (idx >= 0) setActiveIndex(idx);
|
|
1869
|
+
} else {
|
|
1870
|
+
commit([optionValue]);
|
|
1871
|
+
if (closeOnSelect) {
|
|
1872
|
+
closeListbox(true);
|
|
1873
|
+
} else {
|
|
1874
|
+
const idx = opts.findIndex((o) => o.value === optionValue);
|
|
1875
|
+
if (idx >= 0) setActiveIndex(idx);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
const reset = () => {
|
|
1880
|
+
commit([]);
|
|
1881
|
+
};
|
|
1882
|
+
React21__namespace.useEffect(() => {
|
|
1883
|
+
if (!open) return;
|
|
1884
|
+
const onReflow = () => measure();
|
|
1885
|
+
window.addEventListener("scroll", onReflow, true);
|
|
1886
|
+
window.addEventListener("resize", onReflow);
|
|
1887
|
+
return () => {
|
|
1888
|
+
window.removeEventListener("scroll", onReflow, true);
|
|
1889
|
+
window.removeEventListener("resize", onReflow);
|
|
1890
|
+
};
|
|
1891
|
+
}, [open, measure]);
|
|
1892
|
+
React21__namespace.useEffect(() => {
|
|
1893
|
+
if (!open) return;
|
|
1894
|
+
const onPointerDown = (event) => {
|
|
1895
|
+
const target = event.target;
|
|
1896
|
+
const trigger = triggerRef.current;
|
|
1897
|
+
const listbox = document.getElementById(listboxId);
|
|
1898
|
+
const panel = listbox?.closest("[data-slot=select-listbox-panel]") ?? listbox;
|
|
1899
|
+
if (trigger?.contains(target) || panel?.contains(target)) return;
|
|
1900
|
+
closeListbox(false);
|
|
1901
|
+
};
|
|
1902
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
1903
|
+
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
1904
|
+
}, [open, listboxId, closeListbox]);
|
|
1905
|
+
const handleKeyDown = (event) => {
|
|
1906
|
+
if (disabled) return;
|
|
1907
|
+
switch (event.key) {
|
|
1908
|
+
case "ArrowDown": {
|
|
1909
|
+
event.preventDefault();
|
|
1910
|
+
if (!open) {
|
|
1911
|
+
openListbox();
|
|
1912
|
+
} else {
|
|
1913
|
+
setActiveIndex((i) => nextEnabledIndex(opts, i < 0 ? -1 : i, 1));
|
|
1914
|
+
}
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
case "ArrowUp": {
|
|
1918
|
+
event.preventDefault();
|
|
1919
|
+
if (!open) {
|
|
1920
|
+
openListbox();
|
|
1921
|
+
} else {
|
|
1922
|
+
setActiveIndex(
|
|
1923
|
+
(i) => i < 0 ? firstEnabledIndex(opts) : nextEnabledIndex(opts, i, -1)
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
case "Home": {
|
|
1929
|
+
if (!open) return;
|
|
1930
|
+
event.preventDefault();
|
|
1931
|
+
setActiveIndex(firstEnabledIndex(opts));
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
case "End": {
|
|
1935
|
+
if (!open) return;
|
|
1936
|
+
event.preventDefault();
|
|
1937
|
+
setActiveIndex(nextEnabledIndex(opts, opts.length, -1));
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
case "Enter":
|
|
1941
|
+
case " ": {
|
|
1942
|
+
event.preventDefault();
|
|
1943
|
+
if (!open) {
|
|
1944
|
+
openListbox();
|
|
1945
|
+
} else if (activeIndex >= 0 && !opts[activeIndex]?.disabled) {
|
|
1946
|
+
handleSelect(opts[activeIndex].value);
|
|
1947
|
+
}
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
case "Escape": {
|
|
1951
|
+
if (open) {
|
|
1952
|
+
event.preventDefault();
|
|
1953
|
+
closeListbox(true);
|
|
1954
|
+
}
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
case "Tab": {
|
|
1958
|
+
if (open) closeListbox(false);
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
default: {
|
|
1962
|
+
if (event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {
|
|
1963
|
+
if (!open) openListbox();
|
|
1964
|
+
const now = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
1965
|
+
const t = typeahead.current;
|
|
1966
|
+
t.buffer = now - t.at > TYPEAHEAD_RESET_MS ? event.key : t.buffer + event.key;
|
|
1967
|
+
t.at = now;
|
|
1968
|
+
const query = t.buffer.toLowerCase();
|
|
1969
|
+
const match = opts.findIndex(
|
|
1970
|
+
(o) => !o.disabled && o.label.toLowerCase().startsWith(query)
|
|
1971
|
+
);
|
|
1972
|
+
if (match >= 0) setActiveIndex(match);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
const handleTriggerClick = () => {
|
|
1978
|
+
if (disabled) return;
|
|
1979
|
+
if (open) closeListbox(true);
|
|
1980
|
+
else openListbox();
|
|
1981
|
+
};
|
|
1982
|
+
const { text, isPlaceholder } = selectTriggerLabel(
|
|
1983
|
+
opts,
|
|
1984
|
+
selected,
|
|
1985
|
+
placeholder
|
|
1986
|
+
);
|
|
1987
|
+
const activeId = open && activeIndex >= 0 ? `${listboxId}-opt-${activeIndex}` : void 0;
|
|
1988
|
+
return {
|
|
1989
|
+
selected,
|
|
1990
|
+
open,
|
|
1991
|
+
setTriggerRef,
|
|
1992
|
+
listboxId,
|
|
1993
|
+
activeId,
|
|
1994
|
+
triggerProps: {
|
|
1995
|
+
onClick: handleTriggerClick,
|
|
1996
|
+
onKeyDown: handleKeyDown,
|
|
1997
|
+
"aria-haspopup": "listbox",
|
|
1998
|
+
"aria-expanded": open,
|
|
1999
|
+
"aria-controls": open ? listboxId : void 0,
|
|
2000
|
+
"aria-activedescendant": activeId
|
|
2001
|
+
},
|
|
2002
|
+
listboxProps: {
|
|
2003
|
+
id: listboxId,
|
|
2004
|
+
options: opts,
|
|
2005
|
+
selected,
|
|
2006
|
+
multiple,
|
|
2007
|
+
activeIndex,
|
|
2008
|
+
anchorRect,
|
|
2009
|
+
onSelect: handleSelect,
|
|
2010
|
+
onActiveChange: setActiveIndex
|
|
2011
|
+
},
|
|
2012
|
+
closeListbox,
|
|
2013
|
+
text,
|
|
2014
|
+
isPlaceholder,
|
|
2015
|
+
reset
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
function Select({
|
|
2019
|
+
options,
|
|
2020
|
+
value: valueProp,
|
|
2021
|
+
defaultValue,
|
|
2022
|
+
onValueChange,
|
|
2023
|
+
multiple = false,
|
|
2024
|
+
variation = "field",
|
|
2025
|
+
placeholder = "Select",
|
|
2026
|
+
label,
|
|
2027
|
+
error = false,
|
|
2028
|
+
hint,
|
|
2029
|
+
leadingSlot,
|
|
2030
|
+
showBottomLine = true,
|
|
2031
|
+
disabled,
|
|
2032
|
+
id: idProp,
|
|
2033
|
+
className,
|
|
2034
|
+
wrapperClassName,
|
|
2035
|
+
fieldClassName,
|
|
2036
|
+
ref,
|
|
2037
|
+
...props
|
|
2038
|
+
}) {
|
|
2039
|
+
const generatedId = React21__namespace.useId();
|
|
2040
|
+
const id = idProp ?? generatedId;
|
|
2041
|
+
const hintId = hint != null ? `${id}-hint` : void 0;
|
|
2042
|
+
const {
|
|
2043
|
+
open,
|
|
2044
|
+
setTriggerRef,
|
|
2045
|
+
triggerProps,
|
|
2046
|
+
listboxProps,
|
|
2047
|
+
text,
|
|
2048
|
+
isPlaceholder
|
|
2049
|
+
} = useSelectListbox({
|
|
2050
|
+
options,
|
|
2051
|
+
value: valueProp,
|
|
2052
|
+
defaultValue,
|
|
2053
|
+
onValueChange,
|
|
2054
|
+
multiple,
|
|
2055
|
+
disabled,
|
|
2056
|
+
id,
|
|
2057
|
+
ref,
|
|
2058
|
+
placeholder
|
|
2059
|
+
});
|
|
2060
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2061
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2062
|
+
SelectTrigger,
|
|
2063
|
+
{
|
|
2064
|
+
ref: setTriggerRef,
|
|
2065
|
+
id,
|
|
2066
|
+
variation,
|
|
2067
|
+
error,
|
|
2068
|
+
showBottomLine,
|
|
2069
|
+
open,
|
|
2070
|
+
displayText: text,
|
|
2071
|
+
isPlaceholder,
|
|
2072
|
+
label,
|
|
2073
|
+
hint,
|
|
2074
|
+
hintId,
|
|
2075
|
+
leadingSlot,
|
|
2076
|
+
disabled,
|
|
2077
|
+
className,
|
|
2078
|
+
wrapperClassName,
|
|
2079
|
+
fieldClassName,
|
|
2080
|
+
...triggerProps,
|
|
2081
|
+
...props
|
|
2082
|
+
}
|
|
2083
|
+
),
|
|
2084
|
+
open && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2085
|
+
SelectListbox,
|
|
2086
|
+
{
|
|
2087
|
+
...listboxProps,
|
|
2088
|
+
"aria-labelledby": label != null ? id : void 0,
|
|
2089
|
+
"aria-label": label == null ? props["aria-label"] : void 0
|
|
2090
|
+
}
|
|
2091
|
+
)
|
|
2092
|
+
] });
|
|
2093
|
+
}
|
|
2094
|
+
var filterTriggerVariants = classVarianceAuthority.cva(
|
|
2095
|
+
[
|
|
2096
|
+
// Layout — chip row: label + optional badge + caret, 6px gap (spacing/1-5).
|
|
2097
|
+
"inline-flex h-10 w-fit shrink-0 items-center gap-1.5",
|
|
2098
|
+
"rounded-sm px-3",
|
|
2099
|
+
"cursor-pointer text-left align-middle transition-colors",
|
|
2100
|
+
// Surface — card/neutral/primary (the `neutral` segment is stripped in the
|
|
2101
|
+
// token name transform → `card-primary-*`).
|
|
2102
|
+
"bg-card-primary-background border border-card-primary-border",
|
|
2103
|
+
// System focus ring (ADR-0010/0014). `outline-solid` restores the style the
|
|
2104
|
+
// base `outline-none` zeroed (TW4 gotcha) so the 2px ring actually paints.
|
|
2105
|
+
"outline-none",
|
|
2106
|
+
"focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus",
|
|
2107
|
+
// Decorative svgs (caret) never intercept the click and keep explicit sizes.
|
|
2108
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0"
|
|
2109
|
+
],
|
|
2110
|
+
{
|
|
2111
|
+
variants: {
|
|
2112
|
+
disabled: {
|
|
2113
|
+
// Muted + not-allowed; the ring is suppressed (a disabled control is not
|
|
2114
|
+
// focusable, but belt-and-braces if a consumer forces focus).
|
|
2115
|
+
true: "cursor-not-allowed focus-visible:outline-none",
|
|
2116
|
+
false: ""
|
|
2117
|
+
}
|
|
2118
|
+
},
|
|
2119
|
+
defaultVariants: {
|
|
2120
|
+
disabled: false
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
);
|
|
2124
|
+
var filterCountBadgeVariants = classVarianceAuthority.cva([
|
|
2125
|
+
"inline-flex h-5 min-w-5 items-center justify-center px-0.5",
|
|
2126
|
+
"rounded-xs bg-brand-primary-background",
|
|
2127
|
+
// `body-04` = the Figma badge text style (Archivo Regular 12/16) — NOT
|
|
2128
|
+
// a `caption-*` utility (those render Chakra Petch, `font-caption`).
|
|
2129
|
+
"body-04 text-brand-primary-foreground"
|
|
2130
|
+
]);
|
|
2131
|
+
function FilterTrigger({
|
|
2132
|
+
displayText,
|
|
2133
|
+
count = 0,
|
|
2134
|
+
open = false,
|
|
2135
|
+
disabled = false,
|
|
2136
|
+
id,
|
|
2137
|
+
className,
|
|
2138
|
+
ref,
|
|
2139
|
+
...props
|
|
2140
|
+
}) {
|
|
2141
|
+
const Caret = open ? react.CaretUpIcon : react.CaretDownIcon;
|
|
2142
|
+
const caretColor = disabled ? "text-card-primary-foreground-muted" : open ? "text-card-primary-foreground" : "text-card-primary-foreground-muted group-hover:text-card-primary-foreground";
|
|
2143
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2144
|
+
"button",
|
|
2145
|
+
{
|
|
2146
|
+
type: "button",
|
|
2147
|
+
id,
|
|
2148
|
+
ref,
|
|
2149
|
+
role: "combobox",
|
|
2150
|
+
"data-slot": "filter-trigger",
|
|
2151
|
+
"data-open": open || void 0,
|
|
2152
|
+
disabled,
|
|
2153
|
+
className: cn("group", filterTriggerVariants({ disabled }), className),
|
|
2154
|
+
...props,
|
|
2155
|
+
children: [
|
|
2156
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2157
|
+
"span",
|
|
2158
|
+
{
|
|
2159
|
+
"data-slot": "filter-label",
|
|
2160
|
+
className: cn(
|
|
2161
|
+
"body-03 truncate",
|
|
2162
|
+
disabled ? "text-card-primary-foreground-muted" : "text-card-primary-foreground"
|
|
2163
|
+
),
|
|
2164
|
+
children: displayText
|
|
2165
|
+
}
|
|
2166
|
+
),
|
|
2167
|
+
count > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2168
|
+
"span",
|
|
2169
|
+
{
|
|
2170
|
+
"data-slot": "filter-count",
|
|
2171
|
+
"aria-hidden": "true",
|
|
2172
|
+
className: filterCountBadgeVariants(),
|
|
2173
|
+
children: count
|
|
2174
|
+
}
|
|
2175
|
+
),
|
|
2176
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2177
|
+
"span",
|
|
2178
|
+
{
|
|
2179
|
+
"data-slot": "filter-caret",
|
|
2180
|
+
"aria-hidden": "true",
|
|
2181
|
+
className: "flex items-center",
|
|
2182
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2183
|
+
Caret,
|
|
2184
|
+
{
|
|
2185
|
+
weight: "fill",
|
|
2186
|
+
className: cn("size-3 transition-transform", caretColor)
|
|
2187
|
+
}
|
|
2188
|
+
)
|
|
2189
|
+
}
|
|
2190
|
+
)
|
|
2191
|
+
]
|
|
2192
|
+
}
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
function Filter({
|
|
2196
|
+
options,
|
|
2197
|
+
value: valueProp,
|
|
2198
|
+
defaultValue,
|
|
2199
|
+
onValueChange,
|
|
2200
|
+
multiple = false,
|
|
2201
|
+
label,
|
|
2202
|
+
disabled,
|
|
2203
|
+
id: idProp,
|
|
2204
|
+
className,
|
|
2205
|
+
ref,
|
|
2206
|
+
...props
|
|
2207
|
+
}) {
|
|
2208
|
+
const generatedId = React21__namespace.useId();
|
|
2209
|
+
const id = idProp ?? generatedId;
|
|
2210
|
+
const {
|
|
2211
|
+
selected,
|
|
2212
|
+
open,
|
|
2213
|
+
setTriggerRef,
|
|
2214
|
+
triggerProps,
|
|
2215
|
+
listboxProps,
|
|
2216
|
+
reset,
|
|
2217
|
+
text
|
|
2218
|
+
} = useSelectListbox({
|
|
2219
|
+
options,
|
|
2220
|
+
value: valueProp,
|
|
2221
|
+
defaultValue,
|
|
2222
|
+
onValueChange,
|
|
2223
|
+
multiple,
|
|
2224
|
+
disabled,
|
|
2225
|
+
// Filter's dropdown persists after a pick (single stays open like multi).
|
|
2226
|
+
closeOnSelect: false,
|
|
2227
|
+
id,
|
|
2228
|
+
ref,
|
|
2229
|
+
// The single-select empty state shows the filter `label`, not a generic
|
|
2230
|
+
// "Select" placeholder — so `selectTriggerLabel`'s placeholder IS `label`.
|
|
2231
|
+
// (In multi the trigger text is always `label` regardless — see below.)
|
|
2232
|
+
placeholder: label
|
|
2233
|
+
});
|
|
2234
|
+
const displayText = multiple ? label : text;
|
|
2235
|
+
const count = multiple ? selected.length : 0;
|
|
2236
|
+
const canReset = selected.length > 0;
|
|
2237
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2238
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2239
|
+
FilterTrigger,
|
|
2240
|
+
{
|
|
2241
|
+
ref: setTriggerRef,
|
|
2242
|
+
id,
|
|
2243
|
+
displayText,
|
|
2244
|
+
count,
|
|
2245
|
+
open,
|
|
2246
|
+
disabled,
|
|
2247
|
+
"aria-label": label,
|
|
2248
|
+
className,
|
|
2249
|
+
...triggerProps,
|
|
2250
|
+
...props
|
|
2251
|
+
}
|
|
2252
|
+
),
|
|
2253
|
+
open && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2254
|
+
SelectListbox,
|
|
2255
|
+
{
|
|
2256
|
+
...listboxProps,
|
|
2257
|
+
widthMode: "auto",
|
|
2258
|
+
"aria-label": label,
|
|
2259
|
+
footer: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2260
|
+
"button",
|
|
2261
|
+
{
|
|
2262
|
+
type: "button",
|
|
2263
|
+
"data-slot": "filter-reset",
|
|
2264
|
+
disabled: !canReset,
|
|
2265
|
+
onClick: () => {
|
|
2266
|
+
if (canReset) reset();
|
|
2267
|
+
},
|
|
2268
|
+
className: cn(
|
|
2269
|
+
// A ghost button styled as `button/neutral/secondary`
|
|
2270
|
+
// (`button-secondary-*`): foreground when enabled, muted when
|
|
2271
|
+
// disabled. System focus ring (ADR-0010/0014) — `outline-solid`
|
|
2272
|
+
// beside `outline-2` so it actually paints (TW4 gotcha).
|
|
2273
|
+
"button-01 inline-flex h-8 items-center rounded-xs bg-transparent",
|
|
2274
|
+
"outline-none transition-colors",
|
|
2275
|
+
"focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus",
|
|
2276
|
+
canReset ? "cursor-pointer text-button-secondary-foreground hover:text-button-secondary-foreground-accent" : "cursor-not-allowed text-button-secondary-foreground-muted"
|
|
2277
|
+
),
|
|
2278
|
+
children: "Reset"
|
|
2279
|
+
}
|
|
2280
|
+
)
|
|
2281
|
+
}
|
|
2282
|
+
)
|
|
2283
|
+
] });
|
|
2284
|
+
}
|
|
2285
|
+
var inlineSelectVariants = classVarianceAuthority.cva(
|
|
2286
|
+
[
|
|
2287
|
+
// Inline text run: label + caret, 4px gap (Figma spacing/1), baseline-ish
|
|
2288
|
+
// centered. `w-fit` so the ring hugs the content, not a full-width box.
|
|
2289
|
+
"inline-flex w-fit items-center gap-1",
|
|
2290
|
+
// Behaves as inline text — no field padding/background/border.
|
|
2291
|
+
"cursor-pointer bg-transparent p-0 text-left align-middle",
|
|
2292
|
+
"transition-colors",
|
|
2293
|
+
// System focus ring (ADR-0010): momentary-control treatment, not a border
|
|
2294
|
+
// recolor. Kill the default outline, then draw the 2px --focus ring with a
|
|
2295
|
+
// 2px gap on :focus-visible only. `rounded-xs` matches the Figma ring.
|
|
2296
|
+
"rounded-xs outline-none",
|
|
2297
|
+
"focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus",
|
|
2298
|
+
// Decorative svgs (caret / leading slot) never intercept the click and keep
|
|
2299
|
+
// their explicit sizes.
|
|
2300
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0"
|
|
2301
|
+
],
|
|
2302
|
+
{
|
|
2303
|
+
variants: {
|
|
2304
|
+
variation: {
|
|
2305
|
+
primary: "",
|
|
2306
|
+
secondary: ""
|
|
2307
|
+
},
|
|
2308
|
+
disabled: {
|
|
2309
|
+
// Muted + not-allowed, and the focus ring is suppressed (a disabled
|
|
2310
|
+
// control is not focusable, but belt-and-braces if a consumer forces it).
|
|
2311
|
+
true: "cursor-not-allowed focus-visible:outline-none",
|
|
2312
|
+
false: ""
|
|
2313
|
+
}
|
|
2314
|
+
},
|
|
2315
|
+
defaultVariants: {
|
|
2316
|
+
variation: "primary",
|
|
2317
|
+
disabled: false
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
);
|
|
2321
|
+
function InlineSelectTrigger({
|
|
2322
|
+
variation = "primary",
|
|
2323
|
+
disabled = false,
|
|
2324
|
+
displayText,
|
|
2325
|
+
isPlaceholder,
|
|
2326
|
+
open = false,
|
|
2327
|
+
leadingSlot,
|
|
2328
|
+
className,
|
|
2329
|
+
ref,
|
|
2330
|
+
...props
|
|
2331
|
+
}) {
|
|
2332
|
+
const valueColor = variation === "secondary" ? "text-card-brand-secondary-foreground" : "text-card-primary-foreground";
|
|
2333
|
+
const mutedColor = variation === "secondary" ? "text-card-brand-secondary-foreground-muted" : "text-card-primary-foreground-muted";
|
|
2334
|
+
const labelColor = disabled || isPlaceholder ? mutedColor : valueColor;
|
|
2335
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2336
|
+
"button",
|
|
2337
|
+
{
|
|
2338
|
+
type: "button",
|
|
2339
|
+
ref,
|
|
2340
|
+
role: "combobox",
|
|
2341
|
+
"data-slot": "inline-select-trigger",
|
|
2342
|
+
"data-variation": variation,
|
|
2343
|
+
disabled,
|
|
2344
|
+
className: cn(inlineSelectVariants({ variation, disabled }), className),
|
|
2345
|
+
...props,
|
|
2346
|
+
children: [
|
|
2347
|
+
leadingSlot != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2348
|
+
"span",
|
|
2349
|
+
{
|
|
2350
|
+
"data-slot": "inline-select-leading",
|
|
2351
|
+
"aria-hidden": "true",
|
|
2352
|
+
className: cn(
|
|
2353
|
+
"flex items-center [&_svg:not([class*='size-'])]:size-4",
|
|
2354
|
+
// The leading slot follows the caret/muted colour unless a real
|
|
2355
|
+
// value is shown, then it follows the value colour (like the label).
|
|
2356
|
+
labelColor
|
|
2357
|
+
),
|
|
2358
|
+
children: leadingSlot
|
|
2359
|
+
}
|
|
2360
|
+
),
|
|
2361
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2362
|
+
"span",
|
|
2363
|
+
{
|
|
2364
|
+
"data-slot": "inline-select-value",
|
|
2365
|
+
className: cn("body-03 truncate", labelColor),
|
|
2366
|
+
children: displayText
|
|
2367
|
+
}
|
|
2368
|
+
),
|
|
2369
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2370
|
+
"span",
|
|
2371
|
+
{
|
|
2372
|
+
"data-slot": "inline-select-caret",
|
|
2373
|
+
"aria-hidden": "true",
|
|
2374
|
+
className: "flex items-center",
|
|
2375
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2376
|
+
react.CaretDownIcon,
|
|
2377
|
+
{
|
|
2378
|
+
weight: "fill",
|
|
2379
|
+
className: cn(
|
|
2380
|
+
"size-3 transition-transform",
|
|
2381
|
+
// Caret uses the muted variation colour (Figma resting state).
|
|
2382
|
+
mutedColor,
|
|
2383
|
+
open && "rotate-180"
|
|
2384
|
+
)
|
|
2385
|
+
}
|
|
2386
|
+
)
|
|
2387
|
+
}
|
|
2388
|
+
)
|
|
2389
|
+
]
|
|
2390
|
+
}
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2393
|
+
function InlineSelect({
|
|
2394
|
+
options,
|
|
2395
|
+
value: valueProp,
|
|
2396
|
+
defaultValue,
|
|
2397
|
+
onValueChange,
|
|
2398
|
+
variation = "primary",
|
|
2399
|
+
placeholder = "Select",
|
|
2400
|
+
leadingSlot,
|
|
2401
|
+
disabled,
|
|
2402
|
+
name,
|
|
2403
|
+
id: idProp,
|
|
2404
|
+
className,
|
|
2405
|
+
ref,
|
|
2406
|
+
...props
|
|
2407
|
+
}) {
|
|
2408
|
+
const generatedId = React21__namespace.useId();
|
|
2409
|
+
const id = idProp ?? generatedId;
|
|
2410
|
+
const {
|
|
2411
|
+
selected,
|
|
2412
|
+
open,
|
|
2413
|
+
setTriggerRef,
|
|
2414
|
+
triggerProps,
|
|
2415
|
+
listboxProps,
|
|
2416
|
+
text,
|
|
2417
|
+
isPlaceholder
|
|
2418
|
+
} = useSelectListbox({
|
|
2419
|
+
options,
|
|
2420
|
+
// Normalize the single-select public value to the hook's array form.
|
|
2421
|
+
value: valueProp,
|
|
2422
|
+
defaultValue,
|
|
2423
|
+
// The hook emits `string | string[]`; in single mode it is always a
|
|
2424
|
+
// `string`, so narrow before handing it to the public single callback.
|
|
2425
|
+
onValueChange: onValueChange ? (next) => onValueChange(Array.isArray(next) ? next[0] ?? "" : next) : void 0,
|
|
2426
|
+
multiple: false,
|
|
2427
|
+
disabled,
|
|
2428
|
+
id,
|
|
2429
|
+
ref,
|
|
2430
|
+
placeholder
|
|
2431
|
+
});
|
|
2432
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2433
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2434
|
+
InlineSelectTrigger,
|
|
2435
|
+
{
|
|
2436
|
+
ref: setTriggerRef,
|
|
2437
|
+
id,
|
|
2438
|
+
variation,
|
|
2439
|
+
open,
|
|
2440
|
+
displayText: text,
|
|
2441
|
+
isPlaceholder,
|
|
2442
|
+
leadingSlot,
|
|
2443
|
+
disabled,
|
|
2444
|
+
className,
|
|
2445
|
+
...triggerProps,
|
|
2446
|
+
...props
|
|
2447
|
+
}
|
|
2448
|
+
),
|
|
2449
|
+
name != null && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected[0] ?? "" }),
|
|
2450
|
+
open && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2451
|
+
SelectListbox,
|
|
2452
|
+
{
|
|
2453
|
+
...listboxProps,
|
|
2454
|
+
widthMode: "auto",
|
|
2455
|
+
"aria-label": props["aria-label"]
|
|
2456
|
+
}
|
|
2457
|
+
)
|
|
2458
|
+
] });
|
|
2459
|
+
}
|
|
2460
|
+
function Input({
|
|
2461
|
+
className,
|
|
2462
|
+
fieldClassName,
|
|
2463
|
+
wrapperClassName,
|
|
2464
|
+
variation = "field",
|
|
2465
|
+
size = "lg",
|
|
2466
|
+
error = false,
|
|
2467
|
+
// LINE-only bottom-line toggle. Destructured OUT of `props` so it never
|
|
2468
|
+
// spreads onto the native <input> (invalid DOM attribute → React warning).
|
|
2469
|
+
showBottomLine = true,
|
|
2470
|
+
label,
|
|
2471
|
+
optional = false,
|
|
2472
|
+
hint,
|
|
2473
|
+
leftSlot,
|
|
2474
|
+
rightSlot,
|
|
2475
|
+
disabled,
|
|
2476
|
+
id: idProp,
|
|
2477
|
+
ref,
|
|
2478
|
+
...props
|
|
2479
|
+
}) {
|
|
2480
|
+
const generatedId = React21__namespace.useId();
|
|
2481
|
+
const id = idProp ?? generatedId;
|
|
2482
|
+
const hintId = hint ? `${id}-hint` : void 0;
|
|
2483
|
+
const iconStateClass = cn(
|
|
2484
|
+
error ? "text-input-destructive-foreground-accent" : "text-input-foreground",
|
|
2485
|
+
disabled && "text-input-foreground-muted"
|
|
2486
|
+
);
|
|
2487
|
+
const HintIcon = error ? react.WarningIcon : react.InfoIcon;
|
|
2488
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-1.5", wrapperClassName), children: [
|
|
2489
|
+
label != null && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2490
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2491
|
+
"label",
|
|
2492
|
+
{
|
|
2493
|
+
htmlFor: id,
|
|
2494
|
+
"data-slot": "input-label",
|
|
2495
|
+
className: cn(
|
|
2496
|
+
"caption-03-medium uppercase",
|
|
2497
|
+
error ? "text-input-destructive-foreground-accent" : "text-input-foreground",
|
|
2498
|
+
disabled && "text-input-foreground-muted"
|
|
2499
|
+
),
|
|
2500
|
+
children: label
|
|
2501
|
+
}
|
|
2502
|
+
),
|
|
2503
|
+
optional && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2504
|
+
"span",
|
|
2505
|
+
{
|
|
2506
|
+
"data-slot": "input-optional",
|
|
2507
|
+
className: "body-04 text-input-foreground-muted",
|
|
2508
|
+
children: "Optional"
|
|
2509
|
+
}
|
|
2510
|
+
)
|
|
2511
|
+
] }),
|
|
2512
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2513
|
+
"div",
|
|
2514
|
+
{
|
|
2515
|
+
"data-slot": "input-field",
|
|
2516
|
+
"data-variation": variation,
|
|
2517
|
+
"data-error": error || void 0,
|
|
2518
|
+
className: cn(
|
|
2519
|
+
inputVariants({ variation, size, error, showBottomLine }),
|
|
2520
|
+
// Icon slots inherit the shared state colour; 16px default size.
|
|
2521
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
2522
|
+
iconStateClass,
|
|
2523
|
+
fieldClassName
|
|
2524
|
+
),
|
|
2525
|
+
children: [
|
|
2526
|
+
leftSlot != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2527
|
+
"span",
|
|
2528
|
+
{
|
|
2529
|
+
"data-slot": "input-leading",
|
|
2530
|
+
"aria-hidden": "true",
|
|
2531
|
+
className: "flex items-center",
|
|
2532
|
+
children: leftSlot
|
|
2533
|
+
}
|
|
2534
|
+
),
|
|
2535
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2536
|
+
"input",
|
|
2537
|
+
{
|
|
2538
|
+
id,
|
|
2539
|
+
"data-slot": "input",
|
|
2540
|
+
disabled,
|
|
2541
|
+
"aria-invalid": error || void 0,
|
|
2542
|
+
"aria-describedby": hintId,
|
|
2543
|
+
className: cn(
|
|
2544
|
+
// Typography (Figma `body-03`) + fill the row. `min-w-0` lets the
|
|
2545
|
+
// input shrink inside the flex row instead of overflowing.
|
|
2546
|
+
"body-03 w-full min-w-0 bg-transparent outline-none",
|
|
2547
|
+
// Typed text + caret colour (Figma `input-foreground-accent`).
|
|
2548
|
+
"text-input-foreground-accent caret-input-foreground-accent",
|
|
2549
|
+
// Placeholder colour (Figma `input-foreground-muted`).
|
|
2550
|
+
"placeholder:text-input-foreground-muted",
|
|
2551
|
+
// Disabled text goes muted; keep the not-allowed cursor.
|
|
2552
|
+
"disabled:cursor-not-allowed disabled:text-input-foreground-muted",
|
|
2553
|
+
className
|
|
2554
|
+
),
|
|
2555
|
+
ref,
|
|
2556
|
+
...props
|
|
2557
|
+
}
|
|
2558
|
+
),
|
|
2559
|
+
rightSlot != null && // NOT aria-hidden: the trailing slot may host an interactive control
|
|
2560
|
+
// (e.g. SearchBar's clear button) that must stay reachable by AT +
|
|
2561
|
+
// keyboard, and a focusable element inside aria-hidden is an ARIA
|
|
2562
|
+
// violation. Decorative bare-<svg> icons are silent to AT on their own
|
|
2563
|
+
// (no role / no accessible name), so dropping aria-hidden here does not
|
|
2564
|
+
// make them announced. The leading slot stays decorative (aria-hidden).
|
|
2565
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "data-slot": "input-trailing", className: "flex items-center", children: rightSlot })
|
|
2566
|
+
]
|
|
2567
|
+
}
|
|
2568
|
+
),
|
|
2569
|
+
hint != null && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2570
|
+
"p",
|
|
2571
|
+
{
|
|
2572
|
+
id: hintId,
|
|
2573
|
+
"data-slot": "input-hint",
|
|
2574
|
+
className: cn(
|
|
2575
|
+
"body-04 flex items-center gap-1",
|
|
2576
|
+
error ? "text-input-destructive-foreground-accent" : "text-input-foreground",
|
|
2577
|
+
disabled && "text-input-foreground-muted"
|
|
2578
|
+
),
|
|
2579
|
+
children: [
|
|
2580
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2581
|
+
HintIcon,
|
|
2582
|
+
{
|
|
2583
|
+
"aria-hidden": "true",
|
|
2584
|
+
weight: "regular",
|
|
2585
|
+
className: "size-4 shrink-0"
|
|
2586
|
+
}
|
|
2587
|
+
),
|
|
2588
|
+
hint
|
|
2589
|
+
]
|
|
2590
|
+
}
|
|
2591
|
+
)
|
|
2592
|
+
] });
|
|
2593
|
+
}
|
|
2594
|
+
var panelVariants = classVarianceAuthority.cva([
|
|
2595
|
+
"flex w-80 flex-col gap-4 rounded-md border border-card-border bg-card-background p-5"
|
|
2596
|
+
]);
|
|
2597
|
+
var panelHeaderVariants = classVarianceAuthority.cva(["flex items-start gap-4"]);
|
|
2598
|
+
var panelHeaderContentVariants = classVarianceAuthority.cva([
|
|
2599
|
+
"flex min-w-0 flex-1 items-center gap-3"
|
|
2600
|
+
]);
|
|
2601
|
+
var panelHeaderIconVariants = classVarianceAuthority.cva([
|
|
2602
|
+
"flex items-center text-card-foreground [&_svg]:size-6 [&_svg]:shrink-0"
|
|
2603
|
+
]);
|
|
2604
|
+
var panelHeaderTextVariants = classVarianceAuthority.cva(["flex min-w-0 flex-col"]);
|
|
2605
|
+
var panelCloseButtonVariants = classVarianceAuthority.cva([
|
|
2606
|
+
"flex shrink-0 cursor-pointer items-center justify-center rounded-xs",
|
|
2607
|
+
"text-card-foreground-muted transition-colors hover:text-card-foreground",
|
|
2608
|
+
"[&_svg]:size-4 [&_svg]:shrink-0",
|
|
2609
|
+
"outline-none focus-visible:text-card-foreground focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus"
|
|
2610
|
+
]);
|
|
2611
|
+
var panelBodyVariants = classVarianceAuthority.cva(["text-card-foreground"]);
|
|
2612
|
+
var panelFooterVariants = classVarianceAuthority.cva(["flex items-start gap-2"]);
|
|
2613
|
+
var PanelContext = React21__namespace.createContext(null);
|
|
2614
|
+
function usePanelContext() {
|
|
2615
|
+
return React21__namespace.useContext(PanelContext);
|
|
2616
|
+
}
|
|
2617
|
+
function PanelRoot({
|
|
2618
|
+
className,
|
|
2619
|
+
children,
|
|
2620
|
+
ref,
|
|
2621
|
+
"aria-label": ariaLabel,
|
|
2622
|
+
"aria-labelledby": ariaLabelledby,
|
|
2623
|
+
...props
|
|
2624
|
+
}) {
|
|
2625
|
+
const reactId = React21__namespace.useId();
|
|
2626
|
+
const titleId = `${reactId}-title`;
|
|
2627
|
+
const [hasTitle, setHasTitle] = React21__namespace.useState(false);
|
|
2628
|
+
const labelledBy = hasTitle ? titleId : ariaLabelledby;
|
|
2629
|
+
const hasName = Boolean(labelledBy || ariaLabel);
|
|
2630
|
+
const value = { titleId, setHasTitle };
|
|
2631
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PanelContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2632
|
+
"div",
|
|
2633
|
+
{
|
|
2634
|
+
ref,
|
|
2635
|
+
role: hasName ? "region" : void 0,
|
|
2636
|
+
"aria-label": ariaLabel,
|
|
2637
|
+
"aria-labelledby": labelledBy,
|
|
2638
|
+
"data-slot": "panel",
|
|
2639
|
+
className: cn(panelVariants(), className),
|
|
2640
|
+
...props,
|
|
2641
|
+
children
|
|
2642
|
+
}
|
|
2643
|
+
) });
|
|
2644
|
+
}
|
|
2645
|
+
function PanelHeader({
|
|
2646
|
+
icon,
|
|
2647
|
+
title,
|
|
2648
|
+
description,
|
|
2649
|
+
showClose = true,
|
|
2650
|
+
onClose,
|
|
2651
|
+
className,
|
|
2652
|
+
ref,
|
|
2653
|
+
...props
|
|
2654
|
+
}) {
|
|
2655
|
+
const context = usePanelContext();
|
|
2656
|
+
const titleId = context?.titleId;
|
|
2657
|
+
const setHasTitle = context?.setHasTitle;
|
|
2658
|
+
const hasDescription = description != null;
|
|
2659
|
+
React21__namespace.useEffect(() => {
|
|
2660
|
+
if (!setHasTitle) return;
|
|
2661
|
+
setHasTitle(true);
|
|
2662
|
+
return () => setHasTitle(false);
|
|
2663
|
+
}, [setHasTitle]);
|
|
2664
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2665
|
+
"div",
|
|
2666
|
+
{
|
|
2667
|
+
ref,
|
|
2668
|
+
"data-slot": "panel-header",
|
|
2669
|
+
className: cn(panelHeaderVariants(), className),
|
|
2670
|
+
...props,
|
|
2671
|
+
children: [
|
|
2672
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: panelHeaderContentVariants(), children: [
|
|
2673
|
+
icon != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2674
|
+
"span",
|
|
2675
|
+
{
|
|
2676
|
+
"data-slot": "panel-header-icon",
|
|
2677
|
+
"aria-hidden": "true",
|
|
2678
|
+
className: panelHeaderIconVariants(),
|
|
2679
|
+
children: icon
|
|
2680
|
+
}
|
|
2681
|
+
),
|
|
2682
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: panelHeaderTextVariants(), children: [
|
|
2683
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { id: titleId, className: "body-02-medium text-card-foreground", children: title }),
|
|
2684
|
+
hasDescription && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "body-03 text-card-foreground-muted", children: description })
|
|
2685
|
+
] })
|
|
2686
|
+
] }),
|
|
2687
|
+
showClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2688
|
+
"button",
|
|
2689
|
+
{
|
|
2690
|
+
type: "button",
|
|
2691
|
+
"data-slot": "panel-header-close",
|
|
2692
|
+
"aria-label": "Close",
|
|
2693
|
+
className: panelCloseButtonVariants(),
|
|
2694
|
+
onClick: () => onClose?.(),
|
|
2695
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.XIcon, { weight: "regular" })
|
|
2696
|
+
}
|
|
2697
|
+
)
|
|
2698
|
+
]
|
|
2699
|
+
}
|
|
2700
|
+
);
|
|
2701
|
+
}
|
|
2702
|
+
function PanelBody({ className, children, ref, ...props }) {
|
|
2703
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2704
|
+
"div",
|
|
2705
|
+
{
|
|
2706
|
+
ref,
|
|
2707
|
+
"data-slot": "panel-body",
|
|
2708
|
+
className: cn(panelBodyVariants(), className),
|
|
2709
|
+
...props,
|
|
2710
|
+
children
|
|
2711
|
+
}
|
|
2712
|
+
);
|
|
2713
|
+
}
|
|
2714
|
+
function PanelFooter({ className, children, ref, ...props }) {
|
|
2715
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2716
|
+
"div",
|
|
2717
|
+
{
|
|
2718
|
+
ref,
|
|
2719
|
+
"data-slot": "panel-footer",
|
|
2720
|
+
className: cn(panelFooterVariants(), className),
|
|
2721
|
+
...props,
|
|
2722
|
+
children
|
|
2723
|
+
}
|
|
2724
|
+
);
|
|
2725
|
+
}
|
|
2726
|
+
var Panel = {
|
|
2727
|
+
Root: PanelRoot,
|
|
2728
|
+
Header: PanelHeader,
|
|
2729
|
+
Body: PanelBody,
|
|
2730
|
+
Footer: PanelFooter
|
|
2731
|
+
};
|
|
2732
|
+
var propertyGridCellVariants = classVarianceAuthority.cva(
|
|
2733
|
+
[
|
|
2734
|
+
// Layout — 40px row, single line, slots on a horizontal axis.
|
|
2735
|
+
"flex h-10 min-w-0 items-center gap-1 py-3",
|
|
2736
|
+
// Type — Archivo 14/20.
|
|
2737
|
+
"body-03"
|
|
2738
|
+
],
|
|
2739
|
+
{
|
|
2740
|
+
variants: {
|
|
2741
|
+
variant: {
|
|
2742
|
+
title: "justify-start text-card-foreground-muted",
|
|
2743
|
+
content: "justify-end text-card-foreground"
|
|
2744
|
+
}
|
|
2745
|
+
},
|
|
2746
|
+
defaultVariants: {
|
|
2747
|
+
variant: "title"
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
);
|
|
2751
|
+
var propertyGridItemVariants = classVarianceAuthority.cva(["grid grid-cols-2"], {
|
|
2752
|
+
variants: {
|
|
2753
|
+
last: {
|
|
2754
|
+
false: "border-b border-card-border",
|
|
2755
|
+
true: ""
|
|
2756
|
+
}
|
|
2757
|
+
},
|
|
2758
|
+
defaultVariants: {
|
|
2759
|
+
last: false
|
|
2760
|
+
}
|
|
2761
|
+
});
|
|
2762
|
+
function PropertyGridItem({
|
|
2763
|
+
className,
|
|
2764
|
+
last = false,
|
|
2765
|
+
children,
|
|
2766
|
+
ref,
|
|
2767
|
+
...props
|
|
2768
|
+
}) {
|
|
2769
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2770
|
+
"div",
|
|
2771
|
+
{
|
|
2772
|
+
"data-slot": "property-grid-item",
|
|
2773
|
+
className: cn(propertyGridItemVariants({ last }), className),
|
|
2774
|
+
ref,
|
|
2775
|
+
...props,
|
|
2776
|
+
children
|
|
2777
|
+
}
|
|
2778
|
+
);
|
|
2779
|
+
}
|
|
2780
|
+
function PropertyGridCell({
|
|
2781
|
+
variant,
|
|
2782
|
+
className,
|
|
2783
|
+
leading,
|
|
2784
|
+
trailing,
|
|
2785
|
+
children,
|
|
2786
|
+
ref,
|
|
2787
|
+
...props
|
|
2788
|
+
}) {
|
|
2789
|
+
const slotClass = "flex shrink-0 items-center text-card-foreground-muted [&_svg]:size-4";
|
|
2790
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2791
|
+
"div",
|
|
2792
|
+
{
|
|
2793
|
+
"data-slot": `property-grid-${variant}`,
|
|
2794
|
+
className: cn(propertyGridCellVariants({ variant }), className),
|
|
2795
|
+
ref,
|
|
2796
|
+
...props,
|
|
2797
|
+
children: [
|
|
2798
|
+
leading != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2799
|
+
"span",
|
|
2800
|
+
{
|
|
2801
|
+
"data-slot": `property-grid-${variant}-leading`,
|
|
2802
|
+
className: slotClass,
|
|
2803
|
+
children: leading
|
|
2804
|
+
}
|
|
2805
|
+
),
|
|
2806
|
+
children != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children }),
|
|
2807
|
+
trailing != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2808
|
+
"span",
|
|
2809
|
+
{
|
|
2810
|
+
"data-slot": `property-grid-${variant}-trailing`,
|
|
2811
|
+
className: slotClass,
|
|
2812
|
+
children: trailing
|
|
2813
|
+
}
|
|
2814
|
+
)
|
|
2815
|
+
]
|
|
2816
|
+
}
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
function PropertyGridTitle(props) {
|
|
2820
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PropertyGridCell, { variant: "title", ...props });
|
|
2821
|
+
}
|
|
2822
|
+
function PropertyGridContent(props) {
|
|
2823
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PropertyGridCell, { variant: "content", ...props });
|
|
2824
|
+
}
|
|
2825
|
+
var radioVariants = classVarianceAuthority.cva([
|
|
2826
|
+
// Circle — 20×20 (`size-5`), fully round (Figma `radius/full`), 1px border.
|
|
2827
|
+
// `peer` lets the sibling indicator react to :checked / :disabled.
|
|
2828
|
+
"peer size-5 shrink-0 appearance-none rounded-full border",
|
|
2829
|
+
"cursor-pointer transition-colors",
|
|
2830
|
+
// Default
|
|
2831
|
+
"bg-background border-border",
|
|
2832
|
+
// Hover (unselected) — focus keeps the DEFAULT border; only the ring signals
|
|
2833
|
+
// focus (so focus doesn't visually read as hover).
|
|
2834
|
+
"hover:border-border-accent",
|
|
2835
|
+
// Focus ring — suppress the native ring, draw the shared `--focus` ring the
|
|
2836
|
+
// same way Checkbox and ghost Button do: 2px stroke, 2px offset
|
|
2837
|
+
// (`outline-solid` restores what `outline-none` zeroed).
|
|
2838
|
+
"outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus",
|
|
2839
|
+
// Selected
|
|
2840
|
+
"checked:bg-brand-primary-background checked:border-brand-primary-border",
|
|
2841
|
+
"checked:hover:bg-brand-primary-accent checked:hover:border-brand-primary-border",
|
|
2842
|
+
"checked:focus-visible:bg-brand-primary-accent checked:focus-visible:border-brand-primary-border",
|
|
2843
|
+
// Disabled — shared neutral-muted look whether selected or not (per Figma)
|
|
2844
|
+
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:border-border-muted",
|
|
2845
|
+
"checked:disabled:bg-background checked:disabled:border-border-muted"
|
|
2846
|
+
]);
|
|
2847
|
+
function Radio({
|
|
2848
|
+
className,
|
|
2849
|
+
wrapperClassName,
|
|
2850
|
+
onChange,
|
|
2851
|
+
onCheckedChange,
|
|
2852
|
+
...props
|
|
2853
|
+
}) {
|
|
2854
|
+
const handleChange = (event) => {
|
|
2855
|
+
onChange?.(event);
|
|
2856
|
+
onCheckedChange?.(event.currentTarget.checked);
|
|
2857
|
+
};
|
|
2858
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("relative inline-flex shrink-0", wrapperClassName), children: [
|
|
2859
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2860
|
+
"input",
|
|
2861
|
+
{
|
|
2862
|
+
type: "radio",
|
|
2863
|
+
"data-slot": "radio",
|
|
2864
|
+
className: cn(radioVariants(), className),
|
|
2865
|
+
...props,
|
|
2866
|
+
onChange: handleChange
|
|
2867
|
+
}
|
|
2868
|
+
),
|
|
2869
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2870
|
+
react.CircleIcon,
|
|
2871
|
+
{
|
|
2872
|
+
"aria-hidden": "true",
|
|
2873
|
+
weight: "fill",
|
|
2874
|
+
"data-slot": "radio-indicator",
|
|
2875
|
+
className: cn(
|
|
2876
|
+
"pointer-events-none invisible absolute inset-0 m-auto size-3",
|
|
2877
|
+
"text-brand-primary-foreground",
|
|
2878
|
+
"peer-checked:visible peer-disabled:text-foreground-muted"
|
|
2879
|
+
)
|
|
2880
|
+
}
|
|
2881
|
+
)
|
|
2882
|
+
] });
|
|
2883
|
+
}
|
|
2884
|
+
function useDebouncedCallback(fn, delayMs) {
|
|
2885
|
+
const fnRef = React21__namespace.useRef(fn);
|
|
2886
|
+
const delayRef = React21__namespace.useRef(delayMs);
|
|
2887
|
+
fnRef.current = fn;
|
|
2888
|
+
delayRef.current = delayMs;
|
|
2889
|
+
const timerRef = React21__namespace.useRef(null);
|
|
2890
|
+
const debounced = React21__namespace.useMemo(() => {
|
|
2891
|
+
const clear = () => {
|
|
2892
|
+
if (timerRef.current !== null) {
|
|
2893
|
+
clearTimeout(timerRef.current);
|
|
2894
|
+
timerRef.current = null;
|
|
2895
|
+
}
|
|
2896
|
+
};
|
|
2897
|
+
const call = (...args) => {
|
|
2898
|
+
clear();
|
|
2899
|
+
timerRef.current = setTimeout(() => {
|
|
2900
|
+
timerRef.current = null;
|
|
2901
|
+
fnRef.current(...args);
|
|
2902
|
+
}, delayRef.current);
|
|
2903
|
+
};
|
|
2904
|
+
call.cancel = clear;
|
|
2905
|
+
call.flush = (...args) => {
|
|
2906
|
+
clear();
|
|
2907
|
+
fnRef.current(...args);
|
|
2908
|
+
};
|
|
2909
|
+
return call;
|
|
2910
|
+
}, []);
|
|
2911
|
+
React21__namespace.useEffect(() => {
|
|
2912
|
+
return () => {
|
|
2913
|
+
if (timerRef.current !== null) {
|
|
2914
|
+
clearTimeout(timerRef.current);
|
|
2915
|
+
timerRef.current = null;
|
|
2916
|
+
}
|
|
2917
|
+
};
|
|
2918
|
+
}, []);
|
|
2919
|
+
return debounced;
|
|
2920
|
+
}
|
|
2921
|
+
var DEFAULT_DEBOUNCE_MS = 300;
|
|
2922
|
+
function SearchBar({
|
|
2923
|
+
onSearch,
|
|
2924
|
+
debounceMs = DEFAULT_DEBOUNCE_MS,
|
|
2925
|
+
hint,
|
|
2926
|
+
wrapperClassName,
|
|
2927
|
+
fieldClassName,
|
|
2928
|
+
className,
|
|
2929
|
+
disabled,
|
|
2930
|
+
value,
|
|
2931
|
+
defaultValue,
|
|
2932
|
+
onChange,
|
|
2933
|
+
onKeyDown,
|
|
2934
|
+
ref,
|
|
2935
|
+
...props
|
|
2936
|
+
}) {
|
|
2937
|
+
const isControlled = value !== void 0;
|
|
2938
|
+
const innerRef = React21__namespace.useRef(null);
|
|
2939
|
+
const setInputRef = React21__namespace.useCallback(
|
|
2940
|
+
(node) => {
|
|
2941
|
+
innerRef.current = node;
|
|
2942
|
+
if (typeof ref === "function") ref(node);
|
|
2943
|
+
else if (ref)
|
|
2944
|
+
ref.current = node;
|
|
2945
|
+
},
|
|
2946
|
+
[ref]
|
|
2947
|
+
);
|
|
2948
|
+
const [hasValueState, setHasValueState] = React21__namespace.useState(
|
|
2949
|
+
() => String(value ?? defaultValue ?? "").length > 0
|
|
2950
|
+
);
|
|
2951
|
+
const hasValue = isControlled ? String(value ?? "").length > 0 : hasValueState;
|
|
2952
|
+
const debouncedSearch = useDebouncedCallback((next) => {
|
|
2953
|
+
onSearch?.(next);
|
|
2954
|
+
}, debounceMs);
|
|
2955
|
+
const handleChange = (event) => {
|
|
2956
|
+
onChange?.(event);
|
|
2957
|
+
if (!isControlled) setHasValueState(event.currentTarget.value.length > 0);
|
|
2958
|
+
debouncedSearch(event.currentTarget.value);
|
|
2959
|
+
};
|
|
2960
|
+
const handleKeyDown = (event) => {
|
|
2961
|
+
onKeyDown?.(event);
|
|
2962
|
+
if (event.key === "Enter") {
|
|
2963
|
+
debouncedSearch.flush(event.currentTarget.value);
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
const handleClear = () => {
|
|
2967
|
+
const input = innerRef.current;
|
|
2968
|
+
if (!input) return;
|
|
2969
|
+
const setter = Object.getOwnPropertyDescriptor(
|
|
2970
|
+
window.HTMLInputElement.prototype,
|
|
2971
|
+
"value"
|
|
2972
|
+
)?.set;
|
|
2973
|
+
setter?.call(input, "");
|
|
2974
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2975
|
+
if (!isControlled) setHasValueState(false);
|
|
2976
|
+
debouncedSearch.cancel();
|
|
2977
|
+
onSearch?.("");
|
|
2978
|
+
input.focus();
|
|
2979
|
+
};
|
|
2980
|
+
const showClear = hasValue && !disabled;
|
|
2981
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2982
|
+
Input,
|
|
2983
|
+
{
|
|
2984
|
+
type: "search",
|
|
2985
|
+
variation: "field",
|
|
2986
|
+
size: "lg",
|
|
2987
|
+
ref: setInputRef,
|
|
2988
|
+
disabled,
|
|
2989
|
+
hint,
|
|
2990
|
+
value,
|
|
2991
|
+
defaultValue,
|
|
2992
|
+
onChange: handleChange,
|
|
2993
|
+
onKeyDown: handleKeyDown,
|
|
2994
|
+
className: cn(
|
|
2995
|
+
// Suppress the browser's native `type=search` clear affordance
|
|
2996
|
+
// (WebKit/Chrome render `::-webkit-search-cancel-button`) — SearchBar
|
|
2997
|
+
// provides its own clear button, so the native one would show a second
|
|
2998
|
+
// "X". `hidden` (display:none) is used rather than `appearance-none`:
|
|
2999
|
+
// that pseudo-element is governed by `-webkit-appearance`, which the
|
|
3000
|
+
// unprefixed `appearance:none` Tailwind emits does not affect.
|
|
3001
|
+
// Caller `className` is merged after and can still override.
|
|
3002
|
+
"[&::-webkit-search-cancel-button]:hidden",
|
|
3003
|
+
className
|
|
3004
|
+
),
|
|
3005
|
+
wrapperClassName,
|
|
3006
|
+
fieldClassName,
|
|
3007
|
+
leftSlot: /* @__PURE__ */ jsxRuntime.jsx(react.MagnifyingGlassIcon, { weight: "regular" }),
|
|
3008
|
+
rightSlot: showClear ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3009
|
+
"button",
|
|
3010
|
+
{
|
|
3011
|
+
type: "button",
|
|
3012
|
+
"aria-label": "Clear search",
|
|
3013
|
+
onClick: handleClear,
|
|
3014
|
+
"data-slot": "search-clear",
|
|
3015
|
+
className: cn(
|
|
3016
|
+
// Reset native button chrome; inherit the field's icon colour.
|
|
3017
|
+
"inline-flex cursor-pointer items-center justify-center rounded-xs bg-transparent p-0 text-current",
|
|
3018
|
+
// Standard ADR-0010 system focus ring (this button is a momentary
|
|
3019
|
+
// control — the ADR-0011 no-ring exception applies only to the
|
|
3020
|
+
// text input itself, NOT to this button).
|
|
3021
|
+
"outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus"
|
|
3022
|
+
),
|
|
3023
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.XIcon, { weight: "regular", "aria-hidden": "true" })
|
|
3024
|
+
}
|
|
3025
|
+
) : void 0,
|
|
3026
|
+
...props
|
|
3027
|
+
}
|
|
3028
|
+
);
|
|
3029
|
+
}
|
|
3030
|
+
var sidebarItemVariants = classVarianceAuthority.cva(
|
|
3031
|
+
[
|
|
3032
|
+
"group flex h-9 w-full items-center rounded-xs border-l-2 border-transparent px-3",
|
|
3033
|
+
"no-underline transition-colors outline-none"
|
|
3034
|
+
],
|
|
3035
|
+
{
|
|
3036
|
+
variants: {
|
|
3037
|
+
active: {
|
|
3038
|
+
true: "bg-brand-background border-brand-border",
|
|
3039
|
+
false: ""
|
|
3040
|
+
},
|
|
3041
|
+
interactive: {
|
|
3042
|
+
true: [
|
|
3043
|
+
"cursor-pointer",
|
|
3044
|
+
// Ring only — no fill/text change on focus (see file docstring).
|
|
3045
|
+
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus"
|
|
3046
|
+
],
|
|
3047
|
+
false: ""
|
|
3048
|
+
}
|
|
3049
|
+
},
|
|
3050
|
+
compoundVariants: [
|
|
3051
|
+
// Hover fill only on a non-active, interactive item.
|
|
3052
|
+
{ active: false, interactive: true, class: "hover:bg-background" }
|
|
3053
|
+
],
|
|
3054
|
+
defaultVariants: { active: false, interactive: false }
|
|
3055
|
+
}
|
|
3056
|
+
);
|
|
3057
|
+
var sidebarItemLabelVariants = classVarianceAuthority.cva(
|
|
3058
|
+
["button-01 min-w-0 flex-1 truncate"],
|
|
3059
|
+
{
|
|
3060
|
+
variants: {
|
|
3061
|
+
active: {
|
|
3062
|
+
true: "text-brand-foreground",
|
|
3063
|
+
false: "text-foreground-muted"
|
|
3064
|
+
},
|
|
3065
|
+
interactive: { true: "", false: "" }
|
|
3066
|
+
},
|
|
3067
|
+
compoundVariants: [
|
|
3068
|
+
{
|
|
3069
|
+
active: false,
|
|
3070
|
+
interactive: true,
|
|
3071
|
+
class: "group-hover:text-foreground"
|
|
3072
|
+
}
|
|
3073
|
+
],
|
|
3074
|
+
defaultVariants: { active: false, interactive: false }
|
|
3075
|
+
}
|
|
3076
|
+
);
|
|
3077
|
+
function setRef5(ref, node) {
|
|
3078
|
+
if (typeof ref === "function") ref(node);
|
|
3079
|
+
else if (ref) ref.current = node;
|
|
3080
|
+
}
|
|
3081
|
+
function SidebarItem({
|
|
3082
|
+
active = false,
|
|
3083
|
+
asChild = false,
|
|
3084
|
+
href,
|
|
3085
|
+
className,
|
|
3086
|
+
children,
|
|
3087
|
+
ref,
|
|
3088
|
+
...props
|
|
3089
|
+
}) {
|
|
3090
|
+
const interactive = asChild || href != null;
|
|
3091
|
+
const rootClassName = cn(
|
|
3092
|
+
sidebarItemVariants({ active, interactive }),
|
|
3093
|
+
className
|
|
3094
|
+
);
|
|
3095
|
+
const label = (labelNode) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3096
|
+
"span",
|
|
3097
|
+
{
|
|
3098
|
+
"data-slot": "sidebar-item-label",
|
|
3099
|
+
className: sidebarItemLabelVariants({ active, interactive }),
|
|
3100
|
+
children: labelNode
|
|
3101
|
+
}
|
|
3102
|
+
);
|
|
3103
|
+
const dataState = active ? "active" : void 0;
|
|
3104
|
+
const ariaCurrent = active ? "page" : void 0;
|
|
3105
|
+
if (asChild) {
|
|
3106
|
+
if (!React21__namespace.isValidElement(children)) {
|
|
3107
|
+
throw new Error(
|
|
3108
|
+
"SidebarItem: `asChild` requires a single React element child"
|
|
3109
|
+
);
|
|
3110
|
+
}
|
|
3111
|
+
const child = children;
|
|
3112
|
+
const childProps = child.props;
|
|
3113
|
+
return React21__namespace.cloneElement(
|
|
3114
|
+
child,
|
|
3115
|
+
{
|
|
3116
|
+
...props,
|
|
3117
|
+
...childProps,
|
|
3118
|
+
"data-slot": "sidebar-item",
|
|
3119
|
+
"data-state": dataState,
|
|
3120
|
+
"aria-current": ariaCurrent,
|
|
3121
|
+
className: cn(
|
|
3122
|
+
rootClassName,
|
|
3123
|
+
childProps.className
|
|
3124
|
+
),
|
|
3125
|
+
ref: (node) => {
|
|
3126
|
+
setRef5(ref, node);
|
|
3127
|
+
setRef5(childProps.ref, node);
|
|
3128
|
+
},
|
|
3129
|
+
onClick: (event) => {
|
|
3130
|
+
props.onClick?.(event);
|
|
3131
|
+
childProps.onClick?.(event);
|
|
3132
|
+
},
|
|
3133
|
+
style: {
|
|
3134
|
+
...props.style,
|
|
3135
|
+
...childProps.style
|
|
3136
|
+
}
|
|
3137
|
+
},
|
|
3138
|
+
label(childProps.children)
|
|
3139
|
+
);
|
|
3140
|
+
}
|
|
3141
|
+
if (href != null) {
|
|
3142
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3143
|
+
"a",
|
|
3144
|
+
{
|
|
3145
|
+
"data-slot": "sidebar-item",
|
|
3146
|
+
"data-state": dataState,
|
|
3147
|
+
"aria-current": ariaCurrent,
|
|
3148
|
+
href,
|
|
3149
|
+
className: rootClassName,
|
|
3150
|
+
ref,
|
|
3151
|
+
...props,
|
|
3152
|
+
children: label(children)
|
|
3153
|
+
}
|
|
3154
|
+
);
|
|
3155
|
+
}
|
|
3156
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3157
|
+
"div",
|
|
3158
|
+
{
|
|
3159
|
+
"data-slot": "sidebar-item",
|
|
3160
|
+
"data-state": dataState,
|
|
3161
|
+
"aria-current": ariaCurrent,
|
|
3162
|
+
className: rootClassName,
|
|
3163
|
+
ref,
|
|
3164
|
+
...props,
|
|
3165
|
+
children: label(children)
|
|
3166
|
+
}
|
|
3167
|
+
);
|
|
3168
|
+
}
|
|
3169
|
+
var selectableFeatureCardVariants = classVarianceAuthority.cva([
|
|
3170
|
+
"group overflow-hidden rounded-md border border-border bg-card-background",
|
|
3171
|
+
"has-[:checked]:border-brand-primary-border",
|
|
3172
|
+
"has-[:disabled]:opacity-60"
|
|
3173
|
+
]);
|
|
3174
|
+
var selectableFeatureCardRowVariants = classVarianceAuthority.cva(
|
|
3175
|
+
[
|
|
3176
|
+
"flex h-16 items-center gap-3 bg-card-background px-4 py-3",
|
|
3177
|
+
"cursor-pointer has-[:disabled]:cursor-not-allowed"
|
|
3178
|
+
],
|
|
3179
|
+
{
|
|
3180
|
+
variants: {
|
|
3181
|
+
editable: {
|
|
3182
|
+
true: "group-has-[:checked]:border-b group-has-[:checked]:border-border",
|
|
3183
|
+
false: ""
|
|
3184
|
+
}
|
|
3185
|
+
},
|
|
3186
|
+
defaultVariants: { editable: false }
|
|
3187
|
+
}
|
|
3188
|
+
);
|
|
3189
|
+
var selectableFeatureCardTextVariants = classVarianceAuthority.cva(
|
|
3190
|
+
"flex min-w-0 flex-1 flex-col"
|
|
3191
|
+
);
|
|
3192
|
+
var selectableFeatureCardTrailingVariants = classVarianceAuthority.cva(
|
|
3193
|
+
"flex shrink-0 items-center text-card-foreground-muted [&_svg]:size-6 [&_svg]:shrink-0"
|
|
3194
|
+
);
|
|
3195
|
+
var selectableFeatureCardBottomVariants = classVarianceAuthority.cva(
|
|
3196
|
+
"hidden bg-card-accent p-4 group-has-[:checked]:block"
|
|
3197
|
+
);
|
|
3198
|
+
function SelectableFeatureCard({
|
|
3199
|
+
title,
|
|
3200
|
+
subtitle,
|
|
3201
|
+
trailing,
|
|
3202
|
+
variation = "basic",
|
|
3203
|
+
children,
|
|
3204
|
+
className,
|
|
3205
|
+
ref,
|
|
3206
|
+
...radioProps
|
|
3207
|
+
}) {
|
|
3208
|
+
const editable = variation === "editable";
|
|
3209
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3210
|
+
"div",
|
|
3211
|
+
{
|
|
3212
|
+
"data-slot": "selectable-feature-card",
|
|
3213
|
+
className: cn(selectableFeatureCardVariants(), className),
|
|
3214
|
+
children: [
|
|
3215
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: selectableFeatureCardRowVariants({ editable }), children: [
|
|
3216
|
+
/* @__PURE__ */ jsxRuntime.jsx(Radio, { ref, ...radioProps }),
|
|
3217
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3218
|
+
"span",
|
|
3219
|
+
{
|
|
3220
|
+
"data-slot": "selectable-feature-card-text",
|
|
3221
|
+
className: selectableFeatureCardTextVariants(),
|
|
3222
|
+
children: [
|
|
3223
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "body-02-medium truncate text-card-foreground", children: title }),
|
|
3224
|
+
subtitle != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "body-03 truncate text-card-foreground-muted", children: subtitle })
|
|
3225
|
+
]
|
|
3226
|
+
}
|
|
3227
|
+
),
|
|
3228
|
+
trailing != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3229
|
+
"span",
|
|
3230
|
+
{
|
|
3231
|
+
"data-slot": "selectable-feature-card-trailing",
|
|
3232
|
+
"aria-hidden": "true",
|
|
3233
|
+
className: selectableFeatureCardTrailingVariants(),
|
|
3234
|
+
children: trailing
|
|
3235
|
+
}
|
|
3236
|
+
)
|
|
3237
|
+
] }),
|
|
3238
|
+
editable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3239
|
+
"div",
|
|
3240
|
+
{
|
|
3241
|
+
"data-slot": "selectable-feature-card-bottom",
|
|
3242
|
+
className: selectableFeatureCardBottomVariants(),
|
|
3243
|
+
children
|
|
3244
|
+
}
|
|
3245
|
+
)
|
|
3246
|
+
]
|
|
3247
|
+
}
|
|
3248
|
+
);
|
|
3249
|
+
}
|
|
3250
|
+
var switchVariants = classVarianceAuthority.cva([
|
|
3251
|
+
// Track — 56×24 (`h-6 w-14`), fully round (Figma `radius/full`), 4px inset
|
|
3252
|
+
// (`p-1` = Figma `spacing/1`). `appearance-none` strips the native checkbox
|
|
3253
|
+
// box; `peer` lets the sibling thumb react to :checked / :disabled.
|
|
3254
|
+
"peer inline-flex h-6 w-14 shrink-0 items-center rounded-full border p-1",
|
|
3255
|
+
"appearance-none cursor-pointer transition-colors",
|
|
3256
|
+
// Off — default
|
|
3257
|
+
"bg-button-secondary-background border-button-secondary-border",
|
|
3258
|
+
// Off — hover strengthens the border (focused keeps the default border)
|
|
3259
|
+
"hover:border-button-secondary-border-accent",
|
|
3260
|
+
// Focus ring — suppress the native ring, draw the shared `--focus` ring the
|
|
3261
|
+
// same way Checkbox/Radio do: 2px stroke, 2px offset (`outline-solid`
|
|
3262
|
+
// restores what `outline-none` zeroed). Circular via the track's rounding.
|
|
3263
|
+
"outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus",
|
|
3264
|
+
// On — brand fill, border made transparent (kept for a constant box model)
|
|
3265
|
+
"checked:bg-brand-primary-background checked:border-transparent",
|
|
3266
|
+
"checked:hover:bg-brand-primary-accent",
|
|
3267
|
+
// Disabled — off keeps the white track with a muted border; on drops to a
|
|
3268
|
+
// neutral muted fill (per Figma). Border transparent when on, muted when off.
|
|
3269
|
+
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:border-button-secondary-border-muted",
|
|
3270
|
+
"checked:disabled:bg-button-primary-muted checked:disabled:border-transparent"
|
|
3271
|
+
]);
|
|
3272
|
+
function Switch({
|
|
3273
|
+
className,
|
|
3274
|
+
wrapperClassName,
|
|
3275
|
+
onChange,
|
|
3276
|
+
onCheckedChange,
|
|
3277
|
+
...props
|
|
3278
|
+
}) {
|
|
3279
|
+
const handleChange = (event) => {
|
|
3280
|
+
onChange?.(event);
|
|
3281
|
+
onCheckedChange?.(event.currentTarget.checked);
|
|
3282
|
+
};
|
|
3283
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3284
|
+
"span",
|
|
3285
|
+
{
|
|
3286
|
+
className: cn(
|
|
3287
|
+
"relative inline-flex shrink-0 items-center",
|
|
3288
|
+
wrapperClassName
|
|
3289
|
+
),
|
|
3290
|
+
children: [
|
|
3291
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3292
|
+
"input",
|
|
3293
|
+
{
|
|
3294
|
+
type: "checkbox",
|
|
3295
|
+
role: "switch",
|
|
3296
|
+
"data-slot": "switch",
|
|
3297
|
+
className: cn(switchVariants(), className),
|
|
3298
|
+
...props,
|
|
3299
|
+
onChange: handleChange
|
|
3300
|
+
}
|
|
3301
|
+
),
|
|
3302
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3303
|
+
"span",
|
|
3304
|
+
{
|
|
3305
|
+
"aria-hidden": "true",
|
|
3306
|
+
"data-slot": "switch-thumb",
|
|
3307
|
+
className: cn(
|
|
3308
|
+
"pointer-events-none absolute left-1 h-4 w-6 rounded-full transition-transform",
|
|
3309
|
+
// Off — dark knob at 90% opacity (Figma `opacity/90`)
|
|
3310
|
+
"bg-button-secondary-foreground opacity-90",
|
|
3311
|
+
"peer-checked:translate-x-5.5 peer-checked:bg-brand-primary-foreground peer-checked:opacity-100",
|
|
3312
|
+
// Disabled — muted knob whether on or off (per Figma)
|
|
3313
|
+
"peer-disabled:bg-button-secondary-foreground-muted peer-disabled:opacity-100",
|
|
3314
|
+
"peer-checked:peer-disabled:bg-button-primary-foreground-muted"
|
|
3315
|
+
)
|
|
3316
|
+
}
|
|
3317
|
+
)
|
|
3318
|
+
]
|
|
3319
|
+
}
|
|
3320
|
+
);
|
|
3321
|
+
}
|
|
3322
|
+
var tableCellVariants = classVarianceAuthority.cva(
|
|
3323
|
+
[
|
|
3324
|
+
// The cell is the group that drives hover/active styling of its descendants.
|
|
3325
|
+
"group",
|
|
3326
|
+
// Layout — 56px row, slots on a horizontal axis; overflow clipped for truncation.
|
|
3327
|
+
"flex h-14 w-full items-center gap-3 overflow-hidden px-2 py-4",
|
|
3328
|
+
// Brand-accent background on hover / keyboard-focus / active (self). Reset
|
|
3329
|
+
// native control chrome so an asChild button/anchor styles cleanly.
|
|
3330
|
+
"appearance-none bg-transparent outline-none transition-colors",
|
|
3331
|
+
"hover:bg-card-brand-accent focus-visible:bg-card-brand-accent data-[state=active]:bg-card-brand-accent"
|
|
3332
|
+
],
|
|
3333
|
+
{
|
|
3334
|
+
variants: {
|
|
3335
|
+
last: {
|
|
3336
|
+
// Row divider on all but the last row; recolors on hover / focus / active.
|
|
3337
|
+
false: [
|
|
3338
|
+
"border-b border-card-border",
|
|
3339
|
+
"hover:border-card-brand-border-accent focus-visible:border-card-brand-border-accent data-[state=active]:border-card-brand-border-accent"
|
|
3340
|
+
],
|
|
3341
|
+
true: ""
|
|
3342
|
+
}
|
|
3343
|
+
},
|
|
3344
|
+
defaultVariants: {
|
|
3345
|
+
last: false
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
);
|
|
3349
|
+
function setRef6(ref, node) {
|
|
3350
|
+
if (typeof ref === "function") ref(node);
|
|
3351
|
+
else if (ref) ref.current = node;
|
|
3352
|
+
}
|
|
3353
|
+
function TableCell({
|
|
3354
|
+
className,
|
|
3355
|
+
last = false,
|
|
3356
|
+
leading,
|
|
3357
|
+
trailing,
|
|
3358
|
+
active = false,
|
|
3359
|
+
asChild = false,
|
|
3360
|
+
children,
|
|
3361
|
+
ref,
|
|
3362
|
+
...props
|
|
3363
|
+
}) {
|
|
3364
|
+
const rootClassName = cn(tableCellVariants({ last }), className);
|
|
3365
|
+
const dataState = active ? "active" : void 0;
|
|
3366
|
+
const leadingSlot = leading != null ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3367
|
+
"span",
|
|
3368
|
+
{
|
|
3369
|
+
"data-slot": "table-cell-leading",
|
|
3370
|
+
className: "flex shrink-0 items-center",
|
|
3371
|
+
children: leading
|
|
3372
|
+
}
|
|
3373
|
+
) : null;
|
|
3374
|
+
const trailingSlot = trailing != null ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3375
|
+
"span",
|
|
3376
|
+
{
|
|
3377
|
+
"data-slot": "table-cell-trailing",
|
|
3378
|
+
className: "flex shrink-0 items-center",
|
|
3379
|
+
children: trailing
|
|
3380
|
+
}
|
|
3381
|
+
) : null;
|
|
3382
|
+
const middleSlot = (middle) => middle != null ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3383
|
+
"span",
|
|
3384
|
+
{
|
|
3385
|
+
"data-slot": "table-cell-middle",
|
|
3386
|
+
className: "flex min-w-0 flex-1 flex-col",
|
|
3387
|
+
children: middle
|
|
3388
|
+
}
|
|
3389
|
+
) : null;
|
|
3390
|
+
if (asChild) {
|
|
3391
|
+
if (!React21__namespace.isValidElement(children)) {
|
|
3392
|
+
throw new Error(
|
|
3393
|
+
"TableCell: `asChild` requires a single React element child"
|
|
3394
|
+
);
|
|
3395
|
+
}
|
|
3396
|
+
const child = children;
|
|
3397
|
+
const childProps = child.props;
|
|
3398
|
+
return React21__namespace.cloneElement(
|
|
3399
|
+
child,
|
|
3400
|
+
{
|
|
3401
|
+
...props,
|
|
3402
|
+
...childProps,
|
|
3403
|
+
"data-slot": "table-cell",
|
|
3404
|
+
"data-state": dataState,
|
|
3405
|
+
className: cn(
|
|
3406
|
+
rootClassName,
|
|
3407
|
+
childProps.className
|
|
3408
|
+
),
|
|
3409
|
+
ref: (node) => {
|
|
3410
|
+
setRef6(ref, node);
|
|
3411
|
+
setRef6(childProps.ref, node);
|
|
3412
|
+
}
|
|
3413
|
+
},
|
|
3414
|
+
leadingSlot,
|
|
3415
|
+
middleSlot(childProps.children),
|
|
3416
|
+
trailingSlot
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3420
|
+
"div",
|
|
3421
|
+
{
|
|
3422
|
+
"data-slot": "table-cell",
|
|
3423
|
+
"data-state": dataState,
|
|
3424
|
+
className: rootClassName,
|
|
3425
|
+
ref,
|
|
3426
|
+
...props,
|
|
3427
|
+
children: [
|
|
3428
|
+
leadingSlot,
|
|
3429
|
+
middleSlot(children),
|
|
3430
|
+
trailingSlot
|
|
3431
|
+
]
|
|
3432
|
+
}
|
|
3433
|
+
);
|
|
3434
|
+
}
|
|
3435
|
+
function CellText({ label, meta, className, ...props }) {
|
|
3436
|
+
const hasMeta = Array.isArray(meta) && meta.length > 0;
|
|
3437
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3438
|
+
"div",
|
|
3439
|
+
{
|
|
3440
|
+
"data-slot": "cell-text",
|
|
3441
|
+
className: cn("flex min-w-0 flex-col gap-0.5", className),
|
|
3442
|
+
...props,
|
|
3443
|
+
children: [
|
|
3444
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3445
|
+
"span",
|
|
3446
|
+
{
|
|
3447
|
+
"data-slot": "cell-text-label",
|
|
3448
|
+
className: cn(
|
|
3449
|
+
"body-03 min-w-0 truncate",
|
|
3450
|
+
"text-card-foreground",
|
|
3451
|
+
"group-hover:text-card-brand-foreground group-focus-visible:text-card-brand-foreground group-data-[state=active]:text-card-brand-foreground"
|
|
3452
|
+
),
|
|
3453
|
+
children: label
|
|
3454
|
+
}
|
|
3455
|
+
),
|
|
3456
|
+
hasMeta && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3457
|
+
"div",
|
|
3458
|
+
{
|
|
3459
|
+
"data-slot": "cell-text-meta",
|
|
3460
|
+
className: cn(
|
|
3461
|
+
"flex min-w-0 items-center gap-1",
|
|
3462
|
+
"body-mono-04",
|
|
3463
|
+
"text-card-foreground-muted",
|
|
3464
|
+
"group-hover:text-card-brand-foreground-muted group-focus-visible:text-card-brand-foreground-muted group-data-[state=active]:text-card-brand-foreground-muted"
|
|
3465
|
+
),
|
|
3466
|
+
children: meta.map((segment, index) => /* @__PURE__ */ jsxRuntime.jsxs(React21__namespace.Fragment, { children: [
|
|
3467
|
+
index > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3468
|
+
"span",
|
|
3469
|
+
{
|
|
3470
|
+
"aria-hidden": "true",
|
|
3471
|
+
className: "size-0.5 shrink-0 rounded-full bg-current"
|
|
3472
|
+
}
|
|
3473
|
+
),
|
|
3474
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: segment })
|
|
3475
|
+
] }, index))
|
|
3476
|
+
}
|
|
3477
|
+
)
|
|
3478
|
+
]
|
|
3479
|
+
}
|
|
3480
|
+
);
|
|
3481
|
+
}
|
|
3482
|
+
function TableTitle({ className, children, ref, ...props }) {
|
|
3483
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3484
|
+
"div",
|
|
3485
|
+
{
|
|
3486
|
+
"data-slot": "table-title",
|
|
3487
|
+
className: cn(
|
|
3488
|
+
"flex h-8 w-full items-center overflow-hidden border-b border-card-border px-2 py-1.5",
|
|
3489
|
+
className
|
|
3490
|
+
),
|
|
3491
|
+
ref,
|
|
3492
|
+
...props,
|
|
3493
|
+
children: children != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3494
|
+
"p",
|
|
3495
|
+
{
|
|
3496
|
+
"data-slot": "table-title-label",
|
|
3497
|
+
className: "caption-03-medium min-w-0 truncate uppercase text-card-foreground-muted",
|
|
3498
|
+
children
|
|
3499
|
+
}
|
|
3500
|
+
)
|
|
3501
|
+
}
|
|
3502
|
+
);
|
|
3503
|
+
}
|
|
3504
|
+
var tabsRootVariants = classVarianceAuthority.cva(["flex flex-col"]);
|
|
3505
|
+
var tabsListVariants = classVarianceAuthority.cva(["inline-flex items-center"]);
|
|
3506
|
+
var tabsTriggerVariants = classVarianceAuthority.cva(
|
|
3507
|
+
[
|
|
3508
|
+
"group inline-flex cursor-pointer items-center justify-center gap-1 px-2 py-2.5",
|
|
3509
|
+
"body-03-medium border-b-2 transition-colors",
|
|
3510
|
+
"outline-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus",
|
|
3511
|
+
"[&_svg]:size-4 [&_svg]:shrink-0",
|
|
3512
|
+
"disabled:cursor-not-allowed"
|
|
3513
|
+
],
|
|
3514
|
+
{
|
|
3515
|
+
variants: {
|
|
3516
|
+
selected: {
|
|
3517
|
+
true: "border-card-brand-border-accent text-foreground-accent",
|
|
3518
|
+
false: "border-transparent text-foreground"
|
|
3519
|
+
},
|
|
3520
|
+
disabled: {
|
|
3521
|
+
true: "text-foreground-muted",
|
|
3522
|
+
false: ""
|
|
3523
|
+
}
|
|
3524
|
+
},
|
|
3525
|
+
compoundVariants: [
|
|
3526
|
+
// Unselected + enabled: faint underline on hover, purple text+underline
|
|
3527
|
+
// while pressed (Figma hover / active states).
|
|
3528
|
+
{
|
|
3529
|
+
selected: false,
|
|
3530
|
+
disabled: false,
|
|
3531
|
+
className: "hover:border-card-brand-border active:border-card-brand-border-accent active:text-foreground-accent"
|
|
3532
|
+
},
|
|
3533
|
+
// Selected + disabled: the underline drops from accent to the faint token
|
|
3534
|
+
// (Figma disabled-selected). Wins over the accent border from
|
|
3535
|
+
// `selected:true` because compound variants are appended last.
|
|
3536
|
+
{
|
|
3537
|
+
selected: true,
|
|
3538
|
+
disabled: true,
|
|
3539
|
+
className: "border-card-brand-border"
|
|
3540
|
+
}
|
|
3541
|
+
],
|
|
3542
|
+
defaultVariants: {
|
|
3543
|
+
selected: false,
|
|
3544
|
+
disabled: false
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
);
|
|
3548
|
+
var tabsContentVariants = classVarianceAuthority.cva([
|
|
3549
|
+
"outline-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus"
|
|
3550
|
+
]);
|
|
3551
|
+
var TabsContext = React21__namespace.createContext(null);
|
|
3552
|
+
function useTabsContext(part) {
|
|
3553
|
+
const context = React21__namespace.useContext(TabsContext);
|
|
3554
|
+
if (context === null) {
|
|
3555
|
+
throw new Error(`${part} must be used within <Tabs.Root>`);
|
|
3556
|
+
}
|
|
3557
|
+
return context;
|
|
3558
|
+
}
|
|
3559
|
+
function setRef7(ref, node) {
|
|
3560
|
+
if (typeof ref === "function") ref(node);
|
|
3561
|
+
else if (ref) ref.current = node;
|
|
3562
|
+
}
|
|
3563
|
+
function TabsRoot({
|
|
3564
|
+
value: valueProp,
|
|
3565
|
+
defaultValue,
|
|
3566
|
+
onValueChange,
|
|
3567
|
+
className,
|
|
3568
|
+
children,
|
|
3569
|
+
ref,
|
|
3570
|
+
...props
|
|
3571
|
+
}) {
|
|
3572
|
+
const isControlled = valueProp !== void 0;
|
|
3573
|
+
const [uncontrolledValue, setUncontrolledValue] = React21__namespace.useState(defaultValue);
|
|
3574
|
+
const value = isControlled ? valueProp : uncontrolledValue;
|
|
3575
|
+
const select = (next) => {
|
|
3576
|
+
if (!isControlled) setUncontrolledValue(next);
|
|
3577
|
+
onValueChange?.(next);
|
|
3578
|
+
};
|
|
3579
|
+
const baseId = React21__namespace.useId();
|
|
3580
|
+
const getTriggerId = (tabValue) => `${baseId}-tab-${tabValue}`;
|
|
3581
|
+
const getPanelId = (tabValue) => `${baseId}-panel-${tabValue}`;
|
|
3582
|
+
const [triggers, setTriggers] = React21__namespace.useState([]);
|
|
3583
|
+
const registerTrigger = React21__namespace.useCallback((registration) => {
|
|
3584
|
+
setTriggers((prev) => {
|
|
3585
|
+
const existing = prev.findIndex((t) => t.value === registration.value);
|
|
3586
|
+
if (existing === -1) return [...prev, registration];
|
|
3587
|
+
const next = [...prev];
|
|
3588
|
+
next[existing] = registration;
|
|
3589
|
+
return next;
|
|
3590
|
+
});
|
|
3591
|
+
}, []);
|
|
3592
|
+
const unregisterTrigger = React21__namespace.useCallback((tabValue) => {
|
|
3593
|
+
setTriggers((prev) => prev.filter((t) => t.value !== tabValue));
|
|
3594
|
+
}, []);
|
|
3595
|
+
const domOrdered = [...triggers].sort(
|
|
3596
|
+
(a, b) => a.node.compareDocumentPosition(b.node) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1
|
|
3597
|
+
);
|
|
3598
|
+
const firstEnabled = domOrdered.find((t) => !t.disabled)?.value;
|
|
3599
|
+
const tabStopValue = value ?? firstEnabled;
|
|
3600
|
+
const onTriggerKeyDown = (event) => {
|
|
3601
|
+
if (!["ArrowRight", "ArrowLeft", "Home", "End"].includes(event.key)) return;
|
|
3602
|
+
const enabled = domOrdered.filter((t) => !t.disabled);
|
|
3603
|
+
if (enabled.length === 0) return;
|
|
3604
|
+
const currentIndex = enabled.findIndex(
|
|
3605
|
+
(t) => t.node === event.currentTarget
|
|
3606
|
+
);
|
|
3607
|
+
if (currentIndex === -1) return;
|
|
3608
|
+
event.preventDefault();
|
|
3609
|
+
let nextIndex = currentIndex;
|
|
3610
|
+
switch (event.key) {
|
|
3611
|
+
case "ArrowRight":
|
|
3612
|
+
nextIndex = (currentIndex + 1) % enabled.length;
|
|
3613
|
+
break;
|
|
3614
|
+
case "ArrowLeft":
|
|
3615
|
+
nextIndex = (currentIndex - 1 + enabled.length) % enabled.length;
|
|
3616
|
+
break;
|
|
3617
|
+
case "Home":
|
|
3618
|
+
nextIndex = 0;
|
|
3619
|
+
break;
|
|
3620
|
+
case "End":
|
|
3621
|
+
nextIndex = enabled.length - 1;
|
|
3622
|
+
break;
|
|
3623
|
+
}
|
|
3624
|
+
const target = enabled[nextIndex];
|
|
3625
|
+
if (!target) return;
|
|
3626
|
+
target.node.focus();
|
|
3627
|
+
select(target.value);
|
|
3628
|
+
};
|
|
3629
|
+
const contextValue = {
|
|
3630
|
+
value,
|
|
3631
|
+
select,
|
|
3632
|
+
tabStopValue,
|
|
3633
|
+
registerTrigger,
|
|
3634
|
+
unregisterTrigger,
|
|
3635
|
+
onTriggerKeyDown,
|
|
3636
|
+
getTriggerId,
|
|
3637
|
+
getPanelId
|
|
3638
|
+
};
|
|
3639
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TabsContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3640
|
+
"div",
|
|
3641
|
+
{
|
|
3642
|
+
ref,
|
|
3643
|
+
"data-slot": "tabs",
|
|
3644
|
+
className: cn(tabsRootVariants(), className),
|
|
3645
|
+
...props,
|
|
3646
|
+
children
|
|
3647
|
+
}
|
|
3648
|
+
) });
|
|
3649
|
+
}
|
|
3650
|
+
function TabsList({ className, children, ref, ...props }) {
|
|
3651
|
+
useTabsContext("Tabs.List");
|
|
3652
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3653
|
+
"div",
|
|
3654
|
+
{
|
|
3655
|
+
ref,
|
|
3656
|
+
role: "tablist",
|
|
3657
|
+
"data-slot": "tabs-list",
|
|
3658
|
+
className: cn(tabsListVariants(), className),
|
|
3659
|
+
...props,
|
|
3660
|
+
children
|
|
3661
|
+
}
|
|
3662
|
+
);
|
|
3663
|
+
}
|
|
3664
|
+
function TabsTrigger({
|
|
3665
|
+
value,
|
|
3666
|
+
disabled = false,
|
|
3667
|
+
className,
|
|
3668
|
+
children,
|
|
3669
|
+
onClick,
|
|
3670
|
+
onKeyDown,
|
|
3671
|
+
ref,
|
|
3672
|
+
...props
|
|
3673
|
+
}) {
|
|
3674
|
+
const {
|
|
3675
|
+
value: selectedValue,
|
|
3676
|
+
select,
|
|
3677
|
+
tabStopValue,
|
|
3678
|
+
registerTrigger,
|
|
3679
|
+
unregisterTrigger,
|
|
3680
|
+
onTriggerKeyDown,
|
|
3681
|
+
getTriggerId,
|
|
3682
|
+
getPanelId
|
|
3683
|
+
} = useTabsContext("Tabs.Trigger");
|
|
3684
|
+
const selected = value === selectedValue;
|
|
3685
|
+
const triggerId = getTriggerId(value);
|
|
3686
|
+
const panelId = getPanelId(value);
|
|
3687
|
+
const buttonRef = React21__namespace.useRef(null);
|
|
3688
|
+
const setButtonRef = (node) => {
|
|
3689
|
+
buttonRef.current = node;
|
|
3690
|
+
setRef7(ref, node);
|
|
3691
|
+
};
|
|
3692
|
+
React21__namespace.useEffect(() => {
|
|
3693
|
+
const node = buttonRef.current;
|
|
3694
|
+
if (!node) return;
|
|
3695
|
+
registerTrigger({ value, disabled, node });
|
|
3696
|
+
return () => unregisterTrigger(value);
|
|
3697
|
+
}, [registerTrigger, unregisterTrigger, value, disabled]);
|
|
3698
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3699
|
+
"button",
|
|
3700
|
+
{
|
|
3701
|
+
...props,
|
|
3702
|
+
ref: setButtonRef,
|
|
3703
|
+
type: "button",
|
|
3704
|
+
role: "tab",
|
|
3705
|
+
id: triggerId,
|
|
3706
|
+
"data-slot": "tabs-trigger",
|
|
3707
|
+
"data-state": selected ? "active" : "inactive",
|
|
3708
|
+
"data-disabled": disabled || void 0,
|
|
3709
|
+
"aria-selected": selected,
|
|
3710
|
+
"aria-controls": selected ? panelId : void 0,
|
|
3711
|
+
tabIndex: value === tabStopValue ? 0 : -1,
|
|
3712
|
+
disabled,
|
|
3713
|
+
className: cn(tabsTriggerVariants({ selected, disabled }), className),
|
|
3714
|
+
onClick: (event) => {
|
|
3715
|
+
onClick?.(event);
|
|
3716
|
+
if (!event.defaultPrevented) select(value);
|
|
3717
|
+
},
|
|
3718
|
+
onKeyDown: (event) => {
|
|
3719
|
+
onKeyDown?.(event);
|
|
3720
|
+
if (!event.defaultPrevented) onTriggerKeyDown(event);
|
|
3721
|
+
},
|
|
3722
|
+
children
|
|
3723
|
+
}
|
|
3724
|
+
);
|
|
3725
|
+
}
|
|
3726
|
+
function TabsContent({
|
|
3727
|
+
value,
|
|
3728
|
+
className,
|
|
3729
|
+
children,
|
|
3730
|
+
ref,
|
|
3731
|
+
...props
|
|
3732
|
+
}) {
|
|
3733
|
+
const {
|
|
3734
|
+
value: selectedValue,
|
|
3735
|
+
getTriggerId,
|
|
3736
|
+
getPanelId
|
|
3737
|
+
} = useTabsContext("Tabs.Content");
|
|
3738
|
+
if (value !== selectedValue) return null;
|
|
3739
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3740
|
+
"div",
|
|
3741
|
+
{
|
|
3742
|
+
ref,
|
|
3743
|
+
role: "tabpanel",
|
|
3744
|
+
id: getPanelId(value),
|
|
3745
|
+
"aria-labelledby": getTriggerId(value),
|
|
3746
|
+
"data-slot": "tabs-content",
|
|
3747
|
+
"data-state": "active",
|
|
3748
|
+
tabIndex: 0,
|
|
3749
|
+
className: cn(tabsContentVariants(), className),
|
|
3750
|
+
...props,
|
|
3751
|
+
children
|
|
3752
|
+
}
|
|
3753
|
+
);
|
|
3754
|
+
}
|
|
3755
|
+
var Tabs = {
|
|
3756
|
+
Root: TabsRoot,
|
|
3757
|
+
List: TabsList,
|
|
3758
|
+
Trigger: TabsTrigger,
|
|
3759
|
+
Content: TabsContent
|
|
3760
|
+
};
|
|
3761
|
+
var toastVariants = classVarianceAuthority.cva(
|
|
3762
|
+
"flex items-center gap-4 rounded-sm bg-card-reversed-background px-5 py-4"
|
|
3763
|
+
);
|
|
3764
|
+
var toastIconVariants = classVarianceAuthority.cva(
|
|
3765
|
+
"flex shrink-0 items-center [&_svg]:size-5 [&_svg]:shrink-0",
|
|
3766
|
+
{
|
|
3767
|
+
variants: {
|
|
3768
|
+
variation: {
|
|
3769
|
+
success: "text-success-primary-foreground",
|
|
3770
|
+
error: "text-destructive-primary-foreground"
|
|
3771
|
+
}
|
|
3772
|
+
},
|
|
3773
|
+
defaultVariants: { variation: "success" }
|
|
3774
|
+
}
|
|
3775
|
+
);
|
|
3776
|
+
var toastCloseVariants = classVarianceAuthority.cva([
|
|
3777
|
+
"flex shrink-0 cursor-pointer items-center rounded-xs text-card-reversed-foreground-muted",
|
|
3778
|
+
"transition-colors hover:text-card-reversed-foreground",
|
|
3779
|
+
"[&_svg]:size-5 [&_svg]:shrink-0",
|
|
3780
|
+
"outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus"
|
|
3781
|
+
]);
|
|
3782
|
+
var STATUS_ICON = {
|
|
3783
|
+
success: react.CheckCircleIcon,
|
|
3784
|
+
error: react.XCircleIcon
|
|
3785
|
+
};
|
|
3786
|
+
function Toast({
|
|
3787
|
+
variation = "success",
|
|
3788
|
+
children,
|
|
3789
|
+
onClose,
|
|
3790
|
+
className,
|
|
3791
|
+
ref,
|
|
3792
|
+
...props
|
|
3793
|
+
}) {
|
|
3794
|
+
const StatusIcon = STATUS_ICON[variation];
|
|
3795
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3796
|
+
"div",
|
|
3797
|
+
{
|
|
3798
|
+
"data-slot": "toast",
|
|
3799
|
+
role: variation === "error" ? "alert" : "status",
|
|
3800
|
+
className: cn(toastVariants(), className),
|
|
3801
|
+
ref,
|
|
3802
|
+
...props,
|
|
3803
|
+
children: [
|
|
3804
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
|
|
3805
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3806
|
+
"span",
|
|
3807
|
+
{
|
|
3808
|
+
"data-slot": "toast-icon",
|
|
3809
|
+
"aria-hidden": "true",
|
|
3810
|
+
className: toastIconVariants({ variation }),
|
|
3811
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { weight: "fill" })
|
|
3812
|
+
}
|
|
3813
|
+
),
|
|
3814
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "body-03 min-w-0 flex-1 text-card-reversed-foreground", children })
|
|
3815
|
+
] }),
|
|
3816
|
+
onClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3817
|
+
"button",
|
|
3818
|
+
{
|
|
3819
|
+
type: "button",
|
|
3820
|
+
"data-slot": "toast-close",
|
|
3821
|
+
"aria-label": "Dismiss",
|
|
3822
|
+
className: toastCloseVariants(),
|
|
3823
|
+
onClick: onClose,
|
|
3824
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.XIcon, { weight: "regular" })
|
|
3825
|
+
}
|
|
3826
|
+
)
|
|
3827
|
+
]
|
|
3828
|
+
}
|
|
3829
|
+
);
|
|
3830
|
+
}
|
|
3831
|
+
function Token({
|
|
3832
|
+
value,
|
|
3833
|
+
invalid = false,
|
|
3834
|
+
disabled = false,
|
|
3835
|
+
onRemove
|
|
3836
|
+
}) {
|
|
3837
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3838
|
+
"span",
|
|
3839
|
+
{
|
|
3840
|
+
"data-slot": "token",
|
|
3841
|
+
"data-invalid": invalid || void 0,
|
|
3842
|
+
tabIndex: disabled ? void 0 : 0,
|
|
3843
|
+
onKeyDown: disabled ? void 0 : (event) => {
|
|
3844
|
+
if (event.key === "Delete" || event.key === "Backspace") {
|
|
3845
|
+
event.preventDefault();
|
|
3846
|
+
onRemove("keyboard");
|
|
3847
|
+
}
|
|
3848
|
+
},
|
|
3849
|
+
className: cn(
|
|
3850
|
+
// `group` so the ✕ can react to the badge's hover/focus (see below).
|
|
3851
|
+
// Height is exactly 22px = 20px (`body-03` line) + 2px border, with NO
|
|
3852
|
+
// vertical padding: this keeps the field a constant 40px tall
|
|
3853
|
+
// (badge 22 + 8+8 field padding + 1+1 field border = 40, empty or filled).
|
|
3854
|
+
// Adding `py-*` here would push the field past 40px once tokens appear.
|
|
3855
|
+
"group inline-flex items-center gap-1 min-w-5 rounded-sm border px-1",
|
|
3856
|
+
// Default: neutral card border + background. Invalid: destructive accent
|
|
3857
|
+
// recolour (there is no `card-destructive` token — reuse `input-destructive-*`).
|
|
3858
|
+
invalid ? "border-input-destructive-border bg-card-background" : "border-card-border bg-card-background",
|
|
3859
|
+
// Interactive chrome (only when not disabled): hover/focus fill to
|
|
3860
|
+
// `card-accent` (the DS's "stronger/hover" step up from `card-background`)
|
|
3861
|
+
// and the STANDARD ADR-0010/0012 focus ring on the whole badge — the ✕
|
|
3862
|
+
// itself carries no ring. `cursor-default` overrides the field's I-beam
|
|
3863
|
+
// so a badge doesn't read as "type here".
|
|
3864
|
+
!disabled && "cursor-default hover:bg-card-accent focus-visible:bg-card-accent outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus"
|
|
3865
|
+
),
|
|
3866
|
+
children: [
|
|
3867
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3868
|
+
"span",
|
|
3869
|
+
{
|
|
3870
|
+
"data-slot": "token-label",
|
|
3871
|
+
className: cn(
|
|
3872
|
+
"body-03",
|
|
3873
|
+
// Disabled wins over invalid: the Figma disabled badge (561:12782) is
|
|
3874
|
+
// fully muted — a disabled field is inert, so no destructive red.
|
|
3875
|
+
disabled ? "text-card-foreground-muted" : invalid ? "text-input-destructive-foreground-accent" : "text-card-foreground"
|
|
3876
|
+
),
|
|
3877
|
+
children: value
|
|
3878
|
+
}
|
|
3879
|
+
),
|
|
3880
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3881
|
+
"button",
|
|
3882
|
+
{
|
|
3883
|
+
type: "button",
|
|
3884
|
+
"aria-label": `Remove ${value}`,
|
|
3885
|
+
onClick: () => onRemove("pointer"),
|
|
3886
|
+
disabled,
|
|
3887
|
+
tabIndex: -1,
|
|
3888
|
+
"data-slot": "token-remove",
|
|
3889
|
+
className: cn(
|
|
3890
|
+
// Reset native button chrome.
|
|
3891
|
+
"inline-flex cursor-pointer items-center justify-center rounded-xs bg-transparent p-0",
|
|
3892
|
+
// Icon colour follows the badge state. Disabled wins (fully muted, no
|
|
3893
|
+
// hover strengthen — the badge is inert). Valid: muted at rest,
|
|
3894
|
+
// strengthens to `card-foreground` on badge hover/focus (via `group-*`).
|
|
3895
|
+
// Invalid: stays destructive (a strengthen would drop the flag).
|
|
3896
|
+
disabled ? "text-card-foreground-muted" : invalid ? "text-input-destructive-foreground-accent" : "text-card-foreground-muted group-hover:text-card-foreground group-focus-visible:text-card-foreground",
|
|
3897
|
+
"disabled:cursor-not-allowed"
|
|
3898
|
+
),
|
|
3899
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.XIcon, { weight: "regular", "aria-hidden": "true", className: "size-4" })
|
|
3900
|
+
}
|
|
3901
|
+
)
|
|
3902
|
+
]
|
|
3903
|
+
}
|
|
3904
|
+
);
|
|
3905
|
+
}
|
|
3906
|
+
function splitOnSeparators(raw, separators) {
|
|
3907
|
+
if (separators.length === 0) return [raw];
|
|
3908
|
+
const escaped = separators.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("");
|
|
3909
|
+
return raw.split(new RegExp(`[${escaped}]`));
|
|
3910
|
+
}
|
|
3911
|
+
function TokenInput({
|
|
3912
|
+
className,
|
|
3913
|
+
fieldClassName,
|
|
3914
|
+
wrapperClassName,
|
|
3915
|
+
value,
|
|
3916
|
+
defaultValue,
|
|
3917
|
+
onChange,
|
|
3918
|
+
separators = [","],
|
|
3919
|
+
commitOnBlur = false,
|
|
3920
|
+
validate,
|
|
3921
|
+
label,
|
|
3922
|
+
optional = false,
|
|
3923
|
+
hint,
|
|
3924
|
+
error = false,
|
|
3925
|
+
disabled,
|
|
3926
|
+
placeholder,
|
|
3927
|
+
id: idProp,
|
|
3928
|
+
onKeyDown,
|
|
3929
|
+
onBlur,
|
|
3930
|
+
onInput,
|
|
3931
|
+
ref,
|
|
3932
|
+
...props
|
|
3933
|
+
}) {
|
|
3934
|
+
const isControlled = value !== void 0;
|
|
3935
|
+
const [tokensState, setTokensState] = React21__namespace.useState(
|
|
3936
|
+
() => defaultValue ?? []
|
|
3937
|
+
);
|
|
3938
|
+
const tokens = isControlled ? value : tokensState;
|
|
3939
|
+
const innerRef = React21__namespace.useRef(null);
|
|
3940
|
+
const setInputRef = React21__namespace.useCallback(
|
|
3941
|
+
(node) => {
|
|
3942
|
+
innerRef.current = node;
|
|
3943
|
+
if (typeof ref === "function") ref(node);
|
|
3944
|
+
else if (ref)
|
|
3945
|
+
ref.current = node;
|
|
3946
|
+
},
|
|
3947
|
+
[ref]
|
|
3948
|
+
);
|
|
3949
|
+
const generatedId = React21__namespace.useId();
|
|
3950
|
+
const id = idProp ?? generatedId;
|
|
3951
|
+
const hintId = hint ? `${id}-hint` : void 0;
|
|
3952
|
+
const commitTokens = (next) => {
|
|
3953
|
+
if (!isControlled) setTokensState(next);
|
|
3954
|
+
onChange?.(next);
|
|
3955
|
+
};
|
|
3956
|
+
const addTokens = (pieces) => {
|
|
3957
|
+
const next = [...tokens];
|
|
3958
|
+
const seen = new Set(next.map((t) => t.toLowerCase()));
|
|
3959
|
+
let changed = false;
|
|
3960
|
+
for (const piece of pieces) {
|
|
3961
|
+
const trimmed = piece.trim();
|
|
3962
|
+
if (trimmed === "") continue;
|
|
3963
|
+
const key = trimmed.toLowerCase();
|
|
3964
|
+
if (seen.has(key)) continue;
|
|
3965
|
+
seen.add(key);
|
|
3966
|
+
next.push(trimmed);
|
|
3967
|
+
changed = true;
|
|
3968
|
+
}
|
|
3969
|
+
if (changed) commitTokens(next);
|
|
3970
|
+
};
|
|
3971
|
+
const removeTokenAt = (index, origin) => {
|
|
3972
|
+
commitTokens(tokens.filter((_, i) => i !== index));
|
|
3973
|
+
if (origin === "keyboard") innerRef.current?.focus();
|
|
3974
|
+
};
|
|
3975
|
+
const handleInput = (event) => {
|
|
3976
|
+
onInput?.(event);
|
|
3977
|
+
const input = event.currentTarget;
|
|
3978
|
+
const raw = input.value;
|
|
3979
|
+
if (separators.some((sep) => raw.includes(sep))) {
|
|
3980
|
+
addTokens(splitOnSeparators(raw, separators));
|
|
3981
|
+
input.value = "";
|
|
3982
|
+
}
|
|
3983
|
+
};
|
|
3984
|
+
const handleKeyDown = (event) => {
|
|
3985
|
+
onKeyDown?.(event);
|
|
3986
|
+
if (event.key === "Backspace" && event.currentTarget.value === "" && tokens.length > 0) {
|
|
3987
|
+
event.preventDefault();
|
|
3988
|
+
removeTokenAt(tokens.length - 1);
|
|
3989
|
+
}
|
|
3990
|
+
};
|
|
3991
|
+
const handleBlur = (event) => {
|
|
3992
|
+
onBlur?.(event);
|
|
3993
|
+
if (commitOnBlur) {
|
|
3994
|
+
const input = event.currentTarget;
|
|
3995
|
+
if (input.value.trim() !== "") {
|
|
3996
|
+
addTokens([input.value]);
|
|
3997
|
+
input.value = "";
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
};
|
|
4001
|
+
const handleFieldMouseDown = (event) => {
|
|
4002
|
+
const target = event.target;
|
|
4003
|
+
if (target.closest("[data-slot=token]")) return;
|
|
4004
|
+
if (target === innerRef.current) return;
|
|
4005
|
+
event.preventDefault();
|
|
4006
|
+
innerRef.current?.focus();
|
|
4007
|
+
};
|
|
4008
|
+
const iconStateClass = cn(
|
|
4009
|
+
error ? "text-input-destructive-foreground-accent" : "text-input-foreground",
|
|
4010
|
+
disabled && "text-input-foreground-muted"
|
|
4011
|
+
);
|
|
4012
|
+
const HintIcon = error ? react.WarningIcon : react.InfoIcon;
|
|
4013
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-1.5", wrapperClassName), children: [
|
|
4014
|
+
label != null && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
4015
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4016
|
+
"label",
|
|
4017
|
+
{
|
|
4018
|
+
htmlFor: id,
|
|
4019
|
+
"data-slot": "input-label",
|
|
4020
|
+
className: cn(
|
|
4021
|
+
"caption-03-medium uppercase",
|
|
4022
|
+
error ? "text-input-destructive-foreground-accent" : "text-input-foreground",
|
|
4023
|
+
disabled && "text-input-foreground-muted"
|
|
4024
|
+
),
|
|
4025
|
+
children: label
|
|
4026
|
+
}
|
|
4027
|
+
),
|
|
4028
|
+
optional && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4029
|
+
"span",
|
|
4030
|
+
{
|
|
4031
|
+
"data-slot": "input-optional",
|
|
4032
|
+
className: "body-04 text-input-foreground-muted",
|
|
4033
|
+
children: "Optional"
|
|
4034
|
+
}
|
|
4035
|
+
)
|
|
4036
|
+
] }),
|
|
4037
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4038
|
+
"div",
|
|
4039
|
+
{
|
|
4040
|
+
"data-slot": "token-input-field",
|
|
4041
|
+
"data-error": error || void 0,
|
|
4042
|
+
onMouseDown: handleFieldMouseDown,
|
|
4043
|
+
className: cn(
|
|
4044
|
+
// Reuse Input's field styling (bg/border/rounded/focus-within/error).
|
|
4045
|
+
inputVariants({ variation: "field", size: "lg", error }),
|
|
4046
|
+
// Layout overrides (tailwind-merge last-wins): grow with wrapped rows,
|
|
4047
|
+
// keep single-row min height, allow badges + input to wrap, uniform
|
|
4048
|
+
// spacing/2 inset (overrides field-lg's px-3 py-2).
|
|
4049
|
+
"h-auto min-h-10 flex-wrap gap-1 p-2",
|
|
4050
|
+
// Icon slots inherit the shared state colour.
|
|
4051
|
+
iconStateClass,
|
|
4052
|
+
fieldClassName
|
|
4053
|
+
),
|
|
4054
|
+
children: [
|
|
4055
|
+
tokens.map((token, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4056
|
+
Token,
|
|
4057
|
+
{
|
|
4058
|
+
value: token,
|
|
4059
|
+
invalid: validate ? !validate(token) : false,
|
|
4060
|
+
disabled,
|
|
4061
|
+
onRemove: (origin) => removeTokenAt(index, origin)
|
|
4062
|
+
},
|
|
4063
|
+
`${token}-${index}`
|
|
4064
|
+
)),
|
|
4065
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4066
|
+
"input",
|
|
4067
|
+
{
|
|
4068
|
+
id,
|
|
4069
|
+
"data-slot": "token-input",
|
|
4070
|
+
disabled,
|
|
4071
|
+
placeholder: tokens.length === 0 ? placeholder : void 0,
|
|
4072
|
+
"aria-invalid": error || void 0,
|
|
4073
|
+
"aria-describedby": hintId,
|
|
4074
|
+
className: cn(
|
|
4075
|
+
// Typography + grow to fill remaining row width. `min-w-0` lets the
|
|
4076
|
+
// input shrink; a small basis keeps it usable when tokens wrap.
|
|
4077
|
+
"body-03 w-0 min-w-16 flex-1 bg-transparent outline-none",
|
|
4078
|
+
"text-input-foreground-accent caret-input-foreground-accent",
|
|
4079
|
+
"placeholder:text-input-foreground-muted",
|
|
4080
|
+
"disabled:cursor-not-allowed disabled:text-input-foreground-muted",
|
|
4081
|
+
className
|
|
4082
|
+
),
|
|
4083
|
+
ref: setInputRef,
|
|
4084
|
+
onInput: handleInput,
|
|
4085
|
+
onKeyDown: handleKeyDown,
|
|
4086
|
+
onBlur: handleBlur,
|
|
4087
|
+
...props
|
|
4088
|
+
}
|
|
4089
|
+
)
|
|
4090
|
+
]
|
|
4091
|
+
}
|
|
4092
|
+
),
|
|
4093
|
+
hint != null && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4094
|
+
"p",
|
|
4095
|
+
{
|
|
4096
|
+
id: hintId,
|
|
4097
|
+
"data-slot": "input-hint",
|
|
4098
|
+
className: cn(
|
|
4099
|
+
"body-04 flex items-center gap-1",
|
|
4100
|
+
error ? "text-input-destructive-foreground-accent" : "text-input-foreground",
|
|
4101
|
+
disabled && "text-input-foreground-muted"
|
|
4102
|
+
),
|
|
4103
|
+
children: [
|
|
4104
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4105
|
+
HintIcon,
|
|
4106
|
+
{
|
|
4107
|
+
"aria-hidden": "true",
|
|
4108
|
+
weight: "regular",
|
|
4109
|
+
className: "size-4 shrink-0"
|
|
4110
|
+
}
|
|
4111
|
+
),
|
|
4112
|
+
hint
|
|
4113
|
+
]
|
|
4114
|
+
}
|
|
4115
|
+
)
|
|
4116
|
+
] });
|
|
4117
|
+
}
|
|
4118
|
+
var tooltipVariants = classVarianceAuthority.cva([
|
|
4119
|
+
"pointer-events-none max-w-[244px] rounded-sm bg-card-reversed-background px-1.5 py-1",
|
|
4120
|
+
"body-04 text-center text-card-reversed-foreground shadow-xs"
|
|
4121
|
+
]);
|
|
4122
|
+
var TOOLTIP_OFFSET = 6;
|
|
4123
|
+
var VIEWPORT_MARGIN = 8;
|
|
4124
|
+
var OPPOSITE_SIDE = {
|
|
4125
|
+
top: "bottom",
|
|
4126
|
+
bottom: "top",
|
|
4127
|
+
left: "right",
|
|
4128
|
+
right: "left"
|
|
4129
|
+
};
|
|
4130
|
+
function sideFits(trigger, content, side, viewport) {
|
|
4131
|
+
switch (side) {
|
|
4132
|
+
case "top":
|
|
4133
|
+
return trigger.top - TOOLTIP_OFFSET - content.height >= 0;
|
|
4134
|
+
case "bottom":
|
|
4135
|
+
return trigger.top + trigger.height + TOOLTIP_OFFSET + content.height <= viewport.height;
|
|
4136
|
+
case "left":
|
|
4137
|
+
return trigger.left - TOOLTIP_OFFSET - content.width >= 0;
|
|
4138
|
+
case "right":
|
|
4139
|
+
return trigger.left + trigger.width + TOOLTIP_OFFSET + content.width <= viewport.width;
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
function alignCoord(anchorStart, anchorSize, contentSize, align) {
|
|
4143
|
+
switch (align) {
|
|
4144
|
+
case "start":
|
|
4145
|
+
return anchorStart;
|
|
4146
|
+
case "center":
|
|
4147
|
+
return anchorStart + anchorSize / 2 - contentSize / 2;
|
|
4148
|
+
case "end":
|
|
4149
|
+
return anchorStart + anchorSize - contentSize;
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
function clamp(value, min, max) {
|
|
4153
|
+
return Math.max(min, Math.min(value, Math.max(min, max)));
|
|
4154
|
+
}
|
|
4155
|
+
function computeTooltipPosition(trigger, content, side, align, viewport) {
|
|
4156
|
+
const resolvedSide = sideFits(trigger, content, side, viewport) || !sideFits(trigger, content, OPPOSITE_SIDE[side], viewport) ? side : OPPOSITE_SIDE[side];
|
|
4157
|
+
const vertical = resolvedSide === "top" || resolvedSide === "bottom";
|
|
4158
|
+
let top;
|
|
4159
|
+
let left;
|
|
4160
|
+
if (vertical) {
|
|
4161
|
+
top = resolvedSide === "top" ? trigger.top - TOOLTIP_OFFSET - content.height : trigger.top + trigger.height + TOOLTIP_OFFSET;
|
|
4162
|
+
left = alignCoord(trigger.left, trigger.width, content.width, align);
|
|
4163
|
+
left = clamp(
|
|
4164
|
+
left,
|
|
4165
|
+
VIEWPORT_MARGIN,
|
|
4166
|
+
viewport.width - content.width - VIEWPORT_MARGIN
|
|
4167
|
+
);
|
|
4168
|
+
} else {
|
|
4169
|
+
left = resolvedSide === "left" ? trigger.left - TOOLTIP_OFFSET - content.width : trigger.left + trigger.width + TOOLTIP_OFFSET;
|
|
4170
|
+
top = alignCoord(trigger.top, trigger.height, content.height, align);
|
|
4171
|
+
top = clamp(
|
|
4172
|
+
top,
|
|
4173
|
+
VIEWPORT_MARGIN,
|
|
4174
|
+
viewport.height - content.height - VIEWPORT_MARGIN
|
|
4175
|
+
);
|
|
4176
|
+
}
|
|
4177
|
+
return { top, left, side: resolvedSide };
|
|
4178
|
+
}
|
|
4179
|
+
var TooltipContext = React21__namespace.createContext(null);
|
|
4180
|
+
function useTooltipContext(part) {
|
|
4181
|
+
const context = React21__namespace.useContext(TooltipContext);
|
|
4182
|
+
if (context === null) {
|
|
4183
|
+
throw new Error(`${part} must be used within <Tooltip.Root>`);
|
|
4184
|
+
}
|
|
4185
|
+
return context;
|
|
4186
|
+
}
|
|
4187
|
+
function setRef8(ref, node) {
|
|
4188
|
+
if (typeof ref === "function") ref(node);
|
|
4189
|
+
else if (ref) ref.current = node;
|
|
4190
|
+
}
|
|
4191
|
+
function TooltipRoot({
|
|
4192
|
+
open: openProp,
|
|
4193
|
+
defaultOpen = false,
|
|
4194
|
+
onOpenChange,
|
|
4195
|
+
delayDuration = 600,
|
|
4196
|
+
side = "top",
|
|
4197
|
+
align = "center",
|
|
4198
|
+
disabled = false,
|
|
4199
|
+
children
|
|
4200
|
+
}) {
|
|
4201
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React21__namespace.useState(defaultOpen);
|
|
4202
|
+
const isControlled = openProp !== void 0;
|
|
4203
|
+
const rawOpen = isControlled ? openProp : uncontrolledOpen;
|
|
4204
|
+
const open = disabled ? false : rawOpen;
|
|
4205
|
+
const reactId = React21__namespace.useId();
|
|
4206
|
+
const contentId = `${reactId}-tooltip`;
|
|
4207
|
+
const anchorRef = React21__namespace.useRef(null);
|
|
4208
|
+
const [anchorRect, setAnchorRect] = React21__namespace.useState(null);
|
|
4209
|
+
const timerRef = React21__namespace.useRef(null);
|
|
4210
|
+
const setOpen = React21__namespace.useCallback(
|
|
4211
|
+
(next) => {
|
|
4212
|
+
if (!isControlled) setUncontrolledOpen(next);
|
|
4213
|
+
onOpenChange?.(next);
|
|
4214
|
+
},
|
|
4215
|
+
[isControlled, onOpenChange]
|
|
4216
|
+
);
|
|
4217
|
+
const clearTimer = React21__namespace.useCallback(() => {
|
|
4218
|
+
if (timerRef.current !== null) {
|
|
4219
|
+
clearTimeout(timerRef.current);
|
|
4220
|
+
timerRef.current = null;
|
|
4221
|
+
}
|
|
4222
|
+
}, []);
|
|
4223
|
+
React21__namespace.useEffect(() => clearTimer, [clearTimer]);
|
|
4224
|
+
const onTriggerPointerEnter = () => {
|
|
4225
|
+
if (disabled) return;
|
|
4226
|
+
clearTimer();
|
|
4227
|
+
timerRef.current = setTimeout(() => setOpen(true), delayDuration);
|
|
4228
|
+
};
|
|
4229
|
+
const onTriggerPointerLeave = () => {
|
|
4230
|
+
clearTimer();
|
|
4231
|
+
setOpen(false);
|
|
4232
|
+
};
|
|
4233
|
+
const onTriggerFocus = () => {
|
|
4234
|
+
if (disabled) return;
|
|
4235
|
+
clearTimer();
|
|
4236
|
+
setOpen(true);
|
|
4237
|
+
};
|
|
4238
|
+
const onTriggerBlur = () => {
|
|
4239
|
+
clearTimer();
|
|
4240
|
+
setOpen(false);
|
|
4241
|
+
};
|
|
4242
|
+
React21__namespace.useLayoutEffect(() => {
|
|
4243
|
+
if (!open) {
|
|
4244
|
+
setAnchorRect(null);
|
|
4245
|
+
return;
|
|
4246
|
+
}
|
|
4247
|
+
const measure = () => {
|
|
4248
|
+
const node = anchorRef.current;
|
|
4249
|
+
if (node) setAnchorRect(node.getBoundingClientRect());
|
|
4250
|
+
};
|
|
4251
|
+
measure();
|
|
4252
|
+
window.addEventListener("scroll", measure, true);
|
|
4253
|
+
window.addEventListener("resize", measure);
|
|
4254
|
+
return () => {
|
|
4255
|
+
window.removeEventListener("scroll", measure, true);
|
|
4256
|
+
window.removeEventListener("resize", measure);
|
|
4257
|
+
};
|
|
4258
|
+
}, [open]);
|
|
4259
|
+
React21__namespace.useEffect(() => {
|
|
4260
|
+
if (!open) return;
|
|
4261
|
+
const handleEscape = (event) => {
|
|
4262
|
+
if (event.key === "Escape") {
|
|
4263
|
+
clearTimer();
|
|
4264
|
+
setOpen(false);
|
|
4265
|
+
}
|
|
4266
|
+
};
|
|
4267
|
+
document.addEventListener("keydown", handleEscape);
|
|
4268
|
+
return () => document.removeEventListener("keydown", handleEscape);
|
|
4269
|
+
}, [open, setOpen, clearTimer]);
|
|
4270
|
+
const value = {
|
|
4271
|
+
open,
|
|
4272
|
+
setOpen,
|
|
4273
|
+
disabled,
|
|
4274
|
+
contentId,
|
|
4275
|
+
side,
|
|
4276
|
+
align,
|
|
4277
|
+
anchorRef,
|
|
4278
|
+
anchorRect,
|
|
4279
|
+
onTriggerPointerEnter,
|
|
4280
|
+
onTriggerPointerLeave,
|
|
4281
|
+
onTriggerFocus,
|
|
4282
|
+
onTriggerBlur
|
|
4283
|
+
};
|
|
4284
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TooltipContext.Provider, { value, children });
|
|
4285
|
+
}
|
|
4286
|
+
function TooltipTrigger({
|
|
4287
|
+
asChild = false,
|
|
4288
|
+
className,
|
|
4289
|
+
children,
|
|
4290
|
+
ref,
|
|
4291
|
+
onPointerEnter,
|
|
4292
|
+
onPointerLeave,
|
|
4293
|
+
onFocus,
|
|
4294
|
+
onBlur,
|
|
4295
|
+
...props
|
|
4296
|
+
}) {
|
|
4297
|
+
const {
|
|
4298
|
+
open,
|
|
4299
|
+
disabled,
|
|
4300
|
+
contentId,
|
|
4301
|
+
anchorRef,
|
|
4302
|
+
onTriggerPointerEnter,
|
|
4303
|
+
onTriggerPointerLeave,
|
|
4304
|
+
onTriggerFocus,
|
|
4305
|
+
onTriggerBlur
|
|
4306
|
+
} = useTooltipContext("Tooltip.Trigger");
|
|
4307
|
+
const describedBy = !disabled && open ? contentId : void 0;
|
|
4308
|
+
const handlePointerEnter = (event) => {
|
|
4309
|
+
onPointerEnter?.(event);
|
|
4310
|
+
onTriggerPointerEnter();
|
|
4311
|
+
};
|
|
4312
|
+
const handlePointerLeave = (event) => {
|
|
4313
|
+
onPointerLeave?.(event);
|
|
4314
|
+
onTriggerPointerLeave();
|
|
4315
|
+
};
|
|
4316
|
+
const handleFocus = (event) => {
|
|
4317
|
+
onFocus?.(event);
|
|
4318
|
+
onTriggerFocus();
|
|
4319
|
+
};
|
|
4320
|
+
const handleBlur = (event) => {
|
|
4321
|
+
onBlur?.(event);
|
|
4322
|
+
onTriggerBlur();
|
|
4323
|
+
};
|
|
4324
|
+
if (asChild) {
|
|
4325
|
+
if (!React21__namespace.isValidElement(children)) {
|
|
4326
|
+
throw new Error(
|
|
4327
|
+
"Tooltip.Trigger: `asChild` requires a single React element child"
|
|
4328
|
+
);
|
|
4329
|
+
}
|
|
4330
|
+
const child = children;
|
|
4331
|
+
const childProps = child.props;
|
|
4332
|
+
const childHandlers = childProps;
|
|
4333
|
+
return React21__namespace.cloneElement(
|
|
4334
|
+
child,
|
|
4335
|
+
{
|
|
4336
|
+
...props,
|
|
4337
|
+
...childProps,
|
|
4338
|
+
"data-slot": "tooltip-trigger",
|
|
4339
|
+
"aria-describedby": describedBy,
|
|
4340
|
+
onPointerEnter: (event) => {
|
|
4341
|
+
childHandlers.onPointerEnter?.(event);
|
|
4342
|
+
onPointerEnter?.(event);
|
|
4343
|
+
onTriggerPointerEnter();
|
|
4344
|
+
},
|
|
4345
|
+
onPointerLeave: (event) => {
|
|
4346
|
+
childHandlers.onPointerLeave?.(event);
|
|
4347
|
+
onPointerLeave?.(event);
|
|
4348
|
+
onTriggerPointerLeave();
|
|
4349
|
+
},
|
|
4350
|
+
onFocus: (event) => {
|
|
4351
|
+
childHandlers.onFocus?.(event);
|
|
4352
|
+
onFocus?.(event);
|
|
4353
|
+
onTriggerFocus();
|
|
4354
|
+
},
|
|
4355
|
+
onBlur: (event) => {
|
|
4356
|
+
childHandlers.onBlur?.(event);
|
|
4357
|
+
onBlur?.(event);
|
|
4358
|
+
onTriggerBlur();
|
|
4359
|
+
},
|
|
4360
|
+
className: cn(className, childHandlers.className),
|
|
4361
|
+
ref: (node) => {
|
|
4362
|
+
anchorRef.current = node;
|
|
4363
|
+
setRef8(ref, node);
|
|
4364
|
+
setRef8(childHandlers.ref, node);
|
|
4365
|
+
}
|
|
4366
|
+
},
|
|
4367
|
+
childProps.children
|
|
4368
|
+
);
|
|
4369
|
+
}
|
|
4370
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4371
|
+
"button",
|
|
4372
|
+
{
|
|
4373
|
+
...props,
|
|
4374
|
+
type: props.type ?? "button",
|
|
4375
|
+
"data-slot": "tooltip-trigger",
|
|
4376
|
+
"aria-describedby": describedBy,
|
|
4377
|
+
className,
|
|
4378
|
+
onPointerEnter: handlePointerEnter,
|
|
4379
|
+
onPointerLeave: handlePointerLeave,
|
|
4380
|
+
onFocus: handleFocus,
|
|
4381
|
+
onBlur: handleBlur,
|
|
4382
|
+
ref: (node) => {
|
|
4383
|
+
anchorRef.current = node;
|
|
4384
|
+
setRef8(ref, node);
|
|
4385
|
+
},
|
|
4386
|
+
children
|
|
4387
|
+
}
|
|
4388
|
+
);
|
|
4389
|
+
}
|
|
4390
|
+
function TooltipContent({
|
|
4391
|
+
className,
|
|
4392
|
+
children,
|
|
4393
|
+
style,
|
|
4394
|
+
ref,
|
|
4395
|
+
...props
|
|
4396
|
+
}) {
|
|
4397
|
+
const { open, contentId, side, align, anchorRect } = useTooltipContext("Tooltip.Content");
|
|
4398
|
+
const contentRef = React21__namespace.useRef(null);
|
|
4399
|
+
const [placement, setPlacement] = React21__namespace.useState(null);
|
|
4400
|
+
React21__namespace.useLayoutEffect(() => {
|
|
4401
|
+
if (!open || anchorRect == null) {
|
|
4402
|
+
setPlacement(null);
|
|
4403
|
+
return;
|
|
4404
|
+
}
|
|
4405
|
+
const node = contentRef.current;
|
|
4406
|
+
if (!node) return;
|
|
4407
|
+
const rect = node.getBoundingClientRect();
|
|
4408
|
+
setPlacement(
|
|
4409
|
+
computeTooltipPosition(
|
|
4410
|
+
anchorRect,
|
|
4411
|
+
{ width: rect.width, height: rect.height },
|
|
4412
|
+
side,
|
|
4413
|
+
align,
|
|
4414
|
+
{ width: window.innerWidth, height: window.innerHeight }
|
|
4415
|
+
)
|
|
4416
|
+
);
|
|
4417
|
+
}, [open, anchorRect, side, align]);
|
|
4418
|
+
if (!open) return null;
|
|
4419
|
+
const positioned = placement !== null;
|
|
4420
|
+
const positionStyle = positioned ? {
|
|
4421
|
+
position: "absolute",
|
|
4422
|
+
top: placement.top + window.scrollY,
|
|
4423
|
+
left: placement.left + window.scrollX,
|
|
4424
|
+
zIndex: 50,
|
|
4425
|
+
...style
|
|
4426
|
+
} : {
|
|
4427
|
+
position: "absolute",
|
|
4428
|
+
top: 0,
|
|
4429
|
+
left: 0,
|
|
4430
|
+
visibility: "hidden",
|
|
4431
|
+
zIndex: 50,
|
|
4432
|
+
...style
|
|
4433
|
+
};
|
|
4434
|
+
const setContentRef = (node) => {
|
|
4435
|
+
contentRef.current = node;
|
|
4436
|
+
setRef8(ref, node);
|
|
4437
|
+
};
|
|
4438
|
+
return reactDom.createPortal(
|
|
4439
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4440
|
+
"div",
|
|
4441
|
+
{
|
|
4442
|
+
...props,
|
|
4443
|
+
ref: setContentRef,
|
|
4444
|
+
role: "tooltip",
|
|
4445
|
+
id: contentId,
|
|
4446
|
+
"data-slot": "tooltip-content",
|
|
4447
|
+
"data-side": positioned ? placement.side : side,
|
|
4448
|
+
"data-align": align,
|
|
4449
|
+
className: cn(tooltipVariants(), className),
|
|
4450
|
+
style: positionStyle,
|
|
4451
|
+
children
|
|
4452
|
+
}
|
|
4453
|
+
),
|
|
4454
|
+
document.body
|
|
4455
|
+
);
|
|
4456
|
+
}
|
|
4457
|
+
var Tooltip = {
|
|
4458
|
+
Root: TooltipRoot,
|
|
4459
|
+
Trigger: TooltipTrigger,
|
|
4460
|
+
Content: TooltipContent
|
|
4461
|
+
};
|
|
4462
|
+
|
|
4463
|
+
exports.Accordion = Accordion;
|
|
4464
|
+
exports.AccordionContent = AccordionContent;
|
|
4465
|
+
exports.AccordionItem = AccordionItem;
|
|
4466
|
+
exports.AccordionRoot = AccordionRoot;
|
|
4467
|
+
exports.AccordionTrigger = AccordionTrigger;
|
|
4468
|
+
exports.BUTTON_CUT = BUTTON_CUT;
|
|
4469
|
+
exports.Button = Button;
|
|
4470
|
+
exports.CellText = CellText;
|
|
4471
|
+
exports.Checkbox = Checkbox;
|
|
4472
|
+
exports.Dialog = Dialog;
|
|
4473
|
+
exports.DialogBody = DialogBody;
|
|
4474
|
+
exports.DialogClose = DialogClose;
|
|
4475
|
+
exports.DialogContent = DialogContent;
|
|
4476
|
+
exports.DialogFooter = DialogFooter;
|
|
4477
|
+
exports.DialogHeader = DialogHeader;
|
|
4478
|
+
exports.DialogRoot = DialogRoot;
|
|
4479
|
+
exports.DialogTrigger = DialogTrigger;
|
|
4480
|
+
exports.FeatureCard = FeatureCard;
|
|
4481
|
+
exports.Filter = Filter;
|
|
4482
|
+
exports.FilterTrigger = FilterTrigger;
|
|
4483
|
+
exports.InlineSelect = InlineSelect;
|
|
4484
|
+
exports.InlineSelectTrigger = InlineSelectTrigger;
|
|
4485
|
+
exports.Input = Input;
|
|
4486
|
+
exports.Panel = Panel;
|
|
4487
|
+
exports.PanelBody = PanelBody;
|
|
4488
|
+
exports.PanelFooter = PanelFooter;
|
|
4489
|
+
exports.PanelHeader = PanelHeader;
|
|
4490
|
+
exports.PanelRoot = PanelRoot;
|
|
4491
|
+
exports.PropertyGridContent = PropertyGridContent;
|
|
4492
|
+
exports.PropertyGridItem = PropertyGridItem;
|
|
4493
|
+
exports.PropertyGridTitle = PropertyGridTitle;
|
|
4494
|
+
exports.Radio = Radio;
|
|
4495
|
+
exports.SearchBar = SearchBar;
|
|
4496
|
+
exports.Select = Select;
|
|
4497
|
+
exports.SelectListbox = SelectListbox;
|
|
4498
|
+
exports.SelectTrigger = SelectTrigger;
|
|
4499
|
+
exports.SelectableFeatureCard = SelectableFeatureCard;
|
|
4500
|
+
exports.SidebarItem = SidebarItem;
|
|
4501
|
+
exports.Switch = Switch;
|
|
4502
|
+
exports.TableCell = TableCell;
|
|
4503
|
+
exports.TableTitle = TableTitle;
|
|
4504
|
+
exports.Tabs = Tabs;
|
|
4505
|
+
exports.TabsContent = TabsContent;
|
|
4506
|
+
exports.TabsList = TabsList;
|
|
4507
|
+
exports.TabsRoot = TabsRoot;
|
|
4508
|
+
exports.TabsTrigger = TabsTrigger;
|
|
4509
|
+
exports.Toast = Toast;
|
|
4510
|
+
exports.TokenInput = TokenInput;
|
|
4511
|
+
exports.Tooltip = Tooltip;
|
|
4512
|
+
exports.TooltipContent = TooltipContent;
|
|
4513
|
+
exports.TooltipRoot = TooltipRoot;
|
|
4514
|
+
exports.TooltipTrigger = TooltipTrigger;
|
|
4515
|
+
exports.accordionContentVariants = accordionContentVariants;
|
|
4516
|
+
exports.accordionItemVariants = accordionItemVariants;
|
|
4517
|
+
exports.accordionRootVariants = accordionRootVariants;
|
|
4518
|
+
exports.accordionTriggerIconVariants = accordionTriggerIconVariants;
|
|
4519
|
+
exports.accordionTriggerTitleVariants = accordionTriggerTitleVariants;
|
|
4520
|
+
exports.accordionTriggerVariants = accordionTriggerVariants;
|
|
4521
|
+
exports.buttonVariants = buttonVariants;
|
|
4522
|
+
exports.checkboxVariants = checkboxVariants;
|
|
4523
|
+
exports.computeListboxHorizontal = computeListboxHorizontal;
|
|
4524
|
+
exports.computeListboxPosition = computeListboxPosition;
|
|
4525
|
+
exports.computeTooltipPosition = computeTooltipPosition;
|
|
4526
|
+
exports.dialogBodyVariants = dialogBodyVariants;
|
|
4527
|
+
exports.dialogCloseButtonVariants = dialogCloseButtonVariants;
|
|
4528
|
+
exports.dialogContentVariants = dialogContentVariants;
|
|
4529
|
+
exports.dialogFooterVariants = dialogFooterVariants;
|
|
4530
|
+
exports.dialogHeaderIconVariants = dialogHeaderIconVariants;
|
|
4531
|
+
exports.dialogHeaderTextVariants = dialogHeaderTextVariants;
|
|
4532
|
+
exports.dialogHeaderVariants = dialogHeaderVariants;
|
|
4533
|
+
exports.dialogOverlayVariants = dialogOverlayVariants;
|
|
4534
|
+
exports.featureCardCaretVariants = featureCardCaretVariants;
|
|
4535
|
+
exports.featureCardLeadingVariants = featureCardLeadingVariants;
|
|
4536
|
+
exports.featureCardTextVariants = featureCardTextVariants;
|
|
4537
|
+
exports.featureCardTitleRowVariants = featureCardTitleRowVariants;
|
|
4538
|
+
exports.featureCardTrailingVariants = featureCardTrailingVariants;
|
|
4539
|
+
exports.featureCardVariants = featureCardVariants;
|
|
4540
|
+
exports.filterCountBadgeVariants = filterCountBadgeVariants;
|
|
4541
|
+
exports.filterTriggerVariants = filterTriggerVariants;
|
|
4542
|
+
exports.getFocusableElements = getFocusableElements;
|
|
4543
|
+
exports.inlineSelectVariants = inlineSelectVariants;
|
|
4544
|
+
exports.inputVariants = inputVariants;
|
|
4545
|
+
exports.nextEnabledIndex = nextEnabledIndex;
|
|
4546
|
+
exports.panelBodyVariants = panelBodyVariants;
|
|
4547
|
+
exports.panelCloseButtonVariants = panelCloseButtonVariants;
|
|
4548
|
+
exports.panelFooterVariants = panelFooterVariants;
|
|
4549
|
+
exports.panelHeaderContentVariants = panelHeaderContentVariants;
|
|
4550
|
+
exports.panelHeaderIconVariants = panelHeaderIconVariants;
|
|
4551
|
+
exports.panelHeaderTextVariants = panelHeaderTextVariants;
|
|
4552
|
+
exports.panelHeaderVariants = panelHeaderVariants;
|
|
4553
|
+
exports.panelVariants = panelVariants;
|
|
4554
|
+
exports.propertyGridCellVariants = propertyGridCellVariants;
|
|
4555
|
+
exports.propertyGridItemVariants = propertyGridItemVariants;
|
|
4556
|
+
exports.radioVariants = radioVariants;
|
|
4557
|
+
exports.scrollbarVariants = scrollbarVariants;
|
|
4558
|
+
exports.selectListboxFooterVariants = selectListboxFooterVariants;
|
|
4559
|
+
exports.selectListboxPanelVariants = selectListboxPanelVariants;
|
|
4560
|
+
exports.selectListboxScrollVariants = selectListboxScrollVariants;
|
|
4561
|
+
exports.selectListboxVariants = selectListboxVariants;
|
|
4562
|
+
exports.selectOptionVariants = selectOptionVariants;
|
|
4563
|
+
exports.selectTriggerLabel = selectTriggerLabel;
|
|
4564
|
+
exports.selectableFeatureCardBottomVariants = selectableFeatureCardBottomVariants;
|
|
4565
|
+
exports.selectableFeatureCardRowVariants = selectableFeatureCardRowVariants;
|
|
4566
|
+
exports.selectableFeatureCardTextVariants = selectableFeatureCardTextVariants;
|
|
4567
|
+
exports.selectableFeatureCardTrailingVariants = selectableFeatureCardTrailingVariants;
|
|
4568
|
+
exports.selectableFeatureCardVariants = selectableFeatureCardVariants;
|
|
4569
|
+
exports.sidebarItemLabelVariants = sidebarItemLabelVariants;
|
|
4570
|
+
exports.sidebarItemVariants = sidebarItemVariants;
|
|
4571
|
+
exports.switchVariants = switchVariants;
|
|
4572
|
+
exports.tableCellVariants = tableCellVariants;
|
|
4573
|
+
exports.tabsContentVariants = tabsContentVariants;
|
|
4574
|
+
exports.tabsListVariants = tabsListVariants;
|
|
4575
|
+
exports.tabsRootVariants = tabsRootVariants;
|
|
4576
|
+
exports.tabsTriggerVariants = tabsTriggerVariants;
|
|
4577
|
+
exports.toastCloseVariants = toastCloseVariants;
|
|
4578
|
+
exports.toastIconVariants = toastIconVariants;
|
|
4579
|
+
exports.toastVariants = toastVariants;
|
|
4580
|
+
exports.tooltipVariants = tooltipVariants;
|
|
4581
|
+
exports.useCutCornerClipPath = useCutCornerClipPath;
|
|
4582
|
+
exports.useFocusTrap = useFocusTrap;
|
|
4583
|
+
exports.useSelectListbox = useSelectListbox;
|