@olundot/ui 0.16.0 → 0.17.1

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/dist/index.js CHANGED
@@ -213,12 +213,13 @@ var buttonVariants = cva(
213
213
  },
214
214
  size: {
215
215
  // Cross-control height map: Button sm/md/lg match field controls sm/md/lg (30/32/36px).
216
- xs: "h-[28px] gap-[6px] px-pad-compact-x py-pad-compact-y text-[12px] [&_svg]:size-[14px] [&_svg]:stroke-[1.75]",
217
- sm: "h-[30px] gap-[6px] px-pad-compact-x py-pad-compact-y text-body-sm [&_svg]:size-[16px]",
218
- md: "h-[32px] gap-[6px] px-pad-default-x py-pad-default-y text-body-sm [&_svg]:size-[16px]",
219
- lg: "h-[36px] gap-[var(--space-2)] px-pad-default-x py-pad-default-y text-[15px] [&_svg]:size-[18px]",
216
+ // Height comes from h-* alone — no vertical padding (py-12 inside h-32 left a 6px content box).
217
+ xs: "h-[28px] gap-[6px] px-pad-compact-x text-[12px] [&_svg]:size-[14px] [&_svg]:stroke-[1.75]",
218
+ sm: "h-[30px] gap-[6px] px-pad-compact-x text-body-sm [&_svg]:size-[16px]",
219
+ md: "h-[32px] gap-[6px] px-pad-default-x text-body-sm [&_svg]:size-[16px]",
220
+ lg: "h-[36px] gap-[var(--space-2)] px-pad-default-x text-[15px] [&_svg]:size-[18px]",
220
221
  xl: "h-[40px] gap-[var(--space-2)] px-5 text-[15px] [&_svg]:size-[18px]",
221
- default: "h-[32px] gap-[6px] px-pad-default-x py-pad-default-y text-body-sm [&_svg]:size-[16px]",
222
+ default: "h-[32px] gap-[6px] px-pad-default-x text-body-sm [&_svg]:size-[16px]",
222
223
  icon: "size-[36px] p-0 [&_svg]:size-[18px]",
223
224
  "icon-xs": "size-[32px] p-0 [&_svg]:size-[14px] [&_svg]:stroke-[1.75]"
224
225
  }
@@ -403,13 +404,13 @@ var selectSurfaceClassNames = {
403
404
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
404
405
  var inputVariants = cva2(
405
406
  [
406
- "flex rounded-[var(--radius-sm)] border",
407
+ "flex rounded-[var(--radius-md)] border",
407
408
  "border-[var(--border)] text-foreground",
408
409
  "placeholder:text-text-disabled",
409
410
  "transition-[border-color,box-shadow] motion-reduce:transition-none",
410
411
  "focus:outline-none focus:border-[var(--focus-ring)] focus:shadow-[0_0_0_1px_var(--focus-ring)]",
411
412
  "disabled:cursor-not-allowed disabled:border-[var(--border-subtle)] disabled:bg-[var(--action-disabled-bg)] disabled:text-text-disabled",
412
- "read-only:border-[var(--border-subtle)] read-only:bg-[var(--bg-raised)] read-only:focus:border-[var(--border-subtle)] read-only:focus:shadow-none",
413
+ "read-only:border-[var(--border-subtle)] read-only:bg-[var(--bg-inset)] read-only:focus:border-[var(--border-subtle)] read-only:focus:shadow-none",
413
414
  "file:border-0 file:bg-transparent file:text-sm file:font-medium",
414
415
  "data-[state=error]:border-[var(--error)] data-[state=error]:focus:shadow-[0_0_0_1px_var(--error)]",
415
416
  "data-[state=success]:border-[var(--success)] data-[state=success]:focus:shadow-[0_0_0_1px_var(--success)]",
@@ -1125,14 +1126,14 @@ import { AlertCircle as AlertCircle2, CheckCircle2 as CheckCircle22 } from "luci
1125
1126
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1126
1127
  var textareaVariants = cva4(
1127
1128
  [
1128
- "flex resize-y rounded-[var(--radius-sm)] border",
1129
+ "flex resize-y rounded-[var(--radius-md)] border",
1129
1130
  "border-[var(--border)] text-foreground",
1130
1131
  "px-pad-default-x py-pad-default-y text-sm leading-normal",
1131
1132
  "placeholder:text-text-disabled",
1132
1133
  "transition-[border-color,box-shadow] motion-reduce:transition-none",
1133
1134
  "focus:outline-none focus:border-[var(--focus-ring)] focus:shadow-[0_0_0_1px_var(--focus-ring)]",
1134
1135
  "disabled:cursor-not-allowed disabled:border-[var(--border-subtle)] disabled:bg-[var(--action-disabled-bg)] disabled:text-text-disabled",
1135
- "read-only:cursor-default read-only:border-[var(--border-subtle)] read-only:bg-[var(--bg-raised)] read-only:text-text-secondary read-only:focus:border-[var(--border-subtle)] read-only:focus:shadow-none",
1136
+ "read-only:cursor-default read-only:border-[var(--border-subtle)] read-only:bg-[var(--bg-inset)] read-only:text-text-secondary read-only:focus:border-[var(--border-subtle)] read-only:focus:shadow-none",
1136
1137
  "data-[state=error]:border-[var(--error)] data-[state=error]:focus:shadow-[0_0_0_1px_var(--error)]",
1137
1138
  "data-[state=success]:border-[var(--success)] data-[state=success]:focus:shadow-[0_0_0_1px_var(--success)]",
1138
1139
  "aria-[invalid=true]:border-[var(--error)] aria-[invalid=true]:focus:shadow-[0_0_0_1px_var(--error)]",
@@ -1358,7 +1359,7 @@ import { Select as SelectPrimitive } from "radix-ui";
1358
1359
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1359
1360
  var selectVariants = cva5(
1360
1361
  [
1361
- "group flex cursor-pointer items-center justify-between rounded-[var(--radius-sm)] border",
1362
+ "group flex cursor-pointer items-center justify-between rounded-[var(--radius-md)] border",
1362
1363
  "border-[var(--border)] text-foreground",
1363
1364
  "[border-color:var(--border)] [color:var(--text-primary)]",
1364
1365
  "transition-[border-color,box-shadow,background-color,color] motion-reduce:transition-none",
@@ -2940,7 +2941,7 @@ var tabsListVariants = cva8(
2940
2941
  variants: {
2941
2942
  variant: {
2942
2943
  line: "",
2943
- contained: "rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--bg-raised)] p-[var(--space-1)]"
2944
+ contained: "rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--bg-inset)] p-[var(--space-1)]"
2944
2945
  },
2945
2946
  orientation: {
2946
2947
  horizontal: "",
@@ -3012,7 +3013,7 @@ var tabsBadgeVariants = cva8(
3012
3013
  [
3013
3014
  "inline-flex h-[18px] min-w-[18px] items-center justify-center",
3014
3015
  "rounded-[var(--radius-full)] border border-[var(--border-subtle)]",
3015
- "bg-[var(--bg-raised)] px-[var(--space-1)]",
3016
+ "bg-[var(--bg-inset)] px-[var(--space-1)]",
3016
3017
  "font-[family-name:var(--font-display)] text-xs font-semibold leading-none tabular-nums",
3017
3018
  // R14 — Badge는 metadata, 추가 marker(filled wash)가 아니다. marker 종류는
3018
3019
  // trigger underline 하나(active-state doctrine). single-hue-family: active 시 인접
@@ -3818,7 +3819,7 @@ var chipVariants = cva9(
3818
3819
  {
3819
3820
  pressed: true,
3820
3821
  tone: "muted",
3821
- className: "border-[var(--border-strong)] bg-[var(--bg-raised)] text-text-secondary"
3822
+ className: "border-[var(--border-strong)] bg-[var(--bg-inset)] text-text-secondary"
3822
3823
  }
3823
3824
  ],
3824
3825
  defaultVariants: {
@@ -3838,12 +3839,12 @@ var chipCountVariants = cva9(
3838
3839
  {
3839
3840
  variants: {
3840
3841
  tone: {
3841
- neutral: "bg-[var(--bg-raised)] text-text-tertiary",
3842
+ neutral: "bg-[var(--bg-inset)] text-text-tertiary",
3842
3843
  accent: "bg-[var(--accent-pop)] text-[var(--accent-on-fill)]",
3843
3844
  success: "bg-[var(--success)] text-[var(--success-contrast)]",
3844
3845
  warning: "bg-[var(--warning)] text-[var(--warning-contrast)]",
3845
3846
  danger: "bg-[var(--error)] text-[var(--error-contrast)]",
3846
- muted: "bg-[var(--bg-raised)] text-text-tertiary"
3847
+ muted: "bg-[var(--bg-inset)] text-text-tertiary"
3847
3848
  },
3848
3849
  size: {
3849
3850
  sm: "h-[var(--space-4)] text-xs",
@@ -3970,13 +3971,16 @@ var iconButtonVariants = cva10(
3970
3971
  variant: {
3971
3972
  ghost: "border-transparent bg-transparent text-text-secondary hover:bg-[var(--action-hover)] hover:text-foreground",
3972
3973
  outline: "border-[var(--border-subtle)] bg-[var(--bg-surface)] text-text-secondary hover:border-[var(--border-strong)] hover:bg-[var(--action-hover)] hover:text-foreground",
3973
- subtle: "border-transparent bg-[var(--bg-raised)] text-text-secondary hover:bg-[var(--action-hover)] hover:text-foreground",
3974
+ subtle: "border-transparent bg-[var(--bg-inset)] text-text-secondary hover:bg-[var(--action-hover)] hover:text-foreground",
3974
3975
  danger: "border-transparent bg-[var(--error-bg)] text-[var(--error)] hover:bg-[var(--action-hover)]"
3975
3976
  },
3977
+ // Hit area = 44px on every size. The ::before is absolutely positioned, so its
3978
+ // inset is measured from the padding box — inside the 1px border every variant
3979
+ // carries (transparent or not). Inset = (44 - (size - 2 × 1px border)) / 2.
3976
3980
  size: {
3977
- sm: "size-[28px] before:-inset-[8px]",
3978
- md: "size-[32px] before:-inset-[6px]",
3979
- lg: "size-[40px] before:-inset-[2px]"
3981
+ sm: "size-[28px] before:-inset-[9px]",
3982
+ md: "size-[32px] before:-inset-[7px]",
3983
+ lg: "size-[40px] before:-inset-[3px]"
3980
3984
  },
3981
3985
  pressed: {
3982
3986
  true: "",
@@ -5327,7 +5331,7 @@ function SkeletonRows({
5327
5331
  "div",
5328
5332
  {
5329
5333
  className: cn(
5330
- "h-[14px] animate-pulse rounded-[var(--radius-sm)] bg-[var(--bg-raised)]",
5334
+ "h-[14px] animate-pulse rounded-[var(--radius-sm)] bg-[var(--bg-inset)]",
5331
5335
  columnIndex === 0 ? "w-[72%]" : column.align === "right" ? "ml-auto w-[54%]" : "w-[62%]"
5332
5336
  )
5333
5337
  }
@@ -5353,9 +5357,9 @@ function MobileSkeletonCards({ count }) {
5353
5357
  "data-slot": "data-table-mobile-skeleton-card",
5354
5358
  className: "flex flex-col gap-[var(--space-2)] border-t border-[var(--border-subtle)] px-[var(--space-4)] py-[var(--space-4)]",
5355
5359
  children: [
5356
- /* @__PURE__ */ jsx31("div", { className: "h-[14px] w-[72%] animate-pulse rounded-[var(--radius-sm)] bg-[var(--bg-raised)]" }),
5357
- /* @__PURE__ */ jsx31("div", { className: "h-[12px] w-[56%] animate-pulse rounded-[var(--radius-sm)] bg-[var(--bg-raised)]" }),
5358
- /* @__PURE__ */ jsx31("div", { className: "h-[12px] w-[64%] animate-pulse rounded-[var(--radius-sm)] bg-[var(--bg-raised)]" })
5360
+ /* @__PURE__ */ jsx31("div", { className: "h-[14px] w-[72%] animate-pulse rounded-[var(--radius-sm)] bg-[var(--bg-inset)]" }),
5361
+ /* @__PURE__ */ jsx31("div", { className: "h-[12px] w-[56%] animate-pulse rounded-[var(--radius-sm)] bg-[var(--bg-inset)]" }),
5362
+ /* @__PURE__ */ jsx31("div", { className: "h-[12px] w-[64%] animate-pulse rounded-[var(--radius-sm)] bg-[var(--bg-inset)]" })
5359
5363
  ]
5360
5364
  },
5361
5365
  rowIndex
@@ -5363,6 +5367,329 @@ function MobileSkeletonCards({ count }) {
5363
5367
  }
5364
5368
  );
5365
5369
  }
5370
+ var rowLinkHostClass = "relative [&_:is(a,button,input,select,textarea,label,[role=button],[role=checkbox]):not([data-slot=data-table-row-link])]:relative [&_:is(a,button,input,select,textarea,label,[role=button],[role=checkbox]):not([data-slot=data-table-row-link])]:z-[1]";
5371
+ var rowLinkClass = cn(
5372
+ "text-inherit no-underline outline-none after:absolute after:inset-0 after:content-['']",
5373
+ "focus-visible:after:ring-2 focus-visible:after:ring-inset focus-visible:after:ring-[var(--focus-ring)]"
5374
+ );
5375
+ function useActivationHandlers({ row, href, activatable, onActivate }) {
5376
+ return {
5377
+ onClick: (event) => {
5378
+ if (!activatable || isInteractiveTarget(event.target)) {
5379
+ return;
5380
+ }
5381
+ onActivate(row, href, event);
5382
+ },
5383
+ onKeyDown: (event) => {
5384
+ if (!activatable || isInteractiveTarget(event.target)) {
5385
+ return;
5386
+ }
5387
+ if (event.key === "Enter" || event.key === " ") {
5388
+ event.preventDefault();
5389
+ onActivate(row, href, event);
5390
+ }
5391
+ }
5392
+ };
5393
+ }
5394
+ function DataTableRowView({
5395
+ row,
5396
+ rowId,
5397
+ columns,
5398
+ selectable,
5399
+ selected,
5400
+ visuallySelected,
5401
+ href,
5402
+ activatable,
5403
+ LinkComponent,
5404
+ onToggle,
5405
+ onActivate,
5406
+ onRowContextMenu,
5407
+ gridTemplateColumns,
5408
+ ariaRowIndex
5409
+ }) {
5410
+ const linked = Boolean(LinkComponent && href);
5411
+ const handlers = useActivationHandlers({ row, href, activatable, onActivate });
5412
+ return /* @__PURE__ */ jsxs30(
5413
+ "div",
5414
+ {
5415
+ "data-slot": "data-table-row",
5416
+ "data-row-id": rowId,
5417
+ "data-href": href,
5418
+ role: "row",
5419
+ "aria-rowindex": ariaRowIndex,
5420
+ tabIndex: activatable ? 0 : void 0,
5421
+ className: cn(
5422
+ dataTableRowVariants({ interactive: activatable || linked, selected: visuallySelected }),
5423
+ linked ? rowLinkHostClass : null
5424
+ ),
5425
+ style: { gridTemplateColumns },
5426
+ onContextMenu: (event) => onRowContextMenu?.(rowId, event),
5427
+ onClick: handlers.onClick,
5428
+ onKeyDown: handlers.onKeyDown,
5429
+ children: [
5430
+ selectable ? /* @__PURE__ */ jsx31("div", { role: "cell", className: "px-[var(--space-4)]", children: /* @__PURE__ */ jsx31(
5431
+ Checkbox,
5432
+ {
5433
+ "aria-label": `${String(row[columns[0]?.key] ?? rowId)} \uD589 \uC120\uD0DD`,
5434
+ checked: selected,
5435
+ onCheckedChange: (nextChecked) => onToggle(rowId, nextChecked === true),
5436
+ children: /* @__PURE__ */ jsxs30("span", { className: "sr-only", children: [
5437
+ rowId,
5438
+ " \uD589 \uC120\uD0DD"
5439
+ ] })
5440
+ }
5441
+ ) }) : null,
5442
+ columns.map((column, columnIndex) => {
5443
+ const align = column.align ?? "left";
5444
+ const content = getRenderedCell(row, column);
5445
+ return /* @__PURE__ */ jsx31(
5446
+ "div",
5447
+ {
5448
+ "data-slot": "data-table-cell",
5449
+ role: "cell",
5450
+ className: cn(
5451
+ "flex min-w-0 items-center px-[var(--space-4)] py-[var(--space-2)]",
5452
+ alignmentClass[align],
5453
+ column.tabular ? "font-[family-name:var(--font-display)] tabular-nums" : null,
5454
+ align === "right" ? "text-text-tertiary" : null
5455
+ ),
5456
+ children: /* @__PURE__ */ jsx31("span", { className: "min-w-0 truncate", children: linked && columnIndex === 0 && LinkComponent ? /* @__PURE__ */ jsx31(LinkComponent, { href, "data-slot": "data-table-row-link", className: rowLinkClass, children: content }) : content })
5457
+ },
5458
+ column.key
5459
+ );
5460
+ })
5461
+ ]
5462
+ }
5463
+ );
5464
+ }
5465
+ function DataTableMobileCardView({
5466
+ row,
5467
+ rowId,
5468
+ columns,
5469
+ selectable,
5470
+ selected,
5471
+ visuallySelected,
5472
+ href,
5473
+ activatable,
5474
+ LinkComponent,
5475
+ onToggle,
5476
+ onActivate,
5477
+ onRowContextMenu,
5478
+ ariaPosInSet,
5479
+ ariaSetSize
5480
+ }) {
5481
+ const linked = Boolean(LinkComponent && href);
5482
+ const handlers = useActivationHandlers({ row, href, activatable, onActivate });
5483
+ const primaryColumn = columns[0];
5484
+ const secondaryColumns = columns.slice(1);
5485
+ const primaryText = getAccessibleCellText(row, primaryColumn, rowId);
5486
+ const primaryLabel = primaryColumn ? getHeaderLabel(primaryColumn) : "\uD589";
5487
+ const primaryContent = primaryColumn ? getRenderedCell(row, primaryColumn) : primaryText;
5488
+ return /* @__PURE__ */ jsxs30(
5489
+ "div",
5490
+ {
5491
+ "data-slot": "data-table-mobile-card",
5492
+ "data-row-id": rowId,
5493
+ "data-href": href,
5494
+ role: "listitem",
5495
+ "aria-posinset": ariaPosInSet,
5496
+ "aria-setsize": ariaSetSize,
5497
+ tabIndex: activatable ? 0 : void 0,
5498
+ "aria-label": `${primaryLabel}: ${primaryText}`,
5499
+ className: cn(
5500
+ "flex min-w-0 flex-col gap-[var(--space-3)] border-t border-[var(--border-subtle)] px-[var(--space-4)] py-[var(--space-4)]",
5501
+ "text-body-sm leading-compact text-foreground transition-colors motion-reduce:transition-none",
5502
+ activatable || linked ? "cursor-pointer hover:bg-[var(--action-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--focus-ring)]" : null,
5503
+ visuallySelected ? "bg-[var(--action-selected)]" : null,
5504
+ linked ? rowLinkHostClass : null
5505
+ ),
5506
+ onContextMenu: (event) => onRowContextMenu?.(rowId, event),
5507
+ onClick: handlers.onClick,
5508
+ onKeyDown: handlers.onKeyDown,
5509
+ children: [
5510
+ /* @__PURE__ */ jsxs30("div", { className: "flex min-w-0 items-start gap-[var(--space-3)]", children: [
5511
+ selectable ? /* @__PURE__ */ jsx31("div", { "data-slot": "data-table-mobile-card-select", className: "shrink-0 pt-[2px]", children: /* @__PURE__ */ jsx31(
5512
+ Checkbox,
5513
+ {
5514
+ "aria-label": `${primaryText} \uBAA8\uBC14\uC77C \uCE74\uB4DC \uC120\uD0DD`,
5515
+ checked: selected,
5516
+ onCheckedChange: (nextChecked) => onToggle(rowId, nextChecked === true),
5517
+ children: /* @__PURE__ */ jsxs30("span", { className: "sr-only", children: [
5518
+ rowId,
5519
+ " \uBAA8\uBC14\uC77C \uCE74\uB4DC \uC120\uD0DD"
5520
+ ] })
5521
+ }
5522
+ ) }) : null,
5523
+ /* @__PURE__ */ jsx31(
5524
+ "div",
5525
+ {
5526
+ "data-slot": "data-table-mobile-card-primary",
5527
+ className: "min-w-0 flex-1 font-medium text-foreground",
5528
+ children: linked && LinkComponent ? /* @__PURE__ */ jsx31(LinkComponent, { href, "data-slot": "data-table-row-link", className: rowLinkClass, children: primaryContent }) : primaryContent
5529
+ }
5530
+ )
5531
+ ] }),
5532
+ secondaryColumns.length > 0 ? /* @__PURE__ */ jsx31("dl", { className: "grid min-w-0 gap-[var(--space-2)]", children: secondaryColumns.map((column) => {
5533
+ const align = column.align ?? "left";
5534
+ return /* @__PURE__ */ jsxs30(
5535
+ "div",
5536
+ {
5537
+ "data-slot": "data-table-mobile-card-field",
5538
+ className: "grid min-w-0 grid-cols-[minmax(0,0.42fr)_minmax(0,1fr)] gap-[var(--space-2)] text-[12px] leading-snug",
5539
+ children: [
5540
+ /* @__PURE__ */ jsx31("dt", { className: "min-w-0 text-text-tertiary", children: /* @__PURE__ */ jsx31("span", { className: "truncate", children: column.header }) }),
5541
+ /* @__PURE__ */ jsx31(
5542
+ "dd",
5543
+ {
5544
+ className: cn(
5545
+ "min-w-0 text-text-secondary",
5546
+ alignmentClass[align],
5547
+ column.tabular ? "font-[family-name:var(--font-display)] tabular-nums" : null
5548
+ ),
5549
+ children: /* @__PURE__ */ jsx31("span", { className: "min-w-0 truncate", children: getRenderedCell(row, column) })
5550
+ }
5551
+ )
5552
+ ]
5553
+ },
5554
+ column.key
5555
+ );
5556
+ }) }) : null
5557
+ ]
5558
+ }
5559
+ );
5560
+ }
5561
+ var MemoDataTableRow = React27.memo(DataTableRowView);
5562
+ var MemoDataTableMobileCard = React27.memo(DataTableMobileCardView);
5563
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? React27.useEffect : React27.useLayoutEffect;
5564
+ var VIRTUAL_DEFAULT_OVERSCAN = 8;
5565
+ var VIRTUAL_INITIAL_ROWS = 24;
5566
+ var VIRTUAL_FALLBACK_SIZE = { desktop: 48, mobile: 120 };
5567
+ function getScrollParent(node) {
5568
+ for (let el = node.parentElement; el && el !== document.body; el = el.parentElement) {
5569
+ const { overflowY } = window.getComputedStyle(el);
5570
+ if ((overflowY === "auto" || overflowY === "scroll") && el.scrollHeight > el.clientHeight) {
5571
+ return el;
5572
+ }
5573
+ }
5574
+ return null;
5575
+ }
5576
+ function useVirtualWindow({
5577
+ enabled,
5578
+ count,
5579
+ overscan,
5580
+ onRangeChange,
5581
+ desktopRef,
5582
+ headerRef,
5583
+ desktopListRef,
5584
+ mobileListRef
5585
+ }) {
5586
+ const [win, setWin] = React27.useState({
5587
+ layout: null,
5588
+ start: 0,
5589
+ end: VIRTUAL_INITIAL_ROWS,
5590
+ size: 0
5591
+ });
5592
+ const measuredSize = React27.useRef({ desktop: 0, mobile: 0 });
5593
+ const update = React27.useRef(() => void 0);
5594
+ const reported = React27.useRef("");
5595
+ const latest = React27.useRef({ count, layout: "desktop" });
5596
+ update.current = () => {
5597
+ if (!enabled) {
5598
+ return;
5599
+ }
5600
+ const desktop = desktopRef.current;
5601
+ const layout = desktop && window.getComputedStyle(desktop).display === "none" ? "mobile" : "desktop";
5602
+ const list = (layout === "desktop" ? desktopListRef : mobileListRef).current;
5603
+ const firstItem = list?.firstElementChild;
5604
+ const itemHeight = firstItem ? firstItem.getBoundingClientRect().height : 0;
5605
+ if (itemHeight > 0) {
5606
+ measuredSize.current[layout] = itemHeight;
5607
+ }
5608
+ const size = measuredSize.current[layout] || VIRTUAL_FALLBACK_SIZE[layout];
5609
+ latest.current = { count, layout };
5610
+ let start = 0;
5611
+ let end = Math.min(count, VIRTUAL_INITIAL_ROWS);
5612
+ if (list) {
5613
+ const top = list.getBoundingClientRect().top;
5614
+ const viewport = window.innerHeight || document.documentElement.clientHeight;
5615
+ start = Math.min(count, Math.max(0, Math.floor(-top / size) - overscan));
5616
+ end = Math.min(count, Math.max(start, Math.ceil((viewport - top) / size) + overscan));
5617
+ if (onRangeChange) {
5618
+ const container = getScrollParent(list);
5619
+ const bounds = container?.getBoundingClientRect();
5620
+ let visibleTop = Math.max(0, bounds ? bounds.top : 0);
5621
+ const visibleBottom = Math.min(viewport, bounds ? bounds.bottom : viewport);
5622
+ const header = headerRef.current;
5623
+ if (layout === "desktop" && header) {
5624
+ visibleTop = Math.max(visibleTop, header.getBoundingClientRect().bottom);
5625
+ }
5626
+ const first = Math.min(count, Math.max(0, Math.floor((visibleTop - top) / size)));
5627
+ const last = Math.min(count, Math.max(first, Math.ceil((visibleBottom - top) / size)));
5628
+ const key = `${first}:${last}:${count}`;
5629
+ if (reported.current !== key) {
5630
+ reported.current = key;
5631
+ onRangeChange({ start: first, end: last });
5632
+ }
5633
+ }
5634
+ }
5635
+ setWin(
5636
+ (prev) => prev.layout === layout && prev.start === start && prev.end === end && prev.size === size ? prev : { layout, start, end, size }
5637
+ );
5638
+ };
5639
+ useIsomorphicLayoutEffect(() => {
5640
+ update.current();
5641
+ });
5642
+ React27.useEffect(() => {
5643
+ if (!enabled) {
5644
+ return;
5645
+ }
5646
+ let frame = 0;
5647
+ const schedule = () => {
5648
+ if (frame) {
5649
+ return;
5650
+ }
5651
+ frame = window.requestAnimationFrame(() => {
5652
+ frame = 0;
5653
+ update.current();
5654
+ });
5655
+ };
5656
+ window.addEventListener("scroll", schedule, { capture: true, passive: true });
5657
+ window.addEventListener("resize", schedule);
5658
+ return () => {
5659
+ window.removeEventListener("scroll", schedule, { capture: true });
5660
+ window.removeEventListener("resize", schedule);
5661
+ window.cancelAnimationFrame(frame);
5662
+ };
5663
+ }, [enabled]);
5664
+ const scrollToIndex = React27.useCallback(
5665
+ (index) => {
5666
+ const { count: rowCount, layout } = latest.current;
5667
+ const list = (layout === "desktop" ? desktopListRef : mobileListRef).current;
5668
+ if (!list || rowCount === 0) {
5669
+ return;
5670
+ }
5671
+ const row = Math.min(rowCount - 1, Math.max(0, Math.floor(index)));
5672
+ const size = measuredSize.current[layout] || VIRTUAL_FALLBACK_SIZE[layout];
5673
+ const container = getScrollParent(list);
5674
+ let target = container ? container.getBoundingClientRect().top : 0;
5675
+ const header = headerRef.current;
5676
+ if (layout === "desktop" && header) {
5677
+ const { position, top } = window.getComputedStyle(header);
5678
+ if (position === "sticky") {
5679
+ target += (Number.parseFloat(top) || 0) + header.getBoundingClientRect().height;
5680
+ }
5681
+ }
5682
+ const delta = list.getBoundingClientRect().top + row * size - target;
5683
+ if (container) {
5684
+ container.scrollTop += delta;
5685
+ } else {
5686
+ window.scrollBy(0, delta);
5687
+ }
5688
+ },
5689
+ [desktopListRef, headerRef, mobileListRef]
5690
+ );
5691
+ return { win, scrollToIndex };
5692
+ }
5366
5693
  function DataTableInner({
5367
5694
  className,
5368
5695
  density = "standard",
@@ -5373,6 +5700,8 @@ function DataTableInner({
5373
5700
  selectable = false,
5374
5701
  selectedIds,
5375
5702
  onSelectionChange,
5703
+ selectionUnit = "\uAC1C",
5704
+ announceSelection = true,
5376
5705
  activeRowId,
5377
5706
  onRowContextMenu,
5378
5707
  sortBy,
@@ -5380,18 +5709,36 @@ function DataTableInner({
5380
5709
  loading = false,
5381
5710
  emptyState,
5382
5711
  rowHref,
5712
+ linkComponent,
5713
+ onRowClick,
5714
+ virtual,
5383
5715
  skeletonRowCount = 8,
5384
5716
  "aria-label": ariaLabel = "\uB370\uC774\uD130 \uD14C\uC774\uBE14",
5385
5717
  ...props
5386
5718
  }, ref) {
5719
+ const virtualOptions = virtual ? virtual === true ? {} : virtual : null;
5720
+ const isVirtual = virtualOptions !== null;
5721
+ const desktopRef = React27.useRef(null);
5722
+ const headerRef = React27.useRef(null);
5723
+ const desktopListRef = React27.useRef(null);
5724
+ const mobileListRef = React27.useRef(null);
5725
+ const { win: virtualWindow, scrollToIndex } = useVirtualWindow({
5726
+ enabled: isVirtual,
5727
+ count: data.length,
5728
+ overscan: virtualOptions?.overscan ?? VIRTUAL_DEFAULT_OVERSCAN,
5729
+ onRangeChange: virtualOptions?.onRangeChange,
5730
+ desktopRef,
5731
+ headerRef,
5732
+ desktopListRef,
5733
+ mobileListRef
5734
+ });
5735
+ React27.useImperativeHandle(virtualOptions?.handleRef, () => ({ scrollToIndex }), [scrollToIndex]);
5387
5736
  const resolvedSelectedIds = selectedIds ?? /* @__PURE__ */ new Set();
5388
5737
  const rowIds = data.map((row) => getRowId(row, keyField));
5389
5738
  const selectedCount = rowIds.filter((id) => resolvedSelectedIds.has(id)).length;
5390
5739
  const allVisibleRowsSelected = rowIds.length > 0 && selectedCount === rowIds.length;
5391
5740
  const partiallySelected = selectedCount > 0 && !allVisibleRowsSelected;
5392
5741
  const gridTemplateColumns = getGridTemplate(columns, selectable);
5393
- const primaryColumn = columns[0];
5394
- const secondaryColumns = columns.slice(1);
5395
5742
  const emitSelection = (nextIds) => {
5396
5743
  onSelectionChange?.(nextIds);
5397
5744
  };
@@ -5413,21 +5760,90 @@ function DataTableInner({
5413
5760
  }
5414
5761
  emitSelection(nextIds);
5415
5762
  };
5763
+ const activateRow = (row, href, event) => {
5764
+ if (onRowClick) {
5765
+ onRowClick(row, event);
5766
+ } else if (href) {
5767
+ window.location.assign(href);
5768
+ }
5769
+ };
5770
+ const latestHandlers = React27.useRef({ toggleRow, activateRow, onRowContextMenu });
5771
+ useIsomorphicLayoutEffect(() => {
5772
+ latestHandlers.current = { toggleRow, activateRow, onRowContextMenu };
5773
+ });
5774
+ const stableToggleRow = React27.useCallback(
5775
+ (id, checked) => latestHandlers.current.toggleRow(id, checked),
5776
+ []
5777
+ );
5778
+ const stableActivateRow = React27.useCallback(
5779
+ (row, href, event) => latestHandlers.current.activateRow(row, href, event),
5780
+ []
5781
+ );
5782
+ const stableRowContextMenu = React27.useCallback(
5783
+ (rowId, event) => latestHandlers.current.onRowContextMenu?.(rowId, event),
5784
+ []
5785
+ );
5786
+ const getItemProps = (row) => {
5787
+ const rowId = getRowId(row, keyField);
5788
+ const selected = resolvedSelectedIds.has(rowId);
5789
+ const href = rowHref?.(row);
5790
+ const linked = Boolean(linkComponent && href);
5791
+ return {
5792
+ row,
5793
+ rowId,
5794
+ columns,
5795
+ selectable,
5796
+ selected,
5797
+ visuallySelected: selected || rowId === activeRowId,
5798
+ href,
5799
+ activatable: !linked && Boolean(href || onRowClick),
5800
+ LinkComponent: linkComponent,
5801
+ onToggle: isVirtual ? stableToggleRow : toggleRow,
5802
+ onActivate: isVirtual ? stableActivateRow : activateRow,
5803
+ onRowContextMenu: isVirtual ? stableRowContextMenu : onRowContextMenu
5804
+ };
5805
+ };
5806
+ const sliceFor = (layout) => {
5807
+ if (!isVirtual) {
5808
+ return { rows: data, start: 0, style: void 0 };
5809
+ }
5810
+ if (virtualWindow.layout !== null && virtualWindow.layout !== layout) {
5811
+ return { rows: [], start: 0, style: void 0 };
5812
+ }
5813
+ const size = virtualWindow.size || VIRTUAL_FALLBACK_SIZE[layout];
5814
+ const end = Math.min(data.length, virtualWindow.end);
5815
+ const start = Math.min(virtualWindow.start, end);
5816
+ return {
5817
+ rows: data.slice(start, end),
5818
+ start,
5819
+ style: { paddingTop: start * size, paddingBottom: (data.length - end) * size }
5820
+ };
5821
+ };
5822
+ const desktopSlice = sliceFor("desktop");
5823
+ const mobileSlice = sliceFor("mobile");
5824
+ const RowComponent = isVirtual ? MemoDataTableRow : DataTableRowView;
5825
+ const CardComponent = isVirtual ? MemoDataTableMobileCard : DataTableMobileCardView;
5416
5826
  return /* @__PURE__ */ jsxs30(
5417
5827
  "div",
5418
5828
  {
5419
5829
  ref,
5420
5830
  "data-slot": "data-table",
5421
- className: cn(dataTableVariants({ density, surface, className })),
5831
+ className: cn(
5832
+ dataTableVariants({ density, surface }),
5833
+ // clip, not hidden: keeps the rounded corners without becoming the sticky
5834
+ // header's scroll container.
5835
+ isVirtual && "overflow-clip",
5836
+ className
5837
+ ),
5422
5838
  ...props,
5423
5839
  children: [
5424
- selectable ? /* @__PURE__ */ jsx31(
5840
+ selectable && announceSelection ? /* @__PURE__ */ jsx31(
5425
5841
  "div",
5426
5842
  {
5427
5843
  "data-slot": "data-table-selection-status",
5428
5844
  "aria-live": "polite",
5429
5845
  className: "sr-only",
5430
- children: selectedCount > 0 ? `${selectedCount.toLocaleString("ko-KR")}\uAC1C \uC120\uD0DD\uB428` : ""
5846
+ children: selectedCount > 0 ? `${selectedCount.toLocaleString("ko-KR")}${selectionUnit} \uC120\uD0DD\uB428` : ""
5431
5847
  }
5432
5848
  ) : null,
5433
5849
  loading && data.length > 0 ? /* @__PURE__ */ jsx31(
@@ -5439,149 +5855,109 @@ function DataTableInner({
5439
5855
  children: /* @__PURE__ */ jsx31("div", { className: "h-full w-1/3 animate-pulse bg-[var(--primary)]" })
5440
5856
  }
5441
5857
  ) : null,
5442
- /* @__PURE__ */ jsx31("div", { "data-slot": "data-table-desktop", className: "max-md:hidden md:block", children: /* @__PURE__ */ jsxs30("div", { role: "table", "aria-label": ariaLabel, children: [
5443
- /* @__PURE__ */ jsxs30(
5444
- "div",
5445
- {
5446
- "data-slot": "data-table-header",
5447
- role: "row",
5448
- className: "grid min-h-[40px] items-center bg-[var(--bg-raised)] text-[12px] font-semibold uppercase leading-snug text-text-tertiary",
5449
- style: { gridTemplateColumns },
5450
- children: [
5451
- selectable ? /* @__PURE__ */ jsx31("div", { role: "columnheader", className: "px-[var(--space-4)]", children: /* @__PURE__ */ jsx31(
5452
- Checkbox,
5453
- {
5454
- "aria-label": "\uC804\uCCB4 \uD589 \uC120\uD0DD",
5455
- checked: partiallySelected ? "indeterminate" : allVisibleRowsSelected,
5456
- onCheckedChange: (nextChecked) => toggleAllRows(nextChecked === true),
5457
- children: /* @__PURE__ */ jsx31("span", { className: "sr-only", children: "\uC804\uCCB4 \uD589 \uC120\uD0DD" })
5458
- }
5459
- ) }) : null,
5460
- columns.map((column) => {
5461
- const align = column.align ?? "left";
5462
- const nextSort = getNextSort(sortBy, column.key);
5463
- const ariaSort = sortBy?.key === column.key ? sortBy.dir === "asc" ? "ascending" : "descending" : void 0;
5464
- return /* @__PURE__ */ jsx31(
5465
- "div",
5466
- {
5467
- role: "columnheader",
5468
- "aria-sort": ariaSort,
5469
- className: cn(
5470
- "flex min-w-0 items-center gap-[var(--space-1)] px-[var(--space-4)] py-[var(--space-2)]",
5471
- alignmentClass[align]
5472
- ),
5473
- children: column.sortable ? /* @__PURE__ */ jsxs30(
5474
- "button",
5858
+ /* @__PURE__ */ jsx31("div", { ref: desktopRef, "data-slot": "data-table-desktop", className: "max-md:hidden md:block", children: /* @__PURE__ */ jsxs30(
5859
+ "div",
5860
+ {
5861
+ role: "table",
5862
+ "aria-label": ariaLabel,
5863
+ "aria-rowcount": isVirtual && data.length > 0 ? data.length + 1 : void 0,
5864
+ children: [
5865
+ /* @__PURE__ */ jsxs30(
5866
+ "div",
5867
+ {
5868
+ ref: headerRef,
5869
+ "data-slot": "data-table-header",
5870
+ role: "row",
5871
+ className: cn(
5872
+ "grid min-h-[40px] items-center bg-[var(--bg-raised)] text-[12px] font-semibold uppercase leading-snug text-text-tertiary",
5873
+ // Virtual lists are long: the header sticks inside the scroll container,
5874
+ // below an inline BulkActionBar band (40px, --z-sticky) rendered before
5875
+ // the table — band on top, header under it, rows beneath both.
5876
+ isVirtual && "sticky top-0 z-[2] [[data-slot=bulk-action-bar][data-placement=inline]:not([data-state=idle])~[data-slot=data-table]_&]:top-[40px]"
5877
+ ),
5878
+ style: { gridTemplateColumns },
5879
+ children: [
5880
+ selectable ? /* @__PURE__ */ jsx31("div", { role: "columnheader", className: "px-[var(--space-4)]", children: /* @__PURE__ */ jsx31(
5881
+ Checkbox,
5882
+ {
5883
+ "aria-label": "\uC804\uCCB4 \uD589 \uC120\uD0DD",
5884
+ checked: partiallySelected ? "indeterminate" : allVisibleRowsSelected,
5885
+ onCheckedChange: (nextChecked) => toggleAllRows(nextChecked === true),
5886
+ children: /* @__PURE__ */ jsx31("span", { className: "sr-only", children: "\uC804\uCCB4 \uD589 \uC120\uD0DD" })
5887
+ }
5888
+ ) }) : null,
5889
+ columns.map((column) => {
5890
+ const align = column.align ?? "left";
5891
+ const nextSort = getNextSort(sortBy, column.key);
5892
+ const ariaSort = sortBy?.key === column.key ? sortBy.dir === "asc" ? "ascending" : "descending" : void 0;
5893
+ return /* @__PURE__ */ jsx31(
5894
+ "div",
5475
5895
  {
5476
- type: "button",
5896
+ role: "columnheader",
5897
+ "aria-sort": ariaSort,
5477
5898
  className: cn(
5478
- "inline-flex min-w-0 cursor-pointer items-center gap-[var(--space-1)] rounded-[var(--radius-xs)]",
5479
- "text-inherit transition-colors motion-reduce:transition-none",
5480
- "hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--focus-ring)]"
5899
+ "flex min-w-0 items-center gap-[var(--space-1)] px-[var(--space-4)] py-[var(--space-2)]",
5900
+ alignmentClass[align]
5481
5901
  ),
5482
- onClick: () => onSortChange?.(column.key, nextSort),
5483
- children: [
5484
- /* @__PURE__ */ jsx31("span", { className: "truncate", children: column.header }),
5485
- /* @__PURE__ */ jsx31(SortIcon, { sort: sortBy, columnKey: column.key })
5486
- ]
5487
- }
5488
- ) : /* @__PURE__ */ jsx31("span", { className: "truncate", children: column.header })
5489
- },
5490
- column.key
5491
- );
5492
- })
5493
- ]
5494
- }
5495
- ),
5496
- loading && data.length === 0 ? /* @__PURE__ */ jsx31(
5497
- SkeletonRows,
5498
- {
5499
- columns,
5500
- selectable,
5501
- gridTemplateColumns,
5502
- count: skeletonRowCount
5503
- }
5504
- ) : data.length === 0 ? (
5505
- /* role="table" 직계 자식은 row/rowgroup이어야 한다. data-slot과 테두리는
5506
- 그대로 유지해 시각 계약을 깨지 않는다(빈 상태에서도 표가 테두리를 그린다). */
5507
- /* @__PURE__ */ jsx31("div", { role: "rowgroup", children: /* @__PURE__ */ jsx31("div", { role: "row", children: /* @__PURE__ */ jsx31(
5508
- "div",
5509
- {
5510
- role: "cell",
5511
- "aria-colindex": 1,
5512
- "data-slot": "data-table-empty",
5513
- className: "border-t border-[var(--border-subtle)] p-[var(--space-4)]",
5514
- children: emptyState
5515
- }
5516
- ) }) })
5517
- ) : /* @__PURE__ */ jsx31("div", { role: "rowgroup", children: data.map((row) => {
5518
- const rowId = getRowId(row, keyField);
5519
- const selected = resolvedSelectedIds.has(rowId);
5520
- const visuallySelected = selected || rowId === activeRowId;
5521
- const href = rowHref?.(row);
5522
- return /* @__PURE__ */ jsxs30(
5523
- "div",
5524
- {
5525
- "data-slot": "data-table-row",
5526
- "data-row-id": rowId,
5527
- "data-href": href,
5528
- role: "row",
5529
- tabIndex: href ? 0 : void 0,
5530
- className: cn(dataTableRowVariants({ interactive: Boolean(href), selected: visuallySelected })),
5531
- style: { gridTemplateColumns },
5532
- onContextMenu: (event) => onRowContextMenu?.(rowId, event),
5533
- onClick: (event) => {
5534
- if (!href || isInteractiveTarget(event.target)) {
5535
- return;
5536
- }
5537
- window.location.assign(href);
5538
- },
5539
- onKeyDown: (event) => {
5540
- if (!href || isInteractiveTarget(event.target)) {
5541
- return;
5542
- }
5543
- if (event.key === "Enter" || event.key === " ") {
5544
- event.preventDefault();
5545
- window.location.assign(href);
5902
+ children: column.sortable ? /* @__PURE__ */ jsxs30(
5903
+ "button",
5904
+ {
5905
+ type: "button",
5906
+ className: cn(
5907
+ "inline-flex min-w-0 cursor-pointer items-center gap-[var(--space-1)] rounded-[var(--radius-xs)]",
5908
+ "text-inherit transition-colors motion-reduce:transition-none",
5909
+ "hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--focus-ring)]"
5910
+ ),
5911
+ onClick: () => onSortChange?.(column.key, nextSort),
5912
+ children: [
5913
+ /* @__PURE__ */ jsx31("span", { className: "truncate", children: column.header }),
5914
+ /* @__PURE__ */ jsx31(SortIcon, { sort: sortBy, columnKey: column.key })
5915
+ ]
5916
+ }
5917
+ ) : /* @__PURE__ */ jsx31("span", { className: "truncate", children: column.header })
5918
+ },
5919
+ column.key
5920
+ );
5921
+ })
5922
+ ]
5923
+ }
5924
+ ),
5925
+ loading && data.length === 0 ? /* @__PURE__ */ jsx31(
5926
+ SkeletonRows,
5927
+ {
5928
+ columns,
5929
+ selectable,
5930
+ gridTemplateColumns,
5931
+ count: skeletonRowCount
5932
+ }
5933
+ ) : data.length === 0 ? (
5934
+ /* role="table" 직계 자식은 row/rowgroup이어야 한다. data-slot과 테두리는
5935
+ 그대로 유지해 시각 계약을 깨지 않는다(빈 상태에서도 표가 테두리를 그린다). */
5936
+ /* @__PURE__ */ jsx31("div", { role: "rowgroup", children: /* @__PURE__ */ jsx31("div", { role: "row", children: /* @__PURE__ */ jsx31(
5937
+ "div",
5938
+ {
5939
+ role: "cell",
5940
+ "aria-colindex": 1,
5941
+ "data-slot": "data-table-empty",
5942
+ className: "border-t border-[var(--border-subtle)] p-[var(--space-4)]",
5943
+ children: emptyState
5546
5944
  }
5547
- },
5548
- children: [
5549
- selectable ? /* @__PURE__ */ jsx31("div", { role: "cell", className: "px-[var(--space-4)]", children: /* @__PURE__ */ jsx31(
5550
- Checkbox,
5551
- {
5552
- "aria-label": `${String(row[columns[0]?.key] ?? rowId)} \uD589 \uC120\uD0DD`,
5553
- checked: selected,
5554
- onCheckedChange: (nextChecked) => toggleRow(rowId, nextChecked === true),
5555
- children: /* @__PURE__ */ jsxs30("span", { className: "sr-only", children: [
5556
- rowId,
5557
- " \uD589 \uC120\uD0DD"
5558
- ] })
5559
- }
5560
- ) }) : null,
5561
- columns.map((column) => {
5562
- const align = column.align ?? "left";
5563
- return /* @__PURE__ */ jsx31(
5564
- "div",
5565
- {
5566
- "data-slot": "data-table-cell",
5567
- role: "cell",
5568
- className: cn(
5569
- "flex min-w-0 items-center px-[var(--space-4)] py-[var(--space-2)]",
5570
- alignmentClass[align],
5571
- column.tabular ? "font-[family-name:var(--font-display)] tabular-nums" : null,
5572
- align === "right" ? "text-text-tertiary" : null
5573
- ),
5574
- children: /* @__PURE__ */ jsx31("span", { className: "min-w-0 truncate", children: getRenderedCell(row, column) })
5575
- },
5576
- column.key
5577
- );
5578
- })
5579
- ]
5580
- },
5581
- rowId
5582
- );
5583
- }) })
5584
- ] }) }),
5945
+ ) }) })
5946
+ ) : /* @__PURE__ */ jsx31("div", { ref: desktopListRef, role: "rowgroup", style: desktopSlice.style, children: desktopSlice.rows.map((row, index) => {
5947
+ const itemProps = getItemProps(row);
5948
+ return /* @__PURE__ */ jsx31(
5949
+ RowComponent,
5950
+ {
5951
+ ...itemProps,
5952
+ gridTemplateColumns,
5953
+ ariaRowIndex: isVirtual ? desktopSlice.start + index + 2 : void 0
5954
+ },
5955
+ itemProps.rowId
5956
+ );
5957
+ }) })
5958
+ ]
5959
+ }
5960
+ ) }),
5585
5961
  /* @__PURE__ */ jsx31(
5586
5962
  MobileSortControls,
5587
5963
  {
@@ -5602,99 +5978,22 @@ function DataTableInner({
5602
5978
  ) : /* @__PURE__ */ jsx31(
5603
5979
  "div",
5604
5980
  {
5981
+ ref: mobileListRef,
5605
5982
  "data-slot": "data-table-mobile-list",
5606
5983
  role: "list",
5607
5984
  "aria-label": `${ariaLabel} \uBAA8\uBC14\uC77C \uCE74\uB4DC \uBAA9\uB85D`,
5608
5985
  className: "md:hidden",
5609
- children: data.map((row) => {
5610
- const rowId = getRowId(row, keyField);
5611
- const selected = resolvedSelectedIds.has(rowId);
5612
- const visuallySelected = selected || rowId === activeRowId;
5613
- const href = rowHref?.(row);
5614
- const primaryText = getAccessibleCellText(row, primaryColumn, rowId);
5615
- const primaryLabel = primaryColumn ? getHeaderLabel(primaryColumn) : "\uD589";
5616
- return /* @__PURE__ */ jsxs30(
5617
- "div",
5986
+ style: mobileSlice.style,
5987
+ children: mobileSlice.rows.map((row, index) => {
5988
+ const itemProps = getItemProps(row);
5989
+ return /* @__PURE__ */ jsx31(
5990
+ CardComponent,
5618
5991
  {
5619
- "data-slot": "data-table-mobile-card",
5620
- "data-row-id": rowId,
5621
- "data-href": href,
5622
- role: "listitem",
5623
- tabIndex: href ? 0 : void 0,
5624
- "aria-label": `${primaryLabel}: ${primaryText}`,
5625
- className: cn(
5626
- "flex min-w-0 flex-col gap-[var(--space-3)] border-t border-[var(--border-subtle)] px-[var(--space-4)] py-[var(--space-4)]",
5627
- "text-body-sm leading-compact text-foreground transition-colors motion-reduce:transition-none",
5628
- href ? "cursor-pointer hover:bg-[var(--action-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--focus-ring)]" : null,
5629
- visuallySelected ? "bg-[var(--action-selected)]" : null
5630
- ),
5631
- onContextMenu: (event) => onRowContextMenu?.(rowId, event),
5632
- onClick: (event) => {
5633
- if (!href || isInteractiveTarget(event.target)) {
5634
- return;
5635
- }
5636
- window.location.assign(href);
5637
- },
5638
- onKeyDown: (event) => {
5639
- if (!href || isInteractiveTarget(event.target)) {
5640
- return;
5641
- }
5642
- if (event.key === "Enter" || event.key === " ") {
5643
- event.preventDefault();
5644
- window.location.assign(href);
5645
- }
5646
- },
5647
- children: [
5648
- /* @__PURE__ */ jsxs30("div", { className: "flex min-w-0 items-start gap-[var(--space-3)]", children: [
5649
- selectable ? /* @__PURE__ */ jsx31("div", { "data-slot": "data-table-mobile-card-select", className: "shrink-0 pt-[2px]", children: /* @__PURE__ */ jsx31(
5650
- Checkbox,
5651
- {
5652
- "aria-label": `${primaryText} \uBAA8\uBC14\uC77C \uCE74\uB4DC \uC120\uD0DD`,
5653
- checked: selected,
5654
- onCheckedChange: (nextChecked) => toggleRow(rowId, nextChecked === true),
5655
- children: /* @__PURE__ */ jsxs30("span", { className: "sr-only", children: [
5656
- rowId,
5657
- " \uBAA8\uBC14\uC77C \uCE74\uB4DC \uC120\uD0DD"
5658
- ] })
5659
- }
5660
- ) }) : null,
5661
- /* @__PURE__ */ jsx31(
5662
- "div",
5663
- {
5664
- "data-slot": "data-table-mobile-card-primary",
5665
- className: "min-w-0 flex-1 font-medium text-foreground",
5666
- children: primaryColumn ? getRenderedCell(row, primaryColumn) : primaryText
5667
- }
5668
- )
5669
- ] }),
5670
- secondaryColumns.length > 0 ? /* @__PURE__ */ jsx31("dl", { className: "grid min-w-0 gap-[var(--space-2)]", children: secondaryColumns.map((column) => {
5671
- const align = column.align ?? "left";
5672
- return /* @__PURE__ */ jsxs30(
5673
- "div",
5674
- {
5675
- "data-slot": "data-table-mobile-card-field",
5676
- className: "grid min-w-0 grid-cols-[minmax(0,0.42fr)_minmax(0,1fr)] gap-[var(--space-2)] text-[12px] leading-snug",
5677
- children: [
5678
- /* @__PURE__ */ jsx31("dt", { className: "min-w-0 text-text-tertiary", children: /* @__PURE__ */ jsx31("span", { className: "truncate", children: column.header }) }),
5679
- /* @__PURE__ */ jsx31(
5680
- "dd",
5681
- {
5682
- className: cn(
5683
- "min-w-0 text-text-secondary",
5684
- alignmentClass[align],
5685
- column.tabular ? "font-[family-name:var(--font-display)] tabular-nums" : null
5686
- ),
5687
- children: /* @__PURE__ */ jsx31("span", { className: "min-w-0 truncate", children: getRenderedCell(row, column) })
5688
- }
5689
- )
5690
- ]
5691
- },
5692
- column.key
5693
- );
5694
- }) }) : null
5695
- ]
5992
+ ...itemProps,
5993
+ ariaPosInSet: isVirtual ? mobileSlice.start + index + 1 : void 0,
5994
+ ariaSetSize: isVirtual ? data.length : void 0
5696
5995
  },
5697
- rowId
5996
+ itemProps.rowId
5698
5997
  );
5699
5998
  })
5700
5999
  }
@@ -5988,6 +6287,7 @@ function ContextMenu({
5988
6287
  }
5989
6288
 
5990
6289
  // src/components/ui/bulk-action-bar.tsx
6290
+ import * as React28 from "react";
5991
6291
  import { X as X3 } from "lucide-react";
5992
6292
  import { jsx as jsx34, jsxs as jsxs33 } from "react/jsx-runtime";
5993
6293
  function ActionButton({ action }) {
@@ -6077,18 +6377,281 @@ function ClearButton({ onClear }) {
6077
6377
  }
6078
6378
  );
6079
6379
  }
6380
+ var INLINE_HEIGHT = 40;
6381
+ var HIT_AREA = 44;
6382
+ var HIT_INSET = 8;
6383
+ var floatingStyle = {
6384
+ position: "sticky",
6385
+ bottom: "calc(var(--space-4) + env(safe-area-inset-bottom))",
6386
+ zIndex: 1100,
6387
+ margin: "0 auto",
6388
+ display: "flex",
6389
+ minHeight: 32,
6390
+ width: "fit-content",
6391
+ maxWidth: "calc(100% - var(--space-8))",
6392
+ alignItems: "center",
6393
+ gap: "var(--space-2)",
6394
+ borderRadius: 9999,
6395
+ border: "1px solid var(--border)",
6396
+ background: "var(--bg-raised)",
6397
+ padding: "var(--space-1) var(--space-2)",
6398
+ boxShadow: "var(--shadow-8)"
6399
+ };
6400
+ var inlineStyle = {
6401
+ position: "sticky",
6402
+ top: 0,
6403
+ zIndex: "var(--z-sticky)",
6404
+ display: "flex",
6405
+ boxSizing: "border-box",
6406
+ height: INLINE_HEIGHT,
6407
+ alignItems: "center",
6408
+ gap: "var(--space-2)",
6409
+ borderRadius: "var(--radius-md)",
6410
+ border: "1px solid var(--border)",
6411
+ background: "var(--bg-surface)",
6412
+ padding: "0 var(--space-1) 0 var(--space-3)",
6413
+ boxShadow: "var(--shadow-2)",
6414
+ fontFamily: "var(--font-sans)",
6415
+ fontSize: 13,
6416
+ fontVariantNumeric: "tabular-nums"
6417
+ };
6418
+ var idleLineStyle = {
6419
+ display: "flex",
6420
+ boxSizing: "border-box",
6421
+ height: INLINE_HEIGHT,
6422
+ overflow: "hidden",
6423
+ alignItems: "center",
6424
+ columnGap: "var(--space-3)",
6425
+ whiteSpace: "nowrap",
6426
+ fontFamily: "var(--font-sans)",
6427
+ fontSize: 13,
6428
+ fontVariantNumeric: "tabular-nums",
6429
+ color: "var(--text-secondary)"
6430
+ };
6431
+ var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? React28.useEffect : React28.useLayoutEffect;
6432
+ function useCompactSummary(slot, enabled) {
6433
+ const [fullWidth, setFullWidth] = React28.useState(null);
6434
+ const measure = React28.useCallback(() => {
6435
+ if (!slot) return;
6436
+ setFullWidth((prev) => {
6437
+ if (prev === null) return slot.scrollWidth > slot.clientWidth ? slot.scrollWidth : null;
6438
+ return slot.clientWidth >= prev ? null : prev;
6439
+ });
6440
+ }, [slot]);
6441
+ useIsomorphicLayoutEffect2(() => {
6442
+ if (enabled) measure();
6443
+ });
6444
+ React28.useEffect(() => {
6445
+ if (!enabled || !slot || typeof ResizeObserver === "undefined") return;
6446
+ const observer = new ResizeObserver(measure);
6447
+ observer.observe(slot);
6448
+ return () => observer.disconnect();
6449
+ }, [enabled, slot, measure]);
6450
+ return enabled && fullWidth !== null;
6451
+ }
6080
6452
  function BulkActionBar({
6081
6453
  count,
6082
6454
  actions,
6083
6455
  onClear,
6084
6456
  emptyHint,
6457
+ placement = "floating",
6458
+ unit = "\uAC1C",
6459
+ summary,
6460
+ compactSummary,
6461
+ idleContent,
6462
+ trailing,
6085
6463
  className,
6086
6464
  style,
6087
6465
  ...props
6088
6466
  }) {
6467
+ const inline = placement === "inline";
6468
+ const trailingSlot = trailing !== void 0 && trailing !== null ? /* @__PURE__ */ jsx34(
6469
+ "div",
6470
+ {
6471
+ "data-slot": "bulk-action-bar-trailing",
6472
+ style: {
6473
+ display: "flex",
6474
+ flexShrink: 0,
6475
+ alignItems: "center",
6476
+ gap: "var(--space-2)"
6477
+ },
6478
+ children: trailing
6479
+ }
6480
+ ) : null;
6481
+ const [summaryElement, setSummaryElement] = React28.useState(null);
6482
+ const compact = useCompactSummary(
6483
+ summaryElement,
6484
+ inline && compactSummary !== void 0 && compactSummary !== null
6485
+ );
6486
+ if (count === 0 && idleContent !== void 0 && trailingSlot) {
6487
+ return /* @__PURE__ */ jsxs33(
6488
+ "div",
6489
+ {
6490
+ "data-slot": "bulk-action-bar",
6491
+ "data-placement": placement,
6492
+ "data-state": "idle",
6493
+ className,
6494
+ style: { ...idleLineStyle, overflow: "visible", ...style },
6495
+ ...props,
6496
+ children: [
6497
+ /* @__PURE__ */ jsx34(
6498
+ "div",
6499
+ {
6500
+ role: "status",
6501
+ "data-slot": "bulk-action-bar-idle",
6502
+ style: {
6503
+ display: "flex",
6504
+ flex: "1 1 auto",
6505
+ minWidth: 0,
6506
+ alignItems: "center",
6507
+ columnGap: "var(--space-3)",
6508
+ overflow: "hidden"
6509
+ },
6510
+ children: idleContent
6511
+ }
6512
+ ),
6513
+ trailingSlot
6514
+ ]
6515
+ }
6516
+ );
6517
+ }
6518
+ if (count === 0 && idleContent !== void 0) {
6519
+ return /* @__PURE__ */ jsx34(
6520
+ "div",
6521
+ {
6522
+ role: "status",
6523
+ "data-slot": "bulk-action-bar",
6524
+ "data-placement": placement,
6525
+ "data-state": "idle",
6526
+ className,
6527
+ style: { ...idleLineStyle, ...style },
6528
+ ...props,
6529
+ children: idleContent
6530
+ }
6531
+ );
6532
+ }
6089
6533
  if (count === 0 && emptyHint === void 0) {
6090
6534
  return null;
6091
6535
  }
6536
+ const countLabel = /* @__PURE__ */ jsx34(
6537
+ "span",
6538
+ {
6539
+ "data-slot": "bulk-action-bar-count",
6540
+ style: {
6541
+ paddingLeft: inline ? void 0 : "var(--space-2)",
6542
+ paddingRight: "var(--space-1)",
6543
+ fontFamily: "var(--font-sans)",
6544
+ fontSize: 13,
6545
+ fontWeight: inline ? 600 : 500,
6546
+ fontVariantNumeric: "tabular-nums",
6547
+ color: "var(--text-primary)",
6548
+ whiteSpace: "nowrap"
6549
+ },
6550
+ children: count > 0 ? `${count.toLocaleString("ko-KR")}${unit} \uC120\uD0DD\uB428` : emptyHint
6551
+ }
6552
+ );
6553
+ const summarySlot = summary !== void 0 && summary !== null ? /* @__PURE__ */ jsx34(
6554
+ "span",
6555
+ {
6556
+ ref: setSummaryElement,
6557
+ "data-slot": "bulk-action-bar-summary",
6558
+ style: {
6559
+ display: inline ? "flex" : "inline-flex",
6560
+ minWidth: 0,
6561
+ alignItems: "center",
6562
+ columnGap: "var(--space-3)",
6563
+ fontFamily: "var(--font-sans)",
6564
+ fontSize: 13,
6565
+ color: "var(--text-secondary)",
6566
+ ...inline ? (
6567
+ // inline-size containment: the summary never widens the band's parent. The
6568
+ // slot clips text, so it is as tall as an IconButton's hit area (overhanging
6569
+ // the 40px band) and has that inset as its left gutter: a compact IconButton
6570
+ // keeps its full 44px target. The right gutter stays 3px (focus ring) so
6571
+ // clipped text keeps clear of the controls.
6572
+ {
6573
+ flex: "1 1 0",
6574
+ alignSelf: "center",
6575
+ height: HIT_AREA,
6576
+ margin: `0 -3px 0 -${HIT_INSET}px`,
6577
+ padding: `0 3px 0 ${HIT_INSET}px`,
6578
+ overflow: "hidden",
6579
+ whiteSpace: "nowrap",
6580
+ contain: "inline-size"
6581
+ }
6582
+ ) : { flexWrap: "wrap", wordBreak: "keep-all" }
6583
+ },
6584
+ children: compact ? compactSummary : summary
6585
+ }
6586
+ ) : null;
6587
+ const actionGroup = /* @__PURE__ */ jsx34(
6588
+ "div",
6589
+ {
6590
+ "data-slot": "bulk-action-bar-actions",
6591
+ style: {
6592
+ display: "flex",
6593
+ minWidth: 0,
6594
+ flexWrap: inline ? "nowrap" : "wrap",
6595
+ alignItems: "center",
6596
+ justifyContent: "flex-end",
6597
+ gap: 2
6598
+ },
6599
+ children: actions.map((action) => /* @__PURE__ */ jsx34(ActionButton, { action }, action.key))
6600
+ }
6601
+ );
6602
+ if (inline) {
6603
+ return /* @__PURE__ */ jsxs33(
6604
+ "div",
6605
+ {
6606
+ role: "toolbar",
6607
+ "aria-label": "\uC120\uD0DD \uD56D\uBAA9 \uC77C\uAD04 \uC791\uC5C5",
6608
+ "data-slot": "bulk-action-bar",
6609
+ "data-placement": "inline",
6610
+ className,
6611
+ style: { ...inlineStyle, ...style },
6612
+ ...props,
6613
+ children: [
6614
+ /* @__PURE__ */ jsxs33(
6615
+ "div",
6616
+ {
6617
+ role: "status",
6618
+ "data-slot": "bulk-action-bar-state",
6619
+ style: {
6620
+ display: "flex",
6621
+ flex: "1 1 auto",
6622
+ minWidth: 0,
6623
+ alignItems: "center",
6624
+ columnGap: "var(--space-3)"
6625
+ },
6626
+ children: [
6627
+ countLabel,
6628
+ summarySlot
6629
+ ]
6630
+ }
6631
+ ),
6632
+ /* @__PURE__ */ jsxs33(
6633
+ "div",
6634
+ {
6635
+ "data-slot": "bulk-action-bar-controls",
6636
+ style: {
6637
+ display: "flex",
6638
+ marginLeft: "auto",
6639
+ alignItems: "center",
6640
+ gap: "var(--space-1)"
6641
+ },
6642
+ children: [
6643
+ count > 0 && actions.length > 0 ? /* @__PURE__ */ jsx34(Divider, {}) : null,
6644
+ actionGroup,
6645
+ /* @__PURE__ */ jsx34(Divider, {}),
6646
+ /* @__PURE__ */ jsx34(ClearButton, { onClear })
6647
+ ]
6648
+ }
6649
+ ),
6650
+ trailingSlot
6651
+ ]
6652
+ }
6653
+ );
6654
+ }
6092
6655
  return /* @__PURE__ */ jsxs33(
6093
6656
  "div",
6094
6657
  {
@@ -6101,68 +6664,23 @@ function BulkActionBar({
6101
6664
  "animate-[olun-bulk-action-bar-in_var(--dur-fast)_var(--ease-out)_both] motion-reduce:animate-none",
6102
6665
  className
6103
6666
  ),
6104
- style: {
6105
- position: "sticky",
6106
- bottom: "calc(var(--space-4) + env(safe-area-inset-bottom))",
6107
- zIndex: 1100,
6108
- margin: "0 auto",
6109
- display: "flex",
6110
- minHeight: 32,
6111
- width: "fit-content",
6112
- maxWidth: "calc(100% - var(--space-8))",
6113
- alignItems: "center",
6114
- gap: "var(--space-2)",
6115
- borderRadius: 9999,
6116
- border: "1px solid var(--border)",
6117
- background: "var(--bg-raised)",
6118
- padding: "var(--space-1) var(--space-2)",
6119
- boxShadow: "var(--shadow-8)",
6120
- ...style
6121
- },
6667
+ style: { ...floatingStyle, ...style },
6122
6668
  ...props,
6123
6669
  children: [
6124
- /* @__PURE__ */ jsx34(
6125
- "span",
6126
- {
6127
- "data-slot": "bulk-action-bar-count",
6128
- style: {
6129
- paddingLeft: "var(--space-2)",
6130
- paddingRight: "var(--space-1)",
6131
- fontFamily: "var(--font-sans)",
6132
- fontSize: 13,
6133
- fontWeight: 500,
6134
- fontVariantNumeric: "tabular-nums",
6135
- color: "var(--text-primary)",
6136
- whiteSpace: "nowrap"
6137
- },
6138
- children: count > 0 ? `${count.toLocaleString("ko-KR")}\uAC1C \uC120\uD0DD\uB428` : emptyHint
6139
- }
6140
- ),
6670
+ countLabel,
6671
+ summarySlot,
6141
6672
  count > 0 && actions.length > 0 ? /* @__PURE__ */ jsx34(Divider, {}) : null,
6142
- /* @__PURE__ */ jsx34(
6143
- "div",
6144
- {
6145
- "data-slot": "bulk-action-bar-actions",
6146
- style: {
6147
- display: "flex",
6148
- minWidth: 0,
6149
- flexWrap: "wrap",
6150
- alignItems: "center",
6151
- justifyContent: "flex-end",
6152
- gap: 2
6153
- },
6154
- children: actions.map((action) => /* @__PURE__ */ jsx34(ActionButton, { action }, action.key))
6155
- }
6156
- ),
6673
+ actionGroup,
6157
6674
  /* @__PURE__ */ jsx34(Divider, {}),
6158
- /* @__PURE__ */ jsx34(ClearButton, { onClear })
6675
+ /* @__PURE__ */ jsx34(ClearButton, { onClear }),
6676
+ trailingSlot
6159
6677
  ]
6160
6678
  }
6161
6679
  );
6162
6680
  }
6163
6681
 
6164
6682
  // src/components/ui/confirm-dialog.tsx
6165
- import * as React28 from "react";
6683
+ import * as React29 from "react";
6166
6684
  import { Fragment as Fragment8, jsx as jsx35, jsxs as jsxs34 } from "react/jsx-runtime";
6167
6685
  var confirmDialogContentClassName = cn(
6168
6686
  "fixed left-1/2 top-1/2 z-[var(--z-modal)]",
@@ -6179,6 +6697,7 @@ var confirmDialogTitleClassName = cn(
6179
6697
  var confirmDialogDescriptionClassName = cn(
6180
6698
  "break-keep text-[14px] leading-relaxed text-text-secondary"
6181
6699
  );
6700
+ var confirmDialogBodyClassName = "min-w-0 text-[14px] leading-relaxed text-text-secondary";
6182
6701
  var confirmDialogFooterClassName = "flex items-center justify-end gap-[var(--space-3)]";
6183
6702
  function ConfirmDialog({
6184
6703
  open,
@@ -6189,12 +6708,14 @@ function ConfirmDialog({
6189
6708
  confirmLabel,
6190
6709
  cancelLabel = "\uCDE8\uC18C",
6191
6710
  onConfirm,
6192
- loading = false
6711
+ loading = false,
6712
+ children,
6713
+ confirmDisabled = false
6193
6714
  }) {
6194
- const [pending, setPending] = React28.useState(false);
6195
- const busyRef = React28.useRef(false);
6715
+ const [pending, setPending] = React29.useState(false);
6716
+ const busyRef = React29.useRef(false);
6196
6717
  const isBusy = loading || pending;
6197
- const handleOpenChange = React28.useCallback(
6718
+ const handleOpenChange = React29.useCallback(
6198
6719
  (nextOpen) => {
6199
6720
  if (!nextOpen && isBusy) {
6200
6721
  return;
@@ -6203,8 +6724,8 @@ function ConfirmDialog({
6203
6724
  },
6204
6725
  [isBusy, onOpenChange]
6205
6726
  );
6206
- const handleConfirm = React28.useCallback(async () => {
6207
- if (busyRef.current || loading) {
6727
+ const handleConfirm = React29.useCallback(async () => {
6728
+ if (busyRef.current || loading || confirmDisabled) {
6208
6729
  return;
6209
6730
  }
6210
6731
  busyRef.current = true;
@@ -6216,7 +6737,7 @@ function ConfirmDialog({
6216
6737
  busyRef.current = false;
6217
6738
  setPending(false);
6218
6739
  }
6219
- }, [loading, onConfirm, onOpenChange]);
6740
+ }, [confirmDisabled, loading, onConfirm, onOpenChange]);
6220
6741
  const dialogBody = /* @__PURE__ */ jsxs34(Fragment8, { children: [
6221
6742
  /* @__PURE__ */ jsx35("div", { className: "flex items-start justify-between gap-[var(--space-4)]", children: /* @__PURE__ */ jsxs34("div", { className: "min-w-0 space-y-[var(--space-2)]", children: [
6222
6743
  /* @__PURE__ */ jsx35(
@@ -6236,6 +6757,7 @@ function ConfirmDialog({
6236
6757
  }
6237
6758
  ) : null
6238
6759
  ] }) }),
6760
+ children != null ? /* @__PURE__ */ jsx35("div", { "data-slot": "confirm-dialog-body", className: confirmDialogBodyClassName, children }) : null,
6239
6761
  /* @__PURE__ */ jsxs34(
6240
6762
  "div",
6241
6763
  {
@@ -6248,6 +6770,7 @@ function ConfirmDialog({
6248
6770
  {
6249
6771
  variant: tone === "destructive" ? "destructive" : "primary",
6250
6772
  loading: isBusy,
6773
+ disabled: confirmDisabled,
6251
6774
  onClick: handleConfirm,
6252
6775
  children: confirmLabel
6253
6776
  }
@@ -6301,7 +6824,7 @@ function Avatar({
6301
6824
  "data-size": size,
6302
6825
  className: cn(
6303
6826
  "group/avatar relative flex shrink-0 overflow-hidden rounded-full select-none",
6304
- "border border-[var(--border-subtle)] bg-[var(--bg-raised)] text-text-secondary",
6827
+ "border border-[var(--border-subtle)] bg-[var(--bg-inset)] text-text-secondary",
6305
6828
  "h-9 w-9",
6306
6829
  "data-[size=sm]:h-8 data-[size=sm]:w-8",
6307
6830
  "data-[size=lg]:h-11 data-[size=lg]:w-11",
@@ -6346,7 +6869,7 @@ function AvatarFallback({
6346
6869
  "data-slot": "avatar-fallback",
6347
6870
  className: cn(
6348
6871
  "flex h-full w-full items-center justify-center rounded-full",
6349
- "bg-[var(--bg-raised)] text-sm font-semibold text-text-secondary",
6872
+ "bg-[var(--bg-inset)] text-sm font-semibold text-text-secondary",
6350
6873
  "font-[family-name:var(--font-display)]",
6351
6874
  "group-data-[size=sm]/avatar:text-xs",
6352
6875
  className
@@ -6358,7 +6881,7 @@ function AvatarFallback({
6358
6881
  }
6359
6882
 
6360
6883
  // src/components/ui/badge.tsx
6361
- import * as React29 from "react";
6884
+ import * as React30 from "react";
6362
6885
  import { jsx as jsx38, jsxs as jsxs35 } from "react/jsx-runtime";
6363
6886
  var DOT = {
6364
6887
  error: "var(--semantic-error-fg)",
@@ -6368,7 +6891,7 @@ var DOT = {
6368
6891
  neutral: "var(--text-tertiary)",
6369
6892
  brand: "var(--accent-pop)"
6370
6893
  };
6371
- var Badge = React29.forwardRef(
6894
+ var Badge = React30.forwardRef(
6372
6895
  ({ tone = "neutral", className, children, ...props }, ref) => /* @__PURE__ */ jsxs35(
6373
6896
  "span",
6374
6897
  {
@@ -6400,7 +6923,7 @@ var Badge = React29.forwardRef(
6400
6923
  Badge.displayName = "Badge";
6401
6924
 
6402
6925
  // src/components/ui/two-tier-badge.tsx
6403
- import * as React30 from "react";
6926
+ import * as React31 from "react";
6404
6927
  import { jsx as jsx39, jsxs as jsxs36 } from "react/jsx-runtime";
6405
6928
  var FG = {
6406
6929
  neutral: "var(--text-secondary)",
@@ -6422,7 +6945,7 @@ var SEGMENT = {
6422
6945
  md: "gap-[7px] py-[var(--space-1)] px-[var(--space-3)] text-[14px]"
6423
6946
  };
6424
6947
  var DOT2 = { xs: 5, sm: 6, md: 7 };
6425
- var TwoTierBadge = React30.forwardRef(
6948
+ var TwoTierBadge = React31.forwardRef(
6426
6949
  ({ primary, secondary, tone = "neutral", emphasis = "subtle", size = "sm", showDot = false, className, style, ...props }, ref) => {
6427
6950
  const strong = emphasis === "strong";
6428
6951
  const fg = FG[tone];
@@ -6492,7 +7015,7 @@ var TwoTierBadge = React30.forwardRef(
6492
7015
  TwoTierBadge.displayName = "TwoTierBadge";
6493
7016
 
6494
7017
  // src/components/ui/status-badge.tsx
6495
- import * as React31 from "react";
7018
+ import * as React32 from "react";
6496
7019
  import { jsx as jsx40, jsxs as jsxs37 } from "react/jsx-runtime";
6497
7020
  var ROLE = {
6498
7021
  error: "error",
@@ -6522,7 +7045,7 @@ var SIZE = {
6522
7045
  sm: { cls: "gap-[6px] py-[var(--space-1)] px-[var(--space-2)] text-[12px]", dot: 6 },
6523
7046
  md: { cls: "gap-[var(--space-2)] py-[var(--space-1)] px-[14px] text-[14px]", dot: 7 }
6524
7047
  };
6525
- var StatusBadge = React31.forwardRef(
7048
+ var StatusBadge = React32.forwardRef(
6526
7049
  ({ status = "active", emphasis = "subtle", size = "sm", showDot = true, pulse = true, className, children, style, ...props }, ref) => {
6527
7050
  const role = ROLE[status];
6528
7051
  const fg = FG2[role];
@@ -6573,7 +7096,7 @@ var StatusBadge = React31.forwardRef(
6573
7096
  StatusBadge.displayName = "StatusBadge";
6574
7097
 
6575
7098
  // src/components/ui/notification-badge.tsx
6576
- import * as React32 from "react";
7099
+ import * as React33 from "react";
6577
7100
  import { cva as cva13 } from "class-variance-authority";
6578
7101
  import { jsx as jsx41 } from "react/jsx-runtime";
6579
7102
  var notificationBadgeVariants = cva13(
@@ -6606,7 +7129,7 @@ function formatNotificationCount(count, max) {
6606
7129
  }
6607
7130
  return String(count);
6608
7131
  }
6609
- var NotificationBadge = React32.forwardRef(
7132
+ var NotificationBadge = React33.forwardRef(
6610
7133
  ({
6611
7134
  className,
6612
7135
  count,
@@ -6639,7 +7162,7 @@ var NotificationBadge = React32.forwardRef(
6639
7162
  NotificationBadge.displayName = "NotificationBadge";
6640
7163
 
6641
7164
  // src/components/ui/empty-state.tsx
6642
- import * as React33 from "react";
7165
+ import * as React34 from "react";
6643
7166
  import { cva as cva14 } from "class-variance-authority";
6644
7167
  import { jsx as jsx42, jsxs as jsxs38 } from "react/jsx-runtime";
6645
7168
  var emptyStateBaseVariants = cva14(
@@ -6713,7 +7236,7 @@ var iconSize3 = {
6713
7236
  md: 20,
6714
7237
  lg: 24
6715
7238
  };
6716
- var EmptyState = React33.forwardRef(
7239
+ var EmptyState = React34.forwardRef(
6717
7240
  ({
6718
7241
  className,
6719
7242
  size = "md",
@@ -6781,7 +7304,7 @@ var EmptyState = React33.forwardRef(
6781
7304
  EmptyState.displayName = "EmptyState";
6782
7305
 
6783
7306
  // src/components/ui/error-state.tsx
6784
- import * as React34 from "react";
7307
+ import * as React35 from "react";
6785
7308
  import { AlertTriangle } from "lucide-react";
6786
7309
  import { cva as cva15 } from "class-variance-authority";
6787
7310
  import { jsx as jsx43, jsxs as jsxs39 } from "react/jsx-runtime";
@@ -6806,7 +7329,7 @@ var errorStateVariants = cva15(
6806
7329
  }
6807
7330
  }
6808
7331
  );
6809
- var ErrorState = React34.forwardRef(
7332
+ var ErrorState = React35.forwardRef(
6810
7333
  ({
6811
7334
  className,
6812
7335
  size = "md",
@@ -6869,12 +7392,14 @@ var ErrorState = React34.forwardRef(
6869
7392
  ErrorState.displayName = "ErrorState";
6870
7393
 
6871
7394
  // src/components/ui/skeleton.tsx
6872
- import * as React35 from "react";
7395
+ import * as React36 from "react";
6873
7396
  import { cva as cva16 } from "class-variance-authority";
6874
7397
  import { jsx as jsx44 } from "react/jsx-runtime";
6875
7398
  var skeletonVariants = cva16(
6876
7399
  [
6877
- "animate-pulse rounded-[var(--radius-sm)] bg-[var(--bg-raised)]",
7400
+ // bg-inset: light #F1F2F4 reads on white surfaces (bg-raised was white = invisible);
7401
+ // dark value equals the old bg-raised (#16181D), so dark mode is unchanged.
7402
+ "animate-pulse rounded-[var(--radius-sm)] bg-[var(--bg-inset)]",
6878
7403
  "motion-reduce:animate-none motion-reduce:opacity-80"
6879
7404
  ].join(" "),
6880
7405
  {
@@ -6913,7 +7438,7 @@ var spinnerVariants = cva16(
6913
7438
  }
6914
7439
  }
6915
7440
  );
6916
- var Skeleton = React35.forwardRef(
7441
+ var Skeleton = React36.forwardRef(
6917
7442
  ({ className, shape, style, ...props }, ref) => /* @__PURE__ */ jsx44(
6918
7443
  "div",
6919
7444
  {
@@ -6927,7 +7452,7 @@ var Skeleton = React35.forwardRef(
6927
7452
  );
6928
7453
  Skeleton.displayName = "Skeleton";
6929
7454
  var defaultTextWidths = ["92%", "84%", "68%", "76%"];
6930
- var SkeletonText = React35.forwardRef(
7455
+ var SkeletonText = React36.forwardRef(
6931
7456
  ({ className, lines = 3, widths = defaultTextWidths, ...props }, ref) => /* @__PURE__ */ jsx44(
6932
7457
  "div",
6933
7458
  {
@@ -6947,7 +7472,7 @@ var SkeletonText = React35.forwardRef(
6947
7472
  )
6948
7473
  );
6949
7474
  SkeletonText.displayName = "SkeletonText";
6950
- var SkeletonRow = React35.forwardRef(
7475
+ var SkeletonRow = React36.forwardRef(
6951
7476
  ({
6952
7477
  className,
6953
7478
  columns,
@@ -7004,7 +7529,7 @@ var SkeletonRow = React35.forwardRef(
7004
7529
  }
7005
7530
  );
7006
7531
  SkeletonRow.displayName = "SkeletonRow";
7007
- var Spinner = React35.forwardRef(
7532
+ var Spinner = React36.forwardRef(
7008
7533
  ({ className, size, label = "\uB85C\uB529 \uC911", ...props }, ref) => /* @__PURE__ */ jsx44(
7009
7534
  "span",
7010
7535
  {
@@ -7020,7 +7545,7 @@ var Spinner = React35.forwardRef(
7020
7545
  Spinner.displayName = "Spinner";
7021
7546
 
7022
7547
  // src/components/ui/pagination.tsx
7023
- import * as React36 from "react";
7548
+ import * as React37 from "react";
7024
7549
  import { ChevronsLeft, ChevronLeft, ChevronRight as ChevronRight2, ChevronsRight } from "lucide-react";
7025
7550
  import { cva as cva17 } from "class-variance-authority";
7026
7551
 
@@ -7161,7 +7686,7 @@ function NavigationButton({
7161
7686
  }
7162
7687
  );
7163
7688
  }
7164
- var Pagination = React36.forwardRef(
7689
+ var Pagination = React37.forwardRef(
7165
7690
  ({
7166
7691
  className,
7167
7692
  size = "standard",
@@ -7303,7 +7828,7 @@ import { jsx as jsx47, jsxs as jsxs42 } from "react/jsx-runtime";
7303
7828
  var modeToggleVariants = cva18(
7304
7829
  [
7305
7830
  "inline-flex items-center rounded-[var(--radius-sm)] border border-[var(--border-subtle)]",
7306
- "bg-[var(--bg-raised)] p-[2px]"
7831
+ "bg-[var(--bg-inset)] p-[2px]"
7307
7832
  ].join(" ")
7308
7833
  );
7309
7834
  var modeToggleItemVariants = cva18(
@@ -7367,7 +7892,7 @@ function ModeToggle({
7367
7892
  }
7368
7893
 
7369
7894
  // src/components/ui/action-footer.tsx
7370
- import * as React37 from "react";
7895
+ import * as React38 from "react";
7371
7896
  import { cva as cva19 } from "class-variance-authority";
7372
7897
  import { jsx as jsx48, jsxs as jsxs43 } from "react/jsx-runtime";
7373
7898
  var actionFooterVariants = cva19(
@@ -7454,7 +7979,7 @@ function renderAction(action) {
7454
7979
  action.id ?? action.label
7455
7980
  );
7456
7981
  }
7457
- var ActionFooter = React37.forwardRef(
7982
+ var ActionFooter = React38.forwardRef(
7458
7983
  ({
7459
7984
  className,
7460
7985
  status,
@@ -7524,7 +8049,7 @@ var ActionFooter = React37.forwardRef(
7524
8049
  ActionFooter.displayName = "ActionFooter";
7525
8050
 
7526
8051
  // src/components/ui/toast.tsx
7527
- import * as React38 from "react";
8052
+ import * as React39 from "react";
7528
8053
  import { cva as cva20 } from "class-variance-authority";
7529
8054
  import { jsx as jsx49, jsxs as jsxs44 } from "react/jsx-runtime";
7530
8055
  var DOT3 = {
@@ -7644,7 +8169,7 @@ var toast = {
7644
8169
  }
7645
8170
  };
7646
8171
  var Toast = toast;
7647
- var ToastRoot = React38.forwardRef(
8172
+ var ToastRoot = React39.forwardRef(
7648
8173
  ({
7649
8174
  className,
7650
8175
  id,
@@ -7661,10 +8186,10 @@ var ToastRoot = React38.forwardRef(
7661
8186
  ...props
7662
8187
  }, ref) => {
7663
8188
  const durationMs = getToastDuration({ message, variant, duration });
7664
- const onCloseRef = React38.useRef(onClose);
8189
+ const onCloseRef = React39.useRef(onClose);
7665
8190
  onCloseRef.current = onClose;
7666
- const timerRef = React38.useRef(null);
7667
- React38.useEffect(() => {
8191
+ const timerRef = React39.useRef(null);
8192
+ React39.useEffect(() => {
7668
8193
  if (!onCloseRef.current) return void 0;
7669
8194
  const timer = createToastTimer(durationMs, () => onCloseRef.current?.());
7670
8195
  timerRef.current = timer;
@@ -7761,7 +8286,7 @@ var ToastRoot = React38.forwardRef(
7761
8286
  }
7762
8287
  );
7763
8288
  ToastRoot.displayName = "ToastRoot";
7764
- var ToastViewport = React38.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx49(
8289
+ var ToastViewport = React39.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx49(
7765
8290
  "ol",
7766
8291
  {
7767
8292
  ref,
@@ -7772,8 +8297,8 @@ var ToastViewport = React38.forwardRef(({ className, ...props }, ref) => /* @__P
7772
8297
  ));
7773
8298
  ToastViewport.displayName = "ToastViewport";
7774
8299
  function ToastProvider({ children, maxToasts = 3, className, ...props }) {
7775
- const [items, setItems] = React38.useState([]);
7776
- React38.useEffect(
8300
+ const [items, setItems] = React39.useState([]);
8301
+ React39.useEffect(
7777
8302
  () => toast.subscribe((nextItems) => {
7778
8303
  setItems(limitToasts(nextItems, maxToasts));
7779
8304
  }),
@@ -7786,7 +8311,7 @@ function ToastProvider({ children, maxToasts = 3, className, ...props }) {
7786
8311
  }
7787
8312
 
7788
8313
  // src/components/ui/otp-field.tsx
7789
- import * as React39 from "react";
8314
+ import * as React40 from "react";
7790
8315
  import { cva as cva21 } from "class-variance-authority";
7791
8316
  import { AlertCircle as AlertCircle4 } from "lucide-react";
7792
8317
  import { Fragment as Fragment9, jsx as jsx50, jsxs as jsxs45 } from "react/jsx-runtime";
@@ -7819,14 +8344,14 @@ var otpFieldGroupVariants = cva21("flex items-start gap-[var(--space-2)] pt-[var
7819
8344
  });
7820
8345
  var otpCellVariants = cva21(
7821
8346
  [
7822
- "flex items-center justify-center rounded-[var(--radius-sm)] border",
8347
+ "flex items-center justify-center rounded-[var(--radius-md)] border",
7823
8348
  "border-[var(--border)] text-foreground",
7824
8349
  "font-[family-name:var(--font-display)] tabular-nums",
7825
8350
  "text-center leading-none caret-[var(--text-primary)]",
7826
8351
  "transition-[border-color,box-shadow,background-color,color] motion-reduce:transition-none",
7827
8352
  "focus:outline-none focus:border-[var(--focus-ring)] focus:shadow-[0_0_0_1px_var(--focus-ring)]",
7828
8353
  "disabled:cursor-not-allowed disabled:border-[var(--border-subtle)] disabled:bg-[var(--action-disabled-bg)] disabled:text-text-disabled",
7829
- "read-only:cursor-default read-only:bg-[var(--bg-raised)] read-only:text-text-secondary",
8354
+ "read-only:cursor-default read-only:bg-[var(--bg-inset)] read-only:text-text-secondary",
7830
8355
  "aria-[invalid=true]:border-[var(--error)] aria-[invalid=true]:focus:shadow-[0_0_0_1px_var(--error)]",
7831
8356
  "data-[state=error]:border-[var(--error)] data-[state=error]:focus:shadow-[0_0_0_1px_var(--error)]",
7832
8357
  "data-[state=filled]:border-[var(--border-strong)]"
@@ -7916,7 +8441,7 @@ function fillDigitsFromIndex(slots, index, inserted, length) {
7916
8441
  focusIndex: Math.min(cursor, length - 1)
7917
8442
  };
7918
8443
  }
7919
- var OTPField = React39.forwardRef(
8444
+ var OTPField = React40.forwardRef(
7920
8445
  ({
7921
8446
  className,
7922
8447
  defaultValue,
@@ -7938,24 +8463,24 @@ var OTPField = React39.forwardRef(
7938
8463
  required,
7939
8464
  ...props
7940
8465
  }, ref) => {
7941
- const generatedId = React39.useId();
8466
+ const generatedId = React40.useId();
7942
8467
  const fieldId = id ?? generatedId;
7943
8468
  const labelId = `${fieldId}-label`;
7944
8469
  const helperId = helperText ? `${fieldId}-helper` : void 0;
7945
8470
  const errorId = errorText ? `${fieldId}-error` : void 0;
7946
8471
  const resolvedState = invalid || errorText ? "error" : "default";
7947
8472
  const isControlled = value !== void 0;
7948
- const [uncontrolledSlots, setUncontrolledSlots] = React39.useState(
8473
+ const [uncontrolledSlots, setUncontrolledSlots] = React40.useState(
7949
8474
  () => toDigitsArray(onlyDigits(defaultValue, length), length)
7950
8475
  );
7951
- const resolvedSlots = React39.useMemo(
8476
+ const resolvedSlots = React40.useMemo(
7952
8477
  () => isControlled ? toDigitsArray(onlyDigits(value, length), length) : uncontrolledSlots,
7953
8478
  [isControlled, length, uncontrolledSlots, value]
7954
8479
  );
7955
8480
  const resolvedValue = joinDigits(resolvedSlots).slice(0, length);
7956
- const cellsRef = React39.useRef([]);
8481
+ const cellsRef = React40.useRef([]);
7957
8482
  const groupDescribedBy = [helperId, errorId].filter(Boolean).join(" ") || void 0;
7958
- const setResolvedValue = React39.useCallback(
8483
+ const setResolvedValue = React40.useCallback(
7959
8484
  (nextSlots, focusIndex) => {
7960
8485
  const normalizedSlots = nextSlots.slice(0, length);
7961
8486
  const normalizedValue = joinDigits(normalizedSlots).slice(0, length);
@@ -8145,7 +8670,7 @@ var OTPField = React39.forwardRef(
8145
8670
  OTPField.displayName = "OTPField";
8146
8671
 
8147
8672
  // src/components/ui/search-field.tsx
8148
- import * as React40 from "react";
8673
+ import * as React41 from "react";
8149
8674
  import { Search, X as X4 } from "lucide-react";
8150
8675
  import { cva as cva22 } from "class-variance-authority";
8151
8676
  import { jsx as jsx51, jsxs as jsxs46 } from "react/jsx-runtime";
@@ -8164,7 +8689,7 @@ var searchFieldVariants = cva22("relative flex w-full items-center", {
8164
8689
  });
8165
8690
  var searchFieldInputVariants = cva22(
8166
8691
  [
8167
- "h-full w-full rounded-[var(--radius-sm)] border",
8692
+ "h-full w-full rounded-[var(--radius-md)] border",
8168
8693
  "border-[var(--border)]",
8169
8694
  "appearance-none [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden",
8170
8695
  "font-[family-name:var(--font-body)] text-foreground",
@@ -8213,7 +8738,7 @@ var iconSize4 = {
8213
8738
  function hasVisibleValue(value) {
8214
8739
  return typeof value === "string" ? value.length > 0 : value !== void 0;
8215
8740
  }
8216
- var SearchField = React40.forwardRef(
8741
+ var SearchField = React41.forwardRef(
8217
8742
  ({
8218
8743
  className,
8219
8744
  inputMode = "search",
@@ -8229,12 +8754,12 @@ var SearchField = React40.forwardRef(
8229
8754
  ...props
8230
8755
  }, ref) => {
8231
8756
  const resolvedSize = size ?? "md";
8232
- const inputRef = React40.useRef(null);
8233
- const [hasText, setHasText] = React40.useState(
8757
+ const inputRef = React41.useRef(null);
8758
+ const [hasText, setHasText] = React41.useState(
8234
8759
  () => hasVisibleValue(value ?? defaultValue)
8235
8760
  );
8236
- React40.useImperativeHandle(ref, () => inputRef.current);
8237
- React40.useEffect(() => {
8761
+ React41.useImperativeHandle(ref, () => inputRef.current);
8762
+ React41.useEffect(() => {
8238
8763
  if (value !== void 0) {
8239
8764
  setHasText(hasVisibleValue(value));
8240
8765
  }
@@ -8304,7 +8829,7 @@ var SearchField = React40.forwardRef(
8304
8829
  SearchField.displayName = "SearchField";
8305
8830
 
8306
8831
  // src/components/ui/checkbox-group.tsx
8307
- import * as React41 from "react";
8832
+ import * as React42 from "react";
8308
8833
  import { cva as cva23 } from "class-variance-authority";
8309
8834
  import { jsx as jsx52 } from "react/jsx-runtime";
8310
8835
  var checkboxGroupVariants = cva23("gap-[var(--space-2)]", {
@@ -8332,7 +8857,7 @@ var checkboxGroupVariants = cva23("gap-[var(--space-2)]", {
8332
8857
  function toSelectionSet(value) {
8333
8858
  return new Set(value ?? []);
8334
8859
  }
8335
- var CheckboxGroup = React41.forwardRef(
8860
+ var CheckboxGroup = React42.forwardRef(
8336
8861
  ({
8337
8862
  className,
8338
8863
  children,
@@ -8348,14 +8873,14 @@ var CheckboxGroup = React41.forwardRef(
8348
8873
  ...props
8349
8874
  }, ref) => {
8350
8875
  const isControlled = value != null;
8351
- const [uncontrolledValue, setUncontrolledValue] = React41.useState(
8876
+ const [uncontrolledValue, setUncontrolledValue] = React42.useState(
8352
8877
  () => toSelectionSet(defaultValue)
8353
8878
  );
8354
- const selectedValues = React41.useMemo(
8879
+ const selectedValues = React42.useMemo(
8355
8880
  () => isControlled ? toSelectionSet(value) : uncontrolledValue,
8356
8881
  [isControlled, uncontrolledValue, value]
8357
8882
  );
8358
- const toggleValue = React41.useCallback(
8883
+ const toggleValue = React42.useCallback(
8359
8884
  (itemValue, checked) => {
8360
8885
  const nextValues = new Set(selectedValues);
8361
8886
  if (checked) {
@@ -8371,7 +8896,7 @@ var CheckboxGroup = React41.forwardRef(
8371
8896
  },
8372
8897
  [isControlled, onValueChange, selectedValues]
8373
8898
  );
8374
- const contextValue = React41.useMemo(
8899
+ const contextValue = React42.useMemo(
8375
8900
  () => ({
8376
8901
  appearance,
8377
8902
  density,
@@ -8406,7 +8931,7 @@ var CheckboxGroup = React41.forwardRef(
8406
8931
  CheckboxGroup.displayName = "CheckboxGroup";
8407
8932
 
8408
8933
  // src/components/ui/radio-group.tsx
8409
- import * as React42 from "react";
8934
+ import * as React43 from "react";
8410
8935
  import { cva as cva24 } from "class-variance-authority";
8411
8936
  import { Circle } from "lucide-react";
8412
8937
  import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
@@ -8499,11 +9024,11 @@ var radioControlVariants = cva24(
8499
9024
  }
8500
9025
  }
8501
9026
  );
8502
- var RadioGroupContext = React42.createContext({
9027
+ var RadioGroupContext = React43.createContext({
8503
9028
  appearance: "plain",
8504
9029
  density: "comfortable"
8505
9030
  });
8506
- var RadioGroup = React42.forwardRef(
9031
+ var RadioGroup = React43.forwardRef(
8507
9032
  ({
8508
9033
  className,
8509
9034
  children,
@@ -8514,7 +9039,7 @@ var RadioGroup = React42.forwardRef(
8514
9039
  required,
8515
9040
  ...props
8516
9041
  }, ref) => {
8517
- const contextValue = React42.useMemo(
9042
+ const contextValue = React43.useMemo(
8518
9043
  () => ({ appearance, density }),
8519
9044
  [appearance, density]
8520
9045
  );
@@ -8541,9 +9066,9 @@ var RadioGroup = React42.forwardRef(
8541
9066
  }
8542
9067
  );
8543
9068
  RadioGroup.displayName = "RadioGroup";
8544
- var Radio = React42.forwardRef(({ className, children, description, id, ...props }, ref) => {
8545
- const { appearance, density } = React42.useContext(RadioGroupContext);
8546
- const generatedId = React42.useId();
9069
+ var Radio = React43.forwardRef(({ className, children, description, id, ...props }, ref) => {
9070
+ const { appearance, density } = React43.useContext(RadioGroupContext);
9071
+ const generatedId = React43.useId();
8547
9072
  const radioId = id ?? generatedId;
8548
9073
  const labelId = `${radioId}-label`;
8549
9074
  const descriptionId = description ? `${radioId}-description` : void 0;
@@ -8711,7 +9236,7 @@ function Progress({
8711
9236
  }
8712
9237
 
8713
9238
  // src/components/ui/tree-view.tsx
8714
- import * as React43 from "react";
9239
+ import * as React44 from "react";
8715
9240
  import { ChevronRight as ChevronRight3 } from "lucide-react";
8716
9241
  import { cva as cva25 } from "class-variance-authority";
8717
9242
  import { jsx as jsx56, jsxs as jsxs49 } from "react/jsx-runtime";
@@ -8817,27 +9342,27 @@ function TreeView({
8817
9342
  ...rest
8818
9343
  }) {
8819
9344
  const isExpandedControlled = expandedIds !== void 0;
8820
- const [internalExpanded, setInternalExpanded] = React43.useState(
9345
+ const [internalExpanded, setInternalExpanded] = React44.useState(
8821
9346
  () => defaultExpandedIds ?? []
8822
9347
  );
8823
9348
  const expandedList = isExpandedControlled ? expandedIds : internalExpanded;
8824
- const expandedSet = React43.useMemo(() => new Set(expandedList), [expandedList]);
8825
- const items = React43.useMemo(
9349
+ const expandedSet = React44.useMemo(() => new Set(expandedList), [expandedList]);
9350
+ const items = React44.useMemo(
8826
9351
  () => flattenVisibleNodes(nodes, getChildren, expandedSet),
8827
9352
  [nodes, getChildren, expandedSet]
8828
9353
  );
8829
- const [focusedId, setFocusedId] = React43.useState(null);
9354
+ const [focusedId, setFocusedId] = React44.useState(null);
8830
9355
  const visibleIds = items.map((item) => item.id);
8831
9356
  const activeId = focusedId && visibleIds.includes(focusedId) ? focusedId : selectedId && visibleIds.includes(selectedId) ? selectedId : visibleIds[0] ?? null;
8832
- const itemRefs = React43.useRef(/* @__PURE__ */ new Map());
8833
- const commitExpanded = React43.useCallback(
9357
+ const itemRefs = React44.useRef(/* @__PURE__ */ new Map());
9358
+ const commitExpanded = React44.useCallback(
8834
9359
  (next) => {
8835
9360
  if (!isExpandedControlled) setInternalExpanded(next);
8836
9361
  onExpandedChange?.(next);
8837
9362
  },
8838
9363
  [isExpandedControlled, onExpandedChange]
8839
9364
  );
8840
- const setExpanded = React43.useCallback(
9365
+ const setExpanded = React44.useCallback(
8841
9366
  (id, expand) => {
8842
9367
  const has = expandedSet.has(id);
8843
9368
  if (expand && !has) {
@@ -8848,7 +9373,7 @@ function TreeView({
8848
9373
  },
8849
9374
  [commitExpanded, expandedList, expandedSet]
8850
9375
  );
8851
- const focusItem = React43.useCallback((id) => {
9376
+ const focusItem = React44.useCallback((id) => {
8852
9377
  setFocusedId(id);
8853
9378
  itemRefs.current.get(id)?.focus();
8854
9379
  }, []);