@exsto/ds 0.0.1-rc.18 → 0.0.1-rc.19

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.
Files changed (81) hide show
  1. package/README.md +22 -1
  2. package/dist/index.js +3 -2
  3. package/dist/source-hash.txt +1 -1
  4. package/dist/styles.css +54 -55
  5. package/dist/theme.css +6 -1
  6. package/dist/ui/src/components/edyn/file-input/file-input.js +2 -2
  7. package/dist/ui/src/components/edyn/file-input/file-input.js.map +1 -1
  8. package/dist/ui/src/components/edyn/multi-select/multi-select.js +6 -6
  9. package/dist/ui/src/components/edyn/multi-select/multi-select.js.map +1 -1
  10. package/dist/ui/src/components/edyn/rich-text-editor/mention-list.js +1 -1
  11. package/dist/ui/src/components/edyn/rich-text-editor/rich-text-editor.js +1 -1
  12. package/dist/ui/src/components/edyn/rich-text-editor/rich-text-editor.js.map +1 -1
  13. package/dist/ui/src/components/edyn/rich-text-editor/toolbar.js +1 -1
  14. package/dist/ui/src/components/edyn/rich-text-editor/toolbar.js.map +1 -1
  15. package/dist/ui/src/components/edyn/time-field/time-field.js +8 -1
  16. package/dist/ui/src/components/edyn/time-field/time-field.js.map +1 -1
  17. package/dist/ui/src/components/ui/breadcrumb/breadcrumb.js +1 -1
  18. package/dist/ui/src/components/ui/breadcrumb/breadcrumb.js.map +1 -1
  19. package/dist/ui/src/components/ui/button/button.js +2 -2
  20. package/dist/ui/src/components/ui/button/button.js.map +1 -1
  21. package/dist/ui/src/components/ui/card/card.js +1 -1
  22. package/dist/ui/src/components/ui/card/card.js.map +1 -1
  23. package/dist/ui/src/components/ui/checkbox/checkbox.js +1 -1
  24. package/dist/ui/src/components/ui/checkbox/checkbox.js.map +1 -1
  25. package/dist/ui/src/components/ui/combobox/combobox.d.ts +5 -1
  26. package/dist/ui/src/components/ui/combobox/combobox.js +12 -18
  27. package/dist/ui/src/components/ui/combobox/combobox.js.map +1 -1
  28. package/dist/ui/src/components/ui/command/command.js +2 -2
  29. package/dist/ui/src/components/ui/command/command.js.map +1 -1
  30. package/dist/ui/src/components/ui/context-menu/context-menu.js +5 -9
  31. package/dist/ui/src/components/ui/context-menu/context-menu.js.map +1 -1
  32. package/dist/ui/src/components/ui/dropdown-menu/dropdown-menu.js +5 -9
  33. package/dist/ui/src/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
  34. package/dist/ui/src/components/ui/empty/empty.js +1 -1
  35. package/dist/ui/src/components/ui/empty/empty.js.map +1 -1
  36. package/dist/ui/src/components/ui/field/field.js +2 -2
  37. package/dist/ui/src/components/ui/field/field.js.map +1 -1
  38. package/dist/ui/src/components/ui/hover-card/hover-card.js +2 -1
  39. package/dist/ui/src/components/ui/hover-card/hover-card.js.map +1 -1
  40. package/dist/ui/src/components/ui/input/input.js +1 -1
  41. package/dist/ui/src/components/ui/input/input.js.map +1 -1
  42. package/dist/ui/src/components/ui/input-group/input-group.js +1 -1
  43. package/dist/ui/src/components/ui/input-group/input-group.js.map +1 -1
  44. package/dist/ui/src/components/ui/input-otp/input-otp.js +1 -1
  45. package/dist/ui/src/components/ui/input-otp/input-otp.js.map +1 -1
  46. package/dist/ui/src/components/ui/label/label.js +1 -1
  47. package/dist/ui/src/components/ui/label/label.js.map +1 -1
  48. package/dist/ui/src/components/ui/menubar/menubar.js +7 -4
  49. package/dist/ui/src/components/ui/menubar/menubar.js.map +1 -1
  50. package/dist/ui/src/components/ui/native-select/native-select.js +1 -1
  51. package/dist/ui/src/components/ui/native-select/native-select.js.map +1 -1
  52. package/dist/ui/src/components/ui/navigation-menu/navigation-menu.js +2 -1
  53. package/dist/ui/src/components/ui/navigation-menu/navigation-menu.js.map +1 -1
  54. package/dist/ui/src/components/ui/popover/popover.js +2 -1
  55. package/dist/ui/src/components/ui/popover/popover.js.map +1 -1
  56. package/dist/ui/src/components/ui/questionnaire/questionnaire.js +4 -4
  57. package/dist/ui/src/components/ui/questionnaire/questionnaire.js.map +1 -1
  58. package/dist/ui/src/components/ui/radio-group/radio-group.js +1 -1
  59. package/dist/ui/src/components/ui/radio-group/radio-group.js.map +1 -1
  60. package/dist/ui/src/components/ui/scroll-area/index.d.ts +1 -0
  61. package/dist/ui/src/components/ui/scroll-area/popup-list-scroll.d.ts +13 -0
  62. package/dist/ui/src/components/ui/scroll-area/popup-list-scroll.js +69 -0
  63. package/dist/ui/src/components/ui/scroll-area/popup-list-scroll.js.map +1 -0
  64. package/dist/ui/src/components/ui/scroll-area/scroll-area.d.ts +2 -4
  65. package/dist/ui/src/components/ui/scroll-area/scroll-area.js +27 -4
  66. package/dist/ui/src/components/ui/scroll-area/scroll-area.js.map +1 -1
  67. package/dist/ui/src/components/ui/select/select.js +9 -14
  68. package/dist/ui/src/components/ui/select/select.js.map +1 -1
  69. package/dist/ui/src/components/ui/tabs/tabs.js +1 -1
  70. package/dist/ui/src/components/ui/tabs/tabs.js.map +1 -1
  71. package/dist/ui/src/components/ui/textarea/textarea.js +1 -1
  72. package/dist/ui/src/components/ui/textarea/textarea.js.map +1 -1
  73. package/dist/ui/src/components/ui/toggle/toggle.js +2 -2
  74. package/dist/ui/src/components/ui/toggle/toggle.js.map +1 -1
  75. package/dist/ui/src/components/ui/tooltip/tooltip.js +2 -1
  76. package/dist/ui/src/components/ui/tooltip/tooltip.js.map +1 -1
  77. package/dist/ui/src/index.d.ts +1 -1
  78. package/dist/ui/src/lib/popup-list.d.ts +7 -10
  79. package/dist/ui/src/lib/popup-list.js +8 -11
  80. package/dist/ui/src/lib/popup-list.js.map +1 -1
  81. package/package.json +1 -1
package/README.md CHANGED
@@ -188,7 +188,28 @@ import { cn, POPUP_FRAME_CLASS, POPUP_LIST_HEIGHT_CLASS, POPUP_LIST_WIDTH_CLASS,
188
188
 
189
189
  - `popupItemClass(flavor?, checkable?)` is one row: 32px tall, 8px of padding, the highlight and the chosen weight. `'base-ui'` (the default) is for Base UI parts, `'cmdk'` for cmdk's; `checkable` leaves room at the end for the check, which sits at `POPUP_ITEM_INDICATOR_CLASS`.
190
190
  - `POPUP_GROUP_LABEL_CLASS` is a group's heading. `ComboboxGroup`, `ComboboxLabel`, `ComboboxCollection` and `ComboboxSeparator` give a Combobox its groups, drawn the same way Select draws its own.
191
- - `POPUP_FRAME_CLASS` is the frame (a 10px radius and a 1px ring, no border). `POPUP_LIST_WIDTH_CLASS` is its width: at least the control and 8rem, as wide as the longest row, 28rem at most. `POPUP_LIST_HEIGHT_CLASS` caps the scrolling part: eight rows, or as many whole rows as the room allows, so a list never ends on half a row. Put it on a `ScrollArea` that holds the list's own `p-1`, inside a popup that is a flex column with `max-h-(--available-height)`.
191
+ - `POPUP_FRAME_CLASS` is the frame (a 10px radius and a 1px ring, no border). `POPUP_LIST_WIDTH_CLASS` is its width: at least the control and 8rem, as wide as the longest row, 28rem at most. `POPUP_LIST_HEIGHT_CLASS` caps the scrolling part: eight rows, or as many whole rows as the room allows, so a list never ends on half a row. Put it on a `ScrollArea` inside a popup that is a flex column with `max-h-(--available-height)`; the class carries the 4px of air above and below the rows as a margin, so the list inside takes `px-1` and no vertical padding (padding there scrolls with the rows and leaves a strip of the next row at the foot). `POPUP_LAYER_CLASS` (`z-60`) goes on the popup's positioner, so it is drawn above the Sheet, Dialog or Drawer (`z-50`) it is opened from.
192
+
193
+ ## Changes in 0.0.1-rc.19
194
+
195
+ Sizes and behaviours that moved. An app that patched one of these locally can drop its patch.
196
+
197
+ - **Popups stack above the overlay that holds them.** The positioner of Select, Combobox, MultiSelect, the menus, Popover, HoverCard, Tooltip and NavigationMenu is `z-60` (`POPUP_LAYER_CLASS`), over the `z-50` of Sheet, Dialog and Drawer. A Select opened in a Sheet or Dialog used to be drawn under it.
198
+ - **A popup list ends on a whole row.** Eight rows of 32px exactly, with the 4px of air above and below as the ScrollArea's margin (`POPUP_LIST_HEIGHT_CLASS`) and `px-1`, no vertical padding, on the list inside. The strip of a ninth row at the foot is gone, and a list of eight does not scroll.
199
+ - **A popup list counts rows only.** A group label or separator adds its own height to the eight rows, so a menu of a label and eight items does not scroll (`PopupListScroll`, exported).
200
+ - **A menu's check is a Select's check** (14px, 13px from the list's edge), and **a menu frame draws no focus ring**, in any open mode. A menu opened with the pointer still highlights no row; one opened from the keyboard highlights its first.
201
+ - **The Combobox chevron** is 11px from the end of its field, like a Select's, a 24px target, and named "Show options" (`triggerLabel` on `ComboboxInput`).
202
+ - **Label and FieldLabel are on a 20px line** (were 14px) and **a Field keeps 8px between its parts** (was 6px): a label and a control make a 60px field. Drop any 14/14 label override.
203
+ - **CardTitle is 16/24** (was 16/22), and 14/20 in a small Card (was 14/19.25).
204
+ - **A small Button, Toggle and Select trigger are 12px type on a 16px line** (`text-xs`; was 12.8px). Their height is still 28px.
205
+ - **EmptyDescription is on a 24px line** (was 22.75px) and **a BreadcrumbLink is 24px tall** (was 20px).
206
+ - **Control borders are 3:1** on the page and on a card, in both themes: a new `--control-border` token (`oklch(0.66 0 0)` light, `oklch(1 0 0 / 35%)` dark), utility `border-control`, used by Input, Textarea, Select, Combobox, MultiSelect, the date and time fields, FileInput, Checkbox, RadioGroup, Toggle, Tabs and the outline Button. `--input` stays the fill.
207
+ - **`--muted-foreground` is `oklch(0.545 0 0)`** in the light theme (was 0.556): 4.5:1 on the muted surface, a card and the page.
208
+ - **Input, Textarea, the Combobox input and InputGroup show the one 2px focus outline** and keep their border colour on focus. **Textarea has no resize grip.**
209
+ - **An empty TimeField segment reads HH, MM, SS** (was two en dashes).
210
+ - **The check of a row is at the end in every popup**, in the same column and size (14px, 13px from the list's edge): Menubar checkbox and radio items and Command rows now match Select, Combobox, DropdownMenu and ContextMenu.
211
+ - **The horizontal bar's room is the ScrollArea root's padding**, so a block that fits the box does not scroll vertically for it and no fade dims it.
212
+ - **A ScrollArea viewport is a tab stop only when nothing inside it can take Tab**, and then shows a 1px line inside its edge for the keyboard only. A page or table with links and buttons in it is no longer an extra stop with a 2px ring.
192
213
 
193
214
  ## Status
194
215
 
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { Slider } from "./ui/src/components/ui/slider/slider.js";
5
5
  import { formatMediaTime, useMediaPlayer } from "./ui/src/hooks/use-media-player.js";
6
6
  import { AudioPlayer } from "./ui/src/components/edyn/audio-player/audio-player.js";
7
7
  import { Calendar, CalendarDayButton } from "./ui/src/components/ui/calendar/calendar.js";
8
+ import { POPUP_FRAME_CLASS, POPUP_GROUP_LABEL_CLASS, POPUP_ITEM_INDICATOR_CLASS, POPUP_LAYER_CLASS, POPUP_LIST_HEIGHT_CLASS, POPUP_LIST_WIDTH_CLASS, popupItemClass } from "./ui/src/lib/popup-list.js";
8
9
  import { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger } from "./ui/src/components/ui/popover/popover.js";
9
10
  import { useIsMobile } from "./ui/src/hooks/use-mobile.js";
10
11
  import { DateRangePicker, dateToDay, dayToDate } from "./ui/src/components/edyn/date-range-picker/date-range-picker.js";
@@ -12,8 +13,8 @@ import { Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, Ava
12
13
  import { AVATAR_TONES, InitialsAvatar, avatarTone, initialsOf } from "./ui/src/components/edyn/initials-avatar/initials-avatar.js";
13
14
  import { useMergedRefs } from "./ui/src/hooks/use-merged-refs.js";
14
15
  import { ScrollArea, ScrollBar } from "./ui/src/components/ui/scroll-area/scroll-area.js";
16
+ import { PopupListScroll } from "./ui/src/components/ui/scroll-area/popup-list-scroll.js";
15
17
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/src/components/ui/tooltip/tooltip.js";
16
- import { POPUP_FRAME_CLASS, POPUP_GROUP_LABEL_CLASS, POPUP_ITEM_INDICATOR_CLASS, POPUP_LIST_HEIGHT_CLASS, POPUP_LIST_WIDTH_CLASS, popupItemClass } from "./ui/src/lib/popup-list.js";
17
18
  import { DEFAULT_MULTI_SELECT_LABELS, MultiSelect, SEARCH_FROM, VIRTUALIZE_FROM, fullSelection, resolveChosen, summarize } from "./ui/src/components/edyn/multi-select/multi-select.js";
18
19
  import { DEFAULT_PRESENCE_LABELS, PRESENCE_STATES, Presence } from "./ui/src/components/edyn/presence/presence.js";
19
20
  import { PROGRESS_LINE_COLOR, ProgressLine } from "./ui/src/components/edyn/progress-line/progress-line.js";
@@ -75,4 +76,4 @@ import { Tabs, TabsContent, TabsList, TabsTrigger, tabsListVariants } from "./ui
75
76
  import { Toast, ToastAction, ToastClose, ToastContent, ToastDescription, ToastPortal, ToastProvider, ToastTitle, ToastViewport, Toaster, createToastManager, toast, useToastManager } from "./ui/src/components/ui/toast/toast.js";
76
77
  import { Toggle, toggleVariants } from "./ui/src/components/ui/toggle/toggle.js";
77
78
  import { ToggleGroup, ToggleGroupItem } from "./ui/src/components/ui/toggle-group/toggle-group.js";
78
- export { AVATAR_TONES, Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertAction, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AnimatedNumber, AspectRatio, Attachment, AttachmentAction, AttachmentActions, AttachmentContent, AttachmentDescription, AttachmentGroup, AttachmentMedia, AttachmentTitle, AttachmentTrigger, AudioPlayer, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Bubble, BubbleContent, BubbleGroup, BubbleReactions, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, Calendar, CalendarDayButton, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Combobox, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxLabel, ComboboxSeparator, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DEFAULT_MULTI_SELECT_LABELS, DEFAULT_PRESENCE_LABELS, DateRangePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DirectionProvider, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Field, FieldDescription, FieldError, FieldLabel, HoverCard, HoverCardContent, HoverCardTrigger, Indicator, InitialsAvatar, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle, Kbd, KbdGroup, Label, Marker, MarkerContent, MarkerIcon, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, Message, MessageAvatar, MessageContent, MessageFooter, MessageGroup, MessageHeader, MessageScroller, MessageScrollerButton, MessageScrollerContent, MessageScrollerItem, MessageScrollerProvider, MessageScrollerViewport, MultiSelect, NativeSelect, NativeSelectOptGroup, NativeSelectOption, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuPositioner, NavigationMenuTrigger, NumberText, POPUP_FRAME_CLASS, POPUP_GROUP_LABEL_CLASS, POPUP_ITEM_INDICATOR_CLASS, POPUP_LIST_HEIGHT_CLASS, POPUP_LIST_WIDTH_CLASS, PRESENCE_STATES, PROGRESS_DURATION, PROGRESS_EASE, PROGRESS_LINE_COLOR, Pagination, Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, Presence, Progress, ProgressLine, Questionnaire, QuestionnaireActions, QuestionnaireChoice, QuestionnaireChoiceDescription, QuestionnaireChoices, QuestionnaireDescription, QuestionnaireError, QuestionnaireInput, QuestionnaireItem, QuestionnaireNext, QuestionnairePrevious, QuestionnaireProgress, QuestionnaireSkip, QuestionnaireSubmit, QuestionnaireTitle, RICH_TEXT_ATTRIBUTES, RICH_TEXT_BODY_CLASS, RICH_TEXT_ELEMENTS, RICH_TEXT_LINK_PROTOCOLS, RICH_TEXT_MENTION_CLASS, RadioGroup, RadioGroupItem, ResizableHandle, ResizablePanel, ResizablePanelGroup, RichTextContent, SEARCH_FROM, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Slider, Spinner, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toast, ToastAction, ToastClose, ToastContent, ToastDescription, ToastPortal, ToastProvider, ToastTitle, ToastViewport, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, VIRTUALIZE_FROM, VideoPlayer, avatarTone, badgeVariants, buttonVariants, clampPage, cn, createToastManager, dateToDay, dayToDate, formatCount, formatMediaTime, fullSelection, getPaginationRange, indicatorVariants, initialsOf, isAllowedRichTextHref, markerVariants, navigationMenuTriggerStyle, popupItemClass, progressFill, progressTrack, resolveChosen, richTextToNodes, spinnerVariants, summarize, tabsListVariants, toast, toggleVariants, useAnimatedValues, useCarousel, useDirection, useIsMobile, useMediaPlayer, useMergedRefs, useMessageScroller, useMessageScrollerScrollable, useMessageScrollerVisibility, useSidebar, useToastManager };
79
+ export { AVATAR_TONES, Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertAction, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AnimatedNumber, AspectRatio, Attachment, AttachmentAction, AttachmentActions, AttachmentContent, AttachmentDescription, AttachmentGroup, AttachmentMedia, AttachmentTitle, AttachmentTrigger, AudioPlayer, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Bubble, BubbleContent, BubbleGroup, BubbleReactions, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, Calendar, CalendarDayButton, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Combobox, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxLabel, ComboboxSeparator, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DEFAULT_MULTI_SELECT_LABELS, DEFAULT_PRESENCE_LABELS, DateRangePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DirectionProvider, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Field, FieldDescription, FieldError, FieldLabel, HoverCard, HoverCardContent, HoverCardTrigger, Indicator, InitialsAvatar, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle, Kbd, KbdGroup, Label, Marker, MarkerContent, MarkerIcon, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, Message, MessageAvatar, MessageContent, MessageFooter, MessageGroup, MessageHeader, MessageScroller, MessageScrollerButton, MessageScrollerContent, MessageScrollerItem, MessageScrollerProvider, MessageScrollerViewport, MultiSelect, NativeSelect, NativeSelectOptGroup, NativeSelectOption, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuPositioner, NavigationMenuTrigger, NumberText, POPUP_FRAME_CLASS, POPUP_GROUP_LABEL_CLASS, POPUP_ITEM_INDICATOR_CLASS, POPUP_LAYER_CLASS, POPUP_LIST_HEIGHT_CLASS, POPUP_LIST_WIDTH_CLASS, PRESENCE_STATES, PROGRESS_DURATION, PROGRESS_EASE, PROGRESS_LINE_COLOR, Pagination, Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, PopupListScroll, Presence, Progress, ProgressLine, Questionnaire, QuestionnaireActions, QuestionnaireChoice, QuestionnaireChoiceDescription, QuestionnaireChoices, QuestionnaireDescription, QuestionnaireError, QuestionnaireInput, QuestionnaireItem, QuestionnaireNext, QuestionnairePrevious, QuestionnaireProgress, QuestionnaireSkip, QuestionnaireSubmit, QuestionnaireTitle, RICH_TEXT_ATTRIBUTES, RICH_TEXT_BODY_CLASS, RICH_TEXT_ELEMENTS, RICH_TEXT_LINK_PROTOCOLS, RICH_TEXT_MENTION_CLASS, RadioGroup, RadioGroupItem, ResizableHandle, ResizablePanel, ResizablePanelGroup, RichTextContent, SEARCH_FROM, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Slider, Spinner, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toast, ToastAction, ToastClose, ToastContent, ToastDescription, ToastPortal, ToastProvider, ToastTitle, ToastViewport, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, VIRTUALIZE_FROM, VideoPlayer, avatarTone, badgeVariants, buttonVariants, clampPage, cn, createToastManager, dateToDay, dayToDate, formatCount, formatMediaTime, fullSelection, getPaginationRange, indicatorVariants, initialsOf, isAllowedRichTextHref, markerVariants, navigationMenuTriggerStyle, popupItemClass, progressFill, progressTrack, resolveChosen, richTextToNodes, spinnerVariants, summarize, tabsListVariants, toast, toggleVariants, useAnimatedValues, useCarousel, useDirection, useIsMobile, useMediaPlayer, useMergedRefs, useMessageScroller, useMessageScrollerScrollable, useMessageScrollerVisibility, useSidebar, useToastManager };
@@ -1 +1 @@
1
- e7f64dd8a5e900b4cc6189554ed97571e1a11bc66a714ebfa14d32bfe3bae156
1
+ 33fc5eaaa57e82c0a535b01a4df1d62dc40ad18e49d0bc7e149e3c0ef4b073f2
package/dist/styles.css CHANGED
@@ -601,6 +601,10 @@
601
601
  right: var(--spacing);
602
602
  }
603
603
 
604
+ .right-1\.75 {
605
+ right: calc(var(--spacing) * 1.75);
606
+ }
607
+
604
608
  .right-2 {
605
609
  right: calc(var(--spacing) * 2);
606
610
  }
@@ -633,10 +637,6 @@
633
637
  left: 0;
634
638
  }
635
639
 
636
- .left-1\.5 {
637
- left: calc(var(--spacing) * 1.5);
638
- }
639
-
640
640
  .left-1\/2 {
641
641
  left: 50%;
642
642
  }
@@ -669,6 +669,10 @@
669
669
  z-index: 50;
670
670
  }
671
671
 
672
+ .z-60 {
673
+ z-index: 60;
674
+ }
675
+
672
676
  .z-100 {
673
677
  z-index: 100;
674
678
  }
@@ -739,10 +743,6 @@
739
743
  }
740
744
  }
741
745
 
742
- .m-1 {
743
- margin: var(--spacing);
744
- }
745
-
746
746
  .-mx-1 {
747
747
  margin-inline: calc(var(--spacing) * -1);
748
748
  }
@@ -823,6 +823,10 @@
823
823
  margin-top: auto;
824
824
  }
825
825
 
826
+ .mr-px {
827
+ margin-right: 1px;
828
+ }
829
+
826
830
  .-mb-4 {
827
831
  margin-bottom: calc(var(--spacing) * -4);
828
832
  }
@@ -1128,8 +1132,8 @@
1128
1132
  max-height: calc(var(--spacing) * 88);
1129
1133
  }
1130
1134
 
1131
- .max-h-\[min\(16\.5rem\,round\(down\,var\(--available-height\,100dvh\)_-_0\.5rem\,2rem\)_\+_0\.5rem\)\] {
1132
- max-height: min(16.5rem, round(down, var(--available-height, 100dvh) - .5rem, 2rem) + .5rem);
1135
+ .max-h-\[var\(--popup-list-cap\,min\(16rem\,round\(down\,var\(--available-height\,100dvh\)_-_0\.5rem\,2rem\)\)\)\] {
1136
+ max-height: var(--popup-list-cap, min(16rem, round(down, var(--available-height, 100dvh) - .5rem, 2rem)));
1133
1137
  }
1134
1138
 
1135
1139
  .max-h-inherit {
@@ -1587,10 +1591,6 @@
1587
1591
  resize: none;
1588
1592
  }
1589
1593
 
1590
- .resize-y {
1591
- resize: vertical;
1592
- }
1593
-
1594
1594
  .snap-x {
1595
1595
  scroll-snap-type: x var(--tw-scroll-snap-strictness);
1596
1596
  }
@@ -1984,12 +1984,12 @@
1984
1984
  }
1985
1985
  }
1986
1986
 
1987
- .border-destructive {
1988
- border-color: var(--destructive);
1987
+ .border-control {
1988
+ border-color: var(--control-border);
1989
1989
  }
1990
1990
 
1991
- .border-input {
1992
- border-color: var(--input);
1991
+ .border-destructive {
1992
+ border-color: var(--destructive);
1993
1993
  }
1994
1994
 
1995
1995
  .border-muted-foreground {
@@ -2353,10 +2353,6 @@
2353
2353
  padding-right: var(--spacing);
2354
2354
  }
2355
2355
 
2356
- .pr-1\.5 {
2357
- padding-right: calc(var(--spacing) * 1.5);
2358
- }
2359
-
2360
2356
  .pr-2 {
2361
2357
  padding-right: calc(var(--spacing) * 2);
2362
2358
  }
@@ -2381,6 +2377,10 @@
2381
2377
  padding-bottom: calc(var(--spacing) * 4);
2382
2378
  }
2383
2379
 
2380
+ .pl-1\.5 {
2381
+ padding-left: calc(var(--spacing) * 1.5);
2382
+ }
2383
+
2384
2384
  .pl-2 {
2385
2385
  padding-left: calc(var(--spacing) * 2);
2386
2386
  }
@@ -2397,10 +2397,6 @@
2397
2397
  padding-left: calc(var(--spacing) * 4);
2398
2398
  }
2399
2399
 
2400
- .pl-7 {
2401
- padding-left: calc(var(--spacing) * 7);
2402
- }
2403
-
2404
2400
  .text-center {
2405
2401
  text-align: center;
2406
2402
  }
@@ -2440,9 +2436,9 @@
2440
2436
  line-height: var(--tw-leading, var(--text-sm--line-height));
2441
2437
  }
2442
2438
 
2443
- .text-sm\/relaxed {
2439
+ .text-sm\/6 {
2444
2440
  font-size: var(--text-sm);
2445
- line-height: var(--leading-relaxed);
2441
+ line-height: calc(var(--spacing) * 6);
2446
2442
  }
2447
2443
 
2448
2444
  .text-xs {
@@ -2458,6 +2454,11 @@
2458
2454
  font-size: .625rem;
2459
2455
  }
2460
2456
 
2457
+ .leading-5 {
2458
+ --tw-leading: calc(var(--spacing) * 5);
2459
+ line-height: calc(var(--spacing) * 5);
2460
+ }
2461
+
2461
2462
  .leading-6 {
2462
2463
  --tw-leading: calc(var(--spacing) * 6);
2463
2464
  line-height: calc(var(--spacing) * 6);
@@ -3322,6 +3323,11 @@
3322
3323
  line-height: var(--tw-leading, var(--text-sm--line-height));
3323
3324
  }
3324
3325
 
3326
+ .group-data-\[size\=sm\]\/card\:leading-5:is(:where(.group\/card)[data-size="sm"] *) {
3327
+ --tw-leading: calc(var(--spacing) * 5);
3328
+ line-height: calc(var(--spacing) * 5);
3329
+ }
3330
+
3325
3331
  .group-data-\[size\=sm\]\/item\:size-8:is(:where(.group\/item)[data-size="sm"] *) {
3326
3332
  width: calc(var(--spacing) * 8);
3327
3333
  height: calc(var(--spacing) * 8);
@@ -4489,25 +4495,6 @@
4489
4495
  background-color: var(--sidebar);
4490
4496
  }
4491
4497
 
4492
- .has-\[\[data-slot\=input-group-control\]\:focus-visible\]\:border-ring:has([data-slot="input-group-control"]:focus-visible) {
4493
- border-color: var(--ring);
4494
- }
4495
-
4496
- .has-\[\[data-slot\=input-group-control\]\:focus-visible\]\:ring-3:has([data-slot="input-group-control"]:focus-visible) {
4497
- --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
4498
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
4499
- }
4500
-
4501
- .has-\[\[data-slot\=input-group-control\]\:focus-visible\]\:ring-ring\/50:has([data-slot="input-group-control"]:focus-visible) {
4502
- --tw-ring-color: var(--ring);
4503
- }
4504
-
4505
- @supports (color: color-mix(in lab, red, red)) {
4506
- .has-\[\[data-slot\=input-group-control\]\:focus-visible\]\:ring-ring\/50:has([data-slot="input-group-control"]:focus-visible) {
4507
- --tw-ring-color: color-mix(in oklab, var(--ring) 50%, transparent);
4508
- }
4509
- }
4510
-
4511
4498
  .has-\[\[data-slot\]\[aria-invalid\=true\]\]\:border-destructive:has([data-slot][aria-invalid="true"]) {
4512
4499
  border-color: var(--destructive);
4513
4500
  }
@@ -5968,6 +5955,10 @@
5968
5955
  translate: var(--tw-translate-x) var(--tw-translate-y);
5969
5956
  }
5970
5957
 
5958
+ .dark\:border-control:is(.dark *) {
5959
+ border-color: var(--control-border);
5960
+ }
5961
+
5971
5962
  .dark\:border-destructive\/50:is(.dark *) {
5972
5963
  border-color: var(--destructive);
5973
5964
  }
@@ -5978,10 +5969,6 @@
5978
5969
  }
5979
5970
  }
5980
5971
 
5981
- .dark\:border-input:is(.dark *) {
5982
- border-color: var(--input);
5983
- }
5984
-
5985
5972
  .dark\:bg-destructive\/15:is(.dark *) {
5986
5973
  background-color: var(--destructive);
5987
5974
  }
@@ -6613,8 +6600,8 @@
6613
6600
  background-color: var(--muted);
6614
6601
  }
6615
6602
 
6616
- .dark\:data-active\:border-input:is(.dark *):where([data-state="active"]), .dark\:data-active\:border-input:is(.dark *):where([data-active]:not([data-active="false"])) {
6617
- border-color: var(--input);
6603
+ .dark\:data-active\:border-control:is(.dark *):where([data-state="active"]), .dark\:data-active\:border-control:is(.dark *):where([data-active]:not([data-active="false"])) {
6604
+ border-color: var(--control-border);
6618
6605
  }
6619
6606
 
6620
6607
  .dark\:data-active\:bg-input\/30:is(.dark *):where([data-state="active"]), .dark\:data-active\:bg-input\/30:is(.dark *):where([data-active]:not([data-active="false"])) {
@@ -6805,6 +6792,11 @@
6805
6792
  pointer-events: none;
6806
6793
  }
6807
6794
 
6795
+ .\[\&_svg\]\:size-3\.5 svg {
6796
+ width: calc(var(--spacing) * 3.5);
6797
+ height: calc(var(--spacing) * 3.5);
6798
+ }
6799
+
6808
6800
  .\[\&_svg\]\:size-4 svg {
6809
6801
  width: calc(var(--spacing) * 4);
6810
6802
  height: calc(var(--spacing) * 4);
@@ -7601,7 +7593,7 @@
7601
7593
  --secondary: oklch(97% 0 0);
7602
7594
  --secondary-foreground: oklch(20.5% 0 0);
7603
7595
  --muted: oklch(97% 0 0);
7604
- --muted-foreground: oklch(55.6% 0 0);
7596
+ --muted-foreground: oklch(54.5% 0 0);
7605
7597
  --accent: oklch(97% 0 0);
7606
7598
  --accent-foreground: oklch(20.5% 0 0);
7607
7599
  --destructive: oklch(50.5% .213 27.518);
@@ -7617,6 +7609,7 @@
7617
7609
  --info-ring: oklch(90.1% .058 230.902);
7618
7610
  --border: oklch(92.2% 0 0);
7619
7611
  --input: oklch(92.2% 0 0);
7612
+ --control-border: oklch(66% 0 0);
7620
7613
  --ring: oklch(60% 0 0);
7621
7614
  --chart-1: oklch(64.6% .222 41.116);
7622
7615
  --chart-2: oklch(60% .118 184.704);
@@ -7686,6 +7679,7 @@
7686
7679
  --info-ring: oklch(44.3% .11 240.79);
7687
7680
  --border: oklch(100% 0 0 / .1);
7688
7681
  --input: oklch(100% 0 0 / .15);
7682
+ --control-border: oklch(100% 0 0 / .35);
7689
7683
  --ring: oklch(55.6% 0 0);
7690
7684
  --chart-1: oklch(48.8% .243 264.376);
7691
7685
  --chart-2: oklch(69.6% .17 162.48);
@@ -7852,10 +7846,15 @@
7852
7846
  outline-style: solid;
7853
7847
  }
7854
7848
 
7855
- [data-slot="input-group"] :focus-visible, :is([role="option"], [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [cmdk-item]):focus-visible, :is([data-slot="command-input-wrapper"], [data-slot="multi-select-content"]) :focus-visible, [data-slot="command-input-wrapper"] [data-slot="input-group"]:has(:focus-visible) {
7849
+ [data-slot="input-group"] :focus-visible, :is([role="option"], [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [cmdk-item]):focus-visible, :is([data-slot="command-input-wrapper"], [data-slot="multi-select-content"]) :focus-visible, [data-slot="command-input-wrapper"] [data-slot="input-group"]:has(:focus-visible), :is([data-slot="dropdown-menu-content"], [data-slot="dropdown-menu-sub-content"], [data-slot="context-menu-content"], [data-slot="context-menu-sub-content"], [data-slot="menubar-content"], [data-slot="menubar-sub-content"], [data-slot="select-content"], [data-slot="combobox-content"], [data-slot="multi-select-content"], [data-slot="popover-content"], [data-slot="hover-card-content"]):focus-visible {
7856
7850
  outline-style: none;
7857
7851
  }
7858
7852
 
7853
+ [data-slot="scroll-area-viewport"]:focus-visible {
7854
+ outline-offset: -1px;
7855
+ outline-width: 1px;
7856
+ }
7857
+
7859
7858
  :root {
7860
7859
  --motion-expand: .2s;
7861
7860
  --motion-collapse: .16s;
package/dist/theme.css CHANGED
@@ -88,7 +88,8 @@
88
88
  --secondary-foreground: oklch(0.205 0 0);
89
89
 
90
90
  --muted: oklch(0.97 0 0);
91
- --muted-foreground: oklch(0.556 0 0);
91
+ /* Muted text: 4.5:1 on the page (white), a card and the muted surface (oklch 0.97). 0.556 was 4.35:1 on the latter. */
92
+ --muted-foreground: oklch(0.545 0 0);
92
93
 
93
94
  --accent: oklch(0.97 0 0);
94
95
  --accent-foreground: oklch(0.205 0 0);
@@ -110,6 +111,8 @@
110
111
 
111
112
  --border: oklch(0.922 0 0);
112
113
  --input: oklch(0.922 0 0);
114
+ /* The outline of a control (field, trigger, outline button): 3:1 on the page and on a card (WCAG 1.4.11). --input stays the fill. */
115
+ --control-border: oklch(0.66 0 0);
113
116
  --ring: oklch(0.6 0 0);
114
117
 
115
118
  --chart-1: oklch(0.646 0.222 41.116);
@@ -200,6 +203,7 @@
200
203
 
201
204
  --border: oklch(1 0 0 / 10%);
202
205
  --input: oklch(1 0 0 / 15%);
206
+ --control-border: oklch(1 0 0 / 35%);
203
207
  --ring: oklch(0.556 0 0);
204
208
 
205
209
  --chart-1: oklch(0.488 0.243 264.376);
@@ -279,6 +283,7 @@
279
283
  --color-info-ring: var(--info-ring);
280
284
  --color-border: var(--border);
281
285
  --color-input: var(--input);
286
+ --color-control: var(--control-border);
282
287
  --color-ring: var(--ring);
283
288
  --color-chart-1: var(--chart-1);
284
289
  --color-chart-2: var(--chart-2);
@@ -163,7 +163,7 @@ var FileInput = ({ value, defaultValue = [], onValueChange, accept, multiple = f
163
163
  "data-slot": "file-input-zone",
164
164
  role: "presentation",
165
165
  "data-dragging": dragging || void 0,
166
- className: cn("relative flex flex-col items-center justify-center gap-1 rounded-lg border border-dashed border-input px-4 py-6 text-center text-sm transition-colors dark:bg-input/30", "hover:bg-foreground/4 has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-3 has-[input:focus-visible]:ring-ring/50", "data-dragging:border-ring data-dragging:bg-foreground/6", invalid && "border-destructive ring-3 ring-destructive/20 dark:border-destructive/50 dark:ring-destructive/40", disabled && "pointer-events-none cursor-not-allowed border-border"),
166
+ className: cn("relative flex flex-col items-center justify-center gap-1 rounded-lg border border-dashed border-control px-4 py-6 text-center text-sm transition-colors dark:bg-input/30", "hover:bg-foreground/4 has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-3 has-[input:focus-visible]:ring-ring/50", "data-dragging:border-ring data-dragging:bg-foreground/6", invalid && "border-destructive ring-3 ring-destructive/20 dark:border-destructive/50 dark:ring-destructive/40", disabled && "pointer-events-none cursor-not-allowed border-border"),
167
167
  onDragEnter: onDrag,
168
168
  onDragOver: onDrag,
169
169
  onDragLeave: (event) => {
@@ -214,7 +214,7 @@ var FileInput = ({ value, defaultValue = [], onValueChange, accept, multiple = f
214
214
  "aria-label": labels.list,
215
215
  className: "flex flex-col gap-1",
216
216
  children: files.map((file, index) => /* @__PURE__ */ jsxs("li", {
217
- className: "flex items-center gap-2 rounded-lg border border-input py-1 pr-1 pl-2 text-sm",
217
+ className: "flex items-center gap-2 rounded-lg border border-control py-1 pr-1 pl-2 text-sm",
218
218
  children: [
219
219
  /* @__PURE__ */ jsx(IconFile, {
220
220
  "aria-hidden": "true",
@@ -1 +1 @@
1
- {"version":3,"file":"file-input.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/file-input/file-input.tsx"],"sourcesContent":["import { Button } from '@edyn/ui/components/ui/button';\nimport { cn } from '@edyn/ui/lib/utils';\nimport { IconFile, IconUpload, IconX } from '@tabler/icons-react';\nimport { type ChangeEvent, type ComponentPropsWithoutRef, type DragEvent, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';\n\nexport type TFileRejectReason = 'type' | 'size' | 'count';\n\nexport interface IFileRejection {\n file: File;\n reason: TFileRejectReason;\n}\n\n/** The words a FileInput says and announces. Pass the ones to change. */\nexport interface IFileInputLabels {\n /** The prompt in the drop area when it takes one file, before the \"browse\" link. */\n dropOne: string;\n /** The same, when it takes several. */\n dropMany: string;\n browse: string;\n /** The accessible name of the file chooser itself, when the caller gives it no name of its own. */\n chooseOne: string;\n chooseMany: string;\n /** The accessible name of the list of chosen files. */\n list: string;\n remove: (name: string) => string;\n /** Announced when files are added. */\n added: (count: number) => string;\n /** Announced when a file is taken out. */\n removed: (name: string) => string;\n /** Said, and shown, for a file that was refused. */\n rejection: (name: string, reason: TFileRejectReason, limit: string) => string;\n}\n\nexport const DEFAULT_FILE_INPUT_LABELS: IFileInputLabels = {\n dropOne: 'Drag a file here or',\n dropMany: 'Drag files here or',\n browse: 'browse',\n chooseOne: 'Choose a file',\n chooseMany: 'Choose files',\n list: 'Chosen files',\n remove: (name) => `Remove ${name}`,\n added: (count) => (count === 1 ? '1 file added' : `${count} files added`),\n removed: (name) => `${name} removed`,\n rejection: (name, reason, limit) => {\n if (reason === 'type') {\n return `${name} was not added: this type is not accepted`;\n }\n if (reason === 'size') {\n return `${name} was not added: it is larger than ${limit}`;\n }\n return `${name} was not added: ${limit === '1' ? '1 file is' : `${limit} files are`} the most`;\n },\n};\n\nconst UNITS = ['B', 'KB', 'MB', 'GB'] as const;\n\n/** 1,536 bytes as \"1.5 KB\": the size a person reads next to a file's name. */\nexport const formatBytes = (bytes: number): string => {\n let size = bytes;\n let unit = 0;\n while (size >= 1024 && unit < UNITS.length - 1) {\n size /= 1024;\n unit += 1;\n }\n const text = unit === 0 || size >= 10 ? String(Math.round(size)) : size.toFixed(1).replace(/\\.0$/, '');\n return `${text} ${UNITS[unit]}`;\n};\n\n/** Whether a file is one `accept` allows: an extension (`.pdf`), a type (`application/pdf`) or a family of types (`image/*`). No `accept` allows all. */\nexport const matchesAccept = (file: { name: string; type: string }, accept: string | undefined): boolean => {\n const tokens = (accept ?? '')\n .split(',')\n .map((token) => token.trim().toLowerCase())\n .filter((token) => token !== '');\n if (tokens.length === 0) {\n return true;\n }\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n return tokens.some((token) => {\n if (token.startsWith('.')) {\n return name.endsWith(token);\n }\n if (token.endsWith('/*')) {\n return type.startsWith(token.slice(0, -1));\n }\n return type === token;\n });\n};\n\nconst sameFile = (a: File, b: File): boolean => a.name === b.name && a.size === b.size && a.lastModified === b.lastModified;\n\nexport interface ISortFilesInput {\n incoming: readonly File[];\n current: readonly File[];\n multiple: boolean;\n accept?: string;\n maxSize?: number;\n maxFiles?: number;\n}\n\n/**\n * Which of the files just offered are taken, and which are refused and why. A file already chosen is skipped without a word. With one\n * file allowed, the first acceptable one replaces what was chosen and the rest are refused as too many.\n */\nexport const sortFiles = ({\n incoming,\n current,\n multiple,\n accept,\n maxSize,\n maxFiles,\n}: ISortFilesInput): { accepted: File[]; rejected: IFileRejection[] } => {\n const accepted: File[] = [];\n const rejected: IFileRejection[] = [];\n const limit = multiple ? maxFiles : 1;\n for (const file of incoming) {\n if (!matchesAccept(file, accept)) {\n rejected.push({ file, reason: 'type' });\n } else if (maxSize !== undefined && file.size > maxSize) {\n rejected.push({ file, reason: 'size' });\n } else if (current.some((chosen) => sameFile(chosen, file)) || accepted.some((taken) => sameFile(taken, file))) {\n } else if (limit !== undefined && (multiple ? current.length : 0) + accepted.length >= limit) {\n rejected.push({ file, reason: 'count' });\n } else {\n accepted.push(file);\n }\n }\n return { accepted, rejected };\n};\n\nexport interface IFileInputProps extends Omit<ComponentPropsWithoutRef<'div'>, 'defaultValue' | 'onChange' | 'children' | 'id'> {\n /** The chosen files. Leave it out and the field keeps them itself. */\n value?: readonly File[];\n defaultValue?: readonly File[];\n onValueChange?: (files: File[]) => void;\n /** What the picker offers, and what a drop is held to: extensions (`.pdf`), types (`application/pdf`) or families (`image/*`). */\n accept?: string;\n /** Several files, added to each other, instead of one that is replaced. */\n multiple?: boolean;\n /** The most a file may weigh, in bytes. */\n maxSize?: number;\n /** The most files that may be chosen. Several-file fields only. */\n maxFiles?: number;\n /** Called with the files that were refused, each with the reason. They are also listed under the field. */\n onReject?: (rejections: IFileRejection[]) => void;\n disabled?: boolean;\n invalid?: boolean;\n required?: boolean;\n /** Submits the chosen files with a form, under this name. */\n name?: string;\n /** For a `<Label htmlFor>` to name the field. */\n id?: string;\n /** What the field accepts, in words (\"PDF or PNG, up to 5 MB\"), read with the field. */\n hint?: ReactNode;\n labels?: Partial<IFileInputLabels>;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n}\n\nconst limitText = (reason: TFileRejectReason, maxSize: number | undefined, maxFiles: number | undefined, multiple: boolean): string => {\n if (reason === 'size') {\n return formatBytes(maxSize ?? 0);\n }\n return String(multiple ? (maxFiles ?? 0) : 1);\n};\n\n/**\n * Chooses files by picking them or by dropping them on the field, in the kit's look. It exists so an app does not fall back to\n * `<input type=\"file\">`, which cannot be styled and has no list of what was chosen.\n *\n * The control is a real file input laid over the whole drop area, so Tab, Enter and Space, the system's file dialog, a drop and a\n * screen reader's name for it all come from the browser. What the browser lacks is added around it: the list of chosen files with a\n * button to take each out, the checks against `accept`, `maxSize` and `maxFiles` (a drop skips the dialog's own filter), and a\n * live region that says what was added, removed or refused.\n *\n * Name the field with `aria-label`, `aria-labelledby` or a `<Label htmlFor={id}>`; with none of those it is named \"Choose files\".\n */\nexport const FileInput = ({\n value,\n defaultValue = [],\n onValueChange,\n accept,\n multiple = false,\n maxSize,\n maxFiles,\n onReject,\n disabled = false,\n invalid = false,\n required = false,\n name,\n id,\n hint,\n labels: labelOverrides,\n className,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...rest\n}: IFileInputProps) => {\n const labels = useMemo(() => ({ ...DEFAULT_FILE_INPUT_LABELS, ...labelOverrides }), [labelOverrides]);\n const [inner, setInner] = useState<readonly File[]>(defaultValue);\n const files = value ?? inner;\n const [dragging, setDragging] = useState(false);\n const [rejections, setRejections] = useState<IFileRejection[]>([]);\n const [announcement, setAnnouncement] = useState('');\n const refInput = useRef<HTMLInputElement | null>(null);\n const hintId = `${id ?? 'file-input'}-hint`;\n\n // The input's own list follows the chosen files, so a form submits exactly what the list shows.\n useEffect(() => {\n const input = refInput.current;\n if (input === null || typeof DataTransfer === 'undefined') {\n return;\n }\n const transfer = new DataTransfer();\n for (const file of files) {\n transfer.items.add(file);\n }\n input.files = transfer.files;\n }, [files]);\n\n const commit = (next: File[]): void => {\n setInner(next);\n onValueChange?.(next);\n };\n\n const take = (incoming: File[]): void => {\n if (incoming.length === 0) {\n return;\n }\n const { accepted, rejected } = sortFiles({ incoming, current: files, multiple, accept, maxSize, maxFiles });\n setRejections(rejected);\n if (rejected.length > 0) {\n onReject?.(rejected);\n }\n if (accepted.length > 0) {\n commit(multiple ? [...files, ...accepted] : accepted);\n setAnnouncement(labels.added(accepted.length));\n } else {\n setAnnouncement('');\n }\n };\n\n const remove = (index: number): void => {\n const gone = files[index];\n if (gone === undefined) {\n return;\n }\n setRejections([]);\n commit(files.filter((_, at) => at !== index));\n setAnnouncement(labels.removed(gone.name));\n refInput.current?.focus();\n };\n\n const onChange = (event: ChangeEvent<HTMLInputElement>): void => {\n take([...(event.target.files ?? [])]);\n };\n\n const carriesFiles = (event: DragEvent): boolean => event.dataTransfer.types.includes('Files');\n\n const onDrag = (event: DragEvent<HTMLDivElement>): void => {\n if (disabled || !carriesFiles(event)) {\n return;\n }\n event.preventDefault();\n setDragging(true);\n };\n\n const onDrop = (event: DragEvent<HTMLDivElement>): void => {\n if (disabled || !carriesFiles(event)) {\n return;\n }\n // The drop is handled here, once, and not again by the input underneath.\n event.preventDefault();\n setDragging(false);\n take([...event.dataTransfer.files]);\n };\n\n const named = ariaLabel !== undefined || ariaLabelledBy !== undefined || id !== undefined;\n\n return (\n <div data-slot=\"file-input\" className={cn('flex w-full flex-col gap-2', className)} {...rest}>\n {/* biome-ignore lint/a11y/noStaticElementInteractions: a drop is only the pointer's way onto the file input laid over this area, which stays the keyboard and screen-reader way. */}\n <div\n data-slot=\"file-input-zone\"\n role=\"presentation\"\n data-dragging={dragging || undefined}\n className={cn(\n 'relative flex flex-col items-center justify-center gap-1 rounded-lg border border-dashed border-input px-4 py-6 text-center text-sm transition-colors dark:bg-input/30',\n 'hover:bg-foreground/4 has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-3 has-[input:focus-visible]:ring-ring/50',\n 'data-dragging:border-ring data-dragging:bg-foreground/6',\n invalid && 'border-destructive ring-3 ring-destructive/20 dark:border-destructive/50 dark:ring-destructive/40',\n // The prompt stays readable (4.5:1) the way a disabled Button's text does: no opacity on the text, a muted colour instead.\n disabled && 'pointer-events-none cursor-not-allowed border-border',\n )}\n onDragEnter={onDrag}\n onDragOver={onDrag}\n onDragLeave={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {\n setDragging(false);\n }\n }}\n onDrop={onDrop}\n >\n <IconUpload aria-hidden=\"true\" className=\"size-5 text-muted-foreground\" />\n <p className={disabled ? 'text-muted-foreground' : 'text-foreground'}>\n {multiple ? labels.dropMany : labels.dropOne} <span className=\"font-medium underline underline-offset-3\">{labels.browse}</span>\n </p>\n {hint !== undefined && (\n <p id={hintId} className=\"text-xs text-muted-foreground\">\n {hint}\n </p>\n )}\n <input\n ref={refInput}\n id={id}\n type=\"file\"\n name={name}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n required={required && files.length === 0}\n aria-label={named ? ariaLabel : multiple ? labels.chooseMany : labels.chooseOne}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={hint === undefined ? undefined : hintId}\n aria-invalid={invalid || undefined}\n className=\"absolute inset-0 size-full cursor-pointer opacity-0 disabled:cursor-not-allowed\"\n onChange={onChange}\n />\n </div>\n {files.length > 0 && (\n <ul data-slot=\"file-input-list\" aria-label={labels.list} className=\"flex flex-col gap-1\">\n {files.map((file, index) => (\n <li\n key={`${file.name}-${file.size}-${file.lastModified}`}\n className=\"flex items-center gap-2 rounded-lg border border-input py-1 pr-1 pl-2 text-sm\"\n >\n <IconFile aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{file.name}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground tabular-nums\">{formatBytes(file.size)}</span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n disabled={disabled}\n aria-label={labels.remove(file.name)}\n onClick={() => remove(index)}\n >\n <IconX aria-hidden=\"true\" />\n </Button>\n </li>\n ))}\n </ul>\n )}\n {/* One live region: it stays mounted and only its words change, which is what makes a screen reader say them. The refused files are\n the visible part; what was added or removed is for the ear. */}\n <div role=\"status\" data-slot=\"file-input-status\" className=\"text-sm empty:hidden\">\n {rejections.map(({ file, reason }) => (\n <p key={`${file.name}-${reason}`} className=\"text-destructive\">\n {labels.rejection(file.name, reason, limitText(reason, maxSize, maxFiles, multiple))}\n </p>\n ))}\n {announcement !== '' && <span className=\"sr-only\">{announcement}</span>}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"file-input.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/file-input/file-input.tsx"],"sourcesContent":["import { Button } from '@edyn/ui/components/ui/button';\nimport { cn } from '@edyn/ui/lib/utils';\nimport { IconFile, IconUpload, IconX } from '@tabler/icons-react';\nimport { type ChangeEvent, type ComponentPropsWithoutRef, type DragEvent, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';\n\nexport type TFileRejectReason = 'type' | 'size' | 'count';\n\nexport interface IFileRejection {\n file: File;\n reason: TFileRejectReason;\n}\n\n/** The words a FileInput says and announces. Pass the ones to change. */\nexport interface IFileInputLabels {\n /** The prompt in the drop area when it takes one file, before the \"browse\" link. */\n dropOne: string;\n /** The same, when it takes several. */\n dropMany: string;\n browse: string;\n /** The accessible name of the file chooser itself, when the caller gives it no name of its own. */\n chooseOne: string;\n chooseMany: string;\n /** The accessible name of the list of chosen files. */\n list: string;\n remove: (name: string) => string;\n /** Announced when files are added. */\n added: (count: number) => string;\n /** Announced when a file is taken out. */\n removed: (name: string) => string;\n /** Said, and shown, for a file that was refused. */\n rejection: (name: string, reason: TFileRejectReason, limit: string) => string;\n}\n\nexport const DEFAULT_FILE_INPUT_LABELS: IFileInputLabels = {\n dropOne: 'Drag a file here or',\n dropMany: 'Drag files here or',\n browse: 'browse',\n chooseOne: 'Choose a file',\n chooseMany: 'Choose files',\n list: 'Chosen files',\n remove: (name) => `Remove ${name}`,\n added: (count) => (count === 1 ? '1 file added' : `${count} files added`),\n removed: (name) => `${name} removed`,\n rejection: (name, reason, limit) => {\n if (reason === 'type') {\n return `${name} was not added: this type is not accepted`;\n }\n if (reason === 'size') {\n return `${name} was not added: it is larger than ${limit}`;\n }\n return `${name} was not added: ${limit === '1' ? '1 file is' : `${limit} files are`} the most`;\n },\n};\n\nconst UNITS = ['B', 'KB', 'MB', 'GB'] as const;\n\n/** 1,536 bytes as \"1.5 KB\": the size a person reads next to a file's name. */\nexport const formatBytes = (bytes: number): string => {\n let size = bytes;\n let unit = 0;\n while (size >= 1024 && unit < UNITS.length - 1) {\n size /= 1024;\n unit += 1;\n }\n const text = unit === 0 || size >= 10 ? String(Math.round(size)) : size.toFixed(1).replace(/\\.0$/, '');\n return `${text} ${UNITS[unit]}`;\n};\n\n/** Whether a file is one `accept` allows: an extension (`.pdf`), a type (`application/pdf`) or a family of types (`image/*`). No `accept` allows all. */\nexport const matchesAccept = (file: { name: string; type: string }, accept: string | undefined): boolean => {\n const tokens = (accept ?? '')\n .split(',')\n .map((token) => token.trim().toLowerCase())\n .filter((token) => token !== '');\n if (tokens.length === 0) {\n return true;\n }\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n return tokens.some((token) => {\n if (token.startsWith('.')) {\n return name.endsWith(token);\n }\n if (token.endsWith('/*')) {\n return type.startsWith(token.slice(0, -1));\n }\n return type === token;\n });\n};\n\nconst sameFile = (a: File, b: File): boolean => a.name === b.name && a.size === b.size && a.lastModified === b.lastModified;\n\nexport interface ISortFilesInput {\n incoming: readonly File[];\n current: readonly File[];\n multiple: boolean;\n accept?: string;\n maxSize?: number;\n maxFiles?: number;\n}\n\n/**\n * Which of the files just offered are taken, and which are refused and why. A file already chosen is skipped without a word. With one\n * file allowed, the first acceptable one replaces what was chosen and the rest are refused as too many.\n */\nexport const sortFiles = ({\n incoming,\n current,\n multiple,\n accept,\n maxSize,\n maxFiles,\n}: ISortFilesInput): { accepted: File[]; rejected: IFileRejection[] } => {\n const accepted: File[] = [];\n const rejected: IFileRejection[] = [];\n const limit = multiple ? maxFiles : 1;\n for (const file of incoming) {\n if (!matchesAccept(file, accept)) {\n rejected.push({ file, reason: 'type' });\n } else if (maxSize !== undefined && file.size > maxSize) {\n rejected.push({ file, reason: 'size' });\n } else if (current.some((chosen) => sameFile(chosen, file)) || accepted.some((taken) => sameFile(taken, file))) {\n } else if (limit !== undefined && (multiple ? current.length : 0) + accepted.length >= limit) {\n rejected.push({ file, reason: 'count' });\n } else {\n accepted.push(file);\n }\n }\n return { accepted, rejected };\n};\n\nexport interface IFileInputProps extends Omit<ComponentPropsWithoutRef<'div'>, 'defaultValue' | 'onChange' | 'children' | 'id'> {\n /** The chosen files. Leave it out and the field keeps them itself. */\n value?: readonly File[];\n defaultValue?: readonly File[];\n onValueChange?: (files: File[]) => void;\n /** What the picker offers, and what a drop is held to: extensions (`.pdf`), types (`application/pdf`) or families (`image/*`). */\n accept?: string;\n /** Several files, added to each other, instead of one that is replaced. */\n multiple?: boolean;\n /** The most a file may weigh, in bytes. */\n maxSize?: number;\n /** The most files that may be chosen. Several-file fields only. */\n maxFiles?: number;\n /** Called with the files that were refused, each with the reason. They are also listed under the field. */\n onReject?: (rejections: IFileRejection[]) => void;\n disabled?: boolean;\n invalid?: boolean;\n required?: boolean;\n /** Submits the chosen files with a form, under this name. */\n name?: string;\n /** For a `<Label htmlFor>` to name the field. */\n id?: string;\n /** What the field accepts, in words (\"PDF or PNG, up to 5 MB\"), read with the field. */\n hint?: ReactNode;\n labels?: Partial<IFileInputLabels>;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n}\n\nconst limitText = (reason: TFileRejectReason, maxSize: number | undefined, maxFiles: number | undefined, multiple: boolean): string => {\n if (reason === 'size') {\n return formatBytes(maxSize ?? 0);\n }\n return String(multiple ? (maxFiles ?? 0) : 1);\n};\n\n/**\n * Chooses files by picking them or by dropping them on the field, in the kit's look. It exists so an app does not fall back to\n * `<input type=\"file\">`, which cannot be styled and has no list of what was chosen.\n *\n * The control is a real file input laid over the whole drop area, so Tab, Enter and Space, the system's file dialog, a drop and a\n * screen reader's name for it all come from the browser. What the browser lacks is added around it: the list of chosen files with a\n * button to take each out, the checks against `accept`, `maxSize` and `maxFiles` (a drop skips the dialog's own filter), and a\n * live region that says what was added, removed or refused.\n *\n * Name the field with `aria-label`, `aria-labelledby` or a `<Label htmlFor={id}>`; with none of those it is named \"Choose files\".\n */\nexport const FileInput = ({\n value,\n defaultValue = [],\n onValueChange,\n accept,\n multiple = false,\n maxSize,\n maxFiles,\n onReject,\n disabled = false,\n invalid = false,\n required = false,\n name,\n id,\n hint,\n labels: labelOverrides,\n className,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...rest\n}: IFileInputProps) => {\n const labels = useMemo(() => ({ ...DEFAULT_FILE_INPUT_LABELS, ...labelOverrides }), [labelOverrides]);\n const [inner, setInner] = useState<readonly File[]>(defaultValue);\n const files = value ?? inner;\n const [dragging, setDragging] = useState(false);\n const [rejections, setRejections] = useState<IFileRejection[]>([]);\n const [announcement, setAnnouncement] = useState('');\n const refInput = useRef<HTMLInputElement | null>(null);\n const hintId = `${id ?? 'file-input'}-hint`;\n\n // The input's own list follows the chosen files, so a form submits exactly what the list shows.\n useEffect(() => {\n const input = refInput.current;\n if (input === null || typeof DataTransfer === 'undefined') {\n return;\n }\n const transfer = new DataTransfer();\n for (const file of files) {\n transfer.items.add(file);\n }\n input.files = transfer.files;\n }, [files]);\n\n const commit = (next: File[]): void => {\n setInner(next);\n onValueChange?.(next);\n };\n\n const take = (incoming: File[]): void => {\n if (incoming.length === 0) {\n return;\n }\n const { accepted, rejected } = sortFiles({ incoming, current: files, multiple, accept, maxSize, maxFiles });\n setRejections(rejected);\n if (rejected.length > 0) {\n onReject?.(rejected);\n }\n if (accepted.length > 0) {\n commit(multiple ? [...files, ...accepted] : accepted);\n setAnnouncement(labels.added(accepted.length));\n } else {\n setAnnouncement('');\n }\n };\n\n const remove = (index: number): void => {\n const gone = files[index];\n if (gone === undefined) {\n return;\n }\n setRejections([]);\n commit(files.filter((_, at) => at !== index));\n setAnnouncement(labels.removed(gone.name));\n refInput.current?.focus();\n };\n\n const onChange = (event: ChangeEvent<HTMLInputElement>): void => {\n take([...(event.target.files ?? [])]);\n };\n\n const carriesFiles = (event: DragEvent): boolean => event.dataTransfer.types.includes('Files');\n\n const onDrag = (event: DragEvent<HTMLDivElement>): void => {\n if (disabled || !carriesFiles(event)) {\n return;\n }\n event.preventDefault();\n setDragging(true);\n };\n\n const onDrop = (event: DragEvent<HTMLDivElement>): void => {\n if (disabled || !carriesFiles(event)) {\n return;\n }\n // The drop is handled here, once, and not again by the input underneath.\n event.preventDefault();\n setDragging(false);\n take([...event.dataTransfer.files]);\n };\n\n const named = ariaLabel !== undefined || ariaLabelledBy !== undefined || id !== undefined;\n\n return (\n <div data-slot=\"file-input\" className={cn('flex w-full flex-col gap-2', className)} {...rest}>\n {/* biome-ignore lint/a11y/noStaticElementInteractions: a drop is only the pointer's way onto the file input laid over this area, which stays the keyboard and screen-reader way. */}\n <div\n data-slot=\"file-input-zone\"\n role=\"presentation\"\n data-dragging={dragging || undefined}\n className={cn(\n 'relative flex flex-col items-center justify-center gap-1 rounded-lg border border-dashed border-control px-4 py-6 text-center text-sm transition-colors dark:bg-input/30',\n 'hover:bg-foreground/4 has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-3 has-[input:focus-visible]:ring-ring/50',\n 'data-dragging:border-ring data-dragging:bg-foreground/6',\n invalid && 'border-destructive ring-3 ring-destructive/20 dark:border-destructive/50 dark:ring-destructive/40',\n // The prompt stays readable (4.5:1) the way a disabled Button's text does: no opacity on the text, a muted colour instead.\n disabled && 'pointer-events-none cursor-not-allowed border-border',\n )}\n onDragEnter={onDrag}\n onDragOver={onDrag}\n onDragLeave={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {\n setDragging(false);\n }\n }}\n onDrop={onDrop}\n >\n <IconUpload aria-hidden=\"true\" className=\"size-5 text-muted-foreground\" />\n <p className={disabled ? 'text-muted-foreground' : 'text-foreground'}>\n {multiple ? labels.dropMany : labels.dropOne} <span className=\"font-medium underline underline-offset-3\">{labels.browse}</span>\n </p>\n {hint !== undefined && (\n <p id={hintId} className=\"text-xs text-muted-foreground\">\n {hint}\n </p>\n )}\n <input\n ref={refInput}\n id={id}\n type=\"file\"\n name={name}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n required={required && files.length === 0}\n aria-label={named ? ariaLabel : multiple ? labels.chooseMany : labels.chooseOne}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={hint === undefined ? undefined : hintId}\n aria-invalid={invalid || undefined}\n className=\"absolute inset-0 size-full cursor-pointer opacity-0 disabled:cursor-not-allowed\"\n onChange={onChange}\n />\n </div>\n {files.length > 0 && (\n <ul data-slot=\"file-input-list\" aria-label={labels.list} className=\"flex flex-col gap-1\">\n {files.map((file, index) => (\n <li\n key={`${file.name}-${file.size}-${file.lastModified}`}\n className=\"flex items-center gap-2 rounded-lg border border-control py-1 pr-1 pl-2 text-sm\"\n >\n <IconFile aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{file.name}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground tabular-nums\">{formatBytes(file.size)}</span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n disabled={disabled}\n aria-label={labels.remove(file.name)}\n onClick={() => remove(index)}\n >\n <IconX aria-hidden=\"true\" />\n </Button>\n </li>\n ))}\n </ul>\n )}\n {/* One live region: it stays mounted and only its words change, which is what makes a screen reader say them. The refused files are\n the visible part; what was added or removed is for the ear. */}\n <div role=\"status\" data-slot=\"file-input-status\" className=\"text-sm empty:hidden\">\n {rejections.map(({ file, reason }) => (\n <p key={`${file.name}-${reason}`} className=\"text-destructive\">\n {labels.rejection(file.name, reason, limitText(reason, maxSize, maxFiles, multiple))}\n </p>\n ))}\n {announcement !== '' && <span className=\"sr-only\">{announcement}</span>}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { cn } from "../../../lib/utils.js";
2
2
  import { useExposedFocusGuards } from "../../../lib/focus-guards.js";
3
+ import { POPUP_FRAME_CLASS, POPUP_LAYER_CLASS, POPUP_LIST_HEIGHT_CLASS, POPUP_LIST_WIDTH_CLASS, popupItemClass } from "../../../lib/popup-list.js";
3
4
  import { ScrollArea } from "../../ui/scroll-area/scroll-area.js";
4
5
  import { Tooltip, TooltipContent, TooltipTrigger } from "../../ui/tooltip/tooltip.js";
5
- import { POPUP_FRAME_CLASS, POPUP_LIST_HEIGHT_CLASS, POPUP_LIST_WIDTH_CLASS, popupItemClass } from "../../../lib/popup-list.js";
6
6
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { IconCheck, IconChevronDown, IconSearch } from "@tabler/icons-react";
@@ -46,7 +46,7 @@ var summarize = (chosen, placeholder, labels, summary = "selection") => {
46
46
  /** Every chosen name, for a tooltip on a trigger whose text is cut. With `label` the names are not shown, so it is the text itself. */
47
47
  var fullSelection = (chosen, placeholder, labels, summary = "selection") => summary === "selection" && chosen.length > 0 ? chosen.map((option) => option.label).join(", ") : summarize(chosen, placeholder, labels, summary);
48
48
  var POPUP = cn("flex max-h-(--available-height) origin-(--transform-origin) flex-col overflow-hidden duration-100", POPUP_FRAME_CLASS, POPUP_LIST_WIDTH_CLASS, "motion-reduce:animate-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95");
49
- var TRIGGER = "flex h-8 w-full min-w-0 cursor-pointer items-center justify-between gap-2 rounded-lg border border-input bg-transparent px-2.5 text-sm text-foreground transition-colors outline-none focus-visible:border-ring disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive data-popup-open:border-ring dark:bg-input/30";
49
+ var TRIGGER = "flex h-8 w-full min-w-0 cursor-pointer items-center justify-between gap-2 rounded-lg border border-control bg-transparent px-2.5 text-sm text-foreground transition-colors outline-none focus-visible:border-ring disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive data-popup-open:border-ring dark:bg-input/30";
50
50
  var Option = ({ option, index, style, measure, highlighted }) => {
51
51
  const [cut, setCut] = useState(false);
52
52
  const measureLabel = useCallback((element) => {
@@ -70,7 +70,7 @@ var Option = ({ option, index, style, measure, highlighted }) => {
70
70
  /* @__PURE__ */ jsx("span", {
71
71
  "data-slot": "multi-select-item-indicator",
72
72
  "aria-hidden": "true",
73
- className: "flex size-4 shrink-0 items-center justify-center rounded-sm border border-input text-primary-foreground transition-colors group-data-[selected]:border-primary group-data-[selected]:bg-primary",
73
+ className: "flex size-4 shrink-0 items-center justify-center rounded-sm border border-control text-primary-foreground transition-colors group-data-[selected]:border-primary group-data-[selected]:bg-primary",
74
74
  children: /* @__PURE__ */ jsx(IconCheck, {
75
75
  className: "size-3 opacity-0 group-data-[selected]:opacity-100",
76
76
  stroke: 3
@@ -120,7 +120,7 @@ var Options = ({ scroller, scrollTo, busy, highlightedValue }) => {
120
120
  ]);
121
121
  if (!virtual) return /* @__PURE__ */ jsx(Combobox.List, {
122
122
  "aria-busy": busy || void 0,
123
- className: "p-1 outline-none",
123
+ className: "px-1 outline-none",
124
124
  children: items.map((option, index) => /* @__PURE__ */ jsx(Option, {
125
125
  option,
126
126
  index,
@@ -130,7 +130,7 @@ var Options = ({ scroller, scrollTo, busy, highlightedValue }) => {
130
130
  return /* @__PURE__ */ jsx(Combobox.List, {
131
131
  "data-virtualized": "",
132
132
  "aria-busy": busy || void 0,
133
- className: "relative m-1 outline-none",
133
+ className: "relative mx-1 outline-none",
134
134
  style: { height: virtualizer.getTotalSize() },
135
135
  children: virtualizer.getVirtualItems().map((row) => {
136
136
  const option = items[row.index];
@@ -262,7 +262,7 @@ var MultiSelect = ({ placeholder, searchPlaceholder, searchFrom = 8, options, va
262
262
  }), /* @__PURE__ */ jsxs(Combobox.Portal, { children: [/* @__PURE__ */ jsx(ExposedFocusGuards, {}), /* @__PURE__ */ jsx(Combobox.Positioner, {
263
263
  sideOffset: 4,
264
264
  align: "start",
265
- className: "z-50",
265
+ className: POPUP_LAYER_CLASS,
266
266
  children: /* @__PURE__ */ jsxs(Combobox.Popup, {
267
267
  "data-slot": "multi-select-content",
268
268
  className: POPUP,