@olundot/ui 0.16.0 → 0.17.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/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,271 @@ 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 useVirtualWindow({
5568
+ enabled,
5569
+ count,
5570
+ overscan,
5571
+ desktopRef,
5572
+ desktopListRef,
5573
+ mobileListRef
5574
+ }) {
5575
+ const [win, setWin] = React27.useState({
5576
+ layout: null,
5577
+ start: 0,
5578
+ end: VIRTUAL_INITIAL_ROWS,
5579
+ size: 0
5580
+ });
5581
+ const measuredSize = React27.useRef({ desktop: 0, mobile: 0 });
5582
+ const update = React27.useRef(() => void 0);
5583
+ update.current = () => {
5584
+ if (!enabled) {
5585
+ return;
5586
+ }
5587
+ const desktop = desktopRef.current;
5588
+ const layout = desktop && window.getComputedStyle(desktop).display === "none" ? "mobile" : "desktop";
5589
+ const list = (layout === "desktop" ? desktopListRef : mobileListRef).current;
5590
+ const firstItem = list?.firstElementChild;
5591
+ const itemHeight = firstItem ? firstItem.getBoundingClientRect().height : 0;
5592
+ if (itemHeight > 0) {
5593
+ measuredSize.current[layout] = itemHeight;
5594
+ }
5595
+ const size = measuredSize.current[layout] || VIRTUAL_FALLBACK_SIZE[layout];
5596
+ let start = 0;
5597
+ let end = Math.min(count, VIRTUAL_INITIAL_ROWS);
5598
+ if (list) {
5599
+ const top = list.getBoundingClientRect().top;
5600
+ const viewport = window.innerHeight || document.documentElement.clientHeight;
5601
+ start = Math.min(count, Math.max(0, Math.floor(-top / size) - overscan));
5602
+ end = Math.min(count, Math.max(start, Math.ceil((viewport - top) / size) + overscan));
5603
+ }
5604
+ setWin(
5605
+ (prev) => prev.layout === layout && prev.start === start && prev.end === end && prev.size === size ? prev : { layout, start, end, size }
5606
+ );
5607
+ };
5608
+ useIsomorphicLayoutEffect(() => {
5609
+ update.current();
5610
+ });
5611
+ React27.useEffect(() => {
5612
+ if (!enabled) {
5613
+ return;
5614
+ }
5615
+ let frame = 0;
5616
+ const schedule = () => {
5617
+ if (frame) {
5618
+ return;
5619
+ }
5620
+ frame = window.requestAnimationFrame(() => {
5621
+ frame = 0;
5622
+ update.current();
5623
+ });
5624
+ };
5625
+ window.addEventListener("scroll", schedule, { capture: true, passive: true });
5626
+ window.addEventListener("resize", schedule);
5627
+ return () => {
5628
+ window.removeEventListener("scroll", schedule, { capture: true });
5629
+ window.removeEventListener("resize", schedule);
5630
+ window.cancelAnimationFrame(frame);
5631
+ };
5632
+ }, [enabled]);
5633
+ return win;
5634
+ }
5366
5635
  function DataTableInner({
5367
5636
  className,
5368
5637
  density = "standard",
@@ -5380,18 +5649,32 @@ function DataTableInner({
5380
5649
  loading = false,
5381
5650
  emptyState,
5382
5651
  rowHref,
5652
+ linkComponent,
5653
+ onRowClick,
5654
+ virtual,
5383
5655
  skeletonRowCount = 8,
5384
5656
  "aria-label": ariaLabel = "\uB370\uC774\uD130 \uD14C\uC774\uBE14",
5385
5657
  ...props
5386
5658
  }, ref) {
5659
+ const virtualOptions = virtual ? virtual === true ? {} : virtual : null;
5660
+ const isVirtual = virtualOptions !== null;
5661
+ const desktopRef = React27.useRef(null);
5662
+ const desktopListRef = React27.useRef(null);
5663
+ const mobileListRef = React27.useRef(null);
5664
+ const virtualWindow = useVirtualWindow({
5665
+ enabled: isVirtual,
5666
+ count: data.length,
5667
+ overscan: virtualOptions?.overscan ?? VIRTUAL_DEFAULT_OVERSCAN,
5668
+ desktopRef,
5669
+ desktopListRef,
5670
+ mobileListRef
5671
+ });
5387
5672
  const resolvedSelectedIds = selectedIds ?? /* @__PURE__ */ new Set();
5388
5673
  const rowIds = data.map((row) => getRowId(row, keyField));
5389
5674
  const selectedCount = rowIds.filter((id) => resolvedSelectedIds.has(id)).length;
5390
5675
  const allVisibleRowsSelected = rowIds.length > 0 && selectedCount === rowIds.length;
5391
5676
  const partiallySelected = selectedCount > 0 && !allVisibleRowsSelected;
5392
5677
  const gridTemplateColumns = getGridTemplate(columns, selectable);
5393
- const primaryColumn = columns[0];
5394
- const secondaryColumns = columns.slice(1);
5395
5678
  const emitSelection = (nextIds) => {
5396
5679
  onSelectionChange?.(nextIds);
5397
5680
  };
@@ -5413,12 +5696,81 @@ function DataTableInner({
5413
5696
  }
5414
5697
  emitSelection(nextIds);
5415
5698
  };
5699
+ const activateRow = (row, href, event) => {
5700
+ if (onRowClick) {
5701
+ onRowClick(row, event);
5702
+ } else if (href) {
5703
+ window.location.assign(href);
5704
+ }
5705
+ };
5706
+ const latestHandlers = React27.useRef({ toggleRow, activateRow, onRowContextMenu });
5707
+ useIsomorphicLayoutEffect(() => {
5708
+ latestHandlers.current = { toggleRow, activateRow, onRowContextMenu };
5709
+ });
5710
+ const stableToggleRow = React27.useCallback(
5711
+ (id, checked) => latestHandlers.current.toggleRow(id, checked),
5712
+ []
5713
+ );
5714
+ const stableActivateRow = React27.useCallback(
5715
+ (row, href, event) => latestHandlers.current.activateRow(row, href, event),
5716
+ []
5717
+ );
5718
+ const stableRowContextMenu = React27.useCallback(
5719
+ (rowId, event) => latestHandlers.current.onRowContextMenu?.(rowId, event),
5720
+ []
5721
+ );
5722
+ const getItemProps = (row) => {
5723
+ const rowId = getRowId(row, keyField);
5724
+ const selected = resolvedSelectedIds.has(rowId);
5725
+ const href = rowHref?.(row);
5726
+ const linked = Boolean(linkComponent && href);
5727
+ return {
5728
+ row,
5729
+ rowId,
5730
+ columns,
5731
+ selectable,
5732
+ selected,
5733
+ visuallySelected: selected || rowId === activeRowId,
5734
+ href,
5735
+ activatable: !linked && Boolean(href || onRowClick),
5736
+ LinkComponent: linkComponent,
5737
+ onToggle: isVirtual ? stableToggleRow : toggleRow,
5738
+ onActivate: isVirtual ? stableActivateRow : activateRow,
5739
+ onRowContextMenu: isVirtual ? stableRowContextMenu : onRowContextMenu
5740
+ };
5741
+ };
5742
+ const sliceFor = (layout) => {
5743
+ if (!isVirtual) {
5744
+ return { rows: data, start: 0, style: void 0 };
5745
+ }
5746
+ if (virtualWindow.layout !== null && virtualWindow.layout !== layout) {
5747
+ return { rows: [], start: 0, style: void 0 };
5748
+ }
5749
+ const size = virtualWindow.size || VIRTUAL_FALLBACK_SIZE[layout];
5750
+ const end = Math.min(data.length, virtualWindow.end);
5751
+ const start = Math.min(virtualWindow.start, end);
5752
+ return {
5753
+ rows: data.slice(start, end),
5754
+ start,
5755
+ style: { paddingTop: start * size, paddingBottom: (data.length - end) * size }
5756
+ };
5757
+ };
5758
+ const desktopSlice = sliceFor("desktop");
5759
+ const mobileSlice = sliceFor("mobile");
5760
+ const RowComponent = isVirtual ? MemoDataTableRow : DataTableRowView;
5761
+ const CardComponent = isVirtual ? MemoDataTableMobileCard : DataTableMobileCardView;
5416
5762
  return /* @__PURE__ */ jsxs30(
5417
5763
  "div",
5418
5764
  {
5419
5765
  ref,
5420
5766
  "data-slot": "data-table",
5421
- className: cn(dataTableVariants({ density, surface, className })),
5767
+ className: cn(
5768
+ dataTableVariants({ density, surface }),
5769
+ // clip, not hidden: keeps the rounded corners without becoming the sticky
5770
+ // header's scroll container.
5771
+ isVirtual && "overflow-clip",
5772
+ className
5773
+ ),
5422
5774
  ...props,
5423
5775
  children: [
5424
5776
  selectable ? /* @__PURE__ */ jsx31(
@@ -5439,149 +5791,108 @@ function DataTableInner({
5439
5791
  children: /* @__PURE__ */ jsx31("div", { className: "h-full w-1/3 animate-pulse bg-[var(--primary)]" })
5440
5792
  }
5441
5793
  ) : 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",
5794
+ /* @__PURE__ */ jsx31("div", { ref: desktopRef, "data-slot": "data-table-desktop", className: "max-md:hidden md:block", children: /* @__PURE__ */ jsxs30(
5795
+ "div",
5796
+ {
5797
+ role: "table",
5798
+ "aria-label": ariaLabel,
5799
+ "aria-rowcount": isVirtual && data.length > 0 ? data.length + 1 : void 0,
5800
+ children: [
5801
+ /* @__PURE__ */ jsxs30(
5802
+ "div",
5803
+ {
5804
+ "data-slot": "data-table-header",
5805
+ role: "row",
5806
+ className: cn(
5807
+ "grid min-h-[40px] items-center bg-[var(--bg-raised)] text-[12px] font-semibold uppercase leading-snug text-text-tertiary",
5808
+ // Virtual lists are long: the header sticks inside the scroll container,
5809
+ // below an inline BulkActionBar band (40px, --z-sticky) rendered before
5810
+ // the table — band on top, header under it, rows beneath both.
5811
+ isVirtual && "sticky top-0 z-[2] [[data-slot=bulk-action-bar][data-placement=inline]:not([data-state=idle])~[data-slot=data-table]_&]:top-[40px]"
5812
+ ),
5813
+ style: { gridTemplateColumns },
5814
+ children: [
5815
+ selectable ? /* @__PURE__ */ jsx31("div", { role: "columnheader", className: "px-[var(--space-4)]", children: /* @__PURE__ */ jsx31(
5816
+ Checkbox,
5817
+ {
5818
+ "aria-label": "\uC804\uCCB4 \uD589 \uC120\uD0DD",
5819
+ checked: partiallySelected ? "indeterminate" : allVisibleRowsSelected,
5820
+ onCheckedChange: (nextChecked) => toggleAllRows(nextChecked === true),
5821
+ children: /* @__PURE__ */ jsx31("span", { className: "sr-only", children: "\uC804\uCCB4 \uD589 \uC120\uD0DD" })
5822
+ }
5823
+ ) }) : null,
5824
+ columns.map((column) => {
5825
+ const align = column.align ?? "left";
5826
+ const nextSort = getNextSort(sortBy, column.key);
5827
+ const ariaSort = sortBy?.key === column.key ? sortBy.dir === "asc" ? "ascending" : "descending" : void 0;
5828
+ return /* @__PURE__ */ jsx31(
5829
+ "div",
5475
5830
  {
5476
- type: "button",
5831
+ role: "columnheader",
5832
+ "aria-sort": ariaSort,
5477
5833
  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)]"
5834
+ "flex min-w-0 items-center gap-[var(--space-1)] px-[var(--space-4)] py-[var(--space-2)]",
5835
+ alignmentClass[align]
5481
5836
  ),
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);
5837
+ children: column.sortable ? /* @__PURE__ */ jsxs30(
5838
+ "button",
5839
+ {
5840
+ type: "button",
5841
+ className: cn(
5842
+ "inline-flex min-w-0 cursor-pointer items-center gap-[var(--space-1)] rounded-[var(--radius-xs)]",
5843
+ "text-inherit transition-colors motion-reduce:transition-none",
5844
+ "hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--focus-ring)]"
5845
+ ),
5846
+ onClick: () => onSortChange?.(column.key, nextSort),
5847
+ children: [
5848
+ /* @__PURE__ */ jsx31("span", { className: "truncate", children: column.header }),
5849
+ /* @__PURE__ */ jsx31(SortIcon, { sort: sortBy, columnKey: column.key })
5850
+ ]
5851
+ }
5852
+ ) : /* @__PURE__ */ jsx31("span", { className: "truncate", children: column.header })
5853
+ },
5854
+ column.key
5855
+ );
5856
+ })
5857
+ ]
5858
+ }
5859
+ ),
5860
+ loading && data.length === 0 ? /* @__PURE__ */ jsx31(
5861
+ SkeletonRows,
5862
+ {
5863
+ columns,
5864
+ selectable,
5865
+ gridTemplateColumns,
5866
+ count: skeletonRowCount
5867
+ }
5868
+ ) : data.length === 0 ? (
5869
+ /* role="table" 직계 자식은 row/rowgroup이어야 한다. data-slot과 테두리는
5870
+ 그대로 유지해 시각 계약을 깨지 않는다(빈 상태에서도 표가 테두리를 그린다). */
5871
+ /* @__PURE__ */ jsx31("div", { role: "rowgroup", children: /* @__PURE__ */ jsx31("div", { role: "row", children: /* @__PURE__ */ jsx31(
5872
+ "div",
5873
+ {
5874
+ role: "cell",
5875
+ "aria-colindex": 1,
5876
+ "data-slot": "data-table-empty",
5877
+ className: "border-t border-[var(--border-subtle)] p-[var(--space-4)]",
5878
+ children: emptyState
5546
5879
  }
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
- ] }) }),
5880
+ ) }) })
5881
+ ) : /* @__PURE__ */ jsx31("div", { ref: desktopListRef, role: "rowgroup", style: desktopSlice.style, children: desktopSlice.rows.map((row, index) => {
5882
+ const itemProps = getItemProps(row);
5883
+ return /* @__PURE__ */ jsx31(
5884
+ RowComponent,
5885
+ {
5886
+ ...itemProps,
5887
+ gridTemplateColumns,
5888
+ ariaRowIndex: isVirtual ? desktopSlice.start + index + 2 : void 0
5889
+ },
5890
+ itemProps.rowId
5891
+ );
5892
+ }) })
5893
+ ]
5894
+ }
5895
+ ) }),
5585
5896
  /* @__PURE__ */ jsx31(
5586
5897
  MobileSortControls,
5587
5898
  {
@@ -5602,99 +5913,22 @@ function DataTableInner({
5602
5913
  ) : /* @__PURE__ */ jsx31(
5603
5914
  "div",
5604
5915
  {
5916
+ ref: mobileListRef,
5605
5917
  "data-slot": "data-table-mobile-list",
5606
5918
  role: "list",
5607
5919
  "aria-label": `${ariaLabel} \uBAA8\uBC14\uC77C \uCE74\uB4DC \uBAA9\uB85D`,
5608
5920
  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",
5921
+ style: mobileSlice.style,
5922
+ children: mobileSlice.rows.map((row, index) => {
5923
+ const itemProps = getItemProps(row);
5924
+ return /* @__PURE__ */ jsx31(
5925
+ CardComponent,
5618
5926
  {
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
- ]
5927
+ ...itemProps,
5928
+ ariaPosInSet: isVirtual ? mobileSlice.start + index + 1 : void 0,
5929
+ ariaSetSize: isVirtual ? data.length : void 0
5696
5930
  },
5697
- rowId
5931
+ itemProps.rowId
5698
5932
  );
5699
5933
  })
5700
5934
  }
@@ -5988,6 +6222,7 @@ function ContextMenu({
5988
6222
  }
5989
6223
 
5990
6224
  // src/components/ui/bulk-action-bar.tsx
6225
+ import * as React28 from "react";
5991
6226
  import { X as X3 } from "lucide-react";
5992
6227
  import { jsx as jsx34, jsxs as jsxs33 } from "react/jsx-runtime";
5993
6228
  function ActionButton({ action }) {
@@ -6077,18 +6312,228 @@ function ClearButton({ onClear }) {
6077
6312
  }
6078
6313
  );
6079
6314
  }
6315
+ var INLINE_HEIGHT = 40;
6316
+ var floatingStyle = {
6317
+ position: "sticky",
6318
+ bottom: "calc(var(--space-4) + env(safe-area-inset-bottom))",
6319
+ zIndex: 1100,
6320
+ margin: "0 auto",
6321
+ display: "flex",
6322
+ minHeight: 32,
6323
+ width: "fit-content",
6324
+ maxWidth: "calc(100% - var(--space-8))",
6325
+ alignItems: "center",
6326
+ gap: "var(--space-2)",
6327
+ borderRadius: 9999,
6328
+ border: "1px solid var(--border)",
6329
+ background: "var(--bg-raised)",
6330
+ padding: "var(--space-1) var(--space-2)",
6331
+ boxShadow: "var(--shadow-8)"
6332
+ };
6333
+ var inlineStyle = {
6334
+ position: "sticky",
6335
+ top: 0,
6336
+ zIndex: "var(--z-sticky)",
6337
+ display: "flex",
6338
+ boxSizing: "border-box",
6339
+ height: INLINE_HEIGHT,
6340
+ alignItems: "center",
6341
+ gap: "var(--space-2)",
6342
+ borderRadius: "var(--radius-md)",
6343
+ border: "1px solid var(--border)",
6344
+ background: "var(--bg-surface)",
6345
+ padding: "0 var(--space-1) 0 var(--space-3)",
6346
+ boxShadow: "var(--shadow-2)",
6347
+ fontFamily: "var(--font-sans)",
6348
+ fontSize: 13,
6349
+ fontVariantNumeric: "tabular-nums"
6350
+ };
6351
+ var idleLineStyle = {
6352
+ display: "flex",
6353
+ boxSizing: "border-box",
6354
+ height: INLINE_HEIGHT,
6355
+ overflow: "hidden",
6356
+ alignItems: "center",
6357
+ columnGap: "var(--space-3)",
6358
+ whiteSpace: "nowrap",
6359
+ fontFamily: "var(--font-sans)",
6360
+ fontSize: 13,
6361
+ fontVariantNumeric: "tabular-nums",
6362
+ color: "var(--text-secondary)"
6363
+ };
6364
+ var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? React28.useEffect : React28.useLayoutEffect;
6365
+ function useCompactSummary(slot, enabled) {
6366
+ const [fullWidth, setFullWidth] = React28.useState(null);
6367
+ const measure = React28.useCallback(() => {
6368
+ if (!slot) return;
6369
+ setFullWidth((prev) => {
6370
+ if (prev === null) return slot.scrollWidth > slot.clientWidth ? slot.scrollWidth : null;
6371
+ return slot.clientWidth >= prev ? null : prev;
6372
+ });
6373
+ }, [slot]);
6374
+ useIsomorphicLayoutEffect2(() => {
6375
+ if (enabled) measure();
6376
+ });
6377
+ React28.useEffect(() => {
6378
+ if (!enabled || !slot || typeof ResizeObserver === "undefined") return;
6379
+ const observer = new ResizeObserver(measure);
6380
+ observer.observe(slot);
6381
+ return () => observer.disconnect();
6382
+ }, [enabled, slot, measure]);
6383
+ return enabled && fullWidth !== null;
6384
+ }
6080
6385
  function BulkActionBar({
6081
6386
  count,
6082
6387
  actions,
6083
6388
  onClear,
6084
6389
  emptyHint,
6390
+ placement = "floating",
6391
+ unit = "\uAC1C",
6392
+ summary,
6393
+ compactSummary,
6394
+ idleContent,
6085
6395
  className,
6086
6396
  style,
6087
6397
  ...props
6088
6398
  }) {
6399
+ const inline = placement === "inline";
6400
+ const [summaryElement, setSummaryElement] = React28.useState(null);
6401
+ const compact = useCompactSummary(
6402
+ summaryElement,
6403
+ inline && compactSummary !== void 0 && compactSummary !== null
6404
+ );
6405
+ if (count === 0 && idleContent !== void 0) {
6406
+ return /* @__PURE__ */ jsx34(
6407
+ "div",
6408
+ {
6409
+ role: "status",
6410
+ "data-slot": "bulk-action-bar",
6411
+ "data-placement": placement,
6412
+ "data-state": "idle",
6413
+ className,
6414
+ style: { ...idleLineStyle, ...style },
6415
+ ...props,
6416
+ children: idleContent
6417
+ }
6418
+ );
6419
+ }
6089
6420
  if (count === 0 && emptyHint === void 0) {
6090
6421
  return null;
6091
6422
  }
6423
+ const countLabel = /* @__PURE__ */ jsx34(
6424
+ "span",
6425
+ {
6426
+ "data-slot": "bulk-action-bar-count",
6427
+ style: {
6428
+ paddingLeft: inline ? void 0 : "var(--space-2)",
6429
+ paddingRight: "var(--space-1)",
6430
+ fontFamily: "var(--font-sans)",
6431
+ fontSize: 13,
6432
+ fontWeight: inline ? 600 : 500,
6433
+ fontVariantNumeric: "tabular-nums",
6434
+ color: "var(--text-primary)",
6435
+ whiteSpace: "nowrap"
6436
+ },
6437
+ children: count > 0 ? `${count.toLocaleString("ko-KR")}${unit} \uC120\uD0DD\uB428` : emptyHint
6438
+ }
6439
+ );
6440
+ const summarySlot = summary !== void 0 && summary !== null ? /* @__PURE__ */ jsx34(
6441
+ "span",
6442
+ {
6443
+ ref: setSummaryElement,
6444
+ "data-slot": "bulk-action-bar-summary",
6445
+ style: {
6446
+ display: inline ? "flex" : "inline-flex",
6447
+ minWidth: 0,
6448
+ alignItems: "center",
6449
+ columnGap: "var(--space-3)",
6450
+ fontFamily: "var(--font-sans)",
6451
+ fontSize: 13,
6452
+ color: "var(--text-secondary)",
6453
+ ...inline ? (
6454
+ // inline-size containment: the summary never widens the band's parent. The
6455
+ // slot is full-height with 3px side gutters so focus rings are not clipped.
6456
+ {
6457
+ flex: "1 1 0",
6458
+ alignSelf: "stretch",
6459
+ margin: "0 -3px",
6460
+ padding: "0 3px",
6461
+ overflow: "hidden",
6462
+ whiteSpace: "nowrap",
6463
+ contain: "inline-size"
6464
+ }
6465
+ ) : { flexWrap: "wrap", wordBreak: "keep-all" }
6466
+ },
6467
+ children: compact ? compactSummary : summary
6468
+ }
6469
+ ) : null;
6470
+ const actionGroup = /* @__PURE__ */ jsx34(
6471
+ "div",
6472
+ {
6473
+ "data-slot": "bulk-action-bar-actions",
6474
+ style: {
6475
+ display: "flex",
6476
+ minWidth: 0,
6477
+ flexWrap: inline ? "nowrap" : "wrap",
6478
+ alignItems: "center",
6479
+ justifyContent: "flex-end",
6480
+ gap: 2
6481
+ },
6482
+ children: actions.map((action) => /* @__PURE__ */ jsx34(ActionButton, { action }, action.key))
6483
+ }
6484
+ );
6485
+ if (inline) {
6486
+ return /* @__PURE__ */ jsxs33(
6487
+ "div",
6488
+ {
6489
+ role: "toolbar",
6490
+ "aria-label": "\uC120\uD0DD \uD56D\uBAA9 \uC77C\uAD04 \uC791\uC5C5",
6491
+ "data-slot": "bulk-action-bar",
6492
+ "data-placement": "inline",
6493
+ className,
6494
+ style: { ...inlineStyle, ...style },
6495
+ ...props,
6496
+ children: [
6497
+ /* @__PURE__ */ jsxs33(
6498
+ "div",
6499
+ {
6500
+ role: "status",
6501
+ "data-slot": "bulk-action-bar-state",
6502
+ style: {
6503
+ display: "flex",
6504
+ flex: "1 1 auto",
6505
+ minWidth: 0,
6506
+ alignItems: "center",
6507
+ columnGap: "var(--space-3)"
6508
+ },
6509
+ children: [
6510
+ countLabel,
6511
+ summarySlot
6512
+ ]
6513
+ }
6514
+ ),
6515
+ /* @__PURE__ */ jsxs33(
6516
+ "div",
6517
+ {
6518
+ "data-slot": "bulk-action-bar-controls",
6519
+ style: {
6520
+ display: "flex",
6521
+ marginLeft: "auto",
6522
+ alignItems: "center",
6523
+ gap: "var(--space-1)"
6524
+ },
6525
+ children: [
6526
+ count > 0 && actions.length > 0 ? /* @__PURE__ */ jsx34(Divider, {}) : null,
6527
+ actionGroup,
6528
+ /* @__PURE__ */ jsx34(Divider, {}),
6529
+ /* @__PURE__ */ jsx34(ClearButton, { onClear })
6530
+ ]
6531
+ }
6532
+ )
6533
+ ]
6534
+ }
6535
+ );
6536
+ }
6092
6537
  return /* @__PURE__ */ jsxs33(
6093
6538
  "div",
6094
6539
  {
@@ -6101,59 +6546,13 @@ function BulkActionBar({
6101
6546
  "animate-[olun-bulk-action-bar-in_var(--dur-fast)_var(--ease-out)_both] motion-reduce:animate-none",
6102
6547
  className
6103
6548
  ),
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
- },
6549
+ style: { ...floatingStyle, ...style },
6122
6550
  ...props,
6123
6551
  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
- ),
6552
+ countLabel,
6553
+ summarySlot,
6141
6554
  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
- ),
6555
+ actionGroup,
6157
6556
  /* @__PURE__ */ jsx34(Divider, {}),
6158
6557
  /* @__PURE__ */ jsx34(ClearButton, { onClear })
6159
6558
  ]
@@ -6162,7 +6561,7 @@ function BulkActionBar({
6162
6561
  }
6163
6562
 
6164
6563
  // src/components/ui/confirm-dialog.tsx
6165
- import * as React28 from "react";
6564
+ import * as React29 from "react";
6166
6565
  import { Fragment as Fragment8, jsx as jsx35, jsxs as jsxs34 } from "react/jsx-runtime";
6167
6566
  var confirmDialogContentClassName = cn(
6168
6567
  "fixed left-1/2 top-1/2 z-[var(--z-modal)]",
@@ -6179,6 +6578,7 @@ var confirmDialogTitleClassName = cn(
6179
6578
  var confirmDialogDescriptionClassName = cn(
6180
6579
  "break-keep text-[14px] leading-relaxed text-text-secondary"
6181
6580
  );
6581
+ var confirmDialogBodyClassName = "min-w-0 text-[14px] leading-relaxed text-text-secondary";
6182
6582
  var confirmDialogFooterClassName = "flex items-center justify-end gap-[var(--space-3)]";
6183
6583
  function ConfirmDialog({
6184
6584
  open,
@@ -6189,12 +6589,14 @@ function ConfirmDialog({
6189
6589
  confirmLabel,
6190
6590
  cancelLabel = "\uCDE8\uC18C",
6191
6591
  onConfirm,
6192
- loading = false
6592
+ loading = false,
6593
+ children,
6594
+ confirmDisabled = false
6193
6595
  }) {
6194
- const [pending, setPending] = React28.useState(false);
6195
- const busyRef = React28.useRef(false);
6596
+ const [pending, setPending] = React29.useState(false);
6597
+ const busyRef = React29.useRef(false);
6196
6598
  const isBusy = loading || pending;
6197
- const handleOpenChange = React28.useCallback(
6599
+ const handleOpenChange = React29.useCallback(
6198
6600
  (nextOpen) => {
6199
6601
  if (!nextOpen && isBusy) {
6200
6602
  return;
@@ -6203,8 +6605,8 @@ function ConfirmDialog({
6203
6605
  },
6204
6606
  [isBusy, onOpenChange]
6205
6607
  );
6206
- const handleConfirm = React28.useCallback(async () => {
6207
- if (busyRef.current || loading) {
6608
+ const handleConfirm = React29.useCallback(async () => {
6609
+ if (busyRef.current || loading || confirmDisabled) {
6208
6610
  return;
6209
6611
  }
6210
6612
  busyRef.current = true;
@@ -6216,7 +6618,7 @@ function ConfirmDialog({
6216
6618
  busyRef.current = false;
6217
6619
  setPending(false);
6218
6620
  }
6219
- }, [loading, onConfirm, onOpenChange]);
6621
+ }, [confirmDisabled, loading, onConfirm, onOpenChange]);
6220
6622
  const dialogBody = /* @__PURE__ */ jsxs34(Fragment8, { children: [
6221
6623
  /* @__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
6624
  /* @__PURE__ */ jsx35(
@@ -6236,6 +6638,7 @@ function ConfirmDialog({
6236
6638
  }
6237
6639
  ) : null
6238
6640
  ] }) }),
6641
+ children != null ? /* @__PURE__ */ jsx35("div", { "data-slot": "confirm-dialog-body", className: confirmDialogBodyClassName, children }) : null,
6239
6642
  /* @__PURE__ */ jsxs34(
6240
6643
  "div",
6241
6644
  {
@@ -6248,6 +6651,7 @@ function ConfirmDialog({
6248
6651
  {
6249
6652
  variant: tone === "destructive" ? "destructive" : "primary",
6250
6653
  loading: isBusy,
6654
+ disabled: confirmDisabled,
6251
6655
  onClick: handleConfirm,
6252
6656
  children: confirmLabel
6253
6657
  }
@@ -6301,7 +6705,7 @@ function Avatar({
6301
6705
  "data-size": size,
6302
6706
  className: cn(
6303
6707
  "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",
6708
+ "border border-[var(--border-subtle)] bg-[var(--bg-inset)] text-text-secondary",
6305
6709
  "h-9 w-9",
6306
6710
  "data-[size=sm]:h-8 data-[size=sm]:w-8",
6307
6711
  "data-[size=lg]:h-11 data-[size=lg]:w-11",
@@ -6346,7 +6750,7 @@ function AvatarFallback({
6346
6750
  "data-slot": "avatar-fallback",
6347
6751
  className: cn(
6348
6752
  "flex h-full w-full items-center justify-center rounded-full",
6349
- "bg-[var(--bg-raised)] text-sm font-semibold text-text-secondary",
6753
+ "bg-[var(--bg-inset)] text-sm font-semibold text-text-secondary",
6350
6754
  "font-[family-name:var(--font-display)]",
6351
6755
  "group-data-[size=sm]/avatar:text-xs",
6352
6756
  className
@@ -6358,7 +6762,7 @@ function AvatarFallback({
6358
6762
  }
6359
6763
 
6360
6764
  // src/components/ui/badge.tsx
6361
- import * as React29 from "react";
6765
+ import * as React30 from "react";
6362
6766
  import { jsx as jsx38, jsxs as jsxs35 } from "react/jsx-runtime";
6363
6767
  var DOT = {
6364
6768
  error: "var(--semantic-error-fg)",
@@ -6368,7 +6772,7 @@ var DOT = {
6368
6772
  neutral: "var(--text-tertiary)",
6369
6773
  brand: "var(--accent-pop)"
6370
6774
  };
6371
- var Badge = React29.forwardRef(
6775
+ var Badge = React30.forwardRef(
6372
6776
  ({ tone = "neutral", className, children, ...props }, ref) => /* @__PURE__ */ jsxs35(
6373
6777
  "span",
6374
6778
  {
@@ -6400,7 +6804,7 @@ var Badge = React29.forwardRef(
6400
6804
  Badge.displayName = "Badge";
6401
6805
 
6402
6806
  // src/components/ui/two-tier-badge.tsx
6403
- import * as React30 from "react";
6807
+ import * as React31 from "react";
6404
6808
  import { jsx as jsx39, jsxs as jsxs36 } from "react/jsx-runtime";
6405
6809
  var FG = {
6406
6810
  neutral: "var(--text-secondary)",
@@ -6422,7 +6826,7 @@ var SEGMENT = {
6422
6826
  md: "gap-[7px] py-[var(--space-1)] px-[var(--space-3)] text-[14px]"
6423
6827
  };
6424
6828
  var DOT2 = { xs: 5, sm: 6, md: 7 };
6425
- var TwoTierBadge = React30.forwardRef(
6829
+ var TwoTierBadge = React31.forwardRef(
6426
6830
  ({ primary, secondary, tone = "neutral", emphasis = "subtle", size = "sm", showDot = false, className, style, ...props }, ref) => {
6427
6831
  const strong = emphasis === "strong";
6428
6832
  const fg = FG[tone];
@@ -6492,7 +6896,7 @@ var TwoTierBadge = React30.forwardRef(
6492
6896
  TwoTierBadge.displayName = "TwoTierBadge";
6493
6897
 
6494
6898
  // src/components/ui/status-badge.tsx
6495
- import * as React31 from "react";
6899
+ import * as React32 from "react";
6496
6900
  import { jsx as jsx40, jsxs as jsxs37 } from "react/jsx-runtime";
6497
6901
  var ROLE = {
6498
6902
  error: "error",
@@ -6522,7 +6926,7 @@ var SIZE = {
6522
6926
  sm: { cls: "gap-[6px] py-[var(--space-1)] px-[var(--space-2)] text-[12px]", dot: 6 },
6523
6927
  md: { cls: "gap-[var(--space-2)] py-[var(--space-1)] px-[14px] text-[14px]", dot: 7 }
6524
6928
  };
6525
- var StatusBadge = React31.forwardRef(
6929
+ var StatusBadge = React32.forwardRef(
6526
6930
  ({ status = "active", emphasis = "subtle", size = "sm", showDot = true, pulse = true, className, children, style, ...props }, ref) => {
6527
6931
  const role = ROLE[status];
6528
6932
  const fg = FG2[role];
@@ -6573,7 +6977,7 @@ var StatusBadge = React31.forwardRef(
6573
6977
  StatusBadge.displayName = "StatusBadge";
6574
6978
 
6575
6979
  // src/components/ui/notification-badge.tsx
6576
- import * as React32 from "react";
6980
+ import * as React33 from "react";
6577
6981
  import { cva as cva13 } from "class-variance-authority";
6578
6982
  import { jsx as jsx41 } from "react/jsx-runtime";
6579
6983
  var notificationBadgeVariants = cva13(
@@ -6606,7 +7010,7 @@ function formatNotificationCount(count, max) {
6606
7010
  }
6607
7011
  return String(count);
6608
7012
  }
6609
- var NotificationBadge = React32.forwardRef(
7013
+ var NotificationBadge = React33.forwardRef(
6610
7014
  ({
6611
7015
  className,
6612
7016
  count,
@@ -6639,7 +7043,7 @@ var NotificationBadge = React32.forwardRef(
6639
7043
  NotificationBadge.displayName = "NotificationBadge";
6640
7044
 
6641
7045
  // src/components/ui/empty-state.tsx
6642
- import * as React33 from "react";
7046
+ import * as React34 from "react";
6643
7047
  import { cva as cva14 } from "class-variance-authority";
6644
7048
  import { jsx as jsx42, jsxs as jsxs38 } from "react/jsx-runtime";
6645
7049
  var emptyStateBaseVariants = cva14(
@@ -6713,7 +7117,7 @@ var iconSize3 = {
6713
7117
  md: 20,
6714
7118
  lg: 24
6715
7119
  };
6716
- var EmptyState = React33.forwardRef(
7120
+ var EmptyState = React34.forwardRef(
6717
7121
  ({
6718
7122
  className,
6719
7123
  size = "md",
@@ -6781,7 +7185,7 @@ var EmptyState = React33.forwardRef(
6781
7185
  EmptyState.displayName = "EmptyState";
6782
7186
 
6783
7187
  // src/components/ui/error-state.tsx
6784
- import * as React34 from "react";
7188
+ import * as React35 from "react";
6785
7189
  import { AlertTriangle } from "lucide-react";
6786
7190
  import { cva as cva15 } from "class-variance-authority";
6787
7191
  import { jsx as jsx43, jsxs as jsxs39 } from "react/jsx-runtime";
@@ -6806,7 +7210,7 @@ var errorStateVariants = cva15(
6806
7210
  }
6807
7211
  }
6808
7212
  );
6809
- var ErrorState = React34.forwardRef(
7213
+ var ErrorState = React35.forwardRef(
6810
7214
  ({
6811
7215
  className,
6812
7216
  size = "md",
@@ -6869,12 +7273,14 @@ var ErrorState = React34.forwardRef(
6869
7273
  ErrorState.displayName = "ErrorState";
6870
7274
 
6871
7275
  // src/components/ui/skeleton.tsx
6872
- import * as React35 from "react";
7276
+ import * as React36 from "react";
6873
7277
  import { cva as cva16 } from "class-variance-authority";
6874
7278
  import { jsx as jsx44 } from "react/jsx-runtime";
6875
7279
  var skeletonVariants = cva16(
6876
7280
  [
6877
- "animate-pulse rounded-[var(--radius-sm)] bg-[var(--bg-raised)]",
7281
+ // bg-inset: light #F1F2F4 reads on white surfaces (bg-raised was white = invisible);
7282
+ // dark value equals the old bg-raised (#16181D), so dark mode is unchanged.
7283
+ "animate-pulse rounded-[var(--radius-sm)] bg-[var(--bg-inset)]",
6878
7284
  "motion-reduce:animate-none motion-reduce:opacity-80"
6879
7285
  ].join(" "),
6880
7286
  {
@@ -6913,7 +7319,7 @@ var spinnerVariants = cva16(
6913
7319
  }
6914
7320
  }
6915
7321
  );
6916
- var Skeleton = React35.forwardRef(
7322
+ var Skeleton = React36.forwardRef(
6917
7323
  ({ className, shape, style, ...props }, ref) => /* @__PURE__ */ jsx44(
6918
7324
  "div",
6919
7325
  {
@@ -6927,7 +7333,7 @@ var Skeleton = React35.forwardRef(
6927
7333
  );
6928
7334
  Skeleton.displayName = "Skeleton";
6929
7335
  var defaultTextWidths = ["92%", "84%", "68%", "76%"];
6930
- var SkeletonText = React35.forwardRef(
7336
+ var SkeletonText = React36.forwardRef(
6931
7337
  ({ className, lines = 3, widths = defaultTextWidths, ...props }, ref) => /* @__PURE__ */ jsx44(
6932
7338
  "div",
6933
7339
  {
@@ -6947,7 +7353,7 @@ var SkeletonText = React35.forwardRef(
6947
7353
  )
6948
7354
  );
6949
7355
  SkeletonText.displayName = "SkeletonText";
6950
- var SkeletonRow = React35.forwardRef(
7356
+ var SkeletonRow = React36.forwardRef(
6951
7357
  ({
6952
7358
  className,
6953
7359
  columns,
@@ -7004,7 +7410,7 @@ var SkeletonRow = React35.forwardRef(
7004
7410
  }
7005
7411
  );
7006
7412
  SkeletonRow.displayName = "SkeletonRow";
7007
- var Spinner = React35.forwardRef(
7413
+ var Spinner = React36.forwardRef(
7008
7414
  ({ className, size, label = "\uB85C\uB529 \uC911", ...props }, ref) => /* @__PURE__ */ jsx44(
7009
7415
  "span",
7010
7416
  {
@@ -7020,7 +7426,7 @@ var Spinner = React35.forwardRef(
7020
7426
  Spinner.displayName = "Spinner";
7021
7427
 
7022
7428
  // src/components/ui/pagination.tsx
7023
- import * as React36 from "react";
7429
+ import * as React37 from "react";
7024
7430
  import { ChevronsLeft, ChevronLeft, ChevronRight as ChevronRight2, ChevronsRight } from "lucide-react";
7025
7431
  import { cva as cva17 } from "class-variance-authority";
7026
7432
 
@@ -7161,7 +7567,7 @@ function NavigationButton({
7161
7567
  }
7162
7568
  );
7163
7569
  }
7164
- var Pagination = React36.forwardRef(
7570
+ var Pagination = React37.forwardRef(
7165
7571
  ({
7166
7572
  className,
7167
7573
  size = "standard",
@@ -7303,7 +7709,7 @@ import { jsx as jsx47, jsxs as jsxs42 } from "react/jsx-runtime";
7303
7709
  var modeToggleVariants = cva18(
7304
7710
  [
7305
7711
  "inline-flex items-center rounded-[var(--radius-sm)] border border-[var(--border-subtle)]",
7306
- "bg-[var(--bg-raised)] p-[2px]"
7712
+ "bg-[var(--bg-inset)] p-[2px]"
7307
7713
  ].join(" ")
7308
7714
  );
7309
7715
  var modeToggleItemVariants = cva18(
@@ -7367,7 +7773,7 @@ function ModeToggle({
7367
7773
  }
7368
7774
 
7369
7775
  // src/components/ui/action-footer.tsx
7370
- import * as React37 from "react";
7776
+ import * as React38 from "react";
7371
7777
  import { cva as cva19 } from "class-variance-authority";
7372
7778
  import { jsx as jsx48, jsxs as jsxs43 } from "react/jsx-runtime";
7373
7779
  var actionFooterVariants = cva19(
@@ -7454,7 +7860,7 @@ function renderAction(action) {
7454
7860
  action.id ?? action.label
7455
7861
  );
7456
7862
  }
7457
- var ActionFooter = React37.forwardRef(
7863
+ var ActionFooter = React38.forwardRef(
7458
7864
  ({
7459
7865
  className,
7460
7866
  status,
@@ -7524,7 +7930,7 @@ var ActionFooter = React37.forwardRef(
7524
7930
  ActionFooter.displayName = "ActionFooter";
7525
7931
 
7526
7932
  // src/components/ui/toast.tsx
7527
- import * as React38 from "react";
7933
+ import * as React39 from "react";
7528
7934
  import { cva as cva20 } from "class-variance-authority";
7529
7935
  import { jsx as jsx49, jsxs as jsxs44 } from "react/jsx-runtime";
7530
7936
  var DOT3 = {
@@ -7644,7 +8050,7 @@ var toast = {
7644
8050
  }
7645
8051
  };
7646
8052
  var Toast = toast;
7647
- var ToastRoot = React38.forwardRef(
8053
+ var ToastRoot = React39.forwardRef(
7648
8054
  ({
7649
8055
  className,
7650
8056
  id,
@@ -7661,10 +8067,10 @@ var ToastRoot = React38.forwardRef(
7661
8067
  ...props
7662
8068
  }, ref) => {
7663
8069
  const durationMs = getToastDuration({ message, variant, duration });
7664
- const onCloseRef = React38.useRef(onClose);
8070
+ const onCloseRef = React39.useRef(onClose);
7665
8071
  onCloseRef.current = onClose;
7666
- const timerRef = React38.useRef(null);
7667
- React38.useEffect(() => {
8072
+ const timerRef = React39.useRef(null);
8073
+ React39.useEffect(() => {
7668
8074
  if (!onCloseRef.current) return void 0;
7669
8075
  const timer = createToastTimer(durationMs, () => onCloseRef.current?.());
7670
8076
  timerRef.current = timer;
@@ -7761,7 +8167,7 @@ var ToastRoot = React38.forwardRef(
7761
8167
  }
7762
8168
  );
7763
8169
  ToastRoot.displayName = "ToastRoot";
7764
- var ToastViewport = React38.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx49(
8170
+ var ToastViewport = React39.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx49(
7765
8171
  "ol",
7766
8172
  {
7767
8173
  ref,
@@ -7772,8 +8178,8 @@ var ToastViewport = React38.forwardRef(({ className, ...props }, ref) => /* @__P
7772
8178
  ));
7773
8179
  ToastViewport.displayName = "ToastViewport";
7774
8180
  function ToastProvider({ children, maxToasts = 3, className, ...props }) {
7775
- const [items, setItems] = React38.useState([]);
7776
- React38.useEffect(
8181
+ const [items, setItems] = React39.useState([]);
8182
+ React39.useEffect(
7777
8183
  () => toast.subscribe((nextItems) => {
7778
8184
  setItems(limitToasts(nextItems, maxToasts));
7779
8185
  }),
@@ -7786,7 +8192,7 @@ function ToastProvider({ children, maxToasts = 3, className, ...props }) {
7786
8192
  }
7787
8193
 
7788
8194
  // src/components/ui/otp-field.tsx
7789
- import * as React39 from "react";
8195
+ import * as React40 from "react";
7790
8196
  import { cva as cva21 } from "class-variance-authority";
7791
8197
  import { AlertCircle as AlertCircle4 } from "lucide-react";
7792
8198
  import { Fragment as Fragment9, jsx as jsx50, jsxs as jsxs45 } from "react/jsx-runtime";
@@ -7819,14 +8225,14 @@ var otpFieldGroupVariants = cva21("flex items-start gap-[var(--space-2)] pt-[var
7819
8225
  });
7820
8226
  var otpCellVariants = cva21(
7821
8227
  [
7822
- "flex items-center justify-center rounded-[var(--radius-sm)] border",
8228
+ "flex items-center justify-center rounded-[var(--radius-md)] border",
7823
8229
  "border-[var(--border)] text-foreground",
7824
8230
  "font-[family-name:var(--font-display)] tabular-nums",
7825
8231
  "text-center leading-none caret-[var(--text-primary)]",
7826
8232
  "transition-[border-color,box-shadow,background-color,color] motion-reduce:transition-none",
7827
8233
  "focus:outline-none focus:border-[var(--focus-ring)] focus:shadow-[0_0_0_1px_var(--focus-ring)]",
7828
8234
  "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",
8235
+ "read-only:cursor-default read-only:bg-[var(--bg-inset)] read-only:text-text-secondary",
7830
8236
  "aria-[invalid=true]:border-[var(--error)] aria-[invalid=true]:focus:shadow-[0_0_0_1px_var(--error)]",
7831
8237
  "data-[state=error]:border-[var(--error)] data-[state=error]:focus:shadow-[0_0_0_1px_var(--error)]",
7832
8238
  "data-[state=filled]:border-[var(--border-strong)]"
@@ -7916,7 +8322,7 @@ function fillDigitsFromIndex(slots, index, inserted, length) {
7916
8322
  focusIndex: Math.min(cursor, length - 1)
7917
8323
  };
7918
8324
  }
7919
- var OTPField = React39.forwardRef(
8325
+ var OTPField = React40.forwardRef(
7920
8326
  ({
7921
8327
  className,
7922
8328
  defaultValue,
@@ -7938,24 +8344,24 @@ var OTPField = React39.forwardRef(
7938
8344
  required,
7939
8345
  ...props
7940
8346
  }, ref) => {
7941
- const generatedId = React39.useId();
8347
+ const generatedId = React40.useId();
7942
8348
  const fieldId = id ?? generatedId;
7943
8349
  const labelId = `${fieldId}-label`;
7944
8350
  const helperId = helperText ? `${fieldId}-helper` : void 0;
7945
8351
  const errorId = errorText ? `${fieldId}-error` : void 0;
7946
8352
  const resolvedState = invalid || errorText ? "error" : "default";
7947
8353
  const isControlled = value !== void 0;
7948
- const [uncontrolledSlots, setUncontrolledSlots] = React39.useState(
8354
+ const [uncontrolledSlots, setUncontrolledSlots] = React40.useState(
7949
8355
  () => toDigitsArray(onlyDigits(defaultValue, length), length)
7950
8356
  );
7951
- const resolvedSlots = React39.useMemo(
8357
+ const resolvedSlots = React40.useMemo(
7952
8358
  () => isControlled ? toDigitsArray(onlyDigits(value, length), length) : uncontrolledSlots,
7953
8359
  [isControlled, length, uncontrolledSlots, value]
7954
8360
  );
7955
8361
  const resolvedValue = joinDigits(resolvedSlots).slice(0, length);
7956
- const cellsRef = React39.useRef([]);
8362
+ const cellsRef = React40.useRef([]);
7957
8363
  const groupDescribedBy = [helperId, errorId].filter(Boolean).join(" ") || void 0;
7958
- const setResolvedValue = React39.useCallback(
8364
+ const setResolvedValue = React40.useCallback(
7959
8365
  (nextSlots, focusIndex) => {
7960
8366
  const normalizedSlots = nextSlots.slice(0, length);
7961
8367
  const normalizedValue = joinDigits(normalizedSlots).slice(0, length);
@@ -8145,7 +8551,7 @@ var OTPField = React39.forwardRef(
8145
8551
  OTPField.displayName = "OTPField";
8146
8552
 
8147
8553
  // src/components/ui/search-field.tsx
8148
- import * as React40 from "react";
8554
+ import * as React41 from "react";
8149
8555
  import { Search, X as X4 } from "lucide-react";
8150
8556
  import { cva as cva22 } from "class-variance-authority";
8151
8557
  import { jsx as jsx51, jsxs as jsxs46 } from "react/jsx-runtime";
@@ -8164,7 +8570,7 @@ var searchFieldVariants = cva22("relative flex w-full items-center", {
8164
8570
  });
8165
8571
  var searchFieldInputVariants = cva22(
8166
8572
  [
8167
- "h-full w-full rounded-[var(--radius-sm)] border",
8573
+ "h-full w-full rounded-[var(--radius-md)] border",
8168
8574
  "border-[var(--border)]",
8169
8575
  "appearance-none [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden",
8170
8576
  "font-[family-name:var(--font-body)] text-foreground",
@@ -8213,7 +8619,7 @@ var iconSize4 = {
8213
8619
  function hasVisibleValue(value) {
8214
8620
  return typeof value === "string" ? value.length > 0 : value !== void 0;
8215
8621
  }
8216
- var SearchField = React40.forwardRef(
8622
+ var SearchField = React41.forwardRef(
8217
8623
  ({
8218
8624
  className,
8219
8625
  inputMode = "search",
@@ -8229,12 +8635,12 @@ var SearchField = React40.forwardRef(
8229
8635
  ...props
8230
8636
  }, ref) => {
8231
8637
  const resolvedSize = size ?? "md";
8232
- const inputRef = React40.useRef(null);
8233
- const [hasText, setHasText] = React40.useState(
8638
+ const inputRef = React41.useRef(null);
8639
+ const [hasText, setHasText] = React41.useState(
8234
8640
  () => hasVisibleValue(value ?? defaultValue)
8235
8641
  );
8236
- React40.useImperativeHandle(ref, () => inputRef.current);
8237
- React40.useEffect(() => {
8642
+ React41.useImperativeHandle(ref, () => inputRef.current);
8643
+ React41.useEffect(() => {
8238
8644
  if (value !== void 0) {
8239
8645
  setHasText(hasVisibleValue(value));
8240
8646
  }
@@ -8304,7 +8710,7 @@ var SearchField = React40.forwardRef(
8304
8710
  SearchField.displayName = "SearchField";
8305
8711
 
8306
8712
  // src/components/ui/checkbox-group.tsx
8307
- import * as React41 from "react";
8713
+ import * as React42 from "react";
8308
8714
  import { cva as cva23 } from "class-variance-authority";
8309
8715
  import { jsx as jsx52 } from "react/jsx-runtime";
8310
8716
  var checkboxGroupVariants = cva23("gap-[var(--space-2)]", {
@@ -8332,7 +8738,7 @@ var checkboxGroupVariants = cva23("gap-[var(--space-2)]", {
8332
8738
  function toSelectionSet(value) {
8333
8739
  return new Set(value ?? []);
8334
8740
  }
8335
- var CheckboxGroup = React41.forwardRef(
8741
+ var CheckboxGroup = React42.forwardRef(
8336
8742
  ({
8337
8743
  className,
8338
8744
  children,
@@ -8348,14 +8754,14 @@ var CheckboxGroup = React41.forwardRef(
8348
8754
  ...props
8349
8755
  }, ref) => {
8350
8756
  const isControlled = value != null;
8351
- const [uncontrolledValue, setUncontrolledValue] = React41.useState(
8757
+ const [uncontrolledValue, setUncontrolledValue] = React42.useState(
8352
8758
  () => toSelectionSet(defaultValue)
8353
8759
  );
8354
- const selectedValues = React41.useMemo(
8760
+ const selectedValues = React42.useMemo(
8355
8761
  () => isControlled ? toSelectionSet(value) : uncontrolledValue,
8356
8762
  [isControlled, uncontrolledValue, value]
8357
8763
  );
8358
- const toggleValue = React41.useCallback(
8764
+ const toggleValue = React42.useCallback(
8359
8765
  (itemValue, checked) => {
8360
8766
  const nextValues = new Set(selectedValues);
8361
8767
  if (checked) {
@@ -8371,7 +8777,7 @@ var CheckboxGroup = React41.forwardRef(
8371
8777
  },
8372
8778
  [isControlled, onValueChange, selectedValues]
8373
8779
  );
8374
- const contextValue = React41.useMemo(
8780
+ const contextValue = React42.useMemo(
8375
8781
  () => ({
8376
8782
  appearance,
8377
8783
  density,
@@ -8406,7 +8812,7 @@ var CheckboxGroup = React41.forwardRef(
8406
8812
  CheckboxGroup.displayName = "CheckboxGroup";
8407
8813
 
8408
8814
  // src/components/ui/radio-group.tsx
8409
- import * as React42 from "react";
8815
+ import * as React43 from "react";
8410
8816
  import { cva as cva24 } from "class-variance-authority";
8411
8817
  import { Circle } from "lucide-react";
8412
8818
  import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
@@ -8499,11 +8905,11 @@ var radioControlVariants = cva24(
8499
8905
  }
8500
8906
  }
8501
8907
  );
8502
- var RadioGroupContext = React42.createContext({
8908
+ var RadioGroupContext = React43.createContext({
8503
8909
  appearance: "plain",
8504
8910
  density: "comfortable"
8505
8911
  });
8506
- var RadioGroup = React42.forwardRef(
8912
+ var RadioGroup = React43.forwardRef(
8507
8913
  ({
8508
8914
  className,
8509
8915
  children,
@@ -8514,7 +8920,7 @@ var RadioGroup = React42.forwardRef(
8514
8920
  required,
8515
8921
  ...props
8516
8922
  }, ref) => {
8517
- const contextValue = React42.useMemo(
8923
+ const contextValue = React43.useMemo(
8518
8924
  () => ({ appearance, density }),
8519
8925
  [appearance, density]
8520
8926
  );
@@ -8541,9 +8947,9 @@ var RadioGroup = React42.forwardRef(
8541
8947
  }
8542
8948
  );
8543
8949
  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();
8950
+ var Radio = React43.forwardRef(({ className, children, description, id, ...props }, ref) => {
8951
+ const { appearance, density } = React43.useContext(RadioGroupContext);
8952
+ const generatedId = React43.useId();
8547
8953
  const radioId = id ?? generatedId;
8548
8954
  const labelId = `${radioId}-label`;
8549
8955
  const descriptionId = description ? `${radioId}-description` : void 0;
@@ -8711,7 +9117,7 @@ function Progress({
8711
9117
  }
8712
9118
 
8713
9119
  // src/components/ui/tree-view.tsx
8714
- import * as React43 from "react";
9120
+ import * as React44 from "react";
8715
9121
  import { ChevronRight as ChevronRight3 } from "lucide-react";
8716
9122
  import { cva as cva25 } from "class-variance-authority";
8717
9123
  import { jsx as jsx56, jsxs as jsxs49 } from "react/jsx-runtime";
@@ -8817,27 +9223,27 @@ function TreeView({
8817
9223
  ...rest
8818
9224
  }) {
8819
9225
  const isExpandedControlled = expandedIds !== void 0;
8820
- const [internalExpanded, setInternalExpanded] = React43.useState(
9226
+ const [internalExpanded, setInternalExpanded] = React44.useState(
8821
9227
  () => defaultExpandedIds ?? []
8822
9228
  );
8823
9229
  const expandedList = isExpandedControlled ? expandedIds : internalExpanded;
8824
- const expandedSet = React43.useMemo(() => new Set(expandedList), [expandedList]);
8825
- const items = React43.useMemo(
9230
+ const expandedSet = React44.useMemo(() => new Set(expandedList), [expandedList]);
9231
+ const items = React44.useMemo(
8826
9232
  () => flattenVisibleNodes(nodes, getChildren, expandedSet),
8827
9233
  [nodes, getChildren, expandedSet]
8828
9234
  );
8829
- const [focusedId, setFocusedId] = React43.useState(null);
9235
+ const [focusedId, setFocusedId] = React44.useState(null);
8830
9236
  const visibleIds = items.map((item) => item.id);
8831
9237
  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(
9238
+ const itemRefs = React44.useRef(/* @__PURE__ */ new Map());
9239
+ const commitExpanded = React44.useCallback(
8834
9240
  (next) => {
8835
9241
  if (!isExpandedControlled) setInternalExpanded(next);
8836
9242
  onExpandedChange?.(next);
8837
9243
  },
8838
9244
  [isExpandedControlled, onExpandedChange]
8839
9245
  );
8840
- const setExpanded = React43.useCallback(
9246
+ const setExpanded = React44.useCallback(
8841
9247
  (id, expand) => {
8842
9248
  const has = expandedSet.has(id);
8843
9249
  if (expand && !has) {
@@ -8848,7 +9254,7 @@ function TreeView({
8848
9254
  },
8849
9255
  [commitExpanded, expandedList, expandedSet]
8850
9256
  );
8851
- const focusItem = React43.useCallback((id) => {
9257
+ const focusItem = React44.useCallback((id) => {
8852
9258
  setFocusedId(id);
8853
9259
  itemRefs.current.get(id)?.focus();
8854
9260
  }, []);