@voila.dev/ui 1.1.9

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 (99) hide show
  1. package/package.json +78 -0
  2. package/src/components/ui/accordion.tsx +79 -0
  3. package/src/components/ui/alert-dialog.tsx +198 -0
  4. package/src/components/ui/alert.tsx +105 -0
  5. package/src/components/ui/aspect-ratio.tsx +30 -0
  6. package/src/components/ui/avatar.tsx +125 -0
  7. package/src/components/ui/badge-variants.ts +166 -0
  8. package/src/components/ui/badge.tsx +45 -0
  9. package/src/components/ui/banner.tsx +84 -0
  10. package/src/components/ui/breadcrumb.tsx +127 -0
  11. package/src/components/ui/button-group.tsx +99 -0
  12. package/src/components/ui/button-variants.ts +108 -0
  13. package/src/components/ui/button.tsx +54 -0
  14. package/src/components/ui/calendar.tsx +266 -0
  15. package/src/components/ui/card.tsx +155 -0
  16. package/src/components/ui/carousel.tsx +319 -0
  17. package/src/components/ui/chat.tsx +896 -0
  18. package/src/components/ui/checkbox-group.tsx +44 -0
  19. package/src/components/ui/checkbox.tsx +34 -0
  20. package/src/components/ui/chip.tsx +60 -0
  21. package/src/components/ui/collapsible.tsx +44 -0
  22. package/src/components/ui/color-picker.tsx +191 -0
  23. package/src/components/ui/combobox.tsx +317 -0
  24. package/src/components/ui/command.tsx +187 -0
  25. package/src/components/ui/confirm-dialog.tsx +125 -0
  26. package/src/components/ui/context-menu.tsx +254 -0
  27. package/src/components/ui/copyable-text.tsx +65 -0
  28. package/src/components/ui/date-picker.tsx +316 -0
  29. package/src/components/ui/date-time-picker.tsx +1071 -0
  30. package/src/components/ui/dialog.tsx +176 -0
  31. package/src/components/ui/direction.tsx +15 -0
  32. package/src/components/ui/drawer.tsx +178 -0
  33. package/src/components/ui/dropdown-menu.tsx +247 -0
  34. package/src/components/ui/empty.tsx +131 -0
  35. package/src/components/ui/field.tsx +257 -0
  36. package/src/components/ui/formatted-input.tsx +219 -0
  37. package/src/components/ui/gallery.tsx +161 -0
  38. package/src/components/ui/hover-card.tsx +77 -0
  39. package/src/components/ui/icon-picker.tsx +193 -0
  40. package/src/components/ui/image-cropper.tsx +787 -0
  41. package/src/components/ui/image-upload-field.tsx +344 -0
  42. package/src/components/ui/input-group.tsx +175 -0
  43. package/src/components/ui/input-otp.tsx +83 -0
  44. package/src/components/ui/input.tsx +21 -0
  45. package/src/components/ui/item.tsx +212 -0
  46. package/src/components/ui/kbd.tsx +62 -0
  47. package/src/components/ui/label.tsx +19 -0
  48. package/src/components/ui/list.tsx +46 -0
  49. package/src/components/ui/menu-variants.ts +71 -0
  50. package/src/components/ui/menubar.tsx +226 -0
  51. package/src/components/ui/money-input.tsx +96 -0
  52. package/src/components/ui/native-date-picker.tsx +120 -0
  53. package/src/components/ui/native-select.tsx +74 -0
  54. package/src/components/ui/navigation-menu.tsx +183 -0
  55. package/src/components/ui/pagination.tsx +150 -0
  56. package/src/components/ui/popover.tsx +104 -0
  57. package/src/components/ui/profile-header.tsx +196 -0
  58. package/src/components/ui/progress.tsx +71 -0
  59. package/src/components/ui/radio-group.tsx +115 -0
  60. package/src/components/ui/rating.tsx +200 -0
  61. package/src/components/ui/resizable.tsx +61 -0
  62. package/src/components/ui/responsive-dialog.tsx +278 -0
  63. package/src/components/ui/responsive-select.tsx +289 -0
  64. package/src/components/ui/responsive-sheet.tsx +225 -0
  65. package/src/components/ui/scroll-area.tsx +51 -0
  66. package/src/components/ui/section.tsx +106 -0
  67. package/src/components/ui/segmented-control.tsx +148 -0
  68. package/src/components/ui/select.tsx +216 -0
  69. package/src/components/ui/separator.tsx +69 -0
  70. package/src/components/ui/sheet.tsx +154 -0
  71. package/src/components/ui/shortcut.tsx +41 -0
  72. package/src/components/ui/sidebar.tsx +739 -0
  73. package/src/components/ui/skeleton.tsx +23 -0
  74. package/src/components/ui/slider.tsx +74 -0
  75. package/src/components/ui/sonner.tsx +64 -0
  76. package/src/components/ui/spinner.tsx +19 -0
  77. package/src/components/ui/stat-card.tsx +138 -0
  78. package/src/components/ui/stepper.tsx +181 -0
  79. package/src/components/ui/sticky-action-bar.tsx +42 -0
  80. package/src/components/ui/switch.tsx +30 -0
  81. package/src/components/ui/table.tsx +132 -0
  82. package/src/components/ui/tabs.tsx +77 -0
  83. package/src/components/ui/textarea.tsx +24 -0
  84. package/src/components/ui/time-picker.tsx +221 -0
  85. package/src/components/ui/toggle-group.tsx +86 -0
  86. package/src/components/ui/toggle.tsx +43 -0
  87. package/src/components/ui/tooltip.tsx +79 -0
  88. package/src/components/ui/translation-input.tsx +126 -0
  89. package/src/components/ui/user-avatar.tsx +74 -0
  90. package/src/css-modules.d.ts +4 -0
  91. package/src/hooks/use-command-palette.ts +28 -0
  92. package/src/hooks/use-mobile.ts +21 -0
  93. package/src/hooks/use-pagination.ts +81 -0
  94. package/src/hooks/use-picker-state.ts +44 -0
  95. package/src/lib/cva.ts +18 -0
  96. package/src/lib/time-math.ts +62 -0
  97. package/src/lib/utils.ts +14 -0
  98. package/src/styles/globals.css +36 -0
  99. package/src/styles.css +10 -0
@@ -0,0 +1,216 @@
1
+ import { Select as SelectPrimitive } from "@base-ui/react/select";
2
+ import { CaretDownIcon, CaretUpIcon, CheckIcon } from "@phosphor-icons/react";
3
+ import { cn } from "#/lib/utils.ts";
4
+
5
+ /* Pass `items` (value → label) so the trigger renders the selected item's
6
+ * label; without it Base UI's Value falls back to the raw value string. */
7
+ function Select<Value, Multiple extends boolean | undefined = false>(
8
+ props: SelectPrimitive.Root.Props<Value, Multiple>,
9
+ ) {
10
+ return <SelectPrimitive.Root data-slot="select" {...props} />;
11
+ }
12
+
13
+ function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
14
+ return (
15
+ <SelectPrimitive.Group
16
+ data-slot="select-group"
17
+ className={cn("scroll-my-1", className)}
18
+ {...props}
19
+ />
20
+ );
21
+ }
22
+
23
+ function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
24
+ return (
25
+ <SelectPrimitive.Value
26
+ data-slot="select-value"
27
+ className={cn("flex flex-1 text-left", className)}
28
+ {...props}
29
+ />
30
+ );
31
+ }
32
+
33
+ function SelectTrigger({
34
+ className,
35
+ size = "default",
36
+ children,
37
+ ...props
38
+ }: SelectPrimitive.Trigger.Props & {
39
+ size?: "sm" | "default";
40
+ }) {
41
+ return (
42
+ <SelectPrimitive.Trigger
43
+ data-slot="select-trigger"
44
+ data-size={size}
45
+ className={cn(
46
+ "flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none hover:bg-muted focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),12px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
47
+ className,
48
+ )}
49
+ {...props}
50
+ >
51
+ {children}
52
+ <SelectPrimitive.Icon
53
+ render={
54
+ <CaretDownIcon className="pointer-events-none size-4 text-muted-foreground" />
55
+ }
56
+ />
57
+ </SelectPrimitive.Trigger>
58
+ );
59
+ }
60
+
61
+ /* With the default `alignItemWithTrigger` the popup overlaps the trigger
62
+ * macOS-style and intentionally suppresses the open/close animation; pass
63
+ * `alignItemWithTrigger={false}` to get the popover-style zoom/fade. */
64
+ function SelectContent({
65
+ className,
66
+ children,
67
+ side = "bottom",
68
+ sideOffset = 4,
69
+ align = "center",
70
+ alignOffset = 0,
71
+ alignItemWithTrigger = true,
72
+ collisionPadding,
73
+ ...props
74
+ }: SelectPrimitive.Popup.Props &
75
+ Pick<
76
+ SelectPrimitive.Positioner.Props,
77
+ | "align"
78
+ | "alignOffset"
79
+ | "side"
80
+ | "sideOffset"
81
+ | "alignItemWithTrigger"
82
+ | "collisionPadding"
83
+ >) {
84
+ return (
85
+ <SelectPrimitive.Portal>
86
+ <SelectPrimitive.Positioner
87
+ side={side}
88
+ sideOffset={sideOffset}
89
+ align={align}
90
+ alignOffset={alignOffset}
91
+ alignItemWithTrigger={alignItemWithTrigger}
92
+ collisionPadding={collisionPadding}
93
+ className="isolate z-50"
94
+ >
95
+ <SelectPrimitive.Popup
96
+ data-slot="select-content"
97
+ data-align-trigger={alignItemWithTrigger}
98
+ className={cn(
99
+ "relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 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",
100
+ className,
101
+ )}
102
+ {...props}
103
+ >
104
+ <SelectScrollUpButton />
105
+ <SelectPrimitive.List className="p-1">
106
+ {children}
107
+ </SelectPrimitive.List>
108
+ <SelectScrollDownButton />
109
+ </SelectPrimitive.Popup>
110
+ </SelectPrimitive.Positioner>
111
+ </SelectPrimitive.Portal>
112
+ );
113
+ }
114
+
115
+ function SelectLabel({
116
+ className,
117
+ ...props
118
+ }: SelectPrimitive.GroupLabel.Props) {
119
+ return (
120
+ <SelectPrimitive.GroupLabel
121
+ data-slot="select-label"
122
+ className={cn("px-1.5 py-1 text-xs text-muted-foreground", className)}
123
+ {...props}
124
+ />
125
+ );
126
+ }
127
+
128
+ function SelectItem({
129
+ className,
130
+ children,
131
+ ...props
132
+ }: SelectPrimitive.Item.Props) {
133
+ return (
134
+ <SelectPrimitive.Item
135
+ data-slot="select-item"
136
+ className={cn(
137
+ "relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
138
+ className,
139
+ )}
140
+ {...props}
141
+ >
142
+ <SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
143
+ {children}
144
+ </SelectPrimitive.ItemText>
145
+ <SelectPrimitive.ItemIndicator
146
+ render={
147
+ <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
148
+ }
149
+ >
150
+ <CheckIcon className="pointer-events-none" />
151
+ </SelectPrimitive.ItemIndicator>
152
+ </SelectPrimitive.Item>
153
+ );
154
+ }
155
+
156
+ function SelectSeparator({
157
+ className,
158
+ ...props
159
+ }: SelectPrimitive.Separator.Props) {
160
+ return (
161
+ <SelectPrimitive.Separator
162
+ data-slot="select-separator"
163
+ className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
164
+ {...props}
165
+ />
166
+ );
167
+ }
168
+
169
+ function SelectScrollUpButton({
170
+ className,
171
+ ...props
172
+ }: SelectPrimitive.ScrollUpArrow.Props) {
173
+ return (
174
+ <SelectPrimitive.ScrollUpArrow
175
+ data-slot="select-scroll-up-button"
176
+ className={cn(
177
+ "top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
178
+ className,
179
+ )}
180
+ {...props}
181
+ >
182
+ <CaretUpIcon />
183
+ </SelectPrimitive.ScrollUpArrow>
184
+ );
185
+ }
186
+
187
+ function SelectScrollDownButton({
188
+ className,
189
+ ...props
190
+ }: SelectPrimitive.ScrollDownArrow.Props) {
191
+ return (
192
+ <SelectPrimitive.ScrollDownArrow
193
+ data-slot="select-scroll-down-button"
194
+ className={cn(
195
+ "bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
196
+ className,
197
+ )}
198
+ {...props}
199
+ >
200
+ <CaretDownIcon />
201
+ </SelectPrimitive.ScrollDownArrow>
202
+ );
203
+ }
204
+
205
+ export {
206
+ Select,
207
+ SelectContent,
208
+ SelectGroup,
209
+ SelectItem,
210
+ SelectLabel,
211
+ SelectScrollDownButton,
212
+ SelectScrollUpButton,
213
+ SelectSeparator,
214
+ SelectTrigger,
215
+ SelectValue,
216
+ };
@@ -0,0 +1,69 @@
1
+ import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
2
+
3
+ import { cn } from "#/lib/utils.ts";
4
+
5
+ /**
6
+ * Visual divider. Pass `children` for a labeled divider (the auth-screen
7
+ * "OU" pattern): the element keeps its separator semantics while two
8
+ * decorative lines flank the label, so it works on any background - no
9
+ * `bg-background` punch-through needed.
10
+ *
11
+ * Vertical separators `self-stretch` to fill the row. To shorten one inside
12
+ * a taller row, pass a height plus `my-auto` (e.g. `className="my-auto h-4"`):
13
+ * a definite height makes `self-stretch` start-align, and the auto margins
14
+ * re-center it.
15
+ */
16
+ function Separator({
17
+ className,
18
+ orientation = "horizontal",
19
+ children,
20
+ ...props
21
+ }: SeparatorPrimitive.Props) {
22
+ if (children == null) {
23
+ return (
24
+ <SeparatorPrimitive
25
+ data-slot="separator"
26
+ orientation={orientation}
27
+ className={cn(
28
+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch",
29
+ className,
30
+ )}
31
+ {...props}
32
+ />
33
+ );
34
+ }
35
+ return (
36
+ <SeparatorPrimitive
37
+ data-slot="separator"
38
+ orientation={orientation}
39
+ className={cn(
40
+ "flex shrink-0 items-center gap-2 text-sm text-muted-foreground data-[orientation=horizontal]:w-full data-[orientation=vertical]:flex-col data-[orientation=vertical]:self-stretch",
41
+ className,
42
+ )}
43
+ {...props}
44
+ >
45
+ <SeparatorLine orientation={orientation} />
46
+ <span data-slot="separator-label">{children}</span>
47
+ <SeparatorLine orientation={orientation} />
48
+ </SeparatorPrimitive>
49
+ );
50
+ }
51
+
52
+ function SeparatorLine({
53
+ orientation,
54
+ }: {
55
+ orientation: NonNullable<SeparatorPrimitive.Props["orientation"]>;
56
+ }) {
57
+ return (
58
+ <span
59
+ aria-hidden
60
+ data-slot="separator-line"
61
+ className={cn(
62
+ "flex-1 bg-border",
63
+ orientation === "vertical" ? "w-px" : "h-px",
64
+ )}
65
+ />
66
+ );
67
+ }
68
+
69
+ export { Separator };
@@ -0,0 +1,154 @@
1
+ import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
2
+ import { XIcon } from "@phosphor-icons/react";
3
+ import type * as React from "react";
4
+ import { Button } from "#/components/ui/button.tsx";
5
+ import { cn } from "#/lib/utils.ts";
6
+
7
+ function Sheet({ ...props }: SheetPrimitive.Root.Props) {
8
+ return <SheetPrimitive.Root data-slot="sheet" {...props} />;
9
+ }
10
+
11
+ function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
12
+ return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
13
+ }
14
+
15
+ function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
16
+ return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
17
+ }
18
+
19
+ function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
20
+ return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
21
+ }
22
+
23
+ function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
24
+ return (
25
+ <SheetPrimitive.Backdrop
26
+ data-slot="sheet-overlay"
27
+ className={cn(
28
+ // `bg-black/10` carries the dimming on light pages; on a near-black
29
+ // dark page the blur alone does the work, so deepen the scrim there.
30
+ "fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 supports-backdrop-filter:backdrop-blur-xs data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none dark:bg-black/40",
31
+ className,
32
+ )}
33
+ {...props}
34
+ />
35
+ );
36
+ }
37
+
38
+ // `xl` and `full` are documented extras beyond the kit's `sm | default | lg`
39
+ // scale for wide editing panels and full-bleed surfaces.
40
+ type SheetContentSize = "sm" | "default" | "lg" | "xl" | "full";
41
+
42
+ function SheetContent({
43
+ className,
44
+ children,
45
+ side = "right",
46
+ size = "default",
47
+ showCloseButton = true,
48
+ closeButtonLabel = "Close",
49
+ ...props
50
+ }: SheetPrimitive.Popup.Props & {
51
+ side?: "top" | "right" | "bottom" | "left";
52
+ size?: SheetContentSize;
53
+ showCloseButton?: boolean;
54
+ closeButtonLabel?: string;
55
+ }) {
56
+ return (
57
+ <SheetPortal>
58
+ <SheetOverlay />
59
+ <SheetPrimitive.Popup
60
+ data-slot="sheet-content"
61
+ data-side={side}
62
+ data-size={size}
63
+ className={cn(
64
+ "fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:rounded-t-xl data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:rounded-r-xl data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:rounded-l-xl data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:rounded-b-xl data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem]",
65
+ // `size` only constrains the left/right panels (top/bottom are
66
+ // content-height, full-bleed). Styled off `data-size` (like
67
+ // `AlertDialogContent`); the default reproduces the historical
68
+ // `sm:max-w-sm` width.
69
+ "data-[size=default]:data-[side=left]:sm:max-w-sm data-[size=default]:data-[side=right]:sm:max-w-sm data-[size=full]:data-[side=left]:w-full data-[size=full]:data-[side=left]:max-w-none data-[size=full]:data-[side=right]:w-full data-[size=full]:data-[side=right]:max-w-none data-[size=lg]:data-[side=left]:sm:max-w-md data-[size=lg]:data-[side=right]:sm:max-w-md data-[size=sm]:data-[side=left]:sm:max-w-xs data-[size=sm]:data-[side=right]:sm:max-w-xs data-[size=xl]:data-[side=left]:sm:max-w-lg data-[size=xl]:data-[side=right]:sm:max-w-lg",
70
+ className,
71
+ )}
72
+ {...props}
73
+ >
74
+ {children}
75
+ {showCloseButton && (
76
+ <SheetPrimitive.Close
77
+ data-slot="sheet-close-button"
78
+ render={
79
+ <Button
80
+ variant="ghost"
81
+ className="absolute top-3 right-3"
82
+ size="icon-sm"
83
+ />
84
+ }
85
+ >
86
+ <XIcon />
87
+ <span className="sr-only">{closeButtonLabel}</span>
88
+ </SheetPrimitive.Close>
89
+ )}
90
+ </SheetPrimitive.Popup>
91
+ </SheetPortal>
92
+ );
93
+ }
94
+
95
+ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
96
+ return (
97
+ <div
98
+ data-slot="sheet-header"
99
+ className={cn("flex flex-col gap-0.5 p-4", className)}
100
+ {...props}
101
+ />
102
+ );
103
+ }
104
+
105
+ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
106
+ return (
107
+ <div
108
+ data-slot="sheet-footer"
109
+ className={cn(
110
+ // Extra bottom padding keeps actions above the iOS home indicator.
111
+ "mt-auto flex flex-col gap-2 p-4 pb-[calc(1rem+env(safe-area-inset-bottom))]",
112
+ className,
113
+ )}
114
+ {...props}
115
+ />
116
+ );
117
+ }
118
+
119
+ function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
120
+ return (
121
+ <SheetPrimitive.Title
122
+ data-slot="sheet-title"
123
+ className={cn("text-base font-medium text-foreground", className)}
124
+ {...props}
125
+ />
126
+ );
127
+ }
128
+
129
+ function SheetDescription({
130
+ className,
131
+ ...props
132
+ }: SheetPrimitive.Description.Props) {
133
+ return (
134
+ <SheetPrimitive.Description
135
+ data-slot="sheet-description"
136
+ className={cn("text-sm text-muted-foreground", className)}
137
+ {...props}
138
+ />
139
+ );
140
+ }
141
+
142
+ export {
143
+ Sheet,
144
+ SheetClose,
145
+ SheetContent,
146
+ type SheetContentSize,
147
+ SheetDescription,
148
+ SheetFooter,
149
+ SheetHeader,
150
+ SheetOverlay,
151
+ SheetPortal,
152
+ SheetTitle,
153
+ SheetTrigger,
154
+ };
@@ -0,0 +1,41 @@
1
+ import type * as React from "react";
2
+ import { Kbd, KbdGroup } from "#/components/ui/kbd.tsx";
3
+ import { menuShortcutVariants } from "#/components/ui/menu-variants.ts";
4
+ import { cn } from "#/lib/utils.ts";
5
+
6
+ /**
7
+ * Right-aligned keyboard hint for menu rows, shared by
8
+ * `DropdownMenuShortcut`, `ContextMenuShortcut`, `MenubarShortcut` and
9
+ * `CommandShortcut`. Pass plain children ("⌘K") for the classic text hint, or
10
+ * `keys` to render each key as a small `Kbd` chip instead.
11
+ */
12
+ function Shortcut({
13
+ className,
14
+ keys,
15
+ children,
16
+ ...props
17
+ }: React.ComponentProps<"span"> & {
18
+ keys?: ReadonlyArray<string>;
19
+ }) {
20
+ return (
21
+ <span
22
+ data-slot="shortcut"
23
+ className={cn(menuShortcutVariants(), className)}
24
+ {...props}
25
+ >
26
+ {keys === undefined ? (
27
+ children
28
+ ) : (
29
+ <KbdGroup>
30
+ {keys.map((key) => (
31
+ <Kbd key={key} size="sm">
32
+ {key}
33
+ </Kbd>
34
+ ))}
35
+ </KbdGroup>
36
+ )}
37
+ </span>
38
+ );
39
+ }
40
+
41
+ export { Shortcut };