@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
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { inputVariants } from './chunk-IEVN3J2F.js';
|
|
2
|
+
import { cn } from './chunk-7EOSDFS3.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { WarningIcon, InfoIcon, XIcon } from '@phosphor-icons/react';
|
|
5
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function Token({
|
|
8
|
+
value,
|
|
9
|
+
invalid = false,
|
|
10
|
+
disabled = false,
|
|
11
|
+
onRemove
|
|
12
|
+
}) {
|
|
13
|
+
return /* @__PURE__ */ jsxs(
|
|
14
|
+
"span",
|
|
15
|
+
{
|
|
16
|
+
"data-slot": "token",
|
|
17
|
+
"data-invalid": invalid || void 0,
|
|
18
|
+
tabIndex: disabled ? void 0 : 0,
|
|
19
|
+
onKeyDown: disabled ? void 0 : (event) => {
|
|
20
|
+
if (event.key === "Delete" || event.key === "Backspace") {
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
onRemove("keyboard");
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
className: cn(
|
|
26
|
+
// `group` so the ✕ can react to the badge's hover/focus (see below).
|
|
27
|
+
// Height is exactly 22px = 20px (`body-03` line) + 2px border, with NO
|
|
28
|
+
// vertical padding: this keeps the field a constant 40px tall
|
|
29
|
+
// (badge 22 + 8+8 field padding + 1+1 field border = 40, empty or filled).
|
|
30
|
+
// Adding `py-*` here would push the field past 40px once tokens appear.
|
|
31
|
+
"group inline-flex items-center gap-1 min-w-5 rounded-sm border px-1",
|
|
32
|
+
// Default: neutral card border + background. Invalid: destructive accent
|
|
33
|
+
// recolour (there is no `card-destructive` token — reuse `input-destructive-*`).
|
|
34
|
+
invalid ? "border-input-destructive-border bg-card-background" : "border-card-border bg-card-background",
|
|
35
|
+
// Interactive chrome (only when not disabled): hover/focus fill to
|
|
36
|
+
// `card-accent` (the DS's "stronger/hover" step up from `card-background`)
|
|
37
|
+
// and the STANDARD ADR-0010/0012 focus ring on the whole badge — the ✕
|
|
38
|
+
// itself carries no ring. `cursor-default` overrides the field's I-beam
|
|
39
|
+
// so a badge doesn't read as "type here".
|
|
40
|
+
!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"
|
|
41
|
+
),
|
|
42
|
+
children: [
|
|
43
|
+
/* @__PURE__ */ jsx(
|
|
44
|
+
"span",
|
|
45
|
+
{
|
|
46
|
+
"data-slot": "token-label",
|
|
47
|
+
className: cn(
|
|
48
|
+
"body-03",
|
|
49
|
+
// Disabled wins over invalid: the Figma disabled badge (561:12782) is
|
|
50
|
+
// fully muted — a disabled field is inert, so no destructive red.
|
|
51
|
+
disabled ? "text-card-foreground-muted" : invalid ? "text-input-destructive-foreground-accent" : "text-card-foreground"
|
|
52
|
+
),
|
|
53
|
+
children: value
|
|
54
|
+
}
|
|
55
|
+
),
|
|
56
|
+
/* @__PURE__ */ jsx(
|
|
57
|
+
"button",
|
|
58
|
+
{
|
|
59
|
+
type: "button",
|
|
60
|
+
"aria-label": `Remove ${value}`,
|
|
61
|
+
onClick: () => onRemove("pointer"),
|
|
62
|
+
disabled,
|
|
63
|
+
tabIndex: -1,
|
|
64
|
+
"data-slot": "token-remove",
|
|
65
|
+
className: cn(
|
|
66
|
+
// Reset native button chrome.
|
|
67
|
+
"inline-flex cursor-pointer items-center justify-center rounded-xs bg-transparent p-0",
|
|
68
|
+
// Icon colour follows the badge state. Disabled wins (fully muted, no
|
|
69
|
+
// hover strengthen — the badge is inert). Valid: muted at rest,
|
|
70
|
+
// strengthens to `card-foreground` on badge hover/focus (via `group-*`).
|
|
71
|
+
// Invalid: stays destructive (a strengthen would drop the flag).
|
|
72
|
+
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",
|
|
73
|
+
"disabled:cursor-not-allowed"
|
|
74
|
+
),
|
|
75
|
+
children: /* @__PURE__ */ jsx(XIcon, { weight: "regular", "aria-hidden": "true", className: "size-4" })
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
function splitOnSeparators(raw, separators) {
|
|
83
|
+
if (separators.length === 0) return [raw];
|
|
84
|
+
const escaped = separators.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("");
|
|
85
|
+
return raw.split(new RegExp(`[${escaped}]`));
|
|
86
|
+
}
|
|
87
|
+
function TokenInput({
|
|
88
|
+
className,
|
|
89
|
+
fieldClassName,
|
|
90
|
+
wrapperClassName,
|
|
91
|
+
value,
|
|
92
|
+
defaultValue,
|
|
93
|
+
onChange,
|
|
94
|
+
separators = [","],
|
|
95
|
+
commitOnBlur = false,
|
|
96
|
+
validate,
|
|
97
|
+
label,
|
|
98
|
+
optional = false,
|
|
99
|
+
hint,
|
|
100
|
+
error = false,
|
|
101
|
+
disabled,
|
|
102
|
+
placeholder,
|
|
103
|
+
id: idProp,
|
|
104
|
+
onKeyDown,
|
|
105
|
+
onBlur,
|
|
106
|
+
onInput,
|
|
107
|
+
ref,
|
|
108
|
+
...props
|
|
109
|
+
}) {
|
|
110
|
+
const isControlled = value !== void 0;
|
|
111
|
+
const [tokensState, setTokensState] = React.useState(
|
|
112
|
+
() => defaultValue ?? []
|
|
113
|
+
);
|
|
114
|
+
const tokens = isControlled ? value : tokensState;
|
|
115
|
+
const innerRef = React.useRef(null);
|
|
116
|
+
const setInputRef = React.useCallback(
|
|
117
|
+
(node) => {
|
|
118
|
+
innerRef.current = node;
|
|
119
|
+
if (typeof ref === "function") ref(node);
|
|
120
|
+
else if (ref)
|
|
121
|
+
ref.current = node;
|
|
122
|
+
},
|
|
123
|
+
[ref]
|
|
124
|
+
);
|
|
125
|
+
const generatedId = React.useId();
|
|
126
|
+
const id = idProp ?? generatedId;
|
|
127
|
+
const hintId = hint ? `${id}-hint` : void 0;
|
|
128
|
+
const commitTokens = (next) => {
|
|
129
|
+
if (!isControlled) setTokensState(next);
|
|
130
|
+
onChange?.(next);
|
|
131
|
+
};
|
|
132
|
+
const addTokens = (pieces) => {
|
|
133
|
+
const next = [...tokens];
|
|
134
|
+
const seen = new Set(next.map((t) => t.toLowerCase()));
|
|
135
|
+
let changed = false;
|
|
136
|
+
for (const piece of pieces) {
|
|
137
|
+
const trimmed = piece.trim();
|
|
138
|
+
if (trimmed === "") continue;
|
|
139
|
+
const key = trimmed.toLowerCase();
|
|
140
|
+
if (seen.has(key)) continue;
|
|
141
|
+
seen.add(key);
|
|
142
|
+
next.push(trimmed);
|
|
143
|
+
changed = true;
|
|
144
|
+
}
|
|
145
|
+
if (changed) commitTokens(next);
|
|
146
|
+
};
|
|
147
|
+
const removeTokenAt = (index, origin) => {
|
|
148
|
+
commitTokens(tokens.filter((_, i) => i !== index));
|
|
149
|
+
if (origin === "keyboard") innerRef.current?.focus();
|
|
150
|
+
};
|
|
151
|
+
const handleInput = (event) => {
|
|
152
|
+
onInput?.(event);
|
|
153
|
+
const input = event.currentTarget;
|
|
154
|
+
const raw = input.value;
|
|
155
|
+
if (separators.some((sep) => raw.includes(sep))) {
|
|
156
|
+
addTokens(splitOnSeparators(raw, separators));
|
|
157
|
+
input.value = "";
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const handleKeyDown = (event) => {
|
|
161
|
+
onKeyDown?.(event);
|
|
162
|
+
if (event.key === "Backspace" && event.currentTarget.value === "" && tokens.length > 0) {
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
removeTokenAt(tokens.length - 1);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const handleBlur = (event) => {
|
|
168
|
+
onBlur?.(event);
|
|
169
|
+
if (commitOnBlur) {
|
|
170
|
+
const input = event.currentTarget;
|
|
171
|
+
if (input.value.trim() !== "") {
|
|
172
|
+
addTokens([input.value]);
|
|
173
|
+
input.value = "";
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const handleFieldMouseDown = (event) => {
|
|
178
|
+
const target = event.target;
|
|
179
|
+
if (target.closest("[data-slot=token]")) return;
|
|
180
|
+
if (target === innerRef.current) return;
|
|
181
|
+
event.preventDefault();
|
|
182
|
+
innerRef.current?.focus();
|
|
183
|
+
};
|
|
184
|
+
const iconStateClass = cn(
|
|
185
|
+
error ? "text-input-destructive-foreground-accent" : "text-input-foreground",
|
|
186
|
+
disabled && "text-input-foreground-muted"
|
|
187
|
+
);
|
|
188
|
+
const HintIcon = error ? WarningIcon : InfoIcon;
|
|
189
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1.5", wrapperClassName), children: [
|
|
190
|
+
label != null && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
191
|
+
/* @__PURE__ */ jsx(
|
|
192
|
+
"label",
|
|
193
|
+
{
|
|
194
|
+
htmlFor: id,
|
|
195
|
+
"data-slot": "input-label",
|
|
196
|
+
className: cn(
|
|
197
|
+
"caption-03-medium uppercase",
|
|
198
|
+
error ? "text-input-destructive-foreground-accent" : "text-input-foreground",
|
|
199
|
+
disabled && "text-input-foreground-muted"
|
|
200
|
+
),
|
|
201
|
+
children: label
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
optional && /* @__PURE__ */ jsx(
|
|
205
|
+
"span",
|
|
206
|
+
{
|
|
207
|
+
"data-slot": "input-optional",
|
|
208
|
+
className: "body-04 text-input-foreground-muted",
|
|
209
|
+
children: "Optional"
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
] }),
|
|
213
|
+
/* @__PURE__ */ jsxs(
|
|
214
|
+
"div",
|
|
215
|
+
{
|
|
216
|
+
"data-slot": "token-input-field",
|
|
217
|
+
"data-error": error || void 0,
|
|
218
|
+
onMouseDown: handleFieldMouseDown,
|
|
219
|
+
className: cn(
|
|
220
|
+
// Reuse Input's field styling (bg/border/rounded/focus-within/error).
|
|
221
|
+
inputVariants({ variation: "field", size: "lg", error }),
|
|
222
|
+
// Layout overrides (tailwind-merge last-wins): grow with wrapped rows,
|
|
223
|
+
// keep single-row min height, allow badges + input to wrap, uniform
|
|
224
|
+
// spacing/2 inset (overrides field-lg's px-3 py-2).
|
|
225
|
+
"h-auto min-h-10 flex-wrap gap-1 p-2",
|
|
226
|
+
// Icon slots inherit the shared state colour.
|
|
227
|
+
iconStateClass,
|
|
228
|
+
fieldClassName
|
|
229
|
+
),
|
|
230
|
+
children: [
|
|
231
|
+
tokens.map((token, index) => /* @__PURE__ */ jsx(
|
|
232
|
+
Token,
|
|
233
|
+
{
|
|
234
|
+
value: token,
|
|
235
|
+
invalid: validate ? !validate(token) : false,
|
|
236
|
+
disabled,
|
|
237
|
+
onRemove: (origin) => removeTokenAt(index, origin)
|
|
238
|
+
},
|
|
239
|
+
`${token}-${index}`
|
|
240
|
+
)),
|
|
241
|
+
/* @__PURE__ */ jsx(
|
|
242
|
+
"input",
|
|
243
|
+
{
|
|
244
|
+
id,
|
|
245
|
+
"data-slot": "token-input",
|
|
246
|
+
disabled,
|
|
247
|
+
placeholder: tokens.length === 0 ? placeholder : void 0,
|
|
248
|
+
"aria-invalid": error || void 0,
|
|
249
|
+
"aria-describedby": hintId,
|
|
250
|
+
className: cn(
|
|
251
|
+
// Typography + grow to fill remaining row width. `min-w-0` lets the
|
|
252
|
+
// input shrink; a small basis keeps it usable when tokens wrap.
|
|
253
|
+
"body-03 w-0 min-w-16 flex-1 bg-transparent outline-none",
|
|
254
|
+
"text-input-foreground-accent caret-input-foreground-accent",
|
|
255
|
+
"placeholder:text-input-foreground-muted",
|
|
256
|
+
"disabled:cursor-not-allowed disabled:text-input-foreground-muted",
|
|
257
|
+
className
|
|
258
|
+
),
|
|
259
|
+
ref: setInputRef,
|
|
260
|
+
onInput: handleInput,
|
|
261
|
+
onKeyDown: handleKeyDown,
|
|
262
|
+
onBlur: handleBlur,
|
|
263
|
+
...props
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
]
|
|
267
|
+
}
|
|
268
|
+
),
|
|
269
|
+
hint != null && /* @__PURE__ */ jsxs(
|
|
270
|
+
"p",
|
|
271
|
+
{
|
|
272
|
+
id: hintId,
|
|
273
|
+
"data-slot": "input-hint",
|
|
274
|
+
className: cn(
|
|
275
|
+
"body-04 flex items-center gap-1",
|
|
276
|
+
error ? "text-input-destructive-foreground-accent" : "text-input-foreground",
|
|
277
|
+
disabled && "text-input-foreground-muted"
|
|
278
|
+
),
|
|
279
|
+
children: [
|
|
280
|
+
/* @__PURE__ */ jsx(
|
|
281
|
+
HintIcon,
|
|
282
|
+
{
|
|
283
|
+
"aria-hidden": "true",
|
|
284
|
+
weight: "regular",
|
|
285
|
+
className: "size-4 shrink-0"
|
|
286
|
+
}
|
|
287
|
+
),
|
|
288
|
+
hint
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
)
|
|
292
|
+
] });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export { TokenInput };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { cn } from './chunk-7EOSDFS3.js';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { CaretRightIcon } from '@phosphor-icons/react';
|
|
4
|
+
import { cva } from 'class-variance-authority';
|
|
5
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
var featureCardVariants = cva(
|
|
8
|
+
"flex h-16 items-center gap-3 px-4 py-3 bg-card-background no-underline",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
interactive: {
|
|
12
|
+
true: [
|
|
13
|
+
"cursor-pointer transition-colors hover:bg-card-accent",
|
|
14
|
+
"outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus"
|
|
15
|
+
],
|
|
16
|
+
false: ""
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: { interactive: false }
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
var featureCardLeadingVariants = cva(
|
|
23
|
+
"flex shrink-0 items-center text-card-foreground [&_svg]:size-6 [&_svg]:shrink-0"
|
|
24
|
+
);
|
|
25
|
+
var featureCardTrailingVariants = cva(
|
|
26
|
+
"flex shrink-0 items-center text-card-foreground-muted [&_svg]:size-6 [&_svg]:shrink-0"
|
|
27
|
+
);
|
|
28
|
+
var featureCardTextVariants = cva("flex min-w-0 flex-1 flex-col");
|
|
29
|
+
var featureCardTitleRowVariants = cva(
|
|
30
|
+
"flex min-w-0 items-center gap-0.5"
|
|
31
|
+
);
|
|
32
|
+
var featureCardCaretVariants = cva(
|
|
33
|
+
"flex shrink-0 items-center text-card-foreground-muted [&_svg]:size-4 [&_svg]:shrink-0"
|
|
34
|
+
);
|
|
35
|
+
function setRef(ref, node) {
|
|
36
|
+
if (typeof ref === "function") ref(node);
|
|
37
|
+
else if (ref) ref.current = node;
|
|
38
|
+
}
|
|
39
|
+
function FeatureCard({
|
|
40
|
+
title,
|
|
41
|
+
subtitle,
|
|
42
|
+
leading,
|
|
43
|
+
trailing,
|
|
44
|
+
caret = true,
|
|
45
|
+
asChild = false,
|
|
46
|
+
href,
|
|
47
|
+
className,
|
|
48
|
+
children,
|
|
49
|
+
ref,
|
|
50
|
+
...props
|
|
51
|
+
}) {
|
|
52
|
+
const interactive = asChild || href != null;
|
|
53
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
54
|
+
leading != null && /* @__PURE__ */ jsx(
|
|
55
|
+
"span",
|
|
56
|
+
{
|
|
57
|
+
"data-slot": "feature-card-leading",
|
|
58
|
+
"aria-hidden": "true",
|
|
59
|
+
className: featureCardLeadingVariants(),
|
|
60
|
+
children: leading
|
|
61
|
+
}
|
|
62
|
+
),
|
|
63
|
+
/* @__PURE__ */ jsxs("span", { "data-slot": "feature-card-text", className: featureCardTextVariants(), children: [
|
|
64
|
+
/* @__PURE__ */ jsxs("span", { className: featureCardTitleRowVariants(), children: [
|
|
65
|
+
/* @__PURE__ */ jsx("span", { className: "body-02-medium truncate text-card-foreground", children: title }),
|
|
66
|
+
caret && /* @__PURE__ */ jsx(
|
|
67
|
+
"span",
|
|
68
|
+
{
|
|
69
|
+
"data-slot": "feature-card-caret",
|
|
70
|
+
"aria-hidden": "true",
|
|
71
|
+
className: featureCardCaretVariants(),
|
|
72
|
+
children: /* @__PURE__ */ jsx(CaretRightIcon, {})
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
] }),
|
|
76
|
+
subtitle != null && /* @__PURE__ */ jsx("span", { className: "body-03 truncate text-card-foreground-muted", children: subtitle })
|
|
77
|
+
] }),
|
|
78
|
+
trailing != null && /* @__PURE__ */ jsx(
|
|
79
|
+
"span",
|
|
80
|
+
{
|
|
81
|
+
"data-slot": "feature-card-trailing",
|
|
82
|
+
"aria-hidden": "true",
|
|
83
|
+
className: featureCardTrailingVariants(),
|
|
84
|
+
children: trailing
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
] });
|
|
88
|
+
const rootClassName = cn(featureCardVariants({ interactive }), className);
|
|
89
|
+
if (asChild) {
|
|
90
|
+
if (!React.isValidElement(children)) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
"FeatureCard: `asChild` requires a single React element child"
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const child = children;
|
|
96
|
+
const childProps = child.props;
|
|
97
|
+
return React.cloneElement(
|
|
98
|
+
child,
|
|
99
|
+
{
|
|
100
|
+
...props,
|
|
101
|
+
...childProps,
|
|
102
|
+
"data-slot": "feature-card",
|
|
103
|
+
ref: (node) => {
|
|
104
|
+
setRef(ref, node);
|
|
105
|
+
setRef(childProps.ref, node);
|
|
106
|
+
},
|
|
107
|
+
className: cn(
|
|
108
|
+
rootClassName,
|
|
109
|
+
childProps.className
|
|
110
|
+
),
|
|
111
|
+
onClick: (event) => {
|
|
112
|
+
props.onClick?.(event);
|
|
113
|
+
childProps.onClick?.(event);
|
|
114
|
+
},
|
|
115
|
+
style: {
|
|
116
|
+
...props.style,
|
|
117
|
+
...childProps.style
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
content,
|
|
121
|
+
childProps.children
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (href != null) {
|
|
125
|
+
return /* @__PURE__ */ jsx(
|
|
126
|
+
"a",
|
|
127
|
+
{
|
|
128
|
+
"data-slot": "feature-card",
|
|
129
|
+
href,
|
|
130
|
+
className: rootClassName,
|
|
131
|
+
ref,
|
|
132
|
+
...props,
|
|
133
|
+
children: content
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return /* @__PURE__ */ jsx(
|
|
138
|
+
"div",
|
|
139
|
+
{
|
|
140
|
+
"data-slot": "feature-card",
|
|
141
|
+
className: rootClassName,
|
|
142
|
+
ref,
|
|
143
|
+
...props,
|
|
144
|
+
children: content
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { FeatureCard, featureCardCaretVariants, featureCardLeadingVariants, featureCardTextVariants, featureCardTitleRowVariants, featureCardTrailingVariants, featureCardVariants };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { cva } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
// src/lib/scrollbar.ts
|
|
4
|
+
var scrollbarVariants = cva(
|
|
5
|
+
[
|
|
6
|
+
// Firefox (approximate): thin themed scrollbar over a transparent track.
|
|
7
|
+
"[scrollbar-width:thin]",
|
|
8
|
+
"[scrollbar-color:var(--color-card-foreground-muted)_transparent]",
|
|
9
|
+
// WebKit gutter — 12px, both axes so the utility is axis-agnostic.
|
|
10
|
+
"[&::-webkit-scrollbar]:w-3 [&::-webkit-scrollbar]:h-3",
|
|
11
|
+
// WebKit thumb — 4px pill: a 4px transparent border + content-box clip
|
|
12
|
+
// leaves a 4px-wide fill centred in the 12px gutter.
|
|
13
|
+
"[&::-webkit-scrollbar-thumb]:rounded-full",
|
|
14
|
+
"[&::-webkit-scrollbar-thumb]:border-4 [&::-webkit-scrollbar-thumb]:border-solid [&::-webkit-scrollbar-thumb]:border-transparent",
|
|
15
|
+
"[&::-webkit-scrollbar-thumb]:bg-clip-content",
|
|
16
|
+
"[&::-webkit-scrollbar-thumb]:bg-card-foreground-muted",
|
|
17
|
+
// WebKit track — transparent by default (stroke is the `stroke` variant).
|
|
18
|
+
"[&::-webkit-scrollbar-track]:bg-transparent"
|
|
19
|
+
],
|
|
20
|
+
{
|
|
21
|
+
variants: {
|
|
22
|
+
stroke: {
|
|
23
|
+
// 1px leading-edge track stroke (Figma `stroke`, on by default).
|
|
24
|
+
true: "[&::-webkit-scrollbar-track]:border-l [&::-webkit-scrollbar-track]:border-card-border",
|
|
25
|
+
false: ""
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: { stroke: true }
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export { scrollbarVariants };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { cn } from './chunk-7EOSDFS3.js';
|
|
2
|
+
import { cva } from 'class-variance-authority';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var switchVariants = cva([
|
|
6
|
+
// Track — 56×24 (`h-6 w-14`), fully round (Figma `radius/full`), 4px inset
|
|
7
|
+
// (`p-1` = Figma `spacing/1`). `appearance-none` strips the native checkbox
|
|
8
|
+
// box; `peer` lets the sibling thumb react to :checked / :disabled.
|
|
9
|
+
"peer inline-flex h-6 w-14 shrink-0 items-center rounded-full border p-1",
|
|
10
|
+
"appearance-none cursor-pointer transition-colors",
|
|
11
|
+
// Off — default
|
|
12
|
+
"bg-button-secondary-background border-button-secondary-border",
|
|
13
|
+
// Off — hover strengthens the border (focused keeps the default border)
|
|
14
|
+
"hover:border-button-secondary-border-accent",
|
|
15
|
+
// Focus ring — suppress the native ring, draw the shared `--focus` ring the
|
|
16
|
+
// same way Checkbox/Radio do: 2px stroke, 2px offset (`outline-solid`
|
|
17
|
+
// restores what `outline-none` zeroed). Circular via the track's rounding.
|
|
18
|
+
"outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus",
|
|
19
|
+
// On — brand fill, border made transparent (kept for a constant box model)
|
|
20
|
+
"checked:bg-brand-primary-background checked:border-transparent",
|
|
21
|
+
"checked:hover:bg-brand-primary-accent",
|
|
22
|
+
// Disabled — off keeps the white track with a muted border; on drops to a
|
|
23
|
+
// neutral muted fill (per Figma). Border transparent when on, muted when off.
|
|
24
|
+
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:border-button-secondary-border-muted",
|
|
25
|
+
"checked:disabled:bg-button-primary-muted checked:disabled:border-transparent"
|
|
26
|
+
]);
|
|
27
|
+
function Switch({
|
|
28
|
+
className,
|
|
29
|
+
wrapperClassName,
|
|
30
|
+
onChange,
|
|
31
|
+
onCheckedChange,
|
|
32
|
+
...props
|
|
33
|
+
}) {
|
|
34
|
+
const handleChange = (event) => {
|
|
35
|
+
onChange?.(event);
|
|
36
|
+
onCheckedChange?.(event.currentTarget.checked);
|
|
37
|
+
};
|
|
38
|
+
return /* @__PURE__ */ jsxs(
|
|
39
|
+
"span",
|
|
40
|
+
{
|
|
41
|
+
className: cn(
|
|
42
|
+
"relative inline-flex shrink-0 items-center",
|
|
43
|
+
wrapperClassName
|
|
44
|
+
),
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ jsx(
|
|
47
|
+
"input",
|
|
48
|
+
{
|
|
49
|
+
type: "checkbox",
|
|
50
|
+
role: "switch",
|
|
51
|
+
"data-slot": "switch",
|
|
52
|
+
className: cn(switchVariants(), className),
|
|
53
|
+
...props,
|
|
54
|
+
onChange: handleChange
|
|
55
|
+
}
|
|
56
|
+
),
|
|
57
|
+
/* @__PURE__ */ jsx(
|
|
58
|
+
"span",
|
|
59
|
+
{
|
|
60
|
+
"aria-hidden": "true",
|
|
61
|
+
"data-slot": "switch-thumb",
|
|
62
|
+
className: cn(
|
|
63
|
+
"pointer-events-none absolute left-1 h-4 w-6 rounded-full transition-transform",
|
|
64
|
+
// Off — dark knob at 90% opacity (Figma `opacity/90`)
|
|
65
|
+
"bg-button-secondary-foreground opacity-90",
|
|
66
|
+
"peer-checked:translate-x-5.5 peer-checked:bg-brand-primary-foreground peer-checked:opacity-100",
|
|
67
|
+
// Disabled — muted knob whether on or off (per Figma)
|
|
68
|
+
"peer-disabled:bg-button-secondary-foreground-muted peer-disabled:opacity-100",
|
|
69
|
+
"peer-checked:peer-disabled:bg-button-primary-foreground-muted"
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { Switch, switchVariants };
|