@olundot/ui 0.20.0-next.12 → 0.20.0-next.13

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/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # @olundot/ui
2
2
 
3
+ ## 0.20.0-next.13
4
+
5
+ ### Minor Changes
6
+
7
+ - 21aac35: `FilterToolbar`에 `layout`과 `appliedFilters`를 더했다.
8
+
9
+ - `layout="grid"`: 640px 이상에서 컨트롤 줄이 폭과 관계없이 한 줄이고 줄 폭을 다 채운다. 칸 비율은 칸 안 컨트롤 종류로 정한다. 검색 3, Select 2, 그 밖의 컨트롤(Combobox 등) 3, Chip과 접힌 필터 버튼, 보조 작업은 내용 폭이다. `filters`의 자식 하나가 칸 하나이고, 3개를 넘기면 개발 빌드에서 경고한다. 640px 이상에서 `collapsible`은 늘 접힌 필터 버튼 뒤에 있다. 640px 미만은 flow와 같다.
10
+ - `appliedFilters`: 640px 이상에서 툴바 아래에 고른 값을 지울 수 있는 Chip 줄로 보이고, `onClearFilters`가 있으면 끝에 "해제"를 둔다. 칩을 지우면 포커스가 다음 칩, 이전 칩, 접힌 필터 버튼 순으로 간다. 640px 미만은 지금처럼 적용 개수 줄이다. `activeFilterCount`를 주지 않으면 칩 개수를 쓴다. `layout`과 따로 쓴다.
11
+ - 기본값 `layout="flow"`와 `appliedFilters`가 없을 때의 출력은 그대로다.
12
+ - 타입 `FilterToolbarLayout`, `FilterToolbarAppliedFilter`를 내보낸다.
13
+
14
+ - 9b17d12: `ModeToggle`에 항목별 개수 `ModeToggleOption.count`를 추가했다. 라벨 뒤에 Tabs badge와 같은 개수 알약이 붙는다.
15
+ - 모양: 테두리가 없고, 채움은 `--action-selected` 반투명 겹침 하나라 바탕 위와 선택된 항목 위에서 모양이 같다. 숫자는 항목 글자색을 따르고 `tabular-nums`다. 높이 16px이라 항목 높이는 그대로다.
16
+ - 100부터 `99+`로 보인다. 0도 보인다. 숨기려면 값을 넘기지 않는다. 음수와 `NaN`은 보이지 않는다(개발 모드에서 경고).
17
+ - 항목 접근 이름에 실제 수가 들어간다. "오답 3"처럼 읽고, `99+`로 보이는 항목도 "전체 128"처럼 읽는다. 보이는 알약은 `aria-hidden`이다.
18
+ - `count`가 없으면 출력은 그대로다.
19
+
20
+ ### Patch Changes
21
+
22
+ - 044b1c0: DataTable `rowActions` 우클릭 메뉴가 열린 채 창이 1024px 미만으로 줄어 카드로 바뀌면 메뉴를 닫는다.
23
+
24
+ - 항목은 실행하지 않는다. 포커스가 메뉴에 있었다면 그 행 카드의 행 메뉴 버튼으로 옮기고, 버튼이 없으면 카드로, 카드도 없으면 카드 목록으로 옮긴다. 포커스가 메뉴 밖이면 건드리지 않는다.
25
+ - `virtual` 표도 같다.
26
+ - 항목을 고른 직후 카드로 바뀌면 그 항목은 한 번 실행하고, 포커스는 그 카드의 행 메뉴 버튼으로 돌아간다.
27
+
28
+ - 30fd979: `--olun-bulk-bar-offset` 값이 화면 아래 끝에서 떠 있는 `BulkActionBar` 카드 위 끝까지의 거리(px)가 된다. 이름은 그대로다.
29
+ - 카드가 화면 아래 16px에 붙은 보통 경우는 예전 값(카드 높이 + `16px + env(safe-area-inset-bottom)`)과 같다.
30
+ - 목록 끝에서 카드가 페이지 푸터 위로 밀려 올라가도 값이 따라간다. 스크롤(문서와 스크롤 조상), 창 크기, 카드 크기가 바뀌거나 다른 이유로 카드가 화면에서 움직이면 다음 프레임에 다시 잰다. 그래서 `ToastViewport`가 카드 윗부분을 덮지 않는다.
31
+ - 카드 아래 끝이 화면 아래 끝에서 204px보다 높으면(짧은 목록이나 화면 밖) 값을 두지 않는다. 한 줄 토스트 3개가 아래 24px 자리에서 카드 밑에 들어간다.
32
+ - 값을 `calc(var(--olun-bulk-bar-offset, 0px) + …)`로 읽는 소비자 토스트는 고치지 않아도 된다.
33
+
3
34
  ## 0.20.0-next.12
4
35
 
5
36
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -857,6 +857,12 @@ interface InfoModalProps {
857
857
  declare function InfoModal({ open, onOpenChange, title, description, children, closeLabel, footer, }: InfoModalProps): react_jsx_runtime.JSX.Element;
858
858
 
859
859
  type FilterToolbarCollapseBelow = "sm" | "md" | "lg" | "never";
860
+ type FilterToolbarLayout = "flow" | "grid";
861
+ interface FilterToolbarAppliedFilter {
862
+ key: string;
863
+ label: string;
864
+ onRemove: () => void;
865
+ }
860
866
  interface FilterToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
861
867
  search?: React.ReactNode;
862
868
  filters?: React.ReactNode;
@@ -868,6 +874,8 @@ interface FilterToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
868
874
  resultSummary?: React.ReactNode;
869
875
  clearAction?: React.ReactNode;
870
876
  actions?: React.ReactNode;
877
+ layout?: FilterToolbarLayout;
878
+ appliedFilters?: FilterToolbarAppliedFilter[];
871
879
  }
872
880
  declare const FilterToolbar: React.ForwardRefExoticComponent<FilterToolbarProps & React.RefAttributes<HTMLDivElement>>;
873
881
 
@@ -1292,8 +1300,8 @@ type BulkActionBarProps = BulkActionBarBaseProps & ({
1292
1300
  * with the clear (✕) button on its right, `summary` under it at full width, then
1293
1301
  * the actions as outline sm Buttons aligned right, wrapping onto more lines when
1294
1302
  * they do not fit. `variant: "destructive"` is outline danger. The card is
1295
- * min(360px, its slot − 32px) wide; while it is mounted the document root carries
1296
- * `--olun-bulk-bar-offset` (its height + bottom gap) for toasts to clear it.
1303
+ * min(360px, its slot − 32px) wide; while it is near the viewport bottom the document root
1304
+ * carries `--olun-bulk-bar-offset` (viewport bottom → card top) for toasts to clear it.
1297
1305
  * DOM order stays count → actions → ✕. It takes no `trailing`.
1298
1306
  *
1299
1307
  * placement="inline" (deprecated) is a top-sticky band that replaces the status
@@ -1966,6 +1974,8 @@ interface ModeToggleOption<T extends string> {
1966
1974
  value: T;
1967
1975
  label: string;
1968
1976
  icon?: LucideIcon;
1977
+ /** 라벨 뒤 개수 알약입니다. 0 이상의 정수만 보이고, 100부터는 `99+`로 보입니다. 접근 이름에는 실제 수가 들어갑니다. */
1978
+ count?: number;
1969
1979
  disabled?: boolean;
1970
1980
  }
1971
1981
  interface ModeToggleProps<T extends string> extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
@@ -2335,4 +2345,4 @@ interface OrganizationLogoProps extends Omit<SVGProps<SVGSVGElement>, "width" |
2335
2345
  }
2336
2346
  declare function OrganizationLogo({ organization, size, className, style, role, "aria-label": ariaLabel, "aria-hidden": ariaHidden, ...props }: OrganizationLogoProps): react_jsx_runtime.JSX.Element;
2337
2347
 
2338
- export { ActionFooter, type ActionFooterAction, type ActionFooterProps, type ActionFooterStatus, ActionMenu, type ActionMenuGroup, type ActionMenuItem, type ActionMenuProps, ActionSheet, type ActionSheetIconProps, type ActionSheetItem, type ActionSheetProps, Alert, AlertDialog, type AlertDialogProps, type AlertDialogTone, type AlertEmphasis, type AlertMarker, type AlertProps, type AlertVariant, Avatar, AvatarFallback, type AvatarFallbackProps, AvatarImage, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, BulkActionBar, type BulkActionBarAction, type BulkActionBarProps, Button, type ButtonProps, type ButtonSize, Card, CardContent, CardDescription, CardFooter, CardHeader, type CardProps, CardProse, CardTitle, type CardVariant, type CardVariantOptions, Checkbox, type CheckboxCheckedState, CheckboxGroup, type CheckboxGroupAppearance, type CheckboxGroupColumns, CheckboxGroupContext, type CheckboxGroupContextValue, type CheckboxGroupDensity, type CheckboxGroupOrientation, type CheckboxGroupProps, type CheckboxProps, type CheckboxSize, Chip, type ChipProps, type ChipSize, type ChipTone, type ChipVariant, ConfirmDialog, type ConfirmDialogProps, ContextMenu, type ContextMenuPosition, type ContextMenuProps, CopyButton, type CopyButtonProps, DataLayout, type DataLayoutProps, DataTable, type DataTableColumn, type DataTableLinkProps, type DataTableMobileCardContent, type DataTableMobileMetaLine, type DataTableProps, type DataTableRowActions, type DataTableSort, type DataTableVirtualHandle, type DataTableVirtualOptions, type DataTableVirtualRange, DetailLayout, type DetailLayoutProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogEyebrow, DialogFooter, DialogHeader, type DialogMobilePresentation, DialogOverlay, DialogPortal, type DialogProps, DialogRawContent, type DialogSize, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateLayout, type EmptyStateProps, type EmptyStateSize, type EmptyStateTone, type EmptyStateVariantOptions, ErrorState, type ErrorStateProps, FileUploadZone, type FileUploadZoneProps, FilterToolbar, type FilterToolbarCollapseBelow, type FilterToolbarProps, FormField, type FormFieldProps, type FormFieldRenderProps, FormLayout, type FormLayoutProps, type FormLayoutSection, FormModal, type FormModalProps, IconButton, type IconButtonBadgeTone, type IconButtonProps, type IconButtonSize, InfoModal, type InfoModalProps, InlineAlert, type InlineAlertProps, Input, type InputProps, type InputSize, type InputSizeLegacy, type InputState, Label, type LabelProps, ListCard, type ListCardActions, type ListCardProps, ModeToggle, type ModeToggleOption, type ModeToggleProps, NotificationBadge, type NotificationBadgePlacement, type NotificationBadgeProps, type NotificationBadgeSize, OTPField, type OTPFieldProps, type OTPFieldSize, OlunLogo, OlunSymbol, type OlunTheme, type OlunThemeDuration, type OlunThemeState, OrganizationLogo, type OrganizationLogoId, type OrganizationLogoProps, type OverlayScrim, PageHeader, type PageHeaderProps, PageSizeSelect, type PageSizeSelectProps, type PageSizeSelectSize, Pagination, type PaginationProps, type PaginationSize, PermissionContext, type PermissionEvaluator, type PermissionFallback, PermissionGuard, type PermissionGuardProps, PermissionProvider, type PermissionProviderProps, type PermissionScope, type PermissionState, Popover, PopoverAnchor, PopoverArrow, PopoverClose, PopoverContent, type PopoverContentProps, type PopoverProps, PopoverTrigger, Progress, type ProgressProps, type ProgressStatus, Radio, RadioGroup, type RadioGroupAppearance, type RadioGroupColumns, type RadioGroupDensity, type RadioGroupOrientation, type RadioGroupProps, type RadioProps, ScrollArea, ScrollBar, SearchField, type SearchFieldProps, type SearchFieldSize, Select, type SelectAppearance, type SelectOption, type SelectProps, type SelectSize, type SelectState, type SelectVariant, type SemanticRole, Separator, type SetOlunThemeOptions, Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetDrawer, type SheetDrawerProps, SheetFooter, SheetHeader, type SheetMotionConfig, type SheetMotionDuration, type SheetMotionEasing, type SheetMotionType, type SheetProps, type SheetSize, SheetTitle, SheetTrigger, Skeleton, type SkeletonProps, SkeletonRow, type SkeletonRowColumn, type SkeletonRowProps, SkeletonText, type SkeletonTextProps, Spinner, type SpinnerProps, StatusBadge, type StatusBadgeEmphasis, type StatusBadgeProps, type StatusBadgeSize, type StatusBadgeStatus, StatusDot, type StatusDotProps, type StatusDotTone, type SurfaceLevel, Switch, type SwitchProps, TABLE_PAGE_SIZE_OPTIONS, type TabItem, TablePagination, type TablePaginationProps, type TablePaginationVisibility, Tabs, TabsContent, type TabsProps, type TabsVariant, Textarea, type TextareaProps, type TextareaSize, type TextareaSizeLegacy, type TextareaState, ThemeFab, type ThemeFabProps, ThemeToggle, Toast, type ToastAction, type ToastItem, type ToastOptions, ToastProvider, ToastRoot, type ToastVariant, ToastViewport, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type TreeItemRowVariantProps, type TreeItemState, type TreeNode, TreeView, type TreeViewProps, TwoTierBadge, type TwoTierBadgeEmphasis, type TwoTierBadgeProps, type TwoTierBadgeSize, type TwoTierBadgeTone, type UseOlunThemeValue, WizardLayout, type WizardLayoutProps, type WizardStep, type WizardStepState, actionFooterStatusVariants, actionFooterVariants, buttonVariants, cardVariants, checkboxBoxVariants, checkboxFieldVariants, checkboxGroupBoxVariants, checkboxGroupItemVariants, checkboxGroupVariants, checkboxRootVariants, checkboxVariants, chipCountVariants, chipVariants, cn, dataTableRowVariants, dataTableVariants, emptyStateIconVariants, emptyStateVariants, errorStateVariants, evaluatePermission, fileUploadIconVariants, fileUploadZoneVariants, formFieldControlStackClassName, formFieldDescriptionClassName, formFieldErrorClassName, formFieldLabelClassName, formFieldRequiredClassName, formFieldVariants, getOlunThemeState, getToastDuration, iconButtonBadgeVariants, iconButtonVariants, inputMessageVariants, inputVariants, limitToasts, modeToggleItemVariants, modeToggleVariants, notificationBadgeVariants, otpCellVariants, otpFieldGroupVariants, otpFieldMessageVariants, otpFieldVariants, paginationControlVariants, paginationVariants, radioControlVariants, radioGroupVariants, radioVariants, searchFieldInputVariants, searchFieldVariants, selectMessageVariants, selectVariants, setOlunTheme, shouldHideTablePagination, skeletonVariants, spinnerVariants, tabsBadgeVariants, tabsListVariants, tabsRootVariants, tabsTriggerVariants, textareaMessageVariants, textareaVariants, toast, toastVariants, toastViewportVariants, treeItemRowVariants, treeViewVariants, useOlunTheme, usePermission };
2348
+ export { ActionFooter, type ActionFooterAction, type ActionFooterProps, type ActionFooterStatus, ActionMenu, type ActionMenuGroup, type ActionMenuItem, type ActionMenuProps, ActionSheet, type ActionSheetIconProps, type ActionSheetItem, type ActionSheetProps, Alert, AlertDialog, type AlertDialogProps, type AlertDialogTone, type AlertEmphasis, type AlertMarker, type AlertProps, type AlertVariant, Avatar, AvatarFallback, type AvatarFallbackProps, AvatarImage, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, BulkActionBar, type BulkActionBarAction, type BulkActionBarProps, Button, type ButtonProps, type ButtonSize, Card, CardContent, CardDescription, CardFooter, CardHeader, type CardProps, CardProse, CardTitle, type CardVariant, type CardVariantOptions, Checkbox, type CheckboxCheckedState, CheckboxGroup, type CheckboxGroupAppearance, type CheckboxGroupColumns, CheckboxGroupContext, type CheckboxGroupContextValue, type CheckboxGroupDensity, type CheckboxGroupOrientation, type CheckboxGroupProps, type CheckboxProps, type CheckboxSize, Chip, type ChipProps, type ChipSize, type ChipTone, type ChipVariant, ConfirmDialog, type ConfirmDialogProps, ContextMenu, type ContextMenuPosition, type ContextMenuProps, CopyButton, type CopyButtonProps, DataLayout, type DataLayoutProps, DataTable, type DataTableColumn, type DataTableLinkProps, type DataTableMobileCardContent, type DataTableMobileMetaLine, type DataTableProps, type DataTableRowActions, type DataTableSort, type DataTableVirtualHandle, type DataTableVirtualOptions, type DataTableVirtualRange, DetailLayout, type DetailLayoutProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogEyebrow, DialogFooter, DialogHeader, type DialogMobilePresentation, DialogOverlay, DialogPortal, type DialogProps, DialogRawContent, type DialogSize, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateLayout, type EmptyStateProps, type EmptyStateSize, type EmptyStateTone, type EmptyStateVariantOptions, ErrorState, type ErrorStateProps, FileUploadZone, type FileUploadZoneProps, FilterToolbar, type FilterToolbarAppliedFilter, type FilterToolbarCollapseBelow, type FilterToolbarLayout, type FilterToolbarProps, FormField, type FormFieldProps, type FormFieldRenderProps, FormLayout, type FormLayoutProps, type FormLayoutSection, FormModal, type FormModalProps, IconButton, type IconButtonBadgeTone, type IconButtonProps, type IconButtonSize, InfoModal, type InfoModalProps, InlineAlert, type InlineAlertProps, Input, type InputProps, type InputSize, type InputSizeLegacy, type InputState, Label, type LabelProps, ListCard, type ListCardActions, type ListCardProps, ModeToggle, type ModeToggleOption, type ModeToggleProps, NotificationBadge, type NotificationBadgePlacement, type NotificationBadgeProps, type NotificationBadgeSize, OTPField, type OTPFieldProps, type OTPFieldSize, OlunLogo, OlunSymbol, type OlunTheme, type OlunThemeDuration, type OlunThemeState, OrganizationLogo, type OrganizationLogoId, type OrganizationLogoProps, type OverlayScrim, PageHeader, type PageHeaderProps, PageSizeSelect, type PageSizeSelectProps, type PageSizeSelectSize, Pagination, type PaginationProps, type PaginationSize, PermissionContext, type PermissionEvaluator, type PermissionFallback, PermissionGuard, type PermissionGuardProps, PermissionProvider, type PermissionProviderProps, type PermissionScope, type PermissionState, Popover, PopoverAnchor, PopoverArrow, PopoverClose, PopoverContent, type PopoverContentProps, type PopoverProps, PopoverTrigger, Progress, type ProgressProps, type ProgressStatus, Radio, RadioGroup, type RadioGroupAppearance, type RadioGroupColumns, type RadioGroupDensity, type RadioGroupOrientation, type RadioGroupProps, type RadioProps, ScrollArea, ScrollBar, SearchField, type SearchFieldProps, type SearchFieldSize, Select, type SelectAppearance, type SelectOption, type SelectProps, type SelectSize, type SelectState, type SelectVariant, type SemanticRole, Separator, type SetOlunThemeOptions, Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetDrawer, type SheetDrawerProps, SheetFooter, SheetHeader, type SheetMotionConfig, type SheetMotionDuration, type SheetMotionEasing, type SheetMotionType, type SheetProps, type SheetSize, SheetTitle, SheetTrigger, Skeleton, type SkeletonProps, SkeletonRow, type SkeletonRowColumn, type SkeletonRowProps, SkeletonText, type SkeletonTextProps, Spinner, type SpinnerProps, StatusBadge, type StatusBadgeEmphasis, type StatusBadgeProps, type StatusBadgeSize, type StatusBadgeStatus, StatusDot, type StatusDotProps, type StatusDotTone, type SurfaceLevel, Switch, type SwitchProps, TABLE_PAGE_SIZE_OPTIONS, type TabItem, TablePagination, type TablePaginationProps, type TablePaginationVisibility, Tabs, TabsContent, type TabsProps, type TabsVariant, Textarea, type TextareaProps, type TextareaSize, type TextareaSizeLegacy, type TextareaState, ThemeFab, type ThemeFabProps, ThemeToggle, Toast, type ToastAction, type ToastItem, type ToastOptions, ToastProvider, ToastRoot, type ToastVariant, ToastViewport, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type TreeItemRowVariantProps, type TreeItemState, type TreeNode, TreeView, type TreeViewProps, TwoTierBadge, type TwoTierBadgeEmphasis, type TwoTierBadgeProps, type TwoTierBadgeSize, type TwoTierBadgeTone, type UseOlunThemeValue, WizardLayout, type WizardLayoutProps, type WizardStep, type WizardStepState, actionFooterStatusVariants, actionFooterVariants, buttonVariants, cardVariants, checkboxBoxVariants, checkboxFieldVariants, checkboxGroupBoxVariants, checkboxGroupItemVariants, checkboxGroupVariants, checkboxRootVariants, checkboxVariants, chipCountVariants, chipVariants, cn, dataTableRowVariants, dataTableVariants, emptyStateIconVariants, emptyStateVariants, errorStateVariants, evaluatePermission, fileUploadIconVariants, fileUploadZoneVariants, formFieldControlStackClassName, formFieldDescriptionClassName, formFieldErrorClassName, formFieldLabelClassName, formFieldRequiredClassName, formFieldVariants, getOlunThemeState, getToastDuration, iconButtonBadgeVariants, iconButtonVariants, inputMessageVariants, inputVariants, limitToasts, modeToggleItemVariants, modeToggleVariants, notificationBadgeVariants, otpCellVariants, otpFieldGroupVariants, otpFieldMessageVariants, otpFieldVariants, paginationControlVariants, paginationVariants, radioControlVariants, radioGroupVariants, radioVariants, searchFieldInputVariants, searchFieldVariants, selectMessageVariants, selectVariants, setOlunTheme, shouldHideTablePagination, skeletonVariants, spinnerVariants, tabsBadgeVariants, tabsListVariants, tabsRootVariants, tabsTriggerVariants, textareaMessageVariants, textareaVariants, toast, toastVariants, toastViewportVariants, treeItemRowVariants, treeViewVariants, useOlunTheme, usePermission };
package/dist/index.js CHANGED
@@ -5547,7 +5547,7 @@ var PopoverArrow = React30.forwardRef(({ className, ...props }, ref) => /* @__PU
5547
5547
  PopoverArrow.displayName = PopoverPrimitive.Arrow.displayName;
5548
5548
 
5549
5549
  // src/components/ui/filter-toolbar.tsx
5550
- import { Fragment as Fragment13, jsx as jsx32, jsxs as jsxs28 } from "react/jsx-runtime";
5550
+ import { Fragment as Fragment14, jsx as jsx32, jsxs as jsxs28 } from "react/jsx-runtime";
5551
5551
  var collapsibleClassNames = {
5552
5552
  sm: {
5553
5553
  inline: "hidden @[640px]:flex",
@@ -5566,6 +5566,41 @@ var collapsibleClassNames = {
5566
5566
  trigger: "hidden"
5567
5567
  }
5568
5568
  };
5569
+ var gridCollapsibleClassNames = {
5570
+ sm: { inline: "hidden", trigger: "" },
5571
+ md: { inline: "hidden", trigger: "" },
5572
+ lg: { inline: "hidden", trigger: "" },
5573
+ never: { inline: "flex @[640px]:hidden", trigger: "hidden @[640px]:inline" }
5574
+ };
5575
+ var gridSearchClassName = "order-1 min-w-[min(160px,100%)] max-w-full flex-[1_1_0%] @[640px]:min-w-0 @[640px]:flex-[3_1_0%]";
5576
+ var gridFiltersClassName = "order-3 flex min-w-0 flex-[1_0_100%] flex-wrap items-center gap-[var(--space-2)] @[640px]:contents";
5577
+ var gridCellClassName = cn(
5578
+ "contents @[640px]:block @[640px]:order-3 @[640px]:min-w-0 @[640px]:flex-[3_1_0%]",
5579
+ "@[640px]:[&:has([data-slot=select-trigger])]:flex-[2_1_0%]",
5580
+ "@[640px]:[&:has(>[data-slot=chip])]:flex-none",
5581
+ // A Chip group is named: a role="group" child that holds the Chips. A control that shows its
5582
+ // values as Chips keeps the default share.
5583
+ "@[640px]:[&:has(>[role=group]>[data-slot=chip])]:flex-none",
5584
+ // The control fills its cell, through a width the consumer put on a wrapper.
5585
+ "@[640px]:[&>:not([data-slot=chip])]:w-full @[640px]:[&_[data-slot=select-trigger]]:w-full"
5586
+ );
5587
+ var MAX_GRID_FILTERS = 3;
5588
+ var focusableSelector = "input, button, [tabindex]:not([tabindex='-1'])";
5589
+ function canTakeFocus(element) {
5590
+ return Boolean(element && element.getClientRects().length > 0 && !element.matches(":disabled"));
5591
+ }
5592
+ function filterCells(node, prefix = "") {
5593
+ return React31.Children.toArray(node).flatMap((child, index) => {
5594
+ const key = `${prefix}${React31.isValidElement(child) ? child.key : index}`;
5595
+ return React31.isValidElement(child) && child.type === React31.Fragment ? filterCells(child.props.children, `${key}/`) : [{ key, cell: child }];
5596
+ });
5597
+ }
5598
+ function warnGridFilters(count) {
5599
+ if (typeof process === "undefined" || process.env.NODE_ENV === "production") return;
5600
+ console.warn(
5601
+ `FilterToolbar: layout="grid" fits ${MAX_GRID_FILTERS} filters on the line, got ${count}. Move the rest to \`collapsible\`.`
5602
+ );
5603
+ }
5569
5604
  var FilterToolbar = React31.forwardRef(
5570
5605
  ({
5571
5606
  className,
@@ -5573,12 +5608,14 @@ var FilterToolbar = React31.forwardRef(
5573
5608
  filters,
5574
5609
  collapsible,
5575
5610
  collapseBelow = "lg",
5576
- activeFilterCount = 0,
5611
+ activeFilterCount: activeFilterCountProp,
5577
5612
  onClearFilters,
5578
5613
  collapsedLabel = "\uD544\uD130",
5579
5614
  resultSummary,
5580
5615
  clearAction,
5581
5616
  actions,
5617
+ layout = "flow",
5618
+ appliedFilters,
5582
5619
  ...props
5583
5620
  }, ref) => {
5584
5621
  const [rootElement, setRootElement] = React31.useState(
@@ -5589,6 +5626,10 @@ var FilterToolbar = React31.forwardRef(
5589
5626
  );
5590
5627
  const [overlayOpen, setOverlayOpen] = React31.useState(false);
5591
5628
  const triggerRef = React31.useRef(null);
5629
+ const rowRef = React31.useRef(null);
5630
+ const chipsRef = React31.useRef(null);
5631
+ const chipRemoveRefs = React31.useRef(/* @__PURE__ */ new Map());
5632
+ const pendingFocusRef = React31.useRef(null);
5592
5633
  const setRootRefs = React31.useCallback(
5593
5634
  (element) => {
5594
5635
  setRootElement(element);
@@ -5612,13 +5653,49 @@ var FilterToolbar = React31.forwardRef(
5612
5653
  setOverlayOpen(open);
5613
5654
  if (!open) queueMicrotask(() => triggerRef.current?.focus());
5614
5655
  }, []);
5656
+ const isGrid = layout === "grid";
5657
+ const gridCells = isGrid ? filterCells(filters) : [];
5658
+ const filterCellCount = gridCells.length;
5659
+ React31.useEffect(() => {
5660
+ if (filterCellCount > MAX_GRID_FILTERS) warnGridFilters(filterCellCount);
5661
+ }, [filterCellCount]);
5662
+ React31.useEffect(() => {
5663
+ const ownerDocument = rootElement?.ownerDocument;
5664
+ if (!ownerDocument) return;
5665
+ const dropOutside = (event) => {
5666
+ if (!chipsRef.current?.contains(event.target)) pendingFocusRef.current = null;
5667
+ };
5668
+ ownerDocument.addEventListener("focusin", dropOutside);
5669
+ ownerDocument.addEventListener("pointerdown", dropOutside, true);
5670
+ return () => {
5671
+ ownerDocument.removeEventListener("focusin", dropOutside);
5672
+ ownerDocument.removeEventListener("pointerdown", dropOutside, true);
5673
+ };
5674
+ }, [rootElement]);
5675
+ React31.useEffect(() => {
5676
+ const request = pendingFocusRef.current;
5677
+ if (!request) return;
5678
+ const present = new Set(appliedFilters?.map((filter) => filter.key));
5679
+ if (request.removed.some((key) => present.has(key))) return;
5680
+ pendingFocusRef.current = null;
5681
+ const active = rootElement?.ownerDocument.activeElement;
5682
+ if (active && active !== rootElement.ownerDocument.body) return;
5683
+ const candidates = [
5684
+ ...request.next.map((key) => chipRemoveRefs.current.get(key)),
5685
+ triggerRef.current,
5686
+ ...rowRef.current?.querySelectorAll(focusableSelector) ?? []
5687
+ ];
5688
+ candidates.find(canTakeFocus)?.focus();
5689
+ }, [appliedFilters, rootElement]);
5615
5690
  const hasControls = Boolean(search || filters || collapsible);
5616
5691
  const hasMeta = Boolean(
5617
5692
  resultSummary || clearAction || actions && !hasControls
5618
5693
  );
5694
+ const activeFilterCount = activeFilterCountProp ?? appliedFilters?.length ?? 0;
5619
5695
  const hasAppliedFilters = activeFilterCount > 0;
5696
+ const hasAppliedChips = Boolean(appliedFilters && appliedFilters.length > 0);
5620
5697
  const usesSheet = containerWidth !== null && containerWidth < 640;
5621
- const collapseClasses = collapsibleClassNames[collapseBelow];
5698
+ const collapseClasses = (isGrid ? gridCollapsibleClassNames : collapsibleClassNames)[collapseBelow];
5622
5699
  const collapseTriggerLabel = hasAppliedFilters ? `${collapsedLabel}, ${activeFilterCount}\uAC1C \uC801\uC6A9` : collapsedLabel;
5623
5700
  const clearFiltersButton = onClearFilters ? /* @__PURE__ */ jsx32(
5624
5701
  "button",
@@ -5689,14 +5766,18 @@ var FilterToolbar = React31.forwardRef(
5689
5766
  hasControls ? /* @__PURE__ */ jsxs28(
5690
5767
  "div",
5691
5768
  {
5769
+ ref: rowRef,
5692
5770
  "data-slot": "filter-toolbar-row",
5693
- className: "flex min-h-[32px] w-full flex-wrap items-center justify-start gap-x-[var(--space-2)] gap-y-[var(--space-2)]",
5771
+ className: cn(
5772
+ "flex min-h-[32px] w-full flex-wrap items-center justify-start gap-x-[var(--space-2)] gap-y-[var(--space-2)]",
5773
+ isGrid && "@[640px]:flex-nowrap"
5774
+ ),
5694
5775
  children: [
5695
5776
  search ? /* @__PURE__ */ jsx32(
5696
5777
  "div",
5697
5778
  {
5698
5779
  "data-slot": "filter-toolbar-search",
5699
- className: "order-1 min-w-[min(160px,100%)] max-w-full flex-[1_1_0%] @[640px]:min-w-0 @[640px]:flex-[1_1_280px]",
5780
+ className: isGrid ? gridSearchClassName : "order-1 min-w-[min(160px,100%)] max-w-full flex-[1_1_0%] @[640px]:min-w-0 @[640px]:flex-[1_1_280px]",
5700
5781
  children: search
5701
5782
  }
5702
5783
  ) : null,
@@ -5723,8 +5804,16 @@ var FilterToolbar = React31.forwardRef(
5723
5804
  "div",
5724
5805
  {
5725
5806
  "data-slot": "filter-toolbar-filters",
5726
- className: "order-3 flex min-w-0 flex-[1_0_100%] flex-wrap items-center gap-[var(--space-2)] @[640px]:flex-[2_1_auto]",
5727
- children: filters
5807
+ className: isGrid ? gridFiltersClassName : "order-3 flex min-w-0 flex-[1_0_100%] flex-wrap items-center gap-[var(--space-2)] @[640px]:flex-[2_1_auto]",
5808
+ children: isGrid ? gridCells.map(({ key, cell }) => /* @__PURE__ */ jsx32(
5809
+ "div",
5810
+ {
5811
+ "data-slot": "filter-toolbar-cell",
5812
+ className: gridCellClassName,
5813
+ children: cell
5814
+ },
5815
+ key
5816
+ )) : filters
5728
5817
  }
5729
5818
  ) : null,
5730
5819
  collapsible && !overlayOpen ? /* @__PURE__ */ jsx32(
@@ -5759,18 +5848,72 @@ var FilterToolbar = React31.forwardRef(
5759
5848
  children: collapsedContent
5760
5849
  }
5761
5850
  ) : null,
5851
+ hasAppliedChips ? /* @__PURE__ */ jsxs28(
5852
+ "div",
5853
+ {
5854
+ ref: chipsRef,
5855
+ role: "group",
5856
+ "aria-label": collapsedLabel,
5857
+ "data-slot": "filter-toolbar-applied-chips",
5858
+ className: "hidden flex-wrap items-center gap-[var(--space-2)] @[640px]:flex",
5859
+ children: [
5860
+ appliedFilters.map((filter, index) => /* @__PURE__ */ jsx32(
5861
+ Chip,
5862
+ {
5863
+ ref: (button) => {
5864
+ if (button) chipRemoveRefs.current.set(filter.key, button);
5865
+ else chipRemoveRefs.current.delete(filter.key);
5866
+ },
5867
+ variant: "removable",
5868
+ size: "md",
5869
+ removeLabel: `${filter.label} \uC81C\uAC70`,
5870
+ onClick: () => {
5871
+ pendingFocusRef.current = {
5872
+ removed: [filter.key],
5873
+ next: [appliedFilters[index + 1]?.key, appliedFilters[index - 1]?.key].filter(
5874
+ (key) => key !== void 0
5875
+ )
5876
+ };
5877
+ filter.onRemove();
5878
+ },
5879
+ children: filter.label
5880
+ },
5881
+ filter.key
5882
+ )),
5883
+ onClearFilters ? /* @__PURE__ */ jsx32(
5884
+ "button",
5885
+ {
5886
+ type: "button",
5887
+ "data-slot": "filter-toolbar-clear-filters",
5888
+ className: "cursor-pointer border-0 bg-transparent p-0 text-body-sm font-medium text-text-secondary no-underline",
5889
+ onClick: () => {
5890
+ pendingFocusRef.current = {
5891
+ removed: appliedFilters.map((filter) => filter.key),
5892
+ next: []
5893
+ };
5894
+ onClearFilters();
5895
+ },
5896
+ children: "\uD574\uC81C"
5897
+ }
5898
+ ) : null
5899
+ ]
5900
+ }
5901
+ ) : null,
5762
5902
  hasAppliedFilters ? /* @__PURE__ */ jsxs28(
5763
5903
  "div",
5764
5904
  {
5765
5905
  "data-slot": "filter-toolbar-applied",
5766
- className: "flex items-center gap-[var(--space-1)] text-[11px] text-text-tertiary",
5906
+ className: cn(
5907
+ "flex items-center gap-[var(--space-1)] text-[11px] text-text-tertiary",
5908
+ hasAppliedChips && "@[640px]:hidden"
5909
+ ),
5767
5910
  children: [
5768
5911
  /* @__PURE__ */ jsxs28("span", { children: [
5769
5912
  "\uD544\uD130 ",
5770
5913
  activeFilterCount,
5771
5914
  "\uAC1C \uC801\uC6A9"
5772
5915
  ] }),
5773
- onClearFilters ? /* @__PURE__ */ jsxs28(Fragment13, { children: [
5916
+ onClearFilters ? /* @__PURE__ */ jsxs28(Fragment14, { children: [
5774
5917
  /* @__PURE__ */ jsx32("span", { "aria-hidden": "true", children: " \xB7 " }),
5775
5918
  /* @__PURE__ */ jsx32(
5776
5919
  "button",
@@ -6078,7 +6221,7 @@ function DropdownMenuSubContent({
6078
6221
  }
6079
6222
 
6080
6223
  // src/components/ui/action-menu.tsx
6081
- import { Fragment as Fragment15, jsx as jsx34, jsxs as jsxs30 } from "react/jsx-runtime";
6224
+ import { Fragment as Fragment16, jsx as jsx34, jsxs as jsxs30 } from "react/jsx-runtime";
6082
6225
  var sheetRowClassName = cn(
6083
6226
  "group flex min-h-12 w-full cursor-pointer items-center gap-[var(--space-3)] px-[var(--space-4)] py-[var(--space-2)] text-left",
6084
6227
  "border-t border-[var(--border-subtle)] text-[13px] font-medium leading-normal text-text-primary",
@@ -6181,10 +6324,10 @@ function ActionMenuSheet({
6181
6324
  },
6182
6325
  children: [
6183
6326
  Icon ? /* @__PURE__ */ jsx34(Icon, { "aria-hidden": true, size: 17, strokeWidth: 1.75, className: cn("shrink-0", reason && unavailableClassName) }) : null,
6184
- /* @__PURE__ */ jsx34("span", { className: "min-w-0 flex-1 break-keep", children: reason ? /* @__PURE__ */ jsxs30(Fragment15, { children: [
6327
+ /* @__PURE__ */ jsx34("span", { className: "min-w-0 flex-1 break-keep", children: reason ? /* @__PURE__ */ jsxs30(Fragment16, { children: [
6185
6328
  /* @__PURE__ */ jsx34("span", { id: itemLabelId, className: unavailableClassName, children: item.label }),
6186
6329
  /* @__PURE__ */ jsx34("span", { id: reasonId, className: reasonClassName, children: reason })
6187
- ] }) : /* @__PURE__ */ jsxs30(Fragment15, { children: [
6330
+ ] }) : /* @__PURE__ */ jsxs30(Fragment16, { children: [
6188
6331
  item.label,
6189
6332
  item.description ? /* @__PURE__ */ jsx34("span", { className: sheetRowDescriptionClassName, children: item.description }) : null
6190
6333
  ] }) })
@@ -6292,7 +6435,7 @@ function ActionMenu({
6292
6435
  triggerRef.current?.focus();
6293
6436
  };
6294
6437
  const triggerLabel = trigger.props["aria-label"];
6295
- return /* @__PURE__ */ jsxs30(Fragment15, { children: [
6438
+ return /* @__PURE__ */ jsxs30(Fragment16, { children: [
6296
6439
  /* @__PURE__ */ jsxs30(DropdownMenu, { open: open && presentation === "dropdown", onOpenChange: setOpen, children: [
6297
6440
  /* @__PURE__ */ jsx34(DropdownMenuTrigger, { asChild: true, "aria-expanded": open, children: /* @__PURE__ */ jsx34(Slot3.Root, { ref: triggerRef, children: trigger }) }),
6298
6441
  /* @__PURE__ */ jsx34(DropdownMenuContent, { align: "end", onCloseAutoFocus: handleDropdownCloseAutoFocus, children: menuGroups.map((group, index) => /* @__PURE__ */ jsxs30(React32.Fragment, { children: [
@@ -6704,7 +6847,7 @@ function ContextMenu({
6704
6847
  // src/components/ui/list-card.tsx
6705
6848
  import * as React34 from "react";
6706
6849
  import { ChevronRight as ChevronRight2, MoreVertical } from "lucide-react";
6707
- import { Fragment as Fragment16, jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
6850
+ import { Fragment as Fragment17, jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
6708
6851
  function lineTokens(value) {
6709
6852
  return React34.Children.toArray(value).filter((token) => token !== "");
6710
6853
  }
@@ -6791,7 +6934,7 @@ function ListCard({
6791
6934
  children: overlay && href != null ? React34.createElement(linkComponent ?? "a", { ...linkProps, href, className: overlayClassName }, title) : title
6792
6935
  }
6793
6936
  );
6794
- const content = /* @__PURE__ */ jsxs33(Fragment16, { children: [
6937
+ const content = /* @__PURE__ */ jsxs33(Fragment17, { children: [
6795
6938
  leading != null ? /* @__PURE__ */ jsx37("span", { className: "flex min-h-[18px] items-center", children: leading }) : null,
6796
6939
  /* @__PURE__ */ jsxs33("span", { className: "min-w-0", children: [
6797
6940
  overlay && href == null ? (
@@ -6822,7 +6965,7 @@ function ListCard({
6822
6965
  ),
6823
6966
  children: [
6824
6967
  hasValue ? /* @__PURE__ */ jsx37("span", { "data-slot": `${slot}-value`, className: "text-[13px] leading-[20px] text-text-secondary tabular-nums", children: value }) : null,
6825
- actions != null ? /* @__PURE__ */ jsx37("span", { className: "relative z-[1] flex", children: /* @__PURE__ */ jsx37(
6968
+ actions != null ? /* @__PURE__ */ jsx37("span", { "data-slot": `${slot}-actions`, className: "relative z-[1] flex", children: /* @__PURE__ */ jsx37(
6826
6969
  ActionMenu,
6827
6970
  {
6828
6971
  title: typeof title === "string" ? title : void 0,
@@ -6856,7 +6999,7 @@ function ListCard({
6856
6999
  }
6857
7000
 
6858
7001
  // src/components/ui/data-table.tsx
6859
- import { Fragment as Fragment18, jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
7002
+ import { Fragment as Fragment19, jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
6860
7003
  var dataTableVariants = cva13(
6861
7004
  [
6862
7005
  "relative overflow-hidden rounded-[var(--radius-md)] border border-[var(--border-subtle)]",
@@ -6979,25 +7122,24 @@ function RowActionsCell({
6979
7122
  );
6980
7123
  }
6981
7124
  function RowContextMenu({
6982
- openKey,
7125
+ menuKey,
6983
7126
  position,
6984
7127
  groups,
7128
+ cards,
6985
7129
  onClose
6986
7130
  }) {
6987
- const [open, setOpen] = React35.useState(false);
6988
- const pendingRef = React35.useRef(null);
6989
- const outsideRef = React35.useRef(false);
7131
+ const [open, setOpen] = React35.useState(true);
7132
+ const closeRef = React35.useRef(null);
7133
+ const pressedOutside = React35.useRef(false);
7134
+ const close = (reason) => {
7135
+ closeRef.current ??= reason;
7136
+ setOpen(false);
7137
+ };
6990
7138
  const shownGroups = React35.useRef([]);
6991
7139
  if (groups) shownGroups.current = groups;
6992
- const gone = openKey !== null && groups === null;
7140
+ const gone = groups === null;
6993
7141
  const contentRef = React35.useRef(null);
6994
7142
  const focusInsideRef = React35.useRef(false);
6995
- React35.useEffect(() => {
6996
- pendingRef.current = null;
6997
- outsideRef.current = false;
6998
- focusInsideRef.current = false;
6999
- setOpen(openKey !== null);
7000
- }, [openKey]);
7001
7143
  useIsomorphicLayoutEffect(() => {
7002
7144
  const content = contentRef.current;
7003
7145
  if (!open || !focusInsideRef.current || !content?.isConnected) return;
@@ -7007,13 +7149,20 @@ function RowContextMenu({
7007
7149
  next.focus();
7008
7150
  });
7009
7151
  React35.useEffect(() => {
7010
- if (gone) setOpen(false);
7152
+ if (gone) close({ by: "dismiss", outside: false });
7011
7153
  }, [gone]);
7154
+ useIsomorphicLayoutEffect(() => {
7155
+ if (!cards || !open || closeRef.current) return;
7156
+ const active = document.activeElement;
7157
+ close({ by: "layout", focusInside: Boolean(active && contentRef.current?.contains(active)) });
7158
+ }, [cards, open]);
7012
7159
  return /* @__PURE__ */ jsx38(
7013
7160
  ContextMenu,
7014
7161
  {
7015
7162
  open: open && !gone,
7016
- onOpenChange: setOpen,
7163
+ onOpenChange: (next) => {
7164
+ if (!next) close({ by: "dismiss", outside: false });
7165
+ },
7017
7166
  position,
7018
7167
  contentProps: {
7019
7168
  align: "start",
@@ -7027,13 +7176,15 @@ function RowContextMenu({
7027
7176
  }
7028
7177
  },
7029
7178
  onInteractOutside: () => {
7030
- outsideRef.current = true;
7179
+ pressedOutside.current = true;
7180
+ close({ by: "dismiss", outside: true });
7031
7181
  },
7032
7182
  onCloseAutoFocus: (event) => {
7033
7183
  event.preventDefault();
7034
- const item = pendingRef.current;
7035
- pendingRef.current = null;
7036
- onClose(item, outsideRef.current);
7184
+ let reason = closeRef.current ?? { by: "dismiss", outside: false };
7185
+ if (pressedOutside.current && reason.by === "dismiss") reason = { by: "dismiss", outside: true };
7186
+ if (pressedOutside.current && reason.by === "layout") reason = { by: "layout", focusInside: false };
7187
+ onClose(menuKey, reason);
7037
7188
  }
7038
7189
  },
7039
7190
  children: shownGroups.current.map((group, index) => /* @__PURE__ */ jsxs34(React35.Fragment, { children: [
@@ -7045,7 +7196,7 @@ function RowContextMenu({
7045
7196
  {
7046
7197
  item,
7047
7198
  onPick: (picked) => {
7048
- pendingRef.current = picked;
7199
+ closeRef.current ??= { by: "item", item: picked };
7049
7200
  }
7050
7201
  },
7051
7202
  item.id
@@ -7130,7 +7281,7 @@ function MobileSortMenu({
7130
7281
  size: "sm",
7131
7282
  className: "ml-auto min-w-0",
7132
7283
  children: [
7133
- active && sortBy ? /* @__PURE__ */ jsxs34(Fragment18, { children: [
7284
+ active && sortBy ? /* @__PURE__ */ jsxs34(Fragment19, { children: [
7134
7285
  /* @__PURE__ */ jsx38("span", { className: "sr-only", children: "\uC815\uB82C " }),
7135
7286
  /* @__PURE__ */ jsx38("span", { className: "truncate", children: active.header }),
7136
7287
  /* @__PURE__ */ jsxs34("span", { className: "sr-only", children: [
@@ -7403,7 +7554,7 @@ function DataTableRowView({
7403
7554
  if (!expand) {
7404
7555
  return dataRow;
7405
7556
  }
7406
- return /* @__PURE__ */ jsxs34(Fragment18, { children: [
7557
+ return /* @__PURE__ */ jsxs34(Fragment19, { children: [
7407
7558
  dataRow,
7408
7559
  /* @__PURE__ */ jsx38(
7409
7560
  "div",
@@ -7640,8 +7791,8 @@ function useVirtualWindow({
7640
7791
  padBottom: 0
7641
7792
  });
7642
7793
  const desktopSize = React35.useRef(0);
7643
- const cardHeights2 = React35.useRef(null);
7644
- cardHeights2.current ??= new CardHeights();
7794
+ const cardHeights = React35.useRef(null);
7795
+ cardHeights.current ??= new CardHeights();
7645
7796
  const update = React35.useRef(() => void 0);
7646
7797
  const reported = React35.useRef("");
7647
7798
  const latest = React35.useRef({
@@ -7667,9 +7818,9 @@ function useVirtualWindow({
7667
7818
  extent = uniformExtent(size);
7668
7819
  } else {
7669
7820
  if (list) {
7670
- cardHeights2.current.measure(list);
7821
+ cardHeights.current.measure(list);
7671
7822
  }
7672
- const { offsets, estimate } = cardHeights2.current.offsets(rowIds);
7823
+ const { offsets, estimate } = cardHeights.current.offsets(rowIds);
7673
7824
  extent = measuredExtent(offsets, estimate);
7674
7825
  }
7675
7826
  latest.current = { count, layout, extent };
@@ -8015,15 +8166,22 @@ function DataTableInner({
8015
8166
  contextMenuKey.current += 1;
8016
8167
  setContextMenu({ rowId, position: { x: event.clientX, y: event.clientY }, key: contextMenuKey.current });
8017
8168
  };
8018
- const closeRowContextMenu = (item, outside) => {
8169
+ const closeRowContextMenu = (key, close) => {
8170
+ if (key !== contextMenuKey.current) {
8171
+ return;
8172
+ }
8019
8173
  const rowId = contextMenu?.rowId;
8020
- if (rowId !== void 0 && (item || !outside)) {
8021
- const list = desktopListRef.current;
8022
- const row = Array.from(list?.querySelectorAll('[data-slot="data-table-row"]') ?? []).find(
8023
- (node) => node.getAttribute("data-row-id") === rowId
8174
+ const refocus = close.by === "item" || (close.by === "dismiss" ? !close.outside : close.focusInside);
8175
+ if (rowId !== void 0 && refocus) {
8176
+ const cards = readLayout(desktopRef.current) === "mobile";
8177
+ const list = cards ? mobileListRef.current : desktopListRef.current;
8178
+ const row = Array.from(
8179
+ list?.querySelectorAll(cards ? '[data-slot="data-table-mobile-card"]' : '[data-slot="data-table-row"]') ?? []
8180
+ ).find((node) => node.getAttribute("data-row-id") === rowId);
8181
+ const trigger = row?.querySelector(
8182
+ cards ? '[data-slot="list-card-actions"] button' : '[data-slot="data-table-row-actions"] button'
8024
8183
  );
8025
- const trigger = row?.querySelector('[data-slot="data-table-row-actions"] button');
8026
- const target = trigger ?? (row?.hasAttribute("tabindex") ? row : null) ?? list ?? desktopRef.current;
8184
+ const target = trigger ?? (row && (cards || row.hasAttribute("tabindex")) ? row : null) ?? list ?? (cards ? mobileRef : desktopRef).current;
8027
8185
  if (target) {
8028
8186
  if (!target.hasAttribute("tabindex") && !target.matches("a[href],button,input")) {
8029
8187
  target.tabIndex = -1;
@@ -8032,7 +8190,7 @@ function DataTableInner({
8032
8190
  }
8033
8191
  }
8034
8192
  setContextMenu(null);
8035
- item?.onSelect();
8193
+ if (close.by === "item") close.item.onSelect();
8036
8194
  };
8037
8195
  const handleRowContextMenu = rowActions ? openRowContextMenu : onRowContextMenu;
8038
8196
  const latestHandlers = React35.useRef({ toggleRow, activateRow, onRowContextMenu, handleRowContextMenu });
@@ -8284,14 +8442,16 @@ function DataTableInner({
8284
8442
  )
8285
8443
  ] }),
8286
8444
  footer != null ? /* @__PURE__ */ jsx38("div", { "data-slot": "data-table-footer", className: "border-t border-[var(--border-subtle)] empty:hidden", children: footer }) : null,
8287
- hasRowActions ? /* @__PURE__ */ jsx38(
8445
+ hasRowActions && contextMenu ? /* @__PURE__ */ jsx38(
8288
8446
  RowContextMenu,
8289
8447
  {
8290
- openKey: contextMenu?.key ?? null,
8291
- position: contextMenu?.position ?? null,
8448
+ menuKey: contextMenu.key,
8449
+ position: contextMenu.position,
8292
8450
  groups: contextGroups,
8451
+ cards: activeLayout === "mobile",
8293
8452
  onClose: closeRowContextMenu
8294
- }
8453
+ },
8454
+ contextMenu.key
8295
8455
  ) : null
8296
8456
  ]
8297
8457
  }
@@ -8488,17 +8648,25 @@ function warnTrailingOnce() {
8488
8648
  console.warn("BulkActionBar: the floating card ignores `trailing`. Put edit-mode controls at the right end of the toolbar.");
8489
8649
  }
8490
8650
  var OFFSET_PROPERTY = "--olun-bulk-bar-offset";
8491
- var cardHeights = /* @__PURE__ */ new Map();
8651
+ var LIFT_ZONE = 204;
8652
+ var cardOffsets = /* @__PURE__ */ new Map();
8492
8653
  function writeOffset() {
8493
8654
  const root = document.documentElement;
8494
- const height = Math.max(0, ...cardHeights.values());
8495
- if (height > 0) root.style.setProperty(OFFSET_PROPERTY, `calc(${height}px + ${FLOATING_BOTTOM})`);
8655
+ const offset = Math.max(0, ...cardOffsets.values());
8656
+ const value = offset > 0 ? `${offset}px` : "";
8657
+ if (root.style.getPropertyValue(OFFSET_PROPERTY) === value) return;
8658
+ if (value) root.style.setProperty(OFFSET_PROPERTY, value);
8496
8659
  else root.style.removeProperty(OFFSET_PROPERTY);
8497
8660
  }
8498
- function reportHeight(key, card) {
8499
- const height = card.offsetHeight;
8500
- if (height > 0) cardHeights.set(key, height);
8501
- else cardHeights.delete(key);
8661
+ function reportOffset(key, card) {
8662
+ let offset = 0;
8663
+ if (card.offsetHeight > 0) {
8664
+ const { top, bottom } = card.getBoundingClientRect();
8665
+ if (window.innerHeight - bottom <= LIFT_ZONE) offset = Math.max(0, Math.round(window.innerHeight - top));
8666
+ }
8667
+ if (offset === (cardOffsets.get(key) ?? 0)) return;
8668
+ if (offset > 0) cardOffsets.set(key, offset);
8669
+ else cardOffsets.delete(key);
8502
8670
  writeOffset();
8503
8671
  }
8504
8672
  var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? React36.useEffect : React36.useLayoutEffect;
@@ -8506,20 +8674,80 @@ function useBottomOffset(card) {
8506
8674
  const [key] = React36.useState(() => ({}));
8507
8675
  React36.useEffect(() => {
8508
8676
  if (!card) return;
8509
- const report = () => reportHeight(key, card);
8510
- report();
8511
- const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(report);
8677
+ let frame = 0;
8678
+ let stopped = false;
8679
+ let listening = false;
8680
+ const schedule = () => {
8681
+ if (frame || stopped) return;
8682
+ frame = requestAnimationFrame(measure);
8683
+ };
8684
+ const listen = (on) => {
8685
+ if (on === listening) return;
8686
+ listening = on;
8687
+ if (on) {
8688
+ window.addEventListener("resize", schedule);
8689
+ window.addEventListener("scroll", schedule, { capture: true, passive: true });
8690
+ } else {
8691
+ window.removeEventListener("resize", schedule);
8692
+ window.removeEventListener("scroll", schedule, { capture: true });
8693
+ }
8694
+ };
8695
+ let watcher = null;
8696
+ let watched = "";
8697
+ const arm = (margin, threshold) => {
8698
+ let first = true;
8699
+ watcher = new IntersectionObserver(
8700
+ ([entry]) => {
8701
+ if (stopped) return;
8702
+ if (first) {
8703
+ first = false;
8704
+ if (entry.intersectionRatio === threshold) return;
8705
+ watcher?.disconnect();
8706
+ arm(margin, entry.intersectionRatio);
8707
+ }
8708
+ schedule();
8709
+ },
8710
+ { root: document, rootMargin: margin, threshold }
8711
+ );
8712
+ watcher.observe(card);
8713
+ };
8714
+ const watch = (on) => {
8715
+ let margin = "";
8716
+ if (on && typeof IntersectionObserver !== "undefined") {
8717
+ const { top, right, bottom, left } = card.getBoundingClientRect();
8718
+ const { clientWidth, clientHeight } = document.documentElement;
8719
+ margin = [top, clientWidth - right, clientHeight - bottom, left].map((inset) => `${-Math.floor(inset)}px`).join(" ");
8720
+ }
8721
+ if (margin === watched) return;
8722
+ watched = margin;
8723
+ watcher?.disconnect();
8724
+ watcher = null;
8725
+ if (margin) arm(margin, 1);
8726
+ };
8727
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => schedule());
8728
+ const measure = () => {
8729
+ frame = 0;
8730
+ reportOffset(key, card);
8731
+ const shown = card.offsetHeight > 0;
8732
+ listen(shown || !observer);
8733
+ watch(shown);
8734
+ };
8735
+ measure();
8512
8736
  observer?.observe(card);
8513
- if (!observer) window.addEventListener("resize", report);
8737
+ card.addEventListener("animationend", schedule);
8514
8738
  return () => {
8739
+ stopped = true;
8740
+ cancelAnimationFrame(frame);
8741
+ listen(false);
8742
+ watcher?.disconnect();
8515
8743
  observer?.disconnect();
8516
- if (!observer) window.removeEventListener("resize", report);
8517
- cardHeights.delete(key);
8744
+ card.removeEventListener("animationend", schedule);
8745
+ cardOffsets.delete(key);
8518
8746
  writeOffset();
8519
8747
  };
8520
8748
  }, [key, card]);
8521
8749
  useIsomorphicLayoutEffect2(() => {
8522
- if (card && typeof ResizeObserver === "undefined") reportHeight(key, card);
8750
+ if (card && typeof ResizeObserver === "undefined") reportOffset(key, card);
8523
8751
  });
8524
8752
  }
8525
8753
  function useCompactSummary(slot, enabled) {
@@ -10155,7 +10383,7 @@ function TablePagination({
10155
10383
 
10156
10384
  // src/components/ui/mode-toggle.tsx
10157
10385
  import { cva as cva19 } from "class-variance-authority";
10158
- import { jsx as jsx56, jsxs as jsxs45 } from "react/jsx-runtime";
10386
+ import { Fragment as Fragment20, jsx as jsx56, jsxs as jsxs45 } from "react/jsx-runtime";
10159
10387
  var modeToggleVariants = cva19(
10160
10388
  [
10161
10389
  "inline-flex items-center rounded-[var(--radius-md)] border border-[var(--border-subtle)]",
@@ -10178,6 +10406,17 @@ var modeToggleItemVariants = cva19(
10178
10406
  "[&_svg]:pointer-events-none [&_svg]:size-[14px] [&_svg]:shrink-0 [&_svg]:stroke-[1.75]"
10179
10407
  ].join(" ")
10180
10408
  );
10409
+ function formatModeToggleCount(value) {
10410
+ return value > 99 ? "99+" : String(value);
10411
+ }
10412
+ function isShownModeToggleCount(value) {
10413
+ if (value === void 0) return false;
10414
+ if (Number.isInteger(value) && value >= 0) return true;
10415
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
10416
+ console.warn(`ModeToggle: count must be an integer 0 or greater (got ${value}). The count is not shown.`);
10417
+ }
10418
+ return false;
10419
+ }
10181
10420
  function ModeToggle({
10182
10421
  className,
10183
10422
  itemClassName,
@@ -10218,7 +10457,11 @@ function ModeToggle({
10218
10457
  },
10219
10458
  children: [
10220
10459
  Icon ? /* @__PURE__ */ jsx56(Icon, { "aria-hidden": "true", "data-slot": "mode-toggle-icon" }) : null,
10221
- /* @__PURE__ */ jsx56("span", { children: option.label })
10460
+ /* @__PURE__ */ jsx56("span", { children: option.label }),
10461
+ isShownModeToggleCount(option.count) ? /* @__PURE__ */ jsxs45(Fragment20, { children: [
10462
+ /* @__PURE__ */ jsx56("span", { "aria-hidden": "true", "data-slot": "mode-toggle-count", className: tabsBadgeVariants(), children: formatModeToggleCount(option.count) }),
10463
+ /* @__PURE__ */ jsx56("span", { className: "sr-only", children: ` ${option.count}` })
10464
+ ] }) : null
10222
10465
  ]
10223
10466
  },
10224
10467
  option.value
@@ -10667,7 +10910,7 @@ function ToastProvider({ children, maxToasts = 3, className, ...props }) {
10667
10910
  import * as React48 from "react";
10668
10911
  import { cva as cva22 } from "class-variance-authority";
10669
10912
  import { AlertCircle as AlertCircle4 } from "lucide-react";
10670
- import { Fragment as Fragment19, jsx as jsx59, jsxs as jsxs48 } from "react/jsx-runtime";
10913
+ import { Fragment as Fragment21, jsx as jsx59, jsxs as jsxs48 } from "react/jsx-runtime";
10671
10914
  var otpFieldVariants = cva22("flex w-full min-w-0 flex-col gap-[var(--space-3)]", {
10672
10915
  variants: {
10673
10916
  size: {
@@ -10997,7 +11240,7 @@ var OTPField = React48.forwardRef(
10997
11240
  "data-slot": "otp-field-message",
10998
11241
  "data-kind": errorText ? "error" : "helper",
10999
11242
  className: otpFieldMessageVariants({ state: resolvedState }),
11000
- children: errorText ? /* @__PURE__ */ jsxs48(Fragment19, { children: [
11243
+ children: errorText ? /* @__PURE__ */ jsxs48(Fragment21, { children: [
11001
11244
  /* @__PURE__ */ jsx59(
11002
11245
  AlertCircle4,
11003
11246
  {
@@ -11885,7 +12128,7 @@ function TreeView({
11885
12128
 
11886
12129
  // src/components/branding/olun-logo.tsx
11887
12130
  import { OLUN_MASTER, OLUN_SYMBOL, OLUN_WORDMARK } from "@olundot/tokens/brand";
11888
- import { Fragment as Fragment20, jsx as jsx66, jsxs as jsxs53 } from "react/jsx-runtime";
12131
+ import { Fragment as Fragment22, jsx as jsx66, jsxs as jsxs53 } from "react/jsx-runtime";
11889
12132
  var ACCENT_FILL = "var(--accent-pop)";
11890
12133
  var INK = { color: "var(--text-primary)" };
11891
12134
  var INK_TIGHT = { ...INK, display: "block", overflow: "visible" };
@@ -11895,7 +12138,7 @@ function symbolGeometry(size) {
11895
12138
  function SymbolCircles({ size }) {
11896
12139
  const { ring } = OLUN_SYMBOL;
11897
12140
  const g = symbolGeometry(size);
11898
- return /* @__PURE__ */ jsxs53(Fragment20, { children: [
12141
+ return /* @__PURE__ */ jsxs53(Fragment22, { children: [
11899
12142
  /* @__PURE__ */ jsx66(
11900
12143
  "circle",
11901
12144
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olundot/ui",
3
- "version": "0.20.0-next.12",
3
+ "version": "0.20.0-next.13",
4
4
  "description": "OLUN. shared React UI components built on OLUN design tokens",
5
5
  "license": "MIT",
6
6
  "keywords": [