@olundot/ui 0.20.0-next.11 → 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,46 @@
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
+
34
+ ## 0.20.0-next.12
35
+
36
+ ### Minor Changes
37
+
38
+ - 2105422: 구조형 `Dialog`의 `size`에 `"xl"`(960px)을 더했다. 두 단 구성처럼 `"lg"`(720px)로 모자란 내용에 쓴다.
39
+
40
+ - 1024px 이상에서 가운데 상자다. 폭은 다른 크기처럼 화면 폭에서 32px을 뺀 값을 넘지 않는다. 높이 상한과 본문 스크롤도 같다.
41
+ - 1024px 미만에서는 다른 크기처럼 아래쪽 시트다.
42
+ - `"sm"`, `"md"`, `"lg"`의 폭과 기본값 `"md"`는 그대로다. Sheet와 AlertDialog 폭도 바뀌지 않는다.
43
+
3
44
  ## 0.20.0-next.11
4
45
 
5
46
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -711,8 +711,8 @@ type OverlayScrim = "blur" | "dim";
711
711
  * Pure CSS, so SSR and hydration render the same tree at every width (no matchMedia swap).
712
712
  */
713
713
  type DialogMobilePresentation = "center" | "sheet";
714
- /** Structured Dialog width: 420 · 560 · 720. */
715
- type DialogSize = "sm" | "md" | "lg";
714
+ /** Structured Dialog width: 420 · 560 · 720 · 960. */
715
+ type DialogSize = "sm" | "md" | "lg" | "xl";
716
716
  /**
717
717
  * Structured Dialog (spec §3.2): a center window for forms, pickers and long content. X + footer,
718
718
  * outside click does not close, Escape does (not while submitting), the body scrolls inside the max
@@ -725,7 +725,10 @@ interface DialogProps {
725
725
  title: string;
726
726
  /** Line under the title, also the accessible description. */
727
727
  description?: string;
728
- /** Box width: 420 · 560 · 720. Default `"md"`. Other widths are not supported. */
728
+ /**
729
+ * Box width: 420 · 560 · 720 · 960. Default `"md"`. `"xl"` is for content 720 cannot hold, such as
730
+ * two columns. Other widths are not supported.
731
+ */
729
732
  size?: DialogSize;
730
733
  /** Body. Scrolls inside the box's max height. */
731
734
  children: React.ReactNode;
@@ -854,6 +857,12 @@ interface InfoModalProps {
854
857
  declare function InfoModal({ open, onOpenChange, title, description, children, closeLabel, footer, }: InfoModalProps): react_jsx_runtime.JSX.Element;
855
858
 
856
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
+ }
857
866
  interface FilterToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
858
867
  search?: React.ReactNode;
859
868
  filters?: React.ReactNode;
@@ -865,6 +874,8 @@ interface FilterToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
865
874
  resultSummary?: React.ReactNode;
866
875
  clearAction?: React.ReactNode;
867
876
  actions?: React.ReactNode;
877
+ layout?: FilterToolbarLayout;
878
+ appliedFilters?: FilterToolbarAppliedFilter[];
868
879
  }
869
880
  declare const FilterToolbar: React.ForwardRefExoticComponent<FilterToolbarProps & React.RefAttributes<HTMLDivElement>>;
870
881
 
@@ -1289,8 +1300,8 @@ type BulkActionBarProps = BulkActionBarBaseProps & ({
1289
1300
  * with the clear (✕) button on its right, `summary` under it at full width, then
1290
1301
  * the actions as outline sm Buttons aligned right, wrapping onto more lines when
1291
1302
  * they do not fit. `variant: "destructive"` is outline danger. The card is
1292
- * min(360px, its slot − 32px) wide; while it is mounted the document root carries
1293
- * `--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.
1294
1305
  * DOM order stays count → actions → ✕. It takes no `trailing`.
1295
1306
  *
1296
1307
  * placement="inline" (deprecated) is a top-sticky band that replaces the status
@@ -1963,6 +1974,8 @@ interface ModeToggleOption<T extends string> {
1963
1974
  value: T;
1964
1975
  label: string;
1965
1976
  icon?: LucideIcon;
1977
+ /** 라벨 뒤 개수 알약입니다. 0 이상의 정수만 보이고, 100부터는 `99+`로 보입니다. 접근 이름에는 실제 수가 들어갑니다. */
1978
+ count?: number;
1966
1979
  disabled?: boolean;
1967
1980
  }
1968
1981
  interface ModeToggleProps<T extends string> extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
@@ -2332,4 +2345,4 @@ interface OrganizationLogoProps extends Omit<SVGProps<SVGSVGElement>, "width" |
2332
2345
  }
2333
2346
  declare function OrganizationLogo({ organization, size, className, style, role, "aria-label": ariaLabel, "aria-hidden": ariaHidden, ...props }: OrganizationLogoProps): react_jsx_runtime.JSX.Element;
2334
2347
 
2335
- 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
@@ -4293,7 +4293,8 @@ var overlaySize = {
4293
4293
  dialog: {
4294
4294
  sm: "max-w-[420px]",
4295
4295
  md: "max-w-[560px]",
4296
- lg: "max-w-[720px]"
4296
+ lg: "max-w-[720px]",
4297
+ xl: "max-w-[960px]"
4297
4298
  },
4298
4299
  sheet: {
4299
4300
  md: "lg:w-[480px] lg:max-w-[480px]",
@@ -5546,7 +5547,7 @@ var PopoverArrow = React30.forwardRef(({ className, ...props }, ref) => /* @__PU
5546
5547
  PopoverArrow.displayName = PopoverPrimitive.Arrow.displayName;
5547
5548
 
5548
5549
  // src/components/ui/filter-toolbar.tsx
5549
- 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";
5550
5551
  var collapsibleClassNames = {
5551
5552
  sm: {
5552
5553
  inline: "hidden @[640px]:flex",
@@ -5565,6 +5566,41 @@ var collapsibleClassNames = {
5565
5566
  trigger: "hidden"
5566
5567
  }
5567
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
+ }
5568
5604
  var FilterToolbar = React31.forwardRef(
5569
5605
  ({
5570
5606
  className,
@@ -5572,12 +5608,14 @@ var FilterToolbar = React31.forwardRef(
5572
5608
  filters,
5573
5609
  collapsible,
5574
5610
  collapseBelow = "lg",
5575
- activeFilterCount = 0,
5611
+ activeFilterCount: activeFilterCountProp,
5576
5612
  onClearFilters,
5577
5613
  collapsedLabel = "\uD544\uD130",
5578
5614
  resultSummary,
5579
5615
  clearAction,
5580
5616
  actions,
5617
+ layout = "flow",
5618
+ appliedFilters,
5581
5619
  ...props
5582
5620
  }, ref) => {
5583
5621
  const [rootElement, setRootElement] = React31.useState(
@@ -5588,6 +5626,10 @@ var FilterToolbar = React31.forwardRef(
5588
5626
  );
5589
5627
  const [overlayOpen, setOverlayOpen] = React31.useState(false);
5590
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);
5591
5633
  const setRootRefs = React31.useCallback(
5592
5634
  (element) => {
5593
5635
  setRootElement(element);
@@ -5611,13 +5653,49 @@ var FilterToolbar = React31.forwardRef(
5611
5653
  setOverlayOpen(open);
5612
5654
  if (!open) queueMicrotask(() => triggerRef.current?.focus());
5613
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]);
5614
5690
  const hasControls = Boolean(search || filters || collapsible);
5615
5691
  const hasMeta = Boolean(
5616
5692
  resultSummary || clearAction || actions && !hasControls
5617
5693
  );
5694
+ const activeFilterCount = activeFilterCountProp ?? appliedFilters?.length ?? 0;
5618
5695
  const hasAppliedFilters = activeFilterCount > 0;
5696
+ const hasAppliedChips = Boolean(appliedFilters && appliedFilters.length > 0);
5619
5697
  const usesSheet = containerWidth !== null && containerWidth < 640;
5620
- const collapseClasses = collapsibleClassNames[collapseBelow];
5698
+ const collapseClasses = (isGrid ? gridCollapsibleClassNames : collapsibleClassNames)[collapseBelow];
5621
5699
  const collapseTriggerLabel = hasAppliedFilters ? `${collapsedLabel}, ${activeFilterCount}\uAC1C \uC801\uC6A9` : collapsedLabel;
5622
5700
  const clearFiltersButton = onClearFilters ? /* @__PURE__ */ jsx32(
5623
5701
  "button",
@@ -5688,14 +5766,18 @@ var FilterToolbar = React31.forwardRef(
5688
5766
  hasControls ? /* @__PURE__ */ jsxs28(
5689
5767
  "div",
5690
5768
  {
5769
+ ref: rowRef,
5691
5770
  "data-slot": "filter-toolbar-row",
5692
- 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
+ ),
5693
5775
  children: [
5694
5776
  search ? /* @__PURE__ */ jsx32(
5695
5777
  "div",
5696
5778
  {
5697
5779
  "data-slot": "filter-toolbar-search",
5698
- 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]",
5699
5781
  children: search
5700
5782
  }
5701
5783
  ) : null,
@@ -5722,8 +5804,16 @@ var FilterToolbar = React31.forwardRef(
5722
5804
  "div",
5723
5805
  {
5724
5806
  "data-slot": "filter-toolbar-filters",
5725
- className: "order-3 flex min-w-0 flex-[1_0_100%] flex-wrap items-center gap-[var(--space-2)] @[640px]:flex-[2_1_auto]",
5726
- 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
5727
5817
  }
5728
5818
  ) : null,
5729
5819
  collapsible && !overlayOpen ? /* @__PURE__ */ jsx32(
@@ -5758,18 +5848,72 @@ var FilterToolbar = React31.forwardRef(
5758
5848
  children: collapsedContent
5759
5849
  }
5760
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,
5761
5902
  hasAppliedFilters ? /* @__PURE__ */ jsxs28(
5762
5903
  "div",
5763
5904
  {
5764
5905
  "data-slot": "filter-toolbar-applied",
5765
- 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
+ ),
5766
5910
  children: [
5767
5911
  /* @__PURE__ */ jsxs28("span", { children: [
5768
5912
  "\uD544\uD130 ",
5769
5913
  activeFilterCount,
5770
5914
  "\uAC1C \uC801\uC6A9"
5771
5915
  ] }),
5772
- onClearFilters ? /* @__PURE__ */ jsxs28(Fragment13, { children: [
5916
+ onClearFilters ? /* @__PURE__ */ jsxs28(Fragment14, { children: [
5773
5917
  /* @__PURE__ */ jsx32("span", { "aria-hidden": "true", children: " \xB7 " }),
5774
5918
  /* @__PURE__ */ jsx32(
5775
5919
  "button",
@@ -6077,7 +6221,7 @@ function DropdownMenuSubContent({
6077
6221
  }
6078
6222
 
6079
6223
  // src/components/ui/action-menu.tsx
6080
- 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";
6081
6225
  var sheetRowClassName = cn(
6082
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",
6083
6227
  "border-t border-[var(--border-subtle)] text-[13px] font-medium leading-normal text-text-primary",
@@ -6180,10 +6324,10 @@ function ActionMenuSheet({
6180
6324
  },
6181
6325
  children: [
6182
6326
  Icon ? /* @__PURE__ */ jsx34(Icon, { "aria-hidden": true, size: 17, strokeWidth: 1.75, className: cn("shrink-0", reason && unavailableClassName) }) : null,
6183
- /* @__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: [
6184
6328
  /* @__PURE__ */ jsx34("span", { id: itemLabelId, className: unavailableClassName, children: item.label }),
6185
6329
  /* @__PURE__ */ jsx34("span", { id: reasonId, className: reasonClassName, children: reason })
6186
- ] }) : /* @__PURE__ */ jsxs30(Fragment15, { children: [
6330
+ ] }) : /* @__PURE__ */ jsxs30(Fragment16, { children: [
6187
6331
  item.label,
6188
6332
  item.description ? /* @__PURE__ */ jsx34("span", { className: sheetRowDescriptionClassName, children: item.description }) : null
6189
6333
  ] }) })
@@ -6291,7 +6435,7 @@ function ActionMenu({
6291
6435
  triggerRef.current?.focus();
6292
6436
  };
6293
6437
  const triggerLabel = trigger.props["aria-label"];
6294
- return /* @__PURE__ */ jsxs30(Fragment15, { children: [
6438
+ return /* @__PURE__ */ jsxs30(Fragment16, { children: [
6295
6439
  /* @__PURE__ */ jsxs30(DropdownMenu, { open: open && presentation === "dropdown", onOpenChange: setOpen, children: [
6296
6440
  /* @__PURE__ */ jsx34(DropdownMenuTrigger, { asChild: true, "aria-expanded": open, children: /* @__PURE__ */ jsx34(Slot3.Root, { ref: triggerRef, children: trigger }) }),
6297
6441
  /* @__PURE__ */ jsx34(DropdownMenuContent, { align: "end", onCloseAutoFocus: handleDropdownCloseAutoFocus, children: menuGroups.map((group, index) => /* @__PURE__ */ jsxs30(React32.Fragment, { children: [
@@ -6703,7 +6847,7 @@ function ContextMenu({
6703
6847
  // src/components/ui/list-card.tsx
6704
6848
  import * as React34 from "react";
6705
6849
  import { ChevronRight as ChevronRight2, MoreVertical } from "lucide-react";
6706
- 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";
6707
6851
  function lineTokens(value) {
6708
6852
  return React34.Children.toArray(value).filter((token) => token !== "");
6709
6853
  }
@@ -6790,7 +6934,7 @@ function ListCard({
6790
6934
  children: overlay && href != null ? React34.createElement(linkComponent ?? "a", { ...linkProps, href, className: overlayClassName }, title) : title
6791
6935
  }
6792
6936
  );
6793
- const content = /* @__PURE__ */ jsxs33(Fragment16, { children: [
6937
+ const content = /* @__PURE__ */ jsxs33(Fragment17, { children: [
6794
6938
  leading != null ? /* @__PURE__ */ jsx37("span", { className: "flex min-h-[18px] items-center", children: leading }) : null,
6795
6939
  /* @__PURE__ */ jsxs33("span", { className: "min-w-0", children: [
6796
6940
  overlay && href == null ? (
@@ -6821,7 +6965,7 @@ function ListCard({
6821
6965
  ),
6822
6966
  children: [
6823
6967
  hasValue ? /* @__PURE__ */ jsx37("span", { "data-slot": `${slot}-value`, className: "text-[13px] leading-[20px] text-text-secondary tabular-nums", children: value }) : null,
6824
- 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(
6825
6969
  ActionMenu,
6826
6970
  {
6827
6971
  title: typeof title === "string" ? title : void 0,
@@ -6855,7 +6999,7 @@ function ListCard({
6855
6999
  }
6856
7000
 
6857
7001
  // src/components/ui/data-table.tsx
6858
- 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";
6859
7003
  var dataTableVariants = cva13(
6860
7004
  [
6861
7005
  "relative overflow-hidden rounded-[var(--radius-md)] border border-[var(--border-subtle)]",
@@ -6978,25 +7122,24 @@ function RowActionsCell({
6978
7122
  );
6979
7123
  }
6980
7124
  function RowContextMenu({
6981
- openKey,
7125
+ menuKey,
6982
7126
  position,
6983
7127
  groups,
7128
+ cards,
6984
7129
  onClose
6985
7130
  }) {
6986
- const [open, setOpen] = React35.useState(false);
6987
- const pendingRef = React35.useRef(null);
6988
- 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
+ };
6989
7138
  const shownGroups = React35.useRef([]);
6990
7139
  if (groups) shownGroups.current = groups;
6991
- const gone = openKey !== null && groups === null;
7140
+ const gone = groups === null;
6992
7141
  const contentRef = React35.useRef(null);
6993
7142
  const focusInsideRef = React35.useRef(false);
6994
- React35.useEffect(() => {
6995
- pendingRef.current = null;
6996
- outsideRef.current = false;
6997
- focusInsideRef.current = false;
6998
- setOpen(openKey !== null);
6999
- }, [openKey]);
7000
7143
  useIsomorphicLayoutEffect(() => {
7001
7144
  const content = contentRef.current;
7002
7145
  if (!open || !focusInsideRef.current || !content?.isConnected) return;
@@ -7006,13 +7149,20 @@ function RowContextMenu({
7006
7149
  next.focus();
7007
7150
  });
7008
7151
  React35.useEffect(() => {
7009
- if (gone) setOpen(false);
7152
+ if (gone) close({ by: "dismiss", outside: false });
7010
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]);
7011
7159
  return /* @__PURE__ */ jsx38(
7012
7160
  ContextMenu,
7013
7161
  {
7014
7162
  open: open && !gone,
7015
- onOpenChange: setOpen,
7163
+ onOpenChange: (next) => {
7164
+ if (!next) close({ by: "dismiss", outside: false });
7165
+ },
7016
7166
  position,
7017
7167
  contentProps: {
7018
7168
  align: "start",
@@ -7026,13 +7176,15 @@ function RowContextMenu({
7026
7176
  }
7027
7177
  },
7028
7178
  onInteractOutside: () => {
7029
- outsideRef.current = true;
7179
+ pressedOutside.current = true;
7180
+ close({ by: "dismiss", outside: true });
7030
7181
  },
7031
7182
  onCloseAutoFocus: (event) => {
7032
7183
  event.preventDefault();
7033
- const item = pendingRef.current;
7034
- pendingRef.current = null;
7035
- 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);
7036
7188
  }
7037
7189
  },
7038
7190
  children: shownGroups.current.map((group, index) => /* @__PURE__ */ jsxs34(React35.Fragment, { children: [
@@ -7044,7 +7196,7 @@ function RowContextMenu({
7044
7196
  {
7045
7197
  item,
7046
7198
  onPick: (picked) => {
7047
- pendingRef.current = picked;
7199
+ closeRef.current ??= { by: "item", item: picked };
7048
7200
  }
7049
7201
  },
7050
7202
  item.id
@@ -7129,7 +7281,7 @@ function MobileSortMenu({
7129
7281
  size: "sm",
7130
7282
  className: "ml-auto min-w-0",
7131
7283
  children: [
7132
- active && sortBy ? /* @__PURE__ */ jsxs34(Fragment18, { children: [
7284
+ active && sortBy ? /* @__PURE__ */ jsxs34(Fragment19, { children: [
7133
7285
  /* @__PURE__ */ jsx38("span", { className: "sr-only", children: "\uC815\uB82C " }),
7134
7286
  /* @__PURE__ */ jsx38("span", { className: "truncate", children: active.header }),
7135
7287
  /* @__PURE__ */ jsxs34("span", { className: "sr-only", children: [
@@ -7402,7 +7554,7 @@ function DataTableRowView({
7402
7554
  if (!expand) {
7403
7555
  return dataRow;
7404
7556
  }
7405
- return /* @__PURE__ */ jsxs34(Fragment18, { children: [
7557
+ return /* @__PURE__ */ jsxs34(Fragment19, { children: [
7406
7558
  dataRow,
7407
7559
  /* @__PURE__ */ jsx38(
7408
7560
  "div",
@@ -7639,8 +7791,8 @@ function useVirtualWindow({
7639
7791
  padBottom: 0
7640
7792
  });
7641
7793
  const desktopSize = React35.useRef(0);
7642
- const cardHeights2 = React35.useRef(null);
7643
- cardHeights2.current ??= new CardHeights();
7794
+ const cardHeights = React35.useRef(null);
7795
+ cardHeights.current ??= new CardHeights();
7644
7796
  const update = React35.useRef(() => void 0);
7645
7797
  const reported = React35.useRef("");
7646
7798
  const latest = React35.useRef({
@@ -7666,9 +7818,9 @@ function useVirtualWindow({
7666
7818
  extent = uniformExtent(size);
7667
7819
  } else {
7668
7820
  if (list) {
7669
- cardHeights2.current.measure(list);
7821
+ cardHeights.current.measure(list);
7670
7822
  }
7671
- const { offsets, estimate } = cardHeights2.current.offsets(rowIds);
7823
+ const { offsets, estimate } = cardHeights.current.offsets(rowIds);
7672
7824
  extent = measuredExtent(offsets, estimate);
7673
7825
  }
7674
7826
  latest.current = { count, layout, extent };
@@ -8014,15 +8166,22 @@ function DataTableInner({
8014
8166
  contextMenuKey.current += 1;
8015
8167
  setContextMenu({ rowId, position: { x: event.clientX, y: event.clientY }, key: contextMenuKey.current });
8016
8168
  };
8017
- const closeRowContextMenu = (item, outside) => {
8169
+ const closeRowContextMenu = (key, close) => {
8170
+ if (key !== contextMenuKey.current) {
8171
+ return;
8172
+ }
8018
8173
  const rowId = contextMenu?.rowId;
8019
- if (rowId !== void 0 && (item || !outside)) {
8020
- const list = desktopListRef.current;
8021
- const row = Array.from(list?.querySelectorAll('[data-slot="data-table-row"]') ?? []).find(
8022
- (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'
8023
8183
  );
8024
- const trigger = row?.querySelector('[data-slot="data-table-row-actions"] button');
8025
- 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;
8026
8185
  if (target) {
8027
8186
  if (!target.hasAttribute("tabindex") && !target.matches("a[href],button,input")) {
8028
8187
  target.tabIndex = -1;
@@ -8031,7 +8190,7 @@ function DataTableInner({
8031
8190
  }
8032
8191
  }
8033
8192
  setContextMenu(null);
8034
- item?.onSelect();
8193
+ if (close.by === "item") close.item.onSelect();
8035
8194
  };
8036
8195
  const handleRowContextMenu = rowActions ? openRowContextMenu : onRowContextMenu;
8037
8196
  const latestHandlers = React35.useRef({ toggleRow, activateRow, onRowContextMenu, handleRowContextMenu });
@@ -8283,14 +8442,16 @@ function DataTableInner({
8283
8442
  )
8284
8443
  ] }),
8285
8444
  footer != null ? /* @__PURE__ */ jsx38("div", { "data-slot": "data-table-footer", className: "border-t border-[var(--border-subtle)] empty:hidden", children: footer }) : null,
8286
- hasRowActions ? /* @__PURE__ */ jsx38(
8445
+ hasRowActions && contextMenu ? /* @__PURE__ */ jsx38(
8287
8446
  RowContextMenu,
8288
8447
  {
8289
- openKey: contextMenu?.key ?? null,
8290
- position: contextMenu?.position ?? null,
8448
+ menuKey: contextMenu.key,
8449
+ position: contextMenu.position,
8291
8450
  groups: contextGroups,
8451
+ cards: activeLayout === "mobile",
8292
8452
  onClose: closeRowContextMenu
8293
- }
8453
+ },
8454
+ contextMenu.key
8294
8455
  ) : null
8295
8456
  ]
8296
8457
  }
@@ -8487,17 +8648,25 @@ function warnTrailingOnce() {
8487
8648
  console.warn("BulkActionBar: the floating card ignores `trailing`. Put edit-mode controls at the right end of the toolbar.");
8488
8649
  }
8489
8650
  var OFFSET_PROPERTY = "--olun-bulk-bar-offset";
8490
- var cardHeights = /* @__PURE__ */ new Map();
8651
+ var LIFT_ZONE = 204;
8652
+ var cardOffsets = /* @__PURE__ */ new Map();
8491
8653
  function writeOffset() {
8492
8654
  const root = document.documentElement;
8493
- const height = Math.max(0, ...cardHeights.values());
8494
- 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);
8495
8659
  else root.style.removeProperty(OFFSET_PROPERTY);
8496
8660
  }
8497
- function reportHeight(key, card) {
8498
- const height = card.offsetHeight;
8499
- if (height > 0) cardHeights.set(key, height);
8500
- 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);
8501
8670
  writeOffset();
8502
8671
  }
8503
8672
  var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? React36.useEffect : React36.useLayoutEffect;
@@ -8505,20 +8674,80 @@ function useBottomOffset(card) {
8505
8674
  const [key] = React36.useState(() => ({}));
8506
8675
  React36.useEffect(() => {
8507
8676
  if (!card) return;
8508
- const report = () => reportHeight(key, card);
8509
- report();
8510
- 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();
8511
8736
  observer?.observe(card);
8512
- if (!observer) window.addEventListener("resize", report);
8737
+ card.addEventListener("animationend", schedule);
8513
8738
  return () => {
8739
+ stopped = true;
8740
+ cancelAnimationFrame(frame);
8741
+ listen(false);
8742
+ watcher?.disconnect();
8514
8743
  observer?.disconnect();
8515
- if (!observer) window.removeEventListener("resize", report);
8516
- cardHeights.delete(key);
8744
+ card.removeEventListener("animationend", schedule);
8745
+ cardOffsets.delete(key);
8517
8746
  writeOffset();
8518
8747
  };
8519
8748
  }, [key, card]);
8520
8749
  useIsomorphicLayoutEffect2(() => {
8521
- if (card && typeof ResizeObserver === "undefined") reportHeight(key, card);
8750
+ if (card && typeof ResizeObserver === "undefined") reportOffset(key, card);
8522
8751
  });
8523
8752
  }
8524
8753
  function useCompactSummary(slot, enabled) {
@@ -10154,7 +10383,7 @@ function TablePagination({
10154
10383
 
10155
10384
  // src/components/ui/mode-toggle.tsx
10156
10385
  import { cva as cva19 } from "class-variance-authority";
10157
- 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";
10158
10387
  var modeToggleVariants = cva19(
10159
10388
  [
10160
10389
  "inline-flex items-center rounded-[var(--radius-md)] border border-[var(--border-subtle)]",
@@ -10177,6 +10406,17 @@ var modeToggleItemVariants = cva19(
10177
10406
  "[&_svg]:pointer-events-none [&_svg]:size-[14px] [&_svg]:shrink-0 [&_svg]:stroke-[1.75]"
10178
10407
  ].join(" ")
10179
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
+ }
10180
10420
  function ModeToggle({
10181
10421
  className,
10182
10422
  itemClassName,
@@ -10217,7 +10457,11 @@ function ModeToggle({
10217
10457
  },
10218
10458
  children: [
10219
10459
  Icon ? /* @__PURE__ */ jsx56(Icon, { "aria-hidden": "true", "data-slot": "mode-toggle-icon" }) : null,
10220
- /* @__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
10221
10465
  ]
10222
10466
  },
10223
10467
  option.value
@@ -10666,7 +10910,7 @@ function ToastProvider({ children, maxToasts = 3, className, ...props }) {
10666
10910
  import * as React48 from "react";
10667
10911
  import { cva as cva22 } from "class-variance-authority";
10668
10912
  import { AlertCircle as AlertCircle4 } from "lucide-react";
10669
- 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";
10670
10914
  var otpFieldVariants = cva22("flex w-full min-w-0 flex-col gap-[var(--space-3)]", {
10671
10915
  variants: {
10672
10916
  size: {
@@ -10996,7 +11240,7 @@ var OTPField = React48.forwardRef(
10996
11240
  "data-slot": "otp-field-message",
10997
11241
  "data-kind": errorText ? "error" : "helper",
10998
11242
  className: otpFieldMessageVariants({ state: resolvedState }),
10999
- children: errorText ? /* @__PURE__ */ jsxs48(Fragment19, { children: [
11243
+ children: errorText ? /* @__PURE__ */ jsxs48(Fragment21, { children: [
11000
11244
  /* @__PURE__ */ jsx59(
11001
11245
  AlertCircle4,
11002
11246
  {
@@ -11884,7 +12128,7 @@ function TreeView({
11884
12128
 
11885
12129
  // src/components/branding/olun-logo.tsx
11886
12130
  import { OLUN_MASTER, OLUN_SYMBOL, OLUN_WORDMARK } from "@olundot/tokens/brand";
11887
- 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";
11888
12132
  var ACCENT_FILL = "var(--accent-pop)";
11889
12133
  var INK = { color: "var(--text-primary)" };
11890
12134
  var INK_TIGHT = { ...INK, display: "block", overflow: "visible" };
@@ -11894,7 +12138,7 @@ function symbolGeometry(size) {
11894
12138
  function SymbolCircles({ size }) {
11895
12139
  const { ring } = OLUN_SYMBOL;
11896
12140
  const g = symbolGeometry(size);
11897
- return /* @__PURE__ */ jsxs53(Fragment20, { children: [
12141
+ return /* @__PURE__ */ jsxs53(Fragment22, { children: [
11898
12142
  /* @__PURE__ */ jsx66(
11899
12143
  "circle",
11900
12144
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olundot/ui",
3
- "version": "0.20.0-next.11",
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": [