@reopt-ai/opt-ui 1.9.0 → 1.11.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/COMPONENT_CATALOG.md +177 -66
- package/README.md +21 -5
- package/dist/core/index.cjs +106 -104
- package/dist/core/index.d.cts +3 -3
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +3 -3
- package/dist/docs/02-components/01-core.md +92 -41
- package/dist/docs/02-components/02-visuals.md +1 -1
- package/dist/docs/02-components/03-shells.md +96 -26
- package/dist/docs/02-components/04-surfaces.md +1 -1
- package/dist/docs/02-components/index.md +5 -5
- package/dist/{field-sidebar-B5vk3luQ.d.cts → field-sidebar-B5SQC35J.d.cts} +226 -3
- package/dist/field-sidebar-B5SQC35J.d.cts.map +1 -0
- package/dist/{field-sidebar-B7QkWvMG.js → field-sidebar-BXl5RJ3q.js} +599 -43
- package/dist/field-sidebar-BXl5RJ3q.js.map +1 -0
- package/dist/{field-sidebar-Dk9gJGA_.d.ts → field-sidebar-CBeWE8xX.d.ts} +226 -3
- package/dist/field-sidebar-CBeWE8xX.d.ts.map +1 -0
- package/dist/{field-sidebar-mFHemxBz.cjs → field-sidebar-DAuN4jPV.cjs} +883 -309
- package/dist/id-registry.cjs +46 -6
- package/dist/id-registry.d.cts.map +1 -1
- package/dist/id-registry.d.ts.map +1 -1
- package/dist/id-registry.js +46 -6
- package/dist/id-registry.js.map +1 -1
- package/dist/id-registry.json +97 -12
- package/dist/index.cjs +593 -587
- package/dist/index.d.cts +10 -6
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +10 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -19
- package/dist/index.js.map +1 -1
- package/dist/{key-pad-menu-D8OS_VCT.d.cts → key-pad-menu-BxTy_u0Q.d.cts} +152 -51
- package/dist/key-pad-menu-BxTy_u0Q.d.cts.map +1 -0
- package/dist/{key-pad-menu-DoGskWbp.cjs → key-pad-menu-Cg6rZBie.cjs} +670 -923
- package/dist/{key-pad-menu-EEb6Ho5h.d.ts → key-pad-menu-u5GTJl9B.d.ts} +152 -51
- package/dist/key-pad-menu-u5GTJl9B.d.ts.map +1 -0
- package/dist/{key-pad-menu-BFd79JoS.js → key-pad-menu-wXkWn1I1.js} +500 -699
- package/dist/key-pad-menu-wXkWn1I1.js.map +1 -0
- package/dist/meta.cjs +486 -9
- package/dist/meta.d.cts.map +1 -1
- package/dist/meta.d.ts.map +1 -1
- package/dist/meta.js +486 -9
- package/dist/meta.js.map +1 -1
- package/dist/shells/index.cjs +4 -1
- package/dist/shells/index.d.cts +2 -2
- package/dist/shells/index.d.ts +2 -2
- package/dist/shells/index.js +2 -2
- package/dist/{highlight-CGcrYGMC.cjs → text-truncate-BZdL1k5Z.cjs} +608 -83
- package/dist/{highlight-C2sfnJN8.js → text-truncate-C17HHCR-.js} +534 -87
- package/dist/text-truncate-C17HHCR-.js.map +1 -0
- package/dist/{field-token-CSOrU5gl.d.ts → text-truncate-DW78lg4S.d.cts} +63 -4
- package/dist/text-truncate-DW78lg4S.d.cts.map +1 -0
- package/dist/{field-token-CSOrU5gl.d.cts → text-truncate-DW78lg4S.d.ts} +63 -4
- package/dist/text-truncate-DW78lg4S.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/field-sidebar-B5vk3luQ.d.cts.map +0 -1
- package/dist/field-sidebar-B7QkWvMG.js.map +0 -1
- package/dist/field-sidebar-Dk9gJGA_.d.ts.map +0 -1
- package/dist/field-token-CSOrU5gl.d.cts.map +0 -1
- package/dist/field-token-CSOrU5gl.d.ts.map +0 -1
- package/dist/highlight-C2sfnJN8.js.map +0 -1
- package/dist/key-pad-menu-BFd79JoS.js.map +0 -1
- package/dist/key-pad-menu-D8OS_VCT.d.cts.map +0 -1
- package/dist/key-pad-menu-EEb6Ho5h.d.ts.map +0 -1
|
@@ -3,8 +3,8 @@ import { clsx } from "clsx";
|
|
|
3
3
|
import { twMerge } from "tailwind-merge";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import * as React from "react";
|
|
6
|
-
import { createContext, forwardRef, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
7
|
-
import { AlertDialogPanel, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxPopover, ComboboxRoot, Composite, CompositeItem, CompositeProvider, CompositeRow, DialogDescription, DialogDisclosure, DialogDismiss, DialogHeading, DialogPanel, DialogRoot, DisclosureContent, DisclosureRoot, DisclosureTrigger, FormCheckbox, FormControl, FormDescription, FormError, FormField, FormGroup, FormGroupLabel, FormInput, FormLabel, FormProvider, FormPush, FormRadio, FormRadioGroup, FormRemove, FormReset, FormRoot, FormSubmit, FormSwitch, MenuButtonArrow, MenuItem, MenuItemCheckbox, MenuItemRadio, MenuPopover, MenuRoot, MenuSeparator, MenuTrigger, MenubarContainer, MenubarRoot, PopoverClose, PopoverContent, PopoverRoot, PopoverTrigger, SelectItem, SelectLabel, SelectPopover, SelectRoot, SelectTrigger, Tab, TabList, TabPanel, TabsRoot, ToolbarButton, ToolbarContainer, ToolbarRoot, ToolbarSeparator, Tooltip, TooltipAnchor, TooltipProvider, useFieldValue, useFormContext, useFormStore } from "@reopt-ai/opt-ui-primitives";
|
|
6
|
+
import { createContext, forwardRef, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
import { AlertDialogPanel, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxPopover, ComboboxRoot, Composite, CompositeItem, CompositeProvider, CompositeRow, DialogDescription, DialogDisclosure, DialogDismiss, DialogHeading, DialogPanel, DialogRoot, DisclosureContent, DisclosureRoot, DisclosureTrigger, FormCheckbox, FormControl, FormDescription, FormError, FormField, FormGroup, FormGroupLabel, FormInput, FormLabel, FormProvider, FormPush, FormRadio, FormRadioGroup, FormRemove, FormReset, FormRoot, FormSubmit, FormSwitch, MenuButtonArrow, MenuItem, MenuItemCheckbox, MenuItemRadio, MenuPopover, MenuRoot, MenuSeparator, MenuTrigger, MenubarContainer, MenubarRoot, Meter, PopoverClose, PopoverContent, PopoverRoot, PopoverTrigger, SelectItem, SelectLabel, SelectPopover, SelectRoot, SelectTrigger, Tab, TabList, TabPanel, TabsRoot, ToolbarButton, ToolbarContainer, ToolbarRoot, ToolbarSeparator, Tooltip, TooltipAnchor, TooltipProvider, useFieldValue, useFormContext, useFormStore } from "@reopt-ai/opt-ui-primitives";
|
|
8
8
|
import { AtSign, Bell, Binary, Braces, Brackets, Calendar, CalendarClock, CalendarDays, ChartColumn, Check, ChevronLeft, ChevronRight, CircleAlert, CircleCheck, CircleDollarSign, CircleHelp, CreditCard, Download, Eye, FileText, Fingerprint, Flag, Folder, Globe, Hash, Heart, Info, Layers, Link2, List, LogIn, Mail, MapPin, MessageCircle, MousePointerClick, Network, Percent, Search, Settings, Share2, ShoppingCart, Sigma, Star, Timer, ToggleLeft, TrendingUp, TriangleAlert, Type, Upload, User, UserPlus, XCircle, Zap } from "lucide-react";
|
|
9
9
|
//#region src/lib/cn.ts
|
|
10
10
|
/** Handles cn. */
|
|
@@ -120,6 +120,7 @@ const OPT_HOVER_ITEM = "[@media(any-hover:hover)]:hover:bg-accent-subtle active:
|
|
|
120
120
|
const OPT_BORDER = "border border-border";
|
|
121
121
|
const OPT_DIVIDER = "divide-border-subtle";
|
|
122
122
|
const OPT_SURFACE = "rounded-lg bg-surface shadow-sm";
|
|
123
|
+
const OPT_SURFACE_RAISED = "rounded-xl bg-surface-raised shadow-md";
|
|
123
124
|
const OPT_SURFACE_POPOVER = "z-50 rounded-lg border border-border bg-surface-overlay p-2 shadow-lg";
|
|
124
125
|
const OPT_TEXT_PRIMARY = "text-text-primary";
|
|
125
126
|
const OPT_TEXT_SECONDARY = "text-text-secondary";
|
|
@@ -218,44 +219,230 @@ function useControlledState(defaultValue, controlledValue, onChange) {
|
|
|
218
219
|
}, [isControlled, onChange])];
|
|
219
220
|
}
|
|
220
221
|
//#endregion
|
|
222
|
+
//#region src/core/skeleton.tsx
|
|
223
|
+
const _excluded$17 = [
|
|
224
|
+
"variant",
|
|
225
|
+
"width",
|
|
226
|
+
"height",
|
|
227
|
+
"lines",
|
|
228
|
+
"animated",
|
|
229
|
+
"className",
|
|
230
|
+
"style"
|
|
231
|
+
];
|
|
232
|
+
const _excluded2$9 = ["lines", "className"];
|
|
233
|
+
const _excluded3$8 = ["size", "className"];
|
|
234
|
+
const _excluded4$6 = ["className"];
|
|
235
|
+
const variantStyles$4 = {
|
|
236
|
+
text: "rounded h-4",
|
|
237
|
+
circular: "rounded-full",
|
|
238
|
+
rectangular: "rounded-lg"
|
|
239
|
+
};
|
|
240
|
+
/** Renders the skeleton component. */
|
|
241
|
+
function Skeleton(_ref) {
|
|
242
|
+
let { variant = "text", width, height, lines = 1, animated = true, className, style } = _ref, props = _objectWithoutProperties(_ref, _excluded$17);
|
|
243
|
+
const baseClass = cn("bg-bg-subtle", animated && "animate-pulse", variantStyles$4[variant], className);
|
|
244
|
+
const computedStyle = _objectSpread2({
|
|
245
|
+
width: typeof width === "number" ? `${width}px` : width,
|
|
246
|
+
height: typeof height === "number" ? `${height}px` : height
|
|
247
|
+
}, style);
|
|
248
|
+
if (variant === "text" && lines > 1) return /* @__PURE__ */ jsxs("div", {
|
|
249
|
+
className: "gap-element flex flex-col",
|
|
250
|
+
role: "status",
|
|
251
|
+
"aria-label": "Loading",
|
|
252
|
+
"data-opt-id": "EJT87",
|
|
253
|
+
"data-opt-slug": "skeleton.Skeleton",
|
|
254
|
+
children: [Array.from({ length: lines }).map((_, i) => /* @__PURE__ */ jsx("div", _objectSpread2({
|
|
255
|
+
className: cn(baseClass, i === lines - 1 && "w-4/5"),
|
|
256
|
+
style: i === lines - 1 ? void 0 : computedStyle
|
|
257
|
+
}, props), i)), /* @__PURE__ */ jsx("span", {
|
|
258
|
+
className: "sr-only",
|
|
259
|
+
children: "Loading..."
|
|
260
|
+
})]
|
|
261
|
+
});
|
|
262
|
+
return /* @__PURE__ */ jsx("div", _objectSpread2(_objectSpread2({
|
|
263
|
+
className: baseClass,
|
|
264
|
+
style: computedStyle,
|
|
265
|
+
role: "status",
|
|
266
|
+
"aria-label": "Loading"
|
|
267
|
+
}, props), {}, {
|
|
268
|
+
"data-opt-id": "EJT87",
|
|
269
|
+
"data-opt-slug": "skeleton.Skeleton",
|
|
270
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
271
|
+
className: "sr-only",
|
|
272
|
+
children: "Loading..."
|
|
273
|
+
})
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
/** Renders the skeleton text component. */
|
|
277
|
+
function SkeletonText(_ref2) {
|
|
278
|
+
let { lines = 3, className } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$9);
|
|
279
|
+
return /* @__PURE__ */ jsx(Skeleton, _objectSpread2(_objectSpread2({
|
|
280
|
+
variant: "text",
|
|
281
|
+
lines,
|
|
282
|
+
className
|
|
283
|
+
}, props), {}, {
|
|
284
|
+
"data-opt-id": "MX3D5",
|
|
285
|
+
"data-opt-slug": "skeleton.SkeletonText"
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
/** Renders the skeleton avatar component. */
|
|
289
|
+
function SkeletonAvatar(_ref3) {
|
|
290
|
+
let { size = 40, className } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$8);
|
|
291
|
+
return /* @__PURE__ */ jsx(Skeleton, _objectSpread2(_objectSpread2({
|
|
292
|
+
variant: "circular",
|
|
293
|
+
width: size,
|
|
294
|
+
height: size,
|
|
295
|
+
className
|
|
296
|
+
}, props), {}, {
|
|
297
|
+
"data-opt-id": "MP0HZ",
|
|
298
|
+
"data-opt-slug": "skeleton.SkeletonAvatar"
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
/** Renders the skeleton card component. */
|
|
302
|
+
function SkeletonCard(_ref4) {
|
|
303
|
+
let { className } = _ref4, props = _objectWithoutProperties(_ref4, _excluded4$6);
|
|
304
|
+
return /* @__PURE__ */ jsxs("div", _objectSpread2(_objectSpread2({ className: cn("gap-group flex flex-col", className) }, props), {}, {
|
|
305
|
+
"data-opt-id": "34V5D",
|
|
306
|
+
"data-opt-slug": "skeleton.SkeletonCard",
|
|
307
|
+
children: [
|
|
308
|
+
/* @__PURE__ */ jsx(Skeleton, {
|
|
309
|
+
variant: "rectangular",
|
|
310
|
+
height: 120
|
|
311
|
+
}),
|
|
312
|
+
/* @__PURE__ */ jsx(Skeleton, {
|
|
313
|
+
variant: "text",
|
|
314
|
+
width: "60%"
|
|
315
|
+
}),
|
|
316
|
+
/* @__PURE__ */ jsx(Skeleton, {
|
|
317
|
+
variant: "text",
|
|
318
|
+
lines: 2
|
|
319
|
+
})
|
|
320
|
+
]
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
/** Renders the skeleton table component. */
|
|
324
|
+
function SkeletonTable({ rows = 5, columns = 4, className }) {
|
|
325
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
326
|
+
className: cn("gap-element flex flex-col", className),
|
|
327
|
+
role: "status",
|
|
328
|
+
"aria-label": "Loading table",
|
|
329
|
+
"data-opt-id": "3KEDN",
|
|
330
|
+
"data-opt-slug": "skeleton.SkeletonTable",
|
|
331
|
+
children: [
|
|
332
|
+
/* @__PURE__ */ jsx("div", {
|
|
333
|
+
className: "border-border-subtle gap-group flex border-b pb-2",
|
|
334
|
+
children: Array.from({ length: columns }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, {
|
|
335
|
+
variant: "text",
|
|
336
|
+
className: "flex-1",
|
|
337
|
+
height: 16
|
|
338
|
+
}, i))
|
|
339
|
+
}),
|
|
340
|
+
Array.from({ length: rows }).map((_, rowIndex) => /* @__PURE__ */ jsx("div", {
|
|
341
|
+
className: "gap-group flex py-2",
|
|
342
|
+
children: Array.from({ length: columns }).map((_, colIndex) => /* @__PURE__ */ jsx(Skeleton, {
|
|
343
|
+
variant: "text",
|
|
344
|
+
className: "flex-1",
|
|
345
|
+
height: 14
|
|
346
|
+
}, colIndex))
|
|
347
|
+
}, rowIndex)),
|
|
348
|
+
/* @__PURE__ */ jsx("span", {
|
|
349
|
+
className: "sr-only",
|
|
350
|
+
children: "Loading..."
|
|
351
|
+
})
|
|
352
|
+
]
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
//#endregion
|
|
221
356
|
//#region src/core/stat-card.tsx
|
|
357
|
+
const TREND_GLYPH = {
|
|
358
|
+
up: "↑",
|
|
359
|
+
down: "↓",
|
|
360
|
+
neutral: "→"
|
|
361
|
+
};
|
|
362
|
+
/**
|
|
363
|
+
* Colour follows meaning, not direction. A rising bounce rate is a fall in
|
|
364
|
+
* quality, so a card declaring `polarity: "negative"` inverts the palette
|
|
365
|
+
* rather than congratulating the reader on a worse number.
|
|
366
|
+
*/
|
|
367
|
+
function deltaToneClass(trend, polarity) {
|
|
368
|
+
if (trend === "neutral") return OPT_TEXT_TERTIARY;
|
|
369
|
+
return (polarity === "positive" ? trend === "up" : trend === "down") ? "text-success-fg" : "text-danger-fg";
|
|
370
|
+
}
|
|
371
|
+
function sparkStroke(trend, polarity) {
|
|
372
|
+
if (!trend || trend === "neutral") return "var(--opt-chart-1)";
|
|
373
|
+
return (polarity === "positive" ? trend === "up" : trend === "down") ? "var(--opt-chart-diverging-positive)" : "var(--opt-chart-diverging-negative)";
|
|
374
|
+
}
|
|
222
375
|
/** Renders the stat card component. */
|
|
223
|
-
function StatCard({ title, value, change, trend, sparklineData, className }) {
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
"
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
className: "
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
376
|
+
function StatCard({ title, value, change, trend, polarity = "positive", description, hint, sparklineData, size = "md", loading = false, href, onSelect, selected = false, className }) {
|
|
377
|
+
const titleId = React.useId();
|
|
378
|
+
const hintId = React.useId();
|
|
379
|
+
const hasDelta = Boolean(change) && trend !== void 0;
|
|
380
|
+
const interactive = Boolean(href || onSelect);
|
|
381
|
+
const body = loading ? /* @__PURE__ */ jsxs("div", {
|
|
382
|
+
className: "flex flex-col gap-2",
|
|
383
|
+
children: [
|
|
384
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24" }),
|
|
385
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-32" }),
|
|
386
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-20" })
|
|
387
|
+
]
|
|
388
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
389
|
+
className: "flex items-start justify-between gap-4",
|
|
390
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
391
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
392
|
+
children: [
|
|
393
|
+
/* @__PURE__ */ jsx("span", {
|
|
394
|
+
id: titleId,
|
|
395
|
+
className: cn(OPT_TEXT_SECONDARY, "truncate text-sm"),
|
|
396
|
+
children: title
|
|
397
|
+
}),
|
|
398
|
+
/* @__PURE__ */ jsx("span", {
|
|
399
|
+
className: cn(OPT_TEXT_PRIMARY, "font-bold tabular-nums", size === "sm" ? "text-xl" : "text-2xl"),
|
|
400
|
+
children: value
|
|
401
|
+
}),
|
|
402
|
+
hasDelta && /* @__PURE__ */ jsxs("span", {
|
|
403
|
+
className: cn("text-sm font-medium tabular-nums", deltaToneClass(trend, polarity)),
|
|
404
|
+
children: [
|
|
405
|
+
TREND_GLYPH[trend],
|
|
406
|
+
" ",
|
|
407
|
+
change
|
|
408
|
+
]
|
|
409
|
+
}),
|
|
410
|
+
description && /* @__PURE__ */ jsx("span", {
|
|
411
|
+
className: cn("text-text-tertiary", "text-xs"),
|
|
412
|
+
children: description
|
|
413
|
+
}),
|
|
414
|
+
hint && /* @__PURE__ */ jsx("span", {
|
|
415
|
+
id: hintId,
|
|
416
|
+
className: "sr-only",
|
|
417
|
+
children: hint
|
|
418
|
+
})
|
|
419
|
+
]
|
|
420
|
+
}), sparklineData && sparklineData.length > 1 && /* @__PURE__ */ jsx(MiniSparkline, {
|
|
421
|
+
data: sparklineData,
|
|
422
|
+
color: sparkStroke(trend, polarity)
|
|
423
|
+
})]
|
|
258
424
|
});
|
|
425
|
+
const shared = {
|
|
426
|
+
className: cn(OPT_SURFACE, OPT_BORDER, "flex flex-col gap-1", size === "sm" ? "p-4" : "p-5", interactive && [
|
|
427
|
+
"hover:border-border-hover",
|
|
428
|
+
"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-ring-offset",
|
|
429
|
+
"cursor-pointer text-left transition-colors"
|
|
430
|
+
], selected && "border-accent", className),
|
|
431
|
+
title: hint,
|
|
432
|
+
"aria-describedby": hint ? hintId : void 0,
|
|
433
|
+
"data-opt-id": "GAX47",
|
|
434
|
+
"data-opt-slug": "stat-card.StatCard"
|
|
435
|
+
};
|
|
436
|
+
if (href) return /* @__PURE__ */ jsx("a", _objectSpread2(_objectSpread2({
|
|
437
|
+
href,
|
|
438
|
+
"aria-current": selected ? "true" : void 0
|
|
439
|
+
}, shared), {}, { children: body }));
|
|
440
|
+
if (onSelect) return /* @__PURE__ */ jsx("button", _objectSpread2(_objectSpread2({
|
|
441
|
+
type: "button",
|
|
442
|
+
onClick: onSelect,
|
|
443
|
+
"aria-pressed": selected
|
|
444
|
+
}, shared), {}, { children: body }));
|
|
445
|
+
return /* @__PURE__ */ jsx("div", _objectSpread2(_objectSpread2({}, shared), {}, { children: body }));
|
|
259
446
|
}
|
|
260
447
|
function MiniSparkline({ data, color }) {
|
|
261
448
|
const width = 80;
|
|
@@ -307,7 +494,7 @@ const koBreadcrumbLabels = {
|
|
|
307
494
|
navAriaLabel: "{label}(으)로 이동" };
|
|
308
495
|
//#endregion
|
|
309
496
|
//#region src/core/button.tsx
|
|
310
|
-
const _excluded$
|
|
497
|
+
const _excluded$16 = [
|
|
311
498
|
"variant",
|
|
312
499
|
"size",
|
|
313
500
|
"loading",
|
|
@@ -318,24 +505,24 @@ const _excluded$15 = [
|
|
|
318
505
|
"children",
|
|
319
506
|
"aria-busy"
|
|
320
507
|
];
|
|
321
|
-
const variantStyles$
|
|
508
|
+
const variantStyles$3 = {
|
|
322
509
|
primary: "bg-accent text-accent-fg hover:bg-accent-hover active:bg-accent-active focus-visible:ring-offset-2",
|
|
323
510
|
secondary: "border border-border bg-surface text-text-primary hover:bg-bg-subtle active:bg-bg-muted focus-visible:ring-offset-1",
|
|
324
511
|
ghost: "text-text-secondary hover:bg-bg-subtle active:bg-bg-muted focus-visible:ring-offset-1",
|
|
325
512
|
danger: "bg-danger text-danger-fg hover:bg-danger active:bg-danger focus-visible:ring-offset-2"
|
|
326
513
|
};
|
|
327
|
-
const sizeStyles$
|
|
514
|
+
const sizeStyles$5 = {
|
|
328
515
|
sm: "h-[var(--opt-control-sm)] px-[var(--opt-control-padding-sm)] text-xs gap-[var(--opt-space-1-5)]",
|
|
329
516
|
md: "h-[var(--opt-control-md)] px-[var(--opt-control-padding-md)] text-sm gap-[var(--opt-space-2)]",
|
|
330
517
|
lg: "h-[var(--opt-control-lg)] px-[var(--opt-control-padding-lg)] text-base gap-[var(--opt-space-2)]"
|
|
331
518
|
};
|
|
332
519
|
/** Renders the button component. */
|
|
333
520
|
function Button(_ref) {
|
|
334
|
-
let { variant = "primary", size = "md", loading = false, leftIcon, rightIcon, className, disabled, children, "aria-busy": ariaBusy } = _ref, props = _objectWithoutProperties(_ref, _excluded$
|
|
521
|
+
let { variant = "primary", size = "md", loading = false, leftIcon, rightIcon, className, disabled, children, "aria-busy": ariaBusy } = _ref, props = _objectWithoutProperties(_ref, _excluded$16);
|
|
335
522
|
const isDisabled = disabled || loading;
|
|
336
523
|
return /* @__PURE__ */ jsxs("button", _objectSpread2(_objectSpread2({
|
|
337
524
|
type: "button",
|
|
338
|
-
className: cn("inline-flex cursor-pointer items-center justify-center rounded-lg font-medium transition-colors", OPT_FOCUS_VISIBLE, variantStyles$
|
|
525
|
+
className: cn("inline-flex cursor-pointer items-center justify-center rounded-lg font-medium transition-colors", OPT_FOCUS_VISIBLE, variantStyles$3[variant], sizeStyles$5[size], isDisabled && "pointer-events-none cursor-not-allowed opacity-50", className),
|
|
339
526
|
disabled: isDisabled
|
|
340
527
|
}, props), {}, {
|
|
341
528
|
"aria-busy": loading ? true : ariaBusy,
|
|
@@ -377,44 +564,68 @@ const iconSizeClasses = {
|
|
|
377
564
|
md: "text-4xl",
|
|
378
565
|
lg: "text-5xl"
|
|
379
566
|
};
|
|
567
|
+
const titleSizeClasses = {
|
|
568
|
+
sm: "text-base font-semibold",
|
|
569
|
+
md: "text-lg font-semibold",
|
|
570
|
+
lg: "text-xl font-semibold"
|
|
571
|
+
};
|
|
572
|
+
const iconToneClasses = {
|
|
573
|
+
default: OPT_TEXT_TERTIARY,
|
|
574
|
+
subdued: OPT_TEXT_TERTIARY,
|
|
575
|
+
error: "text-danger-fg"
|
|
576
|
+
};
|
|
577
|
+
function isActionList(actions) {
|
|
578
|
+
return Array.isArray(actions) && actions.every((entry) => typeof entry === "object" && entry !== null && "onClick" in entry);
|
|
579
|
+
}
|
|
380
580
|
/** Renders the empty state component. */
|
|
381
|
-
function EmptyState({ icon, title, description, actions, className, iconSize = "md", headingLevel = "h3" }) {
|
|
581
|
+
function EmptyState({ icon, title, description, body, footer, actions, className, iconSize = "md", headingLevel = "h3", titleSize = "md", layout = "vertical", hasBorder = false, tone = "default" }) {
|
|
382
582
|
const HeadingTag = headingLevel;
|
|
583
|
+
const horizontal = layout === "horizontal";
|
|
584
|
+
const hasActions = isActionList(actions) ? actions.length > 0 : Boolean(actions);
|
|
383
585
|
return /* @__PURE__ */ jsxs("div", {
|
|
384
|
-
role: "status",
|
|
586
|
+
role: tone === "error" ? "alert" : "status",
|
|
385
587
|
"aria-label": title,
|
|
386
|
-
className: cn("flex flex-
|
|
387
|
-
"data-opt-id": "
|
|
588
|
+
className: cn("flex px-4 py-12", horizontal ? "flex-row items-center justify-center gap-8 text-left" : "flex-col items-center justify-center text-center", hasBorder && ["border border-border", "rounded-xl"], tone === "subdued" && "bg-bg-subtle", className),
|
|
589
|
+
"data-opt-id": "MZV19",
|
|
388
590
|
"data-opt-slug": "empty-state.EmptyState",
|
|
389
|
-
children: [
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
children: action
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
591
|
+
children: [icon && /* @__PURE__ */ jsx("div", {
|
|
592
|
+
className: cn(iconToneClasses[tone], iconSizeClasses[iconSize], horizontal ? "shrink-0" : "mb-4"),
|
|
593
|
+
"aria-hidden": "true",
|
|
594
|
+
children: icon
|
|
595
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
596
|
+
className: cn("flex flex-col", horizontal ? "items-start" : "items-center"),
|
|
597
|
+
children: [
|
|
598
|
+
/* @__PURE__ */ jsx(HeadingTag, {
|
|
599
|
+
className: cn(titleSizeClasses[titleSize], OPT_TEXT_PRIMARY),
|
|
600
|
+
children: title
|
|
601
|
+
}),
|
|
602
|
+
body ? /* @__PURE__ */ jsx("div", {
|
|
603
|
+
className: cn("mt-2 max-w-md text-sm", OPT_TEXT_SECONDARY),
|
|
604
|
+
children: body
|
|
605
|
+
}) : description && /* @__PURE__ */ jsx("p", {
|
|
606
|
+
className: cn("mt-2 max-w-sm text-sm", "text-text-secondary"),
|
|
607
|
+
children: description
|
|
608
|
+
}),
|
|
609
|
+
hasActions && /* @__PURE__ */ jsx("div", {
|
|
610
|
+
className: cn("mt-6 flex flex-wrap items-center gap-3", !horizontal && "justify-center"),
|
|
611
|
+
children: isActionList(actions) ? actions.map((action) => /* @__PURE__ */ jsx(Button, {
|
|
612
|
+
onClick: action.onClick,
|
|
613
|
+
disabled: action.disabled,
|
|
614
|
+
variant: action.variant === "secondary" ? "secondary" : "primary",
|
|
615
|
+
children: action.label
|
|
616
|
+
}, action.id)) : actions
|
|
617
|
+
}),
|
|
618
|
+
footer && /* @__PURE__ */ jsx("div", {
|
|
619
|
+
className: cn("border-border-subtle mt-6 w-full border-t pt-4 text-sm", "text-text-tertiary"),
|
|
620
|
+
children: footer
|
|
621
|
+
})
|
|
622
|
+
]
|
|
623
|
+
})]
|
|
413
624
|
});
|
|
414
625
|
}
|
|
415
626
|
//#endregion
|
|
416
627
|
//#region src/core/badge.tsx
|
|
417
|
-
const _excluded$
|
|
628
|
+
const _excluded$15 = [
|
|
418
629
|
"variant",
|
|
419
630
|
"size",
|
|
420
631
|
"dot",
|
|
@@ -423,7 +634,7 @@ const _excluded$14 = [
|
|
|
423
634
|
"className",
|
|
424
635
|
"children"
|
|
425
636
|
];
|
|
426
|
-
const variantStyles$
|
|
637
|
+
const variantStyles$2 = {
|
|
427
638
|
default: "bg-bg-subtle text-text-secondary",
|
|
428
639
|
success: "bg-success-subtle text-success-fg",
|
|
429
640
|
warning: "bg-warning-subtle text-warning-fg",
|
|
@@ -444,19 +655,19 @@ const progressStroke = {
|
|
|
444
655
|
error: "stroke-danger",
|
|
445
656
|
info: "stroke-info"
|
|
446
657
|
};
|
|
447
|
-
const sizeStyles$
|
|
658
|
+
const sizeStyles$4 = {
|
|
448
659
|
sm: "px-1.5 py-0.5 text-[10px]",
|
|
449
660
|
md: "px-2 py-0.5 text-[11px]"
|
|
450
661
|
};
|
|
451
662
|
/** Renders the badge component. */
|
|
452
663
|
function Badge(_ref) {
|
|
453
|
-
let { variant = "default", size = "md", dot = false, progress, progressSize = 16, className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$
|
|
664
|
+
let { variant = "default", size = "md", dot = false, progress, progressSize = 16, className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$15);
|
|
454
665
|
const hasProgress = progress != null;
|
|
455
666
|
const clampedProgress = hasProgress ? Math.min(100, Math.max(0, progress)) : 0;
|
|
456
667
|
const radius = (progressSize - 3) / 2;
|
|
457
668
|
const circumference = 2 * Math.PI * radius;
|
|
458
669
|
const offset = circumference - clampedProgress / 100 * circumference;
|
|
459
|
-
return /* @__PURE__ */ jsxs("span", _objectSpread2(_objectSpread2({ className: cn(OPT_BADGE, variantStyles$
|
|
670
|
+
return /* @__PURE__ */ jsxs("span", _objectSpread2(_objectSpread2({ className: cn(OPT_BADGE, variantStyles$2[variant], sizeStyles$4[size], className) }, props), {}, {
|
|
460
671
|
"data-opt-id": "9GBGZ",
|
|
461
672
|
"data-opt-slug": "badge.Badge",
|
|
462
673
|
children: [
|
|
@@ -496,24 +707,24 @@ function Badge(_ref) {
|
|
|
496
707
|
}
|
|
497
708
|
//#endregion
|
|
498
709
|
//#region src/core/spinner.tsx
|
|
499
|
-
const _excluded$
|
|
710
|
+
const _excluded$14 = [
|
|
500
711
|
"size",
|
|
501
712
|
"className",
|
|
502
713
|
"role",
|
|
503
714
|
"aria-hidden",
|
|
504
715
|
"aria-label"
|
|
505
716
|
];
|
|
506
|
-
const sizeStyles$
|
|
717
|
+
const sizeStyles$3 = {
|
|
507
718
|
sm: "h-4 w-4",
|
|
508
719
|
md: "h-6 w-6",
|
|
509
720
|
lg: "h-8 w-8"
|
|
510
721
|
};
|
|
511
722
|
/** Renders the spinner component. */
|
|
512
723
|
function Spinner(_ref) {
|
|
513
|
-
let { size = "md", className, role, "aria-hidden": ariaHidden, "aria-label": ariaLabel } = _ref, props = _objectWithoutProperties(_ref, _excluded$
|
|
724
|
+
let { size = "md", className, role, "aria-hidden": ariaHidden, "aria-label": ariaLabel } = _ref, props = _objectWithoutProperties(_ref, _excluded$14);
|
|
514
725
|
const isDecorative = ariaHidden === true || ariaHidden === "true";
|
|
515
726
|
return /* @__PURE__ */ jsxs("svg", _objectSpread2(_objectSpread2({
|
|
516
|
-
className: cn("animate-spin text-current", sizeStyles$
|
|
727
|
+
className: cn("animate-spin text-current", sizeStyles$3[size], className),
|
|
517
728
|
viewBox: "0 0 24 24",
|
|
518
729
|
fill: "none",
|
|
519
730
|
role: isDecorative ? void 0 : role !== null && role !== void 0 ? role : "status",
|
|
@@ -538,7 +749,7 @@ function Spinner(_ref) {
|
|
|
538
749
|
}
|
|
539
750
|
//#endregion
|
|
540
751
|
//#region src/core/alert.tsx
|
|
541
|
-
const _excluded$
|
|
752
|
+
const _excluded$13 = [
|
|
542
753
|
"variant",
|
|
543
754
|
"title",
|
|
544
755
|
"icon",
|
|
@@ -547,7 +758,7 @@ const _excluded$12 = [
|
|
|
547
758
|
"className",
|
|
548
759
|
"children"
|
|
549
760
|
];
|
|
550
|
-
const variantStyles = {
|
|
761
|
+
const variantStyles$1 = {
|
|
551
762
|
info: "border-info bg-info-subtle text-info-fg",
|
|
552
763
|
success: "border-success bg-success-subtle text-success-fg",
|
|
553
764
|
warning: "border-warning bg-warning-subtle text-warning-fg",
|
|
@@ -607,11 +818,11 @@ const defaultIcons = {
|
|
|
607
818
|
};
|
|
608
819
|
/** Renders the alert component. */
|
|
609
820
|
function Alert(_ref) {
|
|
610
|
-
let { variant = "info", title, icon, dismissible = false, onDismiss, className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$
|
|
821
|
+
let { variant = "info", title, icon, dismissible = false, onDismiss, className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$13);
|
|
611
822
|
const displayIcon = icon !== null && icon !== void 0 ? icon : defaultIcons[variant];
|
|
612
823
|
return /* @__PURE__ */ jsx("div", _objectSpread2(_objectSpread2({
|
|
613
824
|
role: "alert",
|
|
614
|
-
className: cn("relative rounded-lg border p-4", variantStyles[variant], className)
|
|
825
|
+
className: cn("relative rounded-lg border p-4", variantStyles$1[variant], className)
|
|
615
826
|
}, props), {}, {
|
|
616
827
|
"data-opt-id": "4SPK6",
|
|
617
828
|
"data-opt-slug": "alert.Alert",
|
|
@@ -663,7 +874,7 @@ function useInputId(id, label, prefix) {
|
|
|
663
874
|
}
|
|
664
875
|
//#endregion
|
|
665
876
|
//#region src/core/input.tsx
|
|
666
|
-
const _excluded$
|
|
877
|
+
const _excluded$12 = [
|
|
667
878
|
"label",
|
|
668
879
|
"error",
|
|
669
880
|
"hint",
|
|
@@ -678,7 +889,7 @@ const _excluded$11 = [
|
|
|
678
889
|
const inputSizeStyles = OPT_INPUT_SIZE;
|
|
679
890
|
const Input = forwardRef((_ref, ref) => {
|
|
680
891
|
var _useInputId;
|
|
681
|
-
let { label, error, hint, leftIcon, rightIcon, fullWidth = false, size = "md", required, className, id } = _ref, props = _objectWithoutProperties(_ref, _excluded$
|
|
892
|
+
let { label, error, hint, leftIcon, rightIcon, fullWidth = false, size = "md", required, className, id } = _ref, props = _objectWithoutProperties(_ref, _excluded$12);
|
|
682
893
|
const fallbackId = useId().replace(/:/g, "");
|
|
683
894
|
const inputId = (_useInputId = useInputId(id, label, "input")) !== null && _useInputId !== void 0 ? _useInputId : `input-${fallbackId}`;
|
|
684
895
|
const hasError = Boolean(error);
|
|
@@ -828,6 +1039,65 @@ function StarRating({ value = 0, onChange, max = 5, readOnly = false, size = "md
|
|
|
828
1039
|
});
|
|
829
1040
|
}
|
|
830
1041
|
//#endregion
|
|
1042
|
+
//#region src/core/meter.tsx
|
|
1043
|
+
const _excluded$11 = [
|
|
1044
|
+
"value",
|
|
1045
|
+
"min",
|
|
1046
|
+
"max",
|
|
1047
|
+
"size",
|
|
1048
|
+
"variant",
|
|
1049
|
+
"showValue",
|
|
1050
|
+
"label",
|
|
1051
|
+
"getValueLabel",
|
|
1052
|
+
"className"
|
|
1053
|
+
];
|
|
1054
|
+
const sizeStyles$2 = {
|
|
1055
|
+
sm: "h-1",
|
|
1056
|
+
md: "h-2",
|
|
1057
|
+
lg: "h-3"
|
|
1058
|
+
};
|
|
1059
|
+
const variantStyles = {
|
|
1060
|
+
default: "bg-accent",
|
|
1061
|
+
success: "bg-success",
|
|
1062
|
+
warning: "bg-warning",
|
|
1063
|
+
error: "bg-danger"
|
|
1064
|
+
};
|
|
1065
|
+
/**
|
|
1066
|
+
* A scalar measurement within a known range (disk usage, score, capacity) —
|
|
1067
|
+
* `role="meter"`, distinct from Progress (task completion). Built on the
|
|
1068
|
+
* opt-ui-primitives Meter primitive, which owns the role/aria/valuetext.
|
|
1069
|
+
*/
|
|
1070
|
+
function Meter$1(_ref) {
|
|
1071
|
+
let { value, min = 0, max = 100, size = "md", variant = "default", showValue = false, label, getValueLabel, className } = _ref, props = _objectWithoutProperties(_ref, _excluded$11);
|
|
1072
|
+
const clamped = Math.min(Math.max(value, min), max);
|
|
1073
|
+
const percentage = max === min ? 0 : (clamped - min) / (max - min) * 100;
|
|
1074
|
+
return /* @__PURE__ */ jsxs("div", _objectSpread2(_objectSpread2({ className: cn("w-full", className) }, props), {}, {
|
|
1075
|
+
"data-opt-id": "G01TE",
|
|
1076
|
+
"data-opt-slug": "meter.Meter",
|
|
1077
|
+
children: [(showValue || label) && /* @__PURE__ */ jsxs("div", {
|
|
1078
|
+
className: "mb-1.5 flex items-center justify-between text-sm",
|
|
1079
|
+
children: [label && /* @__PURE__ */ jsx("span", {
|
|
1080
|
+
className: "text-text-primary font-medium",
|
|
1081
|
+
children: label
|
|
1082
|
+
}), showValue && /* @__PURE__ */ jsxs("span", {
|
|
1083
|
+
className: "text-text-tertiary",
|
|
1084
|
+
children: [Math.round(percentage), "%"]
|
|
1085
|
+
})]
|
|
1086
|
+
}), /* @__PURE__ */ jsx(Meter, {
|
|
1087
|
+
value: clamped,
|
|
1088
|
+
min,
|
|
1089
|
+
max,
|
|
1090
|
+
getValueLabel,
|
|
1091
|
+
"aria-label": label !== null && label !== void 0 ? label : `${Math.round(percentage)}%`,
|
|
1092
|
+
className: cn("bg-bg-subtle w-full overflow-hidden rounded-full", sizeStyles$2[size]),
|
|
1093
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1094
|
+
className: cn("h-full rounded-full transition-all duration-300 ease-out", variantStyles[variant]),
|
|
1095
|
+
style: { width: `${percentage}%` }
|
|
1096
|
+
})
|
|
1097
|
+
})]
|
|
1098
|
+
}));
|
|
1099
|
+
}
|
|
1100
|
+
//#endregion
|
|
831
1101
|
//#region src/core/composite-zone.tsx
|
|
832
1102
|
/** Renders the composite zone component. */
|
|
833
1103
|
function CompositeZone({ children, label, focusLoop = true, focusWrap = true, className }) {
|
|
@@ -3510,6 +3780,55 @@ function FormDateRangePicker(_ref35) {
|
|
|
3510
3780
|
}));
|
|
3511
3781
|
}
|
|
3512
3782
|
//#endregion
|
|
3783
|
+
//#region src/lib/formatters.ts
|
|
3784
|
+
const OPT_UI_DEFAULT_LOCALE = "ko-KR";
|
|
3785
|
+
const KST_OFFSET_MS = 324e5;
|
|
3786
|
+
function getKstDateParts(input) {
|
|
3787
|
+
const date = input instanceof Date ? input : new Date(input);
|
|
3788
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
3789
|
+
const kstDate = new Date(date.getTime() + KST_OFFSET_MS);
|
|
3790
|
+
return {
|
|
3791
|
+
year: kstDate.getUTCFullYear(),
|
|
3792
|
+
month: kstDate.getUTCMonth() + 1,
|
|
3793
|
+
day: kstDate.getUTCDate(),
|
|
3794
|
+
hour: kstDate.getUTCHours(),
|
|
3795
|
+
minute: kstDate.getUTCMinutes(),
|
|
3796
|
+
second: kstDate.getUTCSeconds()
|
|
3797
|
+
};
|
|
3798
|
+
}
|
|
3799
|
+
function pad2(value) {
|
|
3800
|
+
return String(value).padStart(2, "0");
|
|
3801
|
+
}
|
|
3802
|
+
function meridiem(hour) {
|
|
3803
|
+
return hour < 12 ? "오전" : "오후";
|
|
3804
|
+
}
|
|
3805
|
+
function hour12(hour) {
|
|
3806
|
+
const normalized = hour % 12;
|
|
3807
|
+
return normalized === 0 ? 12 : normalized;
|
|
3808
|
+
}
|
|
3809
|
+
/** Formats a date as Korean short date text in the opt-ui default timezone. */
|
|
3810
|
+
function formatOptShortDate(input) {
|
|
3811
|
+
const parts = getKstDateParts(input);
|
|
3812
|
+
if (!parts) return "-";
|
|
3813
|
+
return `${parts.month}월 ${parts.day}일`;
|
|
3814
|
+
}
|
|
3815
|
+
/** Formats a date as Korean clock time in the opt-ui default timezone. */
|
|
3816
|
+
function formatOptTime(input) {
|
|
3817
|
+
const parts = getKstDateParts(input);
|
|
3818
|
+
if (!parts) return "-";
|
|
3819
|
+
return `${meridiem(parts.hour)} ${pad2(hour12(parts.hour))}:${pad2(parts.minute)}`;
|
|
3820
|
+
}
|
|
3821
|
+
/** Formats a date as Korean date-time text in the opt-ui default timezone. */
|
|
3822
|
+
function formatOptDateTime(input) {
|
|
3823
|
+
const parts = getKstDateParts(input);
|
|
3824
|
+
if (!parts) return "-";
|
|
3825
|
+
return `${parts.year}. ${parts.month}. ${parts.day}. ${meridiem(parts.hour)} ${hour12(parts.hour)}:${pad2(parts.minute)}:${pad2(parts.second)}`;
|
|
3826
|
+
}
|
|
3827
|
+
/** Formats numbers without depending on the runtime default locale. */
|
|
3828
|
+
function formatOptNumber(value) {
|
|
3829
|
+
return new Intl.NumberFormat(OPT_UI_DEFAULT_LOCALE).format(value);
|
|
3830
|
+
}
|
|
3831
|
+
//#endregion
|
|
3513
3832
|
//#region src/core/checkbox.tsx
|
|
3514
3833
|
const _excluded$1 = [
|
|
3515
3834
|
"label",
|
|
@@ -4097,6 +4416,134 @@ function Highlight({ text, search, caseSensitive = false, firstOnly = false, cla
|
|
|
4097
4416
|
});
|
|
4098
4417
|
}
|
|
4099
4418
|
//#endregion
|
|
4100
|
-
|
|
4419
|
+
//#region src/core/text-truncate.tsx
|
|
4420
|
+
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
4421
|
+
/** Measures text in the element's own resolved font, not an assumed one. */
|
|
4422
|
+
function createMeasurer(element) {
|
|
4423
|
+
const context = document.createElement("canvas").getContext("2d");
|
|
4424
|
+
if (!context) return null;
|
|
4425
|
+
const computed = window.getComputedStyle(element);
|
|
4426
|
+
context.font = [
|
|
4427
|
+
computed.fontStyle,
|
|
4428
|
+
computed.fontVariant,
|
|
4429
|
+
computed.fontWeight,
|
|
4430
|
+
`${computed.fontSize}/${computed.lineHeight}`,
|
|
4431
|
+
computed.fontFamily
|
|
4432
|
+
].join(" ");
|
|
4433
|
+
const spacing = Number.parseFloat(computed.letterSpacing) || 0;
|
|
4434
|
+
return (value) => context.measureText(value).width + spacing * value.length;
|
|
4435
|
+
}
|
|
4436
|
+
/** Largest `n` in `[0, max]` for which `fits(n)` holds. */
|
|
4437
|
+
function largestFitting(max, fits) {
|
|
4438
|
+
let low = 0;
|
|
4439
|
+
let high = max;
|
|
4440
|
+
while (low < high) {
|
|
4441
|
+
const mid = Math.ceil((low + high) / 2);
|
|
4442
|
+
if (fits(mid)) low = mid;
|
|
4443
|
+
else high = mid - 1;
|
|
4444
|
+
}
|
|
4445
|
+
return low;
|
|
4446
|
+
}
|
|
4447
|
+
function truncateToWidth(text, side, ellipsis, available, measure) {
|
|
4448
|
+
if (measure(text) <= available) return text;
|
|
4449
|
+
const chars = Array.from(text);
|
|
4450
|
+
if (side === "end") {
|
|
4451
|
+
const keep = largestFitting(chars.length, (n) => measure(chars.slice(0, n).join("") + ellipsis) <= available);
|
|
4452
|
+
return chars.slice(0, keep).join("") + ellipsis;
|
|
4453
|
+
}
|
|
4454
|
+
if (side === "start") {
|
|
4455
|
+
const keep = largestFitting(chars.length, (n) => measure(ellipsis + chars.slice(chars.length - n).join("")) <= available);
|
|
4456
|
+
return ellipsis + chars.slice(chars.length - keep).join("");
|
|
4457
|
+
}
|
|
4458
|
+
if (side === "startEnd") {
|
|
4459
|
+
const keep = largestFitting(Math.floor(chars.length / 2), (n) => {
|
|
4460
|
+
const head = chars.slice(0, n).join("");
|
|
4461
|
+
const tail = chars.slice(chars.length - n).join("");
|
|
4462
|
+
return measure(head + ellipsis + tail) <= available;
|
|
4463
|
+
});
|
|
4464
|
+
if (keep === 0) return ellipsis;
|
|
4465
|
+
return chars.slice(0, keep).join("") + ellipsis + chars.slice(chars.length - keep).join("");
|
|
4466
|
+
}
|
|
4467
|
+
const span = largestFitting(chars.length, (n) => {
|
|
4468
|
+
const start = Math.floor((chars.length - n) / 2);
|
|
4469
|
+
return measure(ellipsis + chars.slice(start, start + n).join("") + ellipsis) <= available;
|
|
4470
|
+
});
|
|
4471
|
+
if (span === 0) return ellipsis;
|
|
4472
|
+
const start = Math.floor((chars.length - span) / 2);
|
|
4473
|
+
return ellipsis + chars.slice(start, start + span).join("") + ellipsis;
|
|
4474
|
+
}
|
|
4475
|
+
/**
|
|
4476
|
+
* TextTruncate — drops characters from the part of a string that identifies it
|
|
4477
|
+
* least.
|
|
4478
|
+
*
|
|
4479
|
+
* CSS can only cut the end. That is the wrong end for the strings analytics
|
|
4480
|
+
* shows most: two URLs that differ in their query string, two ids that share a
|
|
4481
|
+
* prefix, a stack frame whose filename is at the tail. This measures the real
|
|
4482
|
+
* text in the real font against the real available width and cuts where the
|
|
4483
|
+
* caller says.
|
|
4484
|
+
*
|
|
4485
|
+
* The full string stays in the accessibility tree and in `title`, so
|
|
4486
|
+
* truncation never costs the reader information — only space.
|
|
4487
|
+
*
|
|
4488
|
+
* @example
|
|
4489
|
+
* ```tsx
|
|
4490
|
+
* <TextTruncate text={event.url} truncation="startEnd" />
|
|
4491
|
+
* ```
|
|
4492
|
+
*/
|
|
4493
|
+
function TextTruncate({ text, truncation = "startEnd", ellipsis = "…", width, children, className }) {
|
|
4494
|
+
const ref = useRef(null);
|
|
4495
|
+
const [available, setAvailable] = useState(width !== null && width !== void 0 ? width : null);
|
|
4496
|
+
useIsomorphicLayoutEffect(() => {
|
|
4497
|
+
if (width !== void 0) {
|
|
4498
|
+
setAvailable(width);
|
|
4499
|
+
return;
|
|
4500
|
+
}
|
|
4501
|
+
const element = ref.current;
|
|
4502
|
+
if (!element || typeof ResizeObserver === "undefined") return;
|
|
4503
|
+
const observer = new ResizeObserver((entries) => {
|
|
4504
|
+
const entry = entries[0];
|
|
4505
|
+
if (entry) setAvailable(entry.contentRect.width);
|
|
4506
|
+
});
|
|
4507
|
+
observer.observe(element);
|
|
4508
|
+
setAvailable(element.getBoundingClientRect().width);
|
|
4509
|
+
return () => observer.disconnect();
|
|
4510
|
+
}, [width]);
|
|
4511
|
+
const [rendered, setRendered] = useState(text);
|
|
4512
|
+
useIsomorphicLayoutEffect(() => {
|
|
4513
|
+
const element = ref.current;
|
|
4514
|
+
if (!element || available === null || available <= 0) {
|
|
4515
|
+
setRendered(text);
|
|
4516
|
+
return;
|
|
4517
|
+
}
|
|
4518
|
+
const measure = createMeasurer(element);
|
|
4519
|
+
if (!measure) {
|
|
4520
|
+
setRendered(text);
|
|
4521
|
+
return;
|
|
4522
|
+
}
|
|
4523
|
+
setRendered(truncateToWidth(text, truncation, ellipsis, available, measure));
|
|
4524
|
+
}, [
|
|
4525
|
+
text,
|
|
4526
|
+
truncation,
|
|
4527
|
+
ellipsis,
|
|
4528
|
+
available
|
|
4529
|
+
]);
|
|
4530
|
+
const truncated = rendered !== text;
|
|
4531
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
4532
|
+
ref,
|
|
4533
|
+
className: cn("block w-full overflow-hidden whitespace-nowrap", className),
|
|
4534
|
+
title: truncated ? text : void 0,
|
|
4535
|
+
"data-opt-id": "Y6BGX",
|
|
4536
|
+
"data-opt-slug": "text-truncate.TextTruncate",
|
|
4537
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4538
|
+
"aria-hidden": truncated ? "true" : void 0,
|
|
4539
|
+
children: children ? children(rendered) : rendered
|
|
4540
|
+
}), truncated && /* @__PURE__ */ jsx("span", {
|
|
4541
|
+
className: "sr-only",
|
|
4542
|
+
children: text
|
|
4543
|
+
})]
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
//#endregion
|
|
4547
|
+
export { PopoverContent$1 as $, OPT_TEXT_PRIMARY as $n, CompositeRow as $t, FormRadioGroup$1 as A, OPT_BADGE_YELLOW as An, DialogDescription$1 as At, FormSwitch$1 as B, OPT_FOCUS_VISIBLE as Bn, SelectPopover$1 as Bt, FormIconPicker as C, OPT_ACTIVE_ITEM as Cn, ComboboxGroupLabel$1 as Ct, FormProvider$1 as D, OPT_BADGE_GREEN as Dn, ComboboxRoot$1 as Dt, FormNumberInput as E, OPT_BADGE_BLUE as En, ComboboxPopover$1 as Et, FormSelectItem as F, OPT_DOT_GRAY as Fn, DialogHeading$1 as Ft, useFormStore as G, OPT_SURFACE as Gn, TabPanel$1 as Gt, FormTextarea as H, OPT_INPUT as Hn, SelectTrigger$1 as Ht, FormSelectPopover as I, OPT_DOT_GREEN as In, DialogPanel$1 as It, TagInput as J, OPT_TABLE_ROW_ACTIVE as Jn, DisclosureRoot$1 as Jt, DEFAULT_PRESETS as K, OPT_SURFACE_POPOVER as Kn, TabsRoot$1 as Kt, FormSlider as L, OPT_DOT_RED as Ln, DialogRoot$1 as Lt, FormReset$1 as M, OPT_DISCLOSURE_CONTENT as Mn, DialogDismiss$1 as Mt, FormRoot$1 as N, OPT_DISCLOSURE_TRIGGER as Nn, DialogFooter as Nt, FormPush$1 as O, OPT_BADGE_NEUTRAL as On, AlertDialogPanel$1 as Ot, FormSelect as P, OPT_DIVIDER as Pn, DialogHeader as Pt, PopoverClose$1 as Q, OPT_TEXT_LABEL as Qn, CompositeProvider as Qt, FormStarRating as R, OPT_DOT_YELLOW as Rn, SelectItem$1 as Rt, FormGroupLabel$1 as S, getDescribedBy as Sn, ComboboxGroup$1 as St, FormLabel$1 as T, OPT_ANIMATE_ENTER_EXIT as Tn, ComboboxItem$1 as Tt, useFieldValue as U, OPT_INPUT_ERROR as Un, Tab$1 as Ut, FormTagInput as V, OPT_HOVER_ITEM as Vn, SelectRoot$1 as Vt, useFormContext as W, OPT_ITEM_BASE as Wn, TabList$1 as Wt, getIconRegistryEntry as X, OPT_TD_MUTED as Xn, Composite as Xt, IconPicker as Y, OPT_TD as Yn, DisclosureTrigger$1 as Yt, ColorPicker as Z, OPT_TEXTAREA_SIZE as Zn, CompositeItem as Zt, FormDateRangePicker as _, SkeletonTable as _n, MenuSeparator$1 as _t, resolveFieldType as a, Alert as an, Kbd as ar, ToolbarContainer$1 as at, FormField$1 as b, FieldMessages as bn, MenubarRoot$1 as bt, CheckboxGroup as c, EmptyState as cn, Tooltip$1 as ct, formatOptNumber as d, koComboboxLabels as dn, MenuButtonArrow$1 as dt, CompositeZone as en, OPT_TEXT_SECONDARY as er, PopoverRoot$1 as et, formatOptShortDate as f, koStarRatingLabels as fn, MenuItem$1 as ft, FormControl$1 as g, SkeletonCard as gn, MenuRoot$1 as gt, FormColorPicker as h, SkeletonAvatar as hn, MenuPopover$1 as ht, FieldToken as i, useInputId as in, _objectWithoutProperties as ir, ToolbarButton$1 as it, FormRemove$1 as j, OPT_BORDER as jn, DialogDisclosure$1 as jt, FormRadio$1 as k, OPT_BADGE_RED as kn, DialogBody as kt, OPT_UI_DEFAULT_LOCALE as l, Button as ln, TooltipAnchor$1 as lt, FormCheckbox$1 as m, Skeleton as mn, MenuItemRadio$1 as mt, Highlight as n, StarRating as nn, OPT_TH as nr, Slider as nt, Pagination as o, Spinner as on, cn as or, ToolbarRoot$1 as ot, formatOptTime as p, StatCard as pn, MenuItemCheckbox$1 as pt, DateRangePicker as q, OPT_SURFACE_RAISED as qn, DisclosureContent$1 as qt, FIELD_TYPE_TOKENS as r, Input as rn, _objectSpread2 as rr, NumberInput as rt, Checkbox as s, Badge as sn, ToolbarSeparator$1 as st, TextTruncate as t, Meter$1 as tn, OPT_TEXT_TERTIARY as tr, PopoverTrigger$1 as tt, formatOptDateTime as u, koBreadcrumbLabels as un, TooltipProvider$1 as ut, FormDescription$1 as v, SkeletonText as vn, MenuTrigger$1 as vt, FormInput$1 as w, OPT_ANIMATE_DRAWER as wn, ComboboxInput$1 as wt, FormGroup$1 as x, RequiredMark as xn, ComboboxEmpty as xt, FormError$1 as y, useControlledState as yn, MenubarContainer$1 as yt, FormSubmit$1 as z, OPT_FOCUS as zn, SelectLabel$1 as zt };
|
|
4101
4548
|
|
|
4102
|
-
//# sourceMappingURL=
|
|
4549
|
+
//# sourceMappingURL=text-truncate-C17HHCR-.js.map
|