@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
|
@@ -142,6 +142,7 @@ const OPT_HOVER_ITEM = "[@media(any-hover:hover)]:hover:bg-accent-subtle active:
|
|
|
142
142
|
const OPT_BORDER = "border border-border";
|
|
143
143
|
const OPT_DIVIDER = "divide-border-subtle";
|
|
144
144
|
const OPT_SURFACE = "rounded-lg bg-surface shadow-sm";
|
|
145
|
+
const OPT_SURFACE_RAISED = "rounded-xl bg-surface-raised shadow-md";
|
|
145
146
|
const OPT_SURFACE_POPOVER = "z-50 rounded-lg border border-border bg-surface-overlay p-2 shadow-lg";
|
|
146
147
|
const OPT_TEXT_PRIMARY = "text-text-primary";
|
|
147
148
|
const OPT_TEXT_SECONDARY = "text-text-secondary";
|
|
@@ -240,44 +241,230 @@ function useControlledState(defaultValue, controlledValue, onChange) {
|
|
|
240
241
|
}, [isControlled, onChange])];
|
|
241
242
|
}
|
|
242
243
|
//#endregion
|
|
244
|
+
//#region src/core/skeleton.tsx
|
|
245
|
+
const _excluded$17 = [
|
|
246
|
+
"variant",
|
|
247
|
+
"width",
|
|
248
|
+
"height",
|
|
249
|
+
"lines",
|
|
250
|
+
"animated",
|
|
251
|
+
"className",
|
|
252
|
+
"style"
|
|
253
|
+
];
|
|
254
|
+
const _excluded2$9 = ["lines", "className"];
|
|
255
|
+
const _excluded3$8 = ["size", "className"];
|
|
256
|
+
const _excluded4$6 = ["className"];
|
|
257
|
+
const variantStyles$4 = {
|
|
258
|
+
text: "rounded h-4",
|
|
259
|
+
circular: "rounded-full",
|
|
260
|
+
rectangular: "rounded-lg"
|
|
261
|
+
};
|
|
262
|
+
/** Renders the skeleton component. */
|
|
263
|
+
function Skeleton(_ref) {
|
|
264
|
+
let { variant = "text", width, height, lines = 1, animated = true, className, style } = _ref, props = _objectWithoutProperties(_ref, _excluded$17);
|
|
265
|
+
const baseClass = cn("bg-bg-subtle", animated && "animate-pulse", variantStyles$4[variant], className);
|
|
266
|
+
const computedStyle = _objectSpread2({
|
|
267
|
+
width: typeof width === "number" ? `${width}px` : width,
|
|
268
|
+
height: typeof height === "number" ? `${height}px` : height
|
|
269
|
+
}, style);
|
|
270
|
+
if (variant === "text" && lines > 1) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
271
|
+
className: "gap-element flex flex-col",
|
|
272
|
+
role: "status",
|
|
273
|
+
"aria-label": "Loading",
|
|
274
|
+
"data-opt-id": "EJT87",
|
|
275
|
+
"data-opt-slug": "skeleton.Skeleton",
|
|
276
|
+
children: [Array.from({ length: lines }).map((_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2({
|
|
277
|
+
className: cn(baseClass, i === lines - 1 && "w-4/5"),
|
|
278
|
+
style: i === lines - 1 ? void 0 : computedStyle
|
|
279
|
+
}, props), i)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
280
|
+
className: "sr-only",
|
|
281
|
+
children: "Loading..."
|
|
282
|
+
})]
|
|
283
|
+
});
|
|
284
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2(_objectSpread2({
|
|
285
|
+
className: baseClass,
|
|
286
|
+
style: computedStyle,
|
|
287
|
+
role: "status",
|
|
288
|
+
"aria-label": "Loading"
|
|
289
|
+
}, props), {}, {
|
|
290
|
+
"data-opt-id": "EJT87",
|
|
291
|
+
"data-opt-slug": "skeleton.Skeleton",
|
|
292
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
293
|
+
className: "sr-only",
|
|
294
|
+
children: "Loading..."
|
|
295
|
+
})
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
/** Renders the skeleton text component. */
|
|
299
|
+
function SkeletonText(_ref2) {
|
|
300
|
+
let { lines = 3, className } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$9);
|
|
301
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, _objectSpread2(_objectSpread2({
|
|
302
|
+
variant: "text",
|
|
303
|
+
lines,
|
|
304
|
+
className
|
|
305
|
+
}, props), {}, {
|
|
306
|
+
"data-opt-id": "MX3D5",
|
|
307
|
+
"data-opt-slug": "skeleton.SkeletonText"
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
310
|
+
/** Renders the skeleton avatar component. */
|
|
311
|
+
function SkeletonAvatar(_ref3) {
|
|
312
|
+
let { size = 40, className } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$8);
|
|
313
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, _objectSpread2(_objectSpread2({
|
|
314
|
+
variant: "circular",
|
|
315
|
+
width: size,
|
|
316
|
+
height: size,
|
|
317
|
+
className
|
|
318
|
+
}, props), {}, {
|
|
319
|
+
"data-opt-id": "MP0HZ",
|
|
320
|
+
"data-opt-slug": "skeleton.SkeletonAvatar"
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
/** Renders the skeleton card component. */
|
|
324
|
+
function SkeletonCard(_ref4) {
|
|
325
|
+
let { className } = _ref4, props = _objectWithoutProperties(_ref4, _excluded4$6);
|
|
326
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", _objectSpread2(_objectSpread2({ className: cn("gap-group flex flex-col", className) }, props), {}, {
|
|
327
|
+
"data-opt-id": "34V5D",
|
|
328
|
+
"data-opt-slug": "skeleton.SkeletonCard",
|
|
329
|
+
children: [
|
|
330
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
331
|
+
variant: "rectangular",
|
|
332
|
+
height: 120
|
|
333
|
+
}),
|
|
334
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
335
|
+
variant: "text",
|
|
336
|
+
width: "60%"
|
|
337
|
+
}),
|
|
338
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
339
|
+
variant: "text",
|
|
340
|
+
lines: 2
|
|
341
|
+
})
|
|
342
|
+
]
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
/** Renders the skeleton table component. */
|
|
346
|
+
function SkeletonTable({ rows = 5, columns = 4, className }) {
|
|
347
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
348
|
+
className: cn("gap-element flex flex-col", className),
|
|
349
|
+
role: "status",
|
|
350
|
+
"aria-label": "Loading table",
|
|
351
|
+
"data-opt-id": "3KEDN",
|
|
352
|
+
"data-opt-slug": "skeleton.SkeletonTable",
|
|
353
|
+
children: [
|
|
354
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
355
|
+
className: "border-border-subtle gap-group flex border-b pb-2",
|
|
356
|
+
children: Array.from({ length: columns }).map((_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
357
|
+
variant: "text",
|
|
358
|
+
className: "flex-1",
|
|
359
|
+
height: 16
|
|
360
|
+
}, i))
|
|
361
|
+
}),
|
|
362
|
+
Array.from({ length: rows }).map((_, rowIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
363
|
+
className: "gap-group flex py-2",
|
|
364
|
+
children: Array.from({ length: columns }).map((_, colIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
365
|
+
variant: "text",
|
|
366
|
+
className: "flex-1",
|
|
367
|
+
height: 14
|
|
368
|
+
}, colIndex))
|
|
369
|
+
}, rowIndex)),
|
|
370
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
371
|
+
className: "sr-only",
|
|
372
|
+
children: "Loading..."
|
|
373
|
+
})
|
|
374
|
+
]
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
//#endregion
|
|
243
378
|
//#region src/core/stat-card.tsx
|
|
379
|
+
const TREND_GLYPH = {
|
|
380
|
+
up: "↑",
|
|
381
|
+
down: "↓",
|
|
382
|
+
neutral: "→"
|
|
383
|
+
};
|
|
384
|
+
/**
|
|
385
|
+
* Colour follows meaning, not direction. A rising bounce rate is a fall in
|
|
386
|
+
* quality, so a card declaring `polarity: "negative"` inverts the palette
|
|
387
|
+
* rather than congratulating the reader on a worse number.
|
|
388
|
+
*/
|
|
389
|
+
function deltaToneClass(trend, polarity) {
|
|
390
|
+
if (trend === "neutral") return OPT_TEXT_TERTIARY;
|
|
391
|
+
return (polarity === "positive" ? trend === "up" : trend === "down") ? "text-success-fg" : "text-danger-fg";
|
|
392
|
+
}
|
|
393
|
+
function sparkStroke(trend, polarity) {
|
|
394
|
+
if (!trend || trend === "neutral") return "var(--opt-chart-1)";
|
|
395
|
+
return (polarity === "positive" ? trend === "up" : trend === "down") ? "var(--opt-chart-diverging-positive)" : "var(--opt-chart-diverging-negative)";
|
|
396
|
+
}
|
|
244
397
|
/** Renders the stat card component. */
|
|
245
|
-
function StatCard({ title, value, change, trend, sparklineData, className }) {
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
"
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
className: "
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
398
|
+
function StatCard({ title, value, change, trend, polarity = "positive", description, hint, sparklineData, size = "md", loading = false, href, onSelect, selected = false, className }) {
|
|
399
|
+
const titleId = react.useId();
|
|
400
|
+
const hintId = react.useId();
|
|
401
|
+
const hasDelta = Boolean(change) && trend !== void 0;
|
|
402
|
+
const interactive = Boolean(href || onSelect);
|
|
403
|
+
const body = loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
404
|
+
className: "flex flex-col gap-2",
|
|
405
|
+
children: [
|
|
406
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, { className: "h-4 w-24" }),
|
|
407
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, { className: "h-7 w-32" }),
|
|
408
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, { className: "h-3 w-20" })
|
|
409
|
+
]
|
|
410
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
411
|
+
className: "flex items-start justify-between gap-4",
|
|
412
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
413
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
414
|
+
children: [
|
|
415
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
416
|
+
id: titleId,
|
|
417
|
+
className: cn(OPT_TEXT_SECONDARY, "truncate text-sm"),
|
|
418
|
+
children: title
|
|
419
|
+
}),
|
|
420
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
421
|
+
className: cn(OPT_TEXT_PRIMARY, "font-bold tabular-nums", size === "sm" ? "text-xl" : "text-2xl"),
|
|
422
|
+
children: value
|
|
423
|
+
}),
|
|
424
|
+
hasDelta && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
425
|
+
className: cn("text-sm font-medium tabular-nums", deltaToneClass(trend, polarity)),
|
|
426
|
+
children: [
|
|
427
|
+
TREND_GLYPH[trend],
|
|
428
|
+
" ",
|
|
429
|
+
change
|
|
430
|
+
]
|
|
431
|
+
}),
|
|
432
|
+
description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
433
|
+
className: cn("text-text-tertiary", "text-xs"),
|
|
434
|
+
children: description
|
|
435
|
+
}),
|
|
436
|
+
hint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
437
|
+
id: hintId,
|
|
438
|
+
className: "sr-only",
|
|
439
|
+
children: hint
|
|
440
|
+
})
|
|
441
|
+
]
|
|
442
|
+
}), sparklineData && sparklineData.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MiniSparkline, {
|
|
443
|
+
data: sparklineData,
|
|
444
|
+
color: sparkStroke(trend, polarity)
|
|
445
|
+
})]
|
|
280
446
|
});
|
|
447
|
+
const shared = {
|
|
448
|
+
className: cn(OPT_SURFACE, OPT_BORDER, "flex flex-col gap-1", size === "sm" ? "p-4" : "p-5", interactive && [
|
|
449
|
+
"hover:border-border-hover",
|
|
450
|
+
"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-ring-offset",
|
|
451
|
+
"cursor-pointer text-left transition-colors"
|
|
452
|
+
], selected && "border-accent", className),
|
|
453
|
+
title: hint,
|
|
454
|
+
"aria-describedby": hint ? hintId : void 0,
|
|
455
|
+
"data-opt-id": "GAX47",
|
|
456
|
+
"data-opt-slug": "stat-card.StatCard"
|
|
457
|
+
};
|
|
458
|
+
if (href) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", _objectSpread2(_objectSpread2({
|
|
459
|
+
href,
|
|
460
|
+
"aria-current": selected ? "true" : void 0
|
|
461
|
+
}, shared), {}, { children: body }));
|
|
462
|
+
if (onSelect) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", _objectSpread2(_objectSpread2({
|
|
463
|
+
type: "button",
|
|
464
|
+
onClick: onSelect,
|
|
465
|
+
"aria-pressed": selected
|
|
466
|
+
}, shared), {}, { children: body }));
|
|
467
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2(_objectSpread2({}, shared), {}, { children: body }));
|
|
281
468
|
}
|
|
282
469
|
function MiniSparkline({ data, color }) {
|
|
283
470
|
const width = 80;
|
|
@@ -329,7 +516,7 @@ const koBreadcrumbLabels = {
|
|
|
329
516
|
navAriaLabel: "{label}(으)로 이동" };
|
|
330
517
|
//#endregion
|
|
331
518
|
//#region src/core/button.tsx
|
|
332
|
-
const _excluded$
|
|
519
|
+
const _excluded$16 = [
|
|
333
520
|
"variant",
|
|
334
521
|
"size",
|
|
335
522
|
"loading",
|
|
@@ -340,24 +527,24 @@ const _excluded$15 = [
|
|
|
340
527
|
"children",
|
|
341
528
|
"aria-busy"
|
|
342
529
|
];
|
|
343
|
-
const variantStyles$
|
|
530
|
+
const variantStyles$3 = {
|
|
344
531
|
primary: "bg-accent text-accent-fg hover:bg-accent-hover active:bg-accent-active focus-visible:ring-offset-2",
|
|
345
532
|
secondary: "border border-border bg-surface text-text-primary hover:bg-bg-subtle active:bg-bg-muted focus-visible:ring-offset-1",
|
|
346
533
|
ghost: "text-text-secondary hover:bg-bg-subtle active:bg-bg-muted focus-visible:ring-offset-1",
|
|
347
534
|
danger: "bg-danger text-danger-fg hover:bg-danger active:bg-danger focus-visible:ring-offset-2"
|
|
348
535
|
};
|
|
349
|
-
const sizeStyles$
|
|
536
|
+
const sizeStyles$5 = {
|
|
350
537
|
sm: "h-[var(--opt-control-sm)] px-[var(--opt-control-padding-sm)] text-xs gap-[var(--opt-space-1-5)]",
|
|
351
538
|
md: "h-[var(--opt-control-md)] px-[var(--opt-control-padding-md)] text-sm gap-[var(--opt-space-2)]",
|
|
352
539
|
lg: "h-[var(--opt-control-lg)] px-[var(--opt-control-padding-lg)] text-base gap-[var(--opt-space-2)]"
|
|
353
540
|
};
|
|
354
541
|
/** Renders the button component. */
|
|
355
542
|
function Button(_ref) {
|
|
356
|
-
let { variant = "primary", size = "md", loading = false, leftIcon, rightIcon, className, disabled, children, "aria-busy": ariaBusy } = _ref, props = _objectWithoutProperties(_ref, _excluded$
|
|
543
|
+
let { variant = "primary", size = "md", loading = false, leftIcon, rightIcon, className, disabled, children, "aria-busy": ariaBusy } = _ref, props = _objectWithoutProperties(_ref, _excluded$16);
|
|
357
544
|
const isDisabled = disabled || loading;
|
|
358
545
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", _objectSpread2(_objectSpread2({
|
|
359
546
|
type: "button",
|
|
360
|
-
className: cn("inline-flex cursor-pointer items-center justify-center rounded-lg font-medium transition-colors", OPT_FOCUS_VISIBLE, variantStyles$
|
|
547
|
+
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),
|
|
361
548
|
disabled: isDisabled
|
|
362
549
|
}, props), {}, {
|
|
363
550
|
"aria-busy": loading ? true : ariaBusy,
|
|
@@ -399,44 +586,68 @@ const iconSizeClasses = {
|
|
|
399
586
|
md: "text-4xl",
|
|
400
587
|
lg: "text-5xl"
|
|
401
588
|
};
|
|
589
|
+
const titleSizeClasses = {
|
|
590
|
+
sm: "text-base font-semibold",
|
|
591
|
+
md: "text-lg font-semibold",
|
|
592
|
+
lg: "text-xl font-semibold"
|
|
593
|
+
};
|
|
594
|
+
const iconToneClasses = {
|
|
595
|
+
default: OPT_TEXT_TERTIARY,
|
|
596
|
+
subdued: OPT_TEXT_TERTIARY,
|
|
597
|
+
error: "text-danger-fg"
|
|
598
|
+
};
|
|
599
|
+
function isActionList(actions) {
|
|
600
|
+
return Array.isArray(actions) && actions.every((entry) => typeof entry === "object" && entry !== null && "onClick" in entry);
|
|
601
|
+
}
|
|
402
602
|
/** Renders the empty state component. */
|
|
403
|
-
function EmptyState({ icon, title, description, actions, className, iconSize = "md", headingLevel = "h3" }) {
|
|
603
|
+
function EmptyState({ icon, title, description, body, footer, actions, className, iconSize = "md", headingLevel = "h3", titleSize = "md", layout = "vertical", hasBorder = false, tone = "default" }) {
|
|
404
604
|
const HeadingTag = headingLevel;
|
|
605
|
+
const horizontal = layout === "horizontal";
|
|
606
|
+
const hasActions = isActionList(actions) ? actions.length > 0 : Boolean(actions);
|
|
405
607
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
406
|
-
role: "status",
|
|
608
|
+
role: tone === "error" ? "alert" : "status",
|
|
407
609
|
"aria-label": title,
|
|
408
|
-
className: cn("flex flex-
|
|
409
|
-
"data-opt-id": "
|
|
610
|
+
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),
|
|
611
|
+
"data-opt-id": "MZV19",
|
|
410
612
|
"data-opt-slug": "empty-state.EmptyState",
|
|
411
|
-
children: [
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
children: action.
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
613
|
+
children: [icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
614
|
+
className: cn(iconToneClasses[tone], iconSizeClasses[iconSize], horizontal ? "shrink-0" : "mb-4"),
|
|
615
|
+
"aria-hidden": "true",
|
|
616
|
+
children: icon
|
|
617
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
618
|
+
className: cn("flex flex-col", horizontal ? "items-start" : "items-center"),
|
|
619
|
+
children: [
|
|
620
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeadingTag, {
|
|
621
|
+
className: cn(titleSizeClasses[titleSize], OPT_TEXT_PRIMARY),
|
|
622
|
+
children: title
|
|
623
|
+
}),
|
|
624
|
+
body ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
625
|
+
className: cn("mt-2 max-w-md text-sm", OPT_TEXT_SECONDARY),
|
|
626
|
+
children: body
|
|
627
|
+
}) : description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
628
|
+
className: cn("mt-2 max-w-sm text-sm", "text-text-secondary"),
|
|
629
|
+
children: description
|
|
630
|
+
}),
|
|
631
|
+
hasActions && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
632
|
+
className: cn("mt-6 flex flex-wrap items-center gap-3", !horizontal && "justify-center"),
|
|
633
|
+
children: isActionList(actions) ? actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
634
|
+
onClick: action.onClick,
|
|
635
|
+
disabled: action.disabled,
|
|
636
|
+
variant: action.variant === "secondary" ? "secondary" : "primary",
|
|
637
|
+
children: action.label
|
|
638
|
+
}, action.id)) : actions
|
|
639
|
+
}),
|
|
640
|
+
footer && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
641
|
+
className: cn("border-border-subtle mt-6 w-full border-t pt-4 text-sm", "text-text-tertiary"),
|
|
642
|
+
children: footer
|
|
643
|
+
})
|
|
644
|
+
]
|
|
645
|
+
})]
|
|
435
646
|
});
|
|
436
647
|
}
|
|
437
648
|
//#endregion
|
|
438
649
|
//#region src/core/badge.tsx
|
|
439
|
-
const _excluded$
|
|
650
|
+
const _excluded$15 = [
|
|
440
651
|
"variant",
|
|
441
652
|
"size",
|
|
442
653
|
"dot",
|
|
@@ -445,7 +656,7 @@ const _excluded$14 = [
|
|
|
445
656
|
"className",
|
|
446
657
|
"children"
|
|
447
658
|
];
|
|
448
|
-
const variantStyles$
|
|
659
|
+
const variantStyles$2 = {
|
|
449
660
|
default: "bg-bg-subtle text-text-secondary",
|
|
450
661
|
success: "bg-success-subtle text-success-fg",
|
|
451
662
|
warning: "bg-warning-subtle text-warning-fg",
|
|
@@ -466,19 +677,19 @@ const progressStroke = {
|
|
|
466
677
|
error: "stroke-danger",
|
|
467
678
|
info: "stroke-info"
|
|
468
679
|
};
|
|
469
|
-
const sizeStyles$
|
|
680
|
+
const sizeStyles$4 = {
|
|
470
681
|
sm: "px-1.5 py-0.5 text-[10px]",
|
|
471
682
|
md: "px-2 py-0.5 text-[11px]"
|
|
472
683
|
};
|
|
473
684
|
/** Renders the badge component. */
|
|
474
685
|
function Badge(_ref) {
|
|
475
|
-
let { variant = "default", size = "md", dot = false, progress, progressSize = 16, className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$
|
|
686
|
+
let { variant = "default", size = "md", dot = false, progress, progressSize = 16, className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$15);
|
|
476
687
|
const hasProgress = progress != null;
|
|
477
688
|
const clampedProgress = hasProgress ? Math.min(100, Math.max(0, progress)) : 0;
|
|
478
689
|
const radius = (progressSize - 3) / 2;
|
|
479
690
|
const circumference = 2 * Math.PI * radius;
|
|
480
691
|
const offset = circumference - clampedProgress / 100 * circumference;
|
|
481
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", _objectSpread2(_objectSpread2({ className: cn(OPT_BADGE, variantStyles$
|
|
692
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", _objectSpread2(_objectSpread2({ className: cn(OPT_BADGE, variantStyles$2[variant], sizeStyles$4[size], className) }, props), {}, {
|
|
482
693
|
"data-opt-id": "9GBGZ",
|
|
483
694
|
"data-opt-slug": "badge.Badge",
|
|
484
695
|
children: [
|
|
@@ -518,24 +729,24 @@ function Badge(_ref) {
|
|
|
518
729
|
}
|
|
519
730
|
//#endregion
|
|
520
731
|
//#region src/core/spinner.tsx
|
|
521
|
-
const _excluded$
|
|
732
|
+
const _excluded$14 = [
|
|
522
733
|
"size",
|
|
523
734
|
"className",
|
|
524
735
|
"role",
|
|
525
736
|
"aria-hidden",
|
|
526
737
|
"aria-label"
|
|
527
738
|
];
|
|
528
|
-
const sizeStyles$
|
|
739
|
+
const sizeStyles$3 = {
|
|
529
740
|
sm: "h-4 w-4",
|
|
530
741
|
md: "h-6 w-6",
|
|
531
742
|
lg: "h-8 w-8"
|
|
532
743
|
};
|
|
533
744
|
/** Renders the spinner component. */
|
|
534
745
|
function Spinner(_ref) {
|
|
535
|
-
let { size = "md", className, role, "aria-hidden": ariaHidden, "aria-label": ariaLabel } = _ref, props = _objectWithoutProperties(_ref, _excluded$
|
|
746
|
+
let { size = "md", className, role, "aria-hidden": ariaHidden, "aria-label": ariaLabel } = _ref, props = _objectWithoutProperties(_ref, _excluded$14);
|
|
536
747
|
const isDecorative = ariaHidden === true || ariaHidden === "true";
|
|
537
748
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", _objectSpread2(_objectSpread2({
|
|
538
|
-
className: cn("animate-spin text-current", sizeStyles$
|
|
749
|
+
className: cn("animate-spin text-current", sizeStyles$3[size], className),
|
|
539
750
|
viewBox: "0 0 24 24",
|
|
540
751
|
fill: "none",
|
|
541
752
|
role: isDecorative ? void 0 : role !== null && role !== void 0 ? role : "status",
|
|
@@ -560,7 +771,7 @@ function Spinner(_ref) {
|
|
|
560
771
|
}
|
|
561
772
|
//#endregion
|
|
562
773
|
//#region src/core/alert.tsx
|
|
563
|
-
const _excluded$
|
|
774
|
+
const _excluded$13 = [
|
|
564
775
|
"variant",
|
|
565
776
|
"title",
|
|
566
777
|
"icon",
|
|
@@ -569,7 +780,7 @@ const _excluded$12 = [
|
|
|
569
780
|
"className",
|
|
570
781
|
"children"
|
|
571
782
|
];
|
|
572
|
-
const variantStyles = {
|
|
783
|
+
const variantStyles$1 = {
|
|
573
784
|
info: "border-info bg-info-subtle text-info-fg",
|
|
574
785
|
success: "border-success bg-success-subtle text-success-fg",
|
|
575
786
|
warning: "border-warning bg-warning-subtle text-warning-fg",
|
|
@@ -629,11 +840,11 @@ const defaultIcons = {
|
|
|
629
840
|
};
|
|
630
841
|
/** Renders the alert component. */
|
|
631
842
|
function Alert(_ref) {
|
|
632
|
-
let { variant = "info", title, icon, dismissible = false, onDismiss, className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$
|
|
843
|
+
let { variant = "info", title, icon, dismissible = false, onDismiss, className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$13);
|
|
633
844
|
const displayIcon = icon !== null && icon !== void 0 ? icon : defaultIcons[variant];
|
|
634
845
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2(_objectSpread2({
|
|
635
846
|
role: "alert",
|
|
636
|
-
className: cn("relative rounded-lg border p-4", variantStyles[variant], className)
|
|
847
|
+
className: cn("relative rounded-lg border p-4", variantStyles$1[variant], className)
|
|
637
848
|
}, props), {}, {
|
|
638
849
|
"data-opt-id": "4SPK6",
|
|
639
850
|
"data-opt-slug": "alert.Alert",
|
|
@@ -685,7 +896,7 @@ function useInputId(id, label, prefix) {
|
|
|
685
896
|
}
|
|
686
897
|
//#endregion
|
|
687
898
|
//#region src/core/input.tsx
|
|
688
|
-
const _excluded$
|
|
899
|
+
const _excluded$12 = [
|
|
689
900
|
"label",
|
|
690
901
|
"error",
|
|
691
902
|
"hint",
|
|
@@ -700,7 +911,7 @@ const _excluded$11 = [
|
|
|
700
911
|
const inputSizeStyles = OPT_INPUT_SIZE;
|
|
701
912
|
const Input = (0, react.forwardRef)((_ref, ref) => {
|
|
702
913
|
var _useInputId;
|
|
703
|
-
let { label, error, hint, leftIcon, rightIcon, fullWidth = false, size = "md", required, className, id } = _ref, props = _objectWithoutProperties(_ref, _excluded$
|
|
914
|
+
let { label, error, hint, leftIcon, rightIcon, fullWidth = false, size = "md", required, className, id } = _ref, props = _objectWithoutProperties(_ref, _excluded$12);
|
|
704
915
|
const fallbackId = (0, react.useId)().replace(/:/g, "");
|
|
705
916
|
const inputId = (_useInputId = useInputId(id, label, "input")) !== null && _useInputId !== void 0 ? _useInputId : `input-${fallbackId}`;
|
|
706
917
|
const hasError = Boolean(error);
|
|
@@ -850,6 +1061,65 @@ function StarRating({ value = 0, onChange, max = 5, readOnly = false, size = "md
|
|
|
850
1061
|
});
|
|
851
1062
|
}
|
|
852
1063
|
//#endregion
|
|
1064
|
+
//#region src/core/meter.tsx
|
|
1065
|
+
const _excluded$11 = [
|
|
1066
|
+
"value",
|
|
1067
|
+
"min",
|
|
1068
|
+
"max",
|
|
1069
|
+
"size",
|
|
1070
|
+
"variant",
|
|
1071
|
+
"showValue",
|
|
1072
|
+
"label",
|
|
1073
|
+
"getValueLabel",
|
|
1074
|
+
"className"
|
|
1075
|
+
];
|
|
1076
|
+
const sizeStyles$2 = {
|
|
1077
|
+
sm: "h-1",
|
|
1078
|
+
md: "h-2",
|
|
1079
|
+
lg: "h-3"
|
|
1080
|
+
};
|
|
1081
|
+
const variantStyles = {
|
|
1082
|
+
default: "bg-accent",
|
|
1083
|
+
success: "bg-success",
|
|
1084
|
+
warning: "bg-warning",
|
|
1085
|
+
error: "bg-danger"
|
|
1086
|
+
};
|
|
1087
|
+
/**
|
|
1088
|
+
* A scalar measurement within a known range (disk usage, score, capacity) —
|
|
1089
|
+
* `role="meter"`, distinct from Progress (task completion). Built on the
|
|
1090
|
+
* opt-ui-primitives Meter primitive, which owns the role/aria/valuetext.
|
|
1091
|
+
*/
|
|
1092
|
+
function Meter(_ref) {
|
|
1093
|
+
let { value, min = 0, max = 100, size = "md", variant = "default", showValue = false, label, getValueLabel, className } = _ref, props = _objectWithoutProperties(_ref, _excluded$11);
|
|
1094
|
+
const clamped = Math.min(Math.max(value, min), max);
|
|
1095
|
+
const percentage = max === min ? 0 : (clamped - min) / (max - min) * 100;
|
|
1096
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", _objectSpread2(_objectSpread2({ className: cn("w-full", className) }, props), {}, {
|
|
1097
|
+
"data-opt-id": "G01TE",
|
|
1098
|
+
"data-opt-slug": "meter.Meter",
|
|
1099
|
+
children: [(showValue || label) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1100
|
+
className: "mb-1.5 flex items-center justify-between text-sm",
|
|
1101
|
+
children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1102
|
+
className: "text-text-primary font-medium",
|
|
1103
|
+
children: label
|
|
1104
|
+
}), showValue && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1105
|
+
className: "text-text-tertiary",
|
|
1106
|
+
children: [Math.round(percentage), "%"]
|
|
1107
|
+
})]
|
|
1108
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.Meter, {
|
|
1109
|
+
value: clamped,
|
|
1110
|
+
min,
|
|
1111
|
+
max,
|
|
1112
|
+
getValueLabel,
|
|
1113
|
+
"aria-label": label !== null && label !== void 0 ? label : `${Math.round(percentage)}%`,
|
|
1114
|
+
className: cn("bg-bg-subtle w-full overflow-hidden rounded-full", sizeStyles$2[size]),
|
|
1115
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1116
|
+
className: cn("h-full rounded-full transition-all duration-300 ease-out", variantStyles[variant]),
|
|
1117
|
+
style: { width: `${percentage}%` }
|
|
1118
|
+
})
|
|
1119
|
+
})]
|
|
1120
|
+
}));
|
|
1121
|
+
}
|
|
1122
|
+
//#endregion
|
|
853
1123
|
//#region src/core/composite-zone.tsx
|
|
854
1124
|
/** Renders the composite zone component. */
|
|
855
1125
|
function CompositeZone({ children, label, focusLoop = true, focusWrap = true, className }) {
|
|
@@ -3532,6 +3802,55 @@ function FormDateRangePicker(_ref35) {
|
|
|
3532
3802
|
}));
|
|
3533
3803
|
}
|
|
3534
3804
|
//#endregion
|
|
3805
|
+
//#region src/lib/formatters.ts
|
|
3806
|
+
const OPT_UI_DEFAULT_LOCALE = "ko-KR";
|
|
3807
|
+
const KST_OFFSET_MS = 324e5;
|
|
3808
|
+
function getKstDateParts(input) {
|
|
3809
|
+
const date = input instanceof Date ? input : new Date(input);
|
|
3810
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
3811
|
+
const kstDate = new Date(date.getTime() + KST_OFFSET_MS);
|
|
3812
|
+
return {
|
|
3813
|
+
year: kstDate.getUTCFullYear(),
|
|
3814
|
+
month: kstDate.getUTCMonth() + 1,
|
|
3815
|
+
day: kstDate.getUTCDate(),
|
|
3816
|
+
hour: kstDate.getUTCHours(),
|
|
3817
|
+
minute: kstDate.getUTCMinutes(),
|
|
3818
|
+
second: kstDate.getUTCSeconds()
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3821
|
+
function pad2(value) {
|
|
3822
|
+
return String(value).padStart(2, "0");
|
|
3823
|
+
}
|
|
3824
|
+
function meridiem(hour) {
|
|
3825
|
+
return hour < 12 ? "오전" : "오후";
|
|
3826
|
+
}
|
|
3827
|
+
function hour12(hour) {
|
|
3828
|
+
const normalized = hour % 12;
|
|
3829
|
+
return normalized === 0 ? 12 : normalized;
|
|
3830
|
+
}
|
|
3831
|
+
/** Formats a date as Korean short date text in the opt-ui default timezone. */
|
|
3832
|
+
function formatOptShortDate(input) {
|
|
3833
|
+
const parts = getKstDateParts(input);
|
|
3834
|
+
if (!parts) return "-";
|
|
3835
|
+
return `${parts.month}월 ${parts.day}일`;
|
|
3836
|
+
}
|
|
3837
|
+
/** Formats a date as Korean clock time in the opt-ui default timezone. */
|
|
3838
|
+
function formatOptTime(input) {
|
|
3839
|
+
const parts = getKstDateParts(input);
|
|
3840
|
+
if (!parts) return "-";
|
|
3841
|
+
return `${meridiem(parts.hour)} ${pad2(hour12(parts.hour))}:${pad2(parts.minute)}`;
|
|
3842
|
+
}
|
|
3843
|
+
/** Formats a date as Korean date-time text in the opt-ui default timezone. */
|
|
3844
|
+
function formatOptDateTime(input) {
|
|
3845
|
+
const parts = getKstDateParts(input);
|
|
3846
|
+
if (!parts) return "-";
|
|
3847
|
+
return `${parts.year}. ${parts.month}. ${parts.day}. ${meridiem(parts.hour)} ${hour12(parts.hour)}:${pad2(parts.minute)}:${pad2(parts.second)}`;
|
|
3848
|
+
}
|
|
3849
|
+
/** Formats numbers without depending on the runtime default locale. */
|
|
3850
|
+
function formatOptNumber(value) {
|
|
3851
|
+
return new Intl.NumberFormat(OPT_UI_DEFAULT_LOCALE).format(value);
|
|
3852
|
+
}
|
|
3853
|
+
//#endregion
|
|
3535
3854
|
//#region src/core/checkbox.tsx
|
|
3536
3855
|
const _excluded$1 = [
|
|
3537
3856
|
"label",
|
|
@@ -4119,6 +4438,134 @@ function Highlight({ text, search, caseSensitive = false, firstOnly = false, cla
|
|
|
4119
4438
|
});
|
|
4120
4439
|
}
|
|
4121
4440
|
//#endregion
|
|
4441
|
+
//#region src/core/text-truncate.tsx
|
|
4442
|
+
const useIsomorphicLayoutEffect = typeof window === "undefined" ? react.useEffect : react.useLayoutEffect;
|
|
4443
|
+
/** Measures text in the element's own resolved font, not an assumed one. */
|
|
4444
|
+
function createMeasurer(element) {
|
|
4445
|
+
const context = document.createElement("canvas").getContext("2d");
|
|
4446
|
+
if (!context) return null;
|
|
4447
|
+
const computed = window.getComputedStyle(element);
|
|
4448
|
+
context.font = [
|
|
4449
|
+
computed.fontStyle,
|
|
4450
|
+
computed.fontVariant,
|
|
4451
|
+
computed.fontWeight,
|
|
4452
|
+
`${computed.fontSize}/${computed.lineHeight}`,
|
|
4453
|
+
computed.fontFamily
|
|
4454
|
+
].join(" ");
|
|
4455
|
+
const spacing = Number.parseFloat(computed.letterSpacing) || 0;
|
|
4456
|
+
return (value) => context.measureText(value).width + spacing * value.length;
|
|
4457
|
+
}
|
|
4458
|
+
/** Largest `n` in `[0, max]` for which `fits(n)` holds. */
|
|
4459
|
+
function largestFitting(max, fits) {
|
|
4460
|
+
let low = 0;
|
|
4461
|
+
let high = max;
|
|
4462
|
+
while (low < high) {
|
|
4463
|
+
const mid = Math.ceil((low + high) / 2);
|
|
4464
|
+
if (fits(mid)) low = mid;
|
|
4465
|
+
else high = mid - 1;
|
|
4466
|
+
}
|
|
4467
|
+
return low;
|
|
4468
|
+
}
|
|
4469
|
+
function truncateToWidth(text, side, ellipsis, available, measure) {
|
|
4470
|
+
if (measure(text) <= available) return text;
|
|
4471
|
+
const chars = Array.from(text);
|
|
4472
|
+
if (side === "end") {
|
|
4473
|
+
const keep = largestFitting(chars.length, (n) => measure(chars.slice(0, n).join("") + ellipsis) <= available);
|
|
4474
|
+
return chars.slice(0, keep).join("") + ellipsis;
|
|
4475
|
+
}
|
|
4476
|
+
if (side === "start") {
|
|
4477
|
+
const keep = largestFitting(chars.length, (n) => measure(ellipsis + chars.slice(chars.length - n).join("")) <= available);
|
|
4478
|
+
return ellipsis + chars.slice(chars.length - keep).join("");
|
|
4479
|
+
}
|
|
4480
|
+
if (side === "startEnd") {
|
|
4481
|
+
const keep = largestFitting(Math.floor(chars.length / 2), (n) => {
|
|
4482
|
+
const head = chars.slice(0, n).join("");
|
|
4483
|
+
const tail = chars.slice(chars.length - n).join("");
|
|
4484
|
+
return measure(head + ellipsis + tail) <= available;
|
|
4485
|
+
});
|
|
4486
|
+
if (keep === 0) return ellipsis;
|
|
4487
|
+
return chars.slice(0, keep).join("") + ellipsis + chars.slice(chars.length - keep).join("");
|
|
4488
|
+
}
|
|
4489
|
+
const span = largestFitting(chars.length, (n) => {
|
|
4490
|
+
const start = Math.floor((chars.length - n) / 2);
|
|
4491
|
+
return measure(ellipsis + chars.slice(start, start + n).join("") + ellipsis) <= available;
|
|
4492
|
+
});
|
|
4493
|
+
if (span === 0) return ellipsis;
|
|
4494
|
+
const start = Math.floor((chars.length - span) / 2);
|
|
4495
|
+
return ellipsis + chars.slice(start, start + span).join("") + ellipsis;
|
|
4496
|
+
}
|
|
4497
|
+
/**
|
|
4498
|
+
* TextTruncate — drops characters from the part of a string that identifies it
|
|
4499
|
+
* least.
|
|
4500
|
+
*
|
|
4501
|
+
* CSS can only cut the end. That is the wrong end for the strings analytics
|
|
4502
|
+
* shows most: two URLs that differ in their query string, two ids that share a
|
|
4503
|
+
* prefix, a stack frame whose filename is at the tail. This measures the real
|
|
4504
|
+
* text in the real font against the real available width and cuts where the
|
|
4505
|
+
* caller says.
|
|
4506
|
+
*
|
|
4507
|
+
* The full string stays in the accessibility tree and in `title`, so
|
|
4508
|
+
* truncation never costs the reader information — only space.
|
|
4509
|
+
*
|
|
4510
|
+
* @example
|
|
4511
|
+
* ```tsx
|
|
4512
|
+
* <TextTruncate text={event.url} truncation="startEnd" />
|
|
4513
|
+
* ```
|
|
4514
|
+
*/
|
|
4515
|
+
function TextTruncate({ text, truncation = "startEnd", ellipsis = "…", width, children, className }) {
|
|
4516
|
+
const ref = (0, react.useRef)(null);
|
|
4517
|
+
const [available, setAvailable] = (0, react.useState)(width !== null && width !== void 0 ? width : null);
|
|
4518
|
+
useIsomorphicLayoutEffect(() => {
|
|
4519
|
+
if (width !== void 0) {
|
|
4520
|
+
setAvailable(width);
|
|
4521
|
+
return;
|
|
4522
|
+
}
|
|
4523
|
+
const element = ref.current;
|
|
4524
|
+
if (!element || typeof ResizeObserver === "undefined") return;
|
|
4525
|
+
const observer = new ResizeObserver((entries) => {
|
|
4526
|
+
const entry = entries[0];
|
|
4527
|
+
if (entry) setAvailable(entry.contentRect.width);
|
|
4528
|
+
});
|
|
4529
|
+
observer.observe(element);
|
|
4530
|
+
setAvailable(element.getBoundingClientRect().width);
|
|
4531
|
+
return () => observer.disconnect();
|
|
4532
|
+
}, [width]);
|
|
4533
|
+
const [rendered, setRendered] = (0, react.useState)(text);
|
|
4534
|
+
useIsomorphicLayoutEffect(() => {
|
|
4535
|
+
const element = ref.current;
|
|
4536
|
+
if (!element || available === null || available <= 0) {
|
|
4537
|
+
setRendered(text);
|
|
4538
|
+
return;
|
|
4539
|
+
}
|
|
4540
|
+
const measure = createMeasurer(element);
|
|
4541
|
+
if (!measure) {
|
|
4542
|
+
setRendered(text);
|
|
4543
|
+
return;
|
|
4544
|
+
}
|
|
4545
|
+
setRendered(truncateToWidth(text, truncation, ellipsis, available, measure));
|
|
4546
|
+
}, [
|
|
4547
|
+
text,
|
|
4548
|
+
truncation,
|
|
4549
|
+
ellipsis,
|
|
4550
|
+
available
|
|
4551
|
+
]);
|
|
4552
|
+
const truncated = rendered !== text;
|
|
4553
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4554
|
+
ref,
|
|
4555
|
+
className: cn("block w-full overflow-hidden whitespace-nowrap", className),
|
|
4556
|
+
title: truncated ? text : void 0,
|
|
4557
|
+
"data-opt-id": "Y6BGX",
|
|
4558
|
+
"data-opt-slug": "text-truncate.TextTruncate",
|
|
4559
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4560
|
+
"aria-hidden": truncated ? "true" : void 0,
|
|
4561
|
+
children: children ? children(rendered) : rendered
|
|
4562
|
+
}), truncated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4563
|
+
className: "sr-only",
|
|
4564
|
+
children: text
|
|
4565
|
+
})]
|
|
4566
|
+
});
|
|
4567
|
+
}
|
|
4568
|
+
//#endregion
|
|
4122
4569
|
Object.defineProperty(exports, "Alert", {
|
|
4123
4570
|
enumerable: true,
|
|
4124
4571
|
get: function() {
|
|
@@ -4575,6 +5022,12 @@ Object.defineProperty(exports, "MenubarRoot", {
|
|
|
4575
5022
|
return MenubarRoot;
|
|
4576
5023
|
}
|
|
4577
5024
|
});
|
|
5025
|
+
Object.defineProperty(exports, "Meter", {
|
|
5026
|
+
enumerable: true,
|
|
5027
|
+
get: function() {
|
|
5028
|
+
return Meter;
|
|
5029
|
+
}
|
|
5030
|
+
});
|
|
4578
5031
|
Object.defineProperty(exports, "NumberInput", {
|
|
4579
5032
|
enumerable: true,
|
|
4580
5033
|
get: function() {
|
|
@@ -4725,6 +5178,12 @@ Object.defineProperty(exports, "OPT_SURFACE_POPOVER", {
|
|
|
4725
5178
|
return OPT_SURFACE_POPOVER;
|
|
4726
5179
|
}
|
|
4727
5180
|
});
|
|
5181
|
+
Object.defineProperty(exports, "OPT_SURFACE_RAISED", {
|
|
5182
|
+
enumerable: true,
|
|
5183
|
+
get: function() {
|
|
5184
|
+
return OPT_SURFACE_RAISED;
|
|
5185
|
+
}
|
|
5186
|
+
});
|
|
4728
5187
|
Object.defineProperty(exports, "OPT_TABLE_ROW_ACTIVE", {
|
|
4729
5188
|
enumerable: true,
|
|
4730
5189
|
get: function() {
|
|
@@ -4779,6 +5238,12 @@ Object.defineProperty(exports, "OPT_TH", {
|
|
|
4779
5238
|
return OPT_TH;
|
|
4780
5239
|
}
|
|
4781
5240
|
});
|
|
5241
|
+
Object.defineProperty(exports, "OPT_UI_DEFAULT_LOCALE", {
|
|
5242
|
+
enumerable: true,
|
|
5243
|
+
get: function() {
|
|
5244
|
+
return OPT_UI_DEFAULT_LOCALE;
|
|
5245
|
+
}
|
|
5246
|
+
});
|
|
4782
5247
|
Object.defineProperty(exports, "Pagination", {
|
|
4783
5248
|
enumerable: true,
|
|
4784
5249
|
get: function() {
|
|
@@ -4845,6 +5310,36 @@ Object.defineProperty(exports, "SelectTrigger", {
|
|
|
4845
5310
|
return SelectTrigger;
|
|
4846
5311
|
}
|
|
4847
5312
|
});
|
|
5313
|
+
Object.defineProperty(exports, "Skeleton", {
|
|
5314
|
+
enumerable: true,
|
|
5315
|
+
get: function() {
|
|
5316
|
+
return Skeleton;
|
|
5317
|
+
}
|
|
5318
|
+
});
|
|
5319
|
+
Object.defineProperty(exports, "SkeletonAvatar", {
|
|
5320
|
+
enumerable: true,
|
|
5321
|
+
get: function() {
|
|
5322
|
+
return SkeletonAvatar;
|
|
5323
|
+
}
|
|
5324
|
+
});
|
|
5325
|
+
Object.defineProperty(exports, "SkeletonCard", {
|
|
5326
|
+
enumerable: true,
|
|
5327
|
+
get: function() {
|
|
5328
|
+
return SkeletonCard;
|
|
5329
|
+
}
|
|
5330
|
+
});
|
|
5331
|
+
Object.defineProperty(exports, "SkeletonTable", {
|
|
5332
|
+
enumerable: true,
|
|
5333
|
+
get: function() {
|
|
5334
|
+
return SkeletonTable;
|
|
5335
|
+
}
|
|
5336
|
+
});
|
|
5337
|
+
Object.defineProperty(exports, "SkeletonText", {
|
|
5338
|
+
enumerable: true,
|
|
5339
|
+
get: function() {
|
|
5340
|
+
return SkeletonText;
|
|
5341
|
+
}
|
|
5342
|
+
});
|
|
4848
5343
|
Object.defineProperty(exports, "Slider", {
|
|
4849
5344
|
enumerable: true,
|
|
4850
5345
|
get: function() {
|
|
@@ -4899,6 +5394,12 @@ Object.defineProperty(exports, "TagInput", {
|
|
|
4899
5394
|
return TagInput;
|
|
4900
5395
|
}
|
|
4901
5396
|
});
|
|
5397
|
+
Object.defineProperty(exports, "TextTruncate", {
|
|
5398
|
+
enumerable: true,
|
|
5399
|
+
get: function() {
|
|
5400
|
+
return TextTruncate;
|
|
5401
|
+
}
|
|
5402
|
+
});
|
|
4902
5403
|
Object.defineProperty(exports, "ToolbarButton", {
|
|
4903
5404
|
enumerable: true,
|
|
4904
5405
|
get: function() {
|
|
@@ -4965,6 +5466,30 @@ Object.defineProperty(exports, "cn", {
|
|
|
4965
5466
|
return cn;
|
|
4966
5467
|
}
|
|
4967
5468
|
});
|
|
5469
|
+
Object.defineProperty(exports, "formatOptDateTime", {
|
|
5470
|
+
enumerable: true,
|
|
5471
|
+
get: function() {
|
|
5472
|
+
return formatOptDateTime;
|
|
5473
|
+
}
|
|
5474
|
+
});
|
|
5475
|
+
Object.defineProperty(exports, "formatOptNumber", {
|
|
5476
|
+
enumerable: true,
|
|
5477
|
+
get: function() {
|
|
5478
|
+
return formatOptNumber;
|
|
5479
|
+
}
|
|
5480
|
+
});
|
|
5481
|
+
Object.defineProperty(exports, "formatOptShortDate", {
|
|
5482
|
+
enumerable: true,
|
|
5483
|
+
get: function() {
|
|
5484
|
+
return formatOptShortDate;
|
|
5485
|
+
}
|
|
5486
|
+
});
|
|
5487
|
+
Object.defineProperty(exports, "formatOptTime", {
|
|
5488
|
+
enumerable: true,
|
|
5489
|
+
get: function() {
|
|
5490
|
+
return formatOptTime;
|
|
5491
|
+
}
|
|
5492
|
+
});
|
|
4968
5493
|
Object.defineProperty(exports, "getDescribedBy", {
|
|
4969
5494
|
enumerable: true,
|
|
4970
5495
|
get: function() {
|