@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.
- package/package.json +78 -0
- package/src/components/ui/accordion.tsx +79 -0
- package/src/components/ui/alert-dialog.tsx +198 -0
- package/src/components/ui/alert.tsx +105 -0
- package/src/components/ui/aspect-ratio.tsx +30 -0
- package/src/components/ui/avatar.tsx +125 -0
- package/src/components/ui/badge-variants.ts +166 -0
- package/src/components/ui/badge.tsx +45 -0
- package/src/components/ui/banner.tsx +84 -0
- package/src/components/ui/breadcrumb.tsx +127 -0
- package/src/components/ui/button-group.tsx +99 -0
- package/src/components/ui/button-variants.ts +108 -0
- package/src/components/ui/button.tsx +54 -0
- package/src/components/ui/calendar.tsx +266 -0
- package/src/components/ui/card.tsx +155 -0
- package/src/components/ui/carousel.tsx +319 -0
- package/src/components/ui/chat.tsx +896 -0
- package/src/components/ui/checkbox-group.tsx +44 -0
- package/src/components/ui/checkbox.tsx +34 -0
- package/src/components/ui/chip.tsx +60 -0
- package/src/components/ui/collapsible.tsx +44 -0
- package/src/components/ui/color-picker.tsx +191 -0
- package/src/components/ui/combobox.tsx +317 -0
- package/src/components/ui/command.tsx +187 -0
- package/src/components/ui/confirm-dialog.tsx +125 -0
- package/src/components/ui/context-menu.tsx +254 -0
- package/src/components/ui/copyable-text.tsx +65 -0
- package/src/components/ui/date-picker.tsx +316 -0
- package/src/components/ui/date-time-picker.tsx +1071 -0
- package/src/components/ui/dialog.tsx +176 -0
- package/src/components/ui/direction.tsx +15 -0
- package/src/components/ui/drawer.tsx +178 -0
- package/src/components/ui/dropdown-menu.tsx +247 -0
- package/src/components/ui/empty.tsx +131 -0
- package/src/components/ui/field.tsx +257 -0
- package/src/components/ui/formatted-input.tsx +219 -0
- package/src/components/ui/gallery.tsx +161 -0
- package/src/components/ui/hover-card.tsx +77 -0
- package/src/components/ui/icon-picker.tsx +193 -0
- package/src/components/ui/image-cropper.tsx +787 -0
- package/src/components/ui/image-upload-field.tsx +344 -0
- package/src/components/ui/input-group.tsx +175 -0
- package/src/components/ui/input-otp.tsx +83 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/item.tsx +212 -0
- package/src/components/ui/kbd.tsx +62 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/list.tsx +46 -0
- package/src/components/ui/menu-variants.ts +71 -0
- package/src/components/ui/menubar.tsx +226 -0
- package/src/components/ui/money-input.tsx +96 -0
- package/src/components/ui/native-date-picker.tsx +120 -0
- package/src/components/ui/native-select.tsx +74 -0
- package/src/components/ui/navigation-menu.tsx +183 -0
- package/src/components/ui/pagination.tsx +150 -0
- package/src/components/ui/popover.tsx +104 -0
- package/src/components/ui/profile-header.tsx +196 -0
- package/src/components/ui/progress.tsx +71 -0
- package/src/components/ui/radio-group.tsx +115 -0
- package/src/components/ui/rating.tsx +200 -0
- package/src/components/ui/resizable.tsx +61 -0
- package/src/components/ui/responsive-dialog.tsx +278 -0
- package/src/components/ui/responsive-select.tsx +289 -0
- package/src/components/ui/responsive-sheet.tsx +225 -0
- package/src/components/ui/scroll-area.tsx +51 -0
- package/src/components/ui/section.tsx +106 -0
- package/src/components/ui/segmented-control.tsx +148 -0
- package/src/components/ui/select.tsx +216 -0
- package/src/components/ui/separator.tsx +69 -0
- package/src/components/ui/sheet.tsx +154 -0
- package/src/components/ui/shortcut.tsx +41 -0
- package/src/components/ui/sidebar.tsx +739 -0
- package/src/components/ui/skeleton.tsx +23 -0
- package/src/components/ui/slider.tsx +74 -0
- package/src/components/ui/sonner.tsx +64 -0
- package/src/components/ui/spinner.tsx +19 -0
- package/src/components/ui/stat-card.tsx +138 -0
- package/src/components/ui/stepper.tsx +181 -0
- package/src/components/ui/sticky-action-bar.tsx +42 -0
- package/src/components/ui/switch.tsx +30 -0
- package/src/components/ui/table.tsx +132 -0
- package/src/components/ui/tabs.tsx +77 -0
- package/src/components/ui/textarea.tsx +24 -0
- package/src/components/ui/time-picker.tsx +221 -0
- package/src/components/ui/toggle-group.tsx +86 -0
- package/src/components/ui/toggle.tsx +43 -0
- package/src/components/ui/tooltip.tsx +79 -0
- package/src/components/ui/translation-input.tsx +126 -0
- package/src/components/ui/user-avatar.tsx +74 -0
- package/src/css-modules.d.ts +4 -0
- package/src/hooks/use-command-palette.ts +28 -0
- package/src/hooks/use-mobile.ts +21 -0
- package/src/hooks/use-pagination.ts +81 -0
- package/src/hooks/use-picker-state.ts +44 -0
- package/src/lib/cva.ts +18 -0
- package/src/lib/time-math.ts +62 -0
- package/src/lib/utils.ts +14 -0
- package/src/styles/globals.css +36 -0
- package/src/styles.css +10 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
import type * as React from "react";
|
|
4
|
+
import { Separator } from "#/components/ui/separator.tsx";
|
|
5
|
+
import { cva, type VariantProps } from "#/lib/cva.ts";
|
|
6
|
+
import { cn } from "#/lib/utils.ts";
|
|
7
|
+
|
|
8
|
+
function ItemGroup({
|
|
9
|
+
className,
|
|
10
|
+
render,
|
|
11
|
+
...props
|
|
12
|
+
}: useRender.ComponentProps<"div">) {
|
|
13
|
+
return useRender({
|
|
14
|
+
defaultTagName: "div",
|
|
15
|
+
props: mergeProps<"div">(
|
|
16
|
+
{
|
|
17
|
+
className: cn(
|
|
18
|
+
"group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
|
|
19
|
+
className,
|
|
20
|
+
),
|
|
21
|
+
},
|
|
22
|
+
props,
|
|
23
|
+
),
|
|
24
|
+
render,
|
|
25
|
+
state: {
|
|
26
|
+
slot: "item-group",
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ItemSeparator({
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentProps<typeof Separator>) {
|
|
35
|
+
return (
|
|
36
|
+
<Separator
|
|
37
|
+
data-slot="item-separator"
|
|
38
|
+
orientation="horizontal"
|
|
39
|
+
className={cn("my-2", className)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const itemVariants = cva({
|
|
46
|
+
base: "group/item flex w-full flex-wrap items-center rounded-lg border text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-muted",
|
|
47
|
+
variants: {
|
|
48
|
+
variant: {
|
|
49
|
+
default: "border-transparent",
|
|
50
|
+
outline: "border-border",
|
|
51
|
+
muted: "border-transparent bg-muted/50",
|
|
52
|
+
},
|
|
53
|
+
size: {
|
|
54
|
+
default: "gap-2.5 px-3 py-2.5",
|
|
55
|
+
sm: "gap-2.5 px-3 py-2",
|
|
56
|
+
xs: "gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
defaultVariants: {
|
|
60
|
+
variant: "default",
|
|
61
|
+
size: "default",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
function Item({
|
|
66
|
+
className,
|
|
67
|
+
variant = "default",
|
|
68
|
+
size = "default",
|
|
69
|
+
render,
|
|
70
|
+
...props
|
|
71
|
+
}: useRender.ComponentProps<"div"> & VariantProps<typeof itemVariants>) {
|
|
72
|
+
return useRender({
|
|
73
|
+
defaultTagName: "div",
|
|
74
|
+
props: mergeProps<"div">(
|
|
75
|
+
{
|
|
76
|
+
className: cn(itemVariants({ variant, size }), className),
|
|
77
|
+
},
|
|
78
|
+
props,
|
|
79
|
+
),
|
|
80
|
+
render,
|
|
81
|
+
state: {
|
|
82
|
+
slot: "item",
|
|
83
|
+
variant,
|
|
84
|
+
size,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const itemMediaVariants = cva({
|
|
90
|
+
base: "flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none",
|
|
91
|
+
variants: {
|
|
92
|
+
variant: {
|
|
93
|
+
default: "bg-transparent",
|
|
94
|
+
icon: "[&_svg:not([class*='size-'])]:size-4",
|
|
95
|
+
image:
|
|
96
|
+
"size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
defaultVariants: {
|
|
100
|
+
variant: "default",
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function ItemMedia({
|
|
105
|
+
className,
|
|
106
|
+
variant = "default",
|
|
107
|
+
render,
|
|
108
|
+
...props
|
|
109
|
+
}: useRender.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>) {
|
|
110
|
+
return useRender({
|
|
111
|
+
defaultTagName: "div",
|
|
112
|
+
props: mergeProps<"div">(
|
|
113
|
+
{
|
|
114
|
+
className: cn(itemMediaVariants({ variant }), className),
|
|
115
|
+
},
|
|
116
|
+
props,
|
|
117
|
+
),
|
|
118
|
+
render,
|
|
119
|
+
state: {
|
|
120
|
+
slot: "item-media",
|
|
121
|
+
variant,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
127
|
+
return (
|
|
128
|
+
<div
|
|
129
|
+
data-slot="item-content"
|
|
130
|
+
className={cn(
|
|
131
|
+
"flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none",
|
|
132
|
+
className,
|
|
133
|
+
)}
|
|
134
|
+
{...props}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function ItemTitle({ className, ...props }: React.ComponentProps<"span">) {
|
|
140
|
+
return (
|
|
141
|
+
<span
|
|
142
|
+
data-slot="item-title"
|
|
143
|
+
className={cn(
|
|
144
|
+
"line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium underline-offset-4",
|
|
145
|
+
className,
|
|
146
|
+
)}
|
|
147
|
+
{...props}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
153
|
+
return (
|
|
154
|
+
<p
|
|
155
|
+
data-slot="item-description"
|
|
156
|
+
className={cn(
|
|
157
|
+
"line-clamp-2 text-left text-sm leading-normal font-normal text-muted-foreground group-data-[size=xs]/item:text-xs [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
158
|
+
className,
|
|
159
|
+
)}
|
|
160
|
+
{...props}
|
|
161
|
+
/>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
|
|
166
|
+
return (
|
|
167
|
+
<div
|
|
168
|
+
data-slot="item-actions"
|
|
169
|
+
className={cn("flex items-center gap-2", className)}
|
|
170
|
+
{...props}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
176
|
+
return (
|
|
177
|
+
<div
|
|
178
|
+
data-slot="item-header"
|
|
179
|
+
className={cn(
|
|
180
|
+
"flex basis-full items-center justify-between gap-2",
|
|
181
|
+
className,
|
|
182
|
+
)}
|
|
183
|
+
{...props}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
189
|
+
return (
|
|
190
|
+
<div
|
|
191
|
+
data-slot="item-footer"
|
|
192
|
+
className={cn(
|
|
193
|
+
"flex basis-full items-center justify-between gap-2",
|
|
194
|
+
className,
|
|
195
|
+
)}
|
|
196
|
+
{...props}
|
|
197
|
+
/>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export {
|
|
202
|
+
Item,
|
|
203
|
+
ItemActions,
|
|
204
|
+
ItemContent,
|
|
205
|
+
ItemDescription,
|
|
206
|
+
ItemFooter,
|
|
207
|
+
ItemGroup,
|
|
208
|
+
ItemHeader,
|
|
209
|
+
ItemMedia,
|
|
210
|
+
ItemSeparator,
|
|
211
|
+
ItemTitle,
|
|
212
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "#/lib/cva.ts";
|
|
3
|
+
import { cn } from "#/lib/utils.ts";
|
|
4
|
+
|
|
5
|
+
const kbdVariants = cva({
|
|
6
|
+
base: "pointer-events-none inline-flex w-fit items-center justify-center rounded-sm bg-muted font-sans font-medium text-muted-foreground select-none in-data-[slot=input-group-addon]:rounded-[calc(var(--radius)-5px)] in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10",
|
|
7
|
+
variants: {
|
|
8
|
+
size: {
|
|
9
|
+
default:
|
|
10
|
+
"h-5 min-w-5 gap-1 px-1 text-xs [&_svg:not([class*='size-'])]:size-3",
|
|
11
|
+
sm: "h-4 min-w-4 gap-0.5 px-0.5 text-[10px] [&_svg:not([class*='size-'])]:size-2.5",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function Kbd({
|
|
17
|
+
className,
|
|
18
|
+
size = "default",
|
|
19
|
+
...props
|
|
20
|
+
}: React.ComponentProps<"kbd"> & VariantProps<typeof kbdVariants>) {
|
|
21
|
+
return (
|
|
22
|
+
<kbd
|
|
23
|
+
data-slot="kbd"
|
|
24
|
+
data-size={size}
|
|
25
|
+
className={cn(kbdVariants({ size }), className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function KbdGroup({
|
|
32
|
+
className,
|
|
33
|
+
separator,
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}: React.ComponentProps<"kbd"> & { separator?: React.ReactNode }) {
|
|
37
|
+
return (
|
|
38
|
+
<kbd
|
|
39
|
+
data-slot="kbd-group"
|
|
40
|
+
className={cn(
|
|
41
|
+
"inline-flex items-center gap-1 text-muted-foreground",
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
{separator === undefined
|
|
47
|
+
? children
|
|
48
|
+
: React.Children.toArray(children).map((child, index) => (
|
|
49
|
+
<React.Fragment key={index}>
|
|
50
|
+
{index > 0 && (
|
|
51
|
+
<span aria-hidden="true" data-slot="kbd-separator">
|
|
52
|
+
{separator}
|
|
53
|
+
</span>
|
|
54
|
+
)}
|
|
55
|
+
{child}
|
|
56
|
+
</React.Fragment>
|
|
57
|
+
))}
|
|
58
|
+
</kbd>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { Kbd, KbdGroup };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "#/lib/utils.ts";
|
|
4
|
+
|
|
5
|
+
function Label({ className, ...props }: React.ComponentProps<"label">) {
|
|
6
|
+
return (
|
|
7
|
+
// biome-ignore lint/a11y/noLabelWithoutControl: This component is meant to be used as a wrapper for form controls, so it may not always have a control associated with it.
|
|
8
|
+
<label
|
|
9
|
+
data-slot="label"
|
|
10
|
+
className={cn(
|
|
11
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50 peer-data-disabled:cursor-not-allowed peer-data-disabled:opacity-50",
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { Label };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { Item, ItemGroup, ItemSeparator } from "#/components/ui/item.tsx";
|
|
4
|
+
import { cn } from "#/lib/utils.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Semantic `ul`/`li` counterparts of `ItemGroup`/`Item`: same styling and
|
|
8
|
+
* variants, but screen readers announce the collection and its size. The
|
|
9
|
+
* explicit `role="list"` restores list semantics that Safari/VoiceOver drop
|
|
10
|
+
* once `list-style: none` is applied.
|
|
11
|
+
*/
|
|
12
|
+
function List({
|
|
13
|
+
className,
|
|
14
|
+
...props
|
|
15
|
+
}: Omit<React.ComponentProps<typeof ItemGroup>, "render">) {
|
|
16
|
+
return (
|
|
17
|
+
<ItemGroup
|
|
18
|
+
data-slot="list"
|
|
19
|
+
render={
|
|
20
|
+
// biome-ignore lint/a11y/noRedundantRoles: Safari/VoiceOver drop the implicit list role once list-style is none; the explicit role restores it.
|
|
21
|
+
<ul role="list" />
|
|
22
|
+
}
|
|
23
|
+
className={cn("list-none", className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ListItem(props: Omit<React.ComponentProps<typeof Item>, "render">) {
|
|
30
|
+
return <Item data-slot="list-item" render={<li />} {...props} />;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A separator between list items, hidden from the accessibility tree so it
|
|
34
|
+
* does not count as an item and the `ul` only contains `li` children. */
|
|
35
|
+
function ListSeparator({
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentProps<typeof ItemSeparator>) {
|
|
39
|
+
return (
|
|
40
|
+
<li role="presentation" aria-hidden="true" data-slot="list-separator">
|
|
41
|
+
<ItemSeparator className={className} {...props} />
|
|
42
|
+
</li>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { List, ListItem, ListSeparator };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { cva } from "#/lib/cva.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared menu recipe consumed by dropdown-menu, context-menu, menubar (via
|
|
5
|
+
* dropdown-menu), combobox and command, so their popups and items stay
|
|
6
|
+
* visually identical instead of each restating — and drifting from — the same
|
|
7
|
+
* class strings.
|
|
8
|
+
*
|
|
9
|
+
* The underlying primitives expose their active/disabled state differently:
|
|
10
|
+
* Base UI menus focus the item (`:focus`), Base UI combobox highlights it
|
|
11
|
+
* (`data-highlighted`), and cmdk selects it (`data-selected="true"`). cmdk
|
|
12
|
+
* also always renders `data-disabled="true"|"false"` while Base UI only adds
|
|
13
|
+
* a bare `data-disabled` when actually disabled. Item-level recipes therefore
|
|
14
|
+
* style every state selector at once — only the one matching the rendering
|
|
15
|
+
* primitive ever activates — and gate disabled styles behind
|
|
16
|
+
* `not-data-[disabled=false]` so cmdk's enabled items are unaffected.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const menuItemBase =
|
|
20
|
+
"group/menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none data-inset:pl-7 data-disabled:not-data-[disabled=false]:pointer-events-none data-disabled:not-data-[disabled=false]:opacity-50 focus:bg-accent focus:text-accent-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 focus:*:[svg]:text-accent-foreground data-highlighted:*:[svg]:text-accent-foreground data-[selected=true]:*:[svg]:text-accent-foreground";
|
|
21
|
+
|
|
22
|
+
/** Popup container for floating menus (dropdown, context, combobox). */
|
|
23
|
+
export const menuContentVariants = cva({
|
|
24
|
+
base: "z-50 max-h-(--available-height) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-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:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Menu row. `indicator: "end"` reserves right-side space (`pr-8`) for the
|
|
29
|
+
* kit-consistent end-aligned check/dot indicator on selectable rows.
|
|
30
|
+
*/
|
|
31
|
+
export const menuItemVariants = cva({
|
|
32
|
+
base: menuItemBase,
|
|
33
|
+
variants: {
|
|
34
|
+
variant: {
|
|
35
|
+
default: "",
|
|
36
|
+
destructive:
|
|
37
|
+
"text-destructive *:[svg]:text-destructive focus:bg-destructive/10 focus:text-destructive data-highlighted:bg-destructive/10 data-highlighted:text-destructive data-[selected=true]:bg-destructive/10 data-[selected=true]:text-destructive dark:focus:bg-destructive/20 dark:data-highlighted:bg-destructive/20 dark:data-[selected=true]:bg-destructive/20",
|
|
38
|
+
},
|
|
39
|
+
indicator: {
|
|
40
|
+
none: "",
|
|
41
|
+
end: "pr-8",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
defaultVariants: {
|
|
45
|
+
variant: "default",
|
|
46
|
+
indicator: "none",
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** Submenu trigger: a menu row that also lights up while its popup is open. */
|
|
51
|
+
export const menuSubTriggerVariants = cva({
|
|
52
|
+
base: `${menuItemBase} data-popup-open:bg-accent data-popup-open:text-accent-foreground`,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/** End-aligned wrapper for the check/dot indicator of selectable rows. */
|
|
56
|
+
export const menuIndicatorVariants = cva({
|
|
57
|
+
base: "pointer-events-none absolute right-2 flex size-4 items-center justify-center",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const menuLabelVariants = cva({
|
|
61
|
+
base: "px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const menuSeparatorVariants = cva({
|
|
65
|
+
base: "-mx-1 my-1 h-px bg-border",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/** Right-aligned shortcut hint; recolors with the active menu row. */
|
|
69
|
+
export const menuShortcutVariants = cva({
|
|
70
|
+
base: "ml-auto text-xs tracking-widest text-muted-foreground group-focus/menu-item:text-accent-foreground group-data-highlighted/menu-item:text-accent-foreground group-data-[selected=true]/menu-item:text-accent-foreground",
|
|
71
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar";
|
|
2
|
+
import type * as React from "react";
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuCheckboxItem,
|
|
6
|
+
DropdownMenuContent,
|
|
7
|
+
DropdownMenuGroup,
|
|
8
|
+
DropdownMenuItem,
|
|
9
|
+
DropdownMenuLabel,
|
|
10
|
+
DropdownMenuPortal,
|
|
11
|
+
DropdownMenuRadioGroup,
|
|
12
|
+
DropdownMenuRadioItem,
|
|
13
|
+
DropdownMenuSeparator,
|
|
14
|
+
DropdownMenuShortcut,
|
|
15
|
+
DropdownMenuSub,
|
|
16
|
+
DropdownMenuSubContent,
|
|
17
|
+
DropdownMenuSubTrigger,
|
|
18
|
+
DropdownMenuTrigger,
|
|
19
|
+
} from "#/components/ui/dropdown-menu.tsx";
|
|
20
|
+
import { cn } from "#/lib/utils.ts";
|
|
21
|
+
|
|
22
|
+
function Menubar({ className, ...props }: MenubarPrimitive.Props) {
|
|
23
|
+
return (
|
|
24
|
+
<MenubarPrimitive
|
|
25
|
+
data-slot="menubar"
|
|
26
|
+
className={cn(
|
|
27
|
+
"flex h-8 w-fit items-center gap-0.5 rounded-lg border p-[3px]",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function MenubarMenu({ ...props }: React.ComponentProps<typeof DropdownMenu>) {
|
|
36
|
+
return <DropdownMenu data-slot="menubar-menu" {...props} />;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function MenubarGroup({
|
|
40
|
+
...props
|
|
41
|
+
}: React.ComponentProps<typeof DropdownMenuGroup>) {
|
|
42
|
+
return <DropdownMenuGroup data-slot="menubar-group" {...props} />;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function MenubarPortal({
|
|
46
|
+
...props
|
|
47
|
+
}: React.ComponentProps<typeof DropdownMenuPortal>) {
|
|
48
|
+
return <DropdownMenuPortal data-slot="menubar-portal" {...props} />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function MenubarTrigger({
|
|
52
|
+
className,
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<typeof DropdownMenuTrigger>) {
|
|
55
|
+
return (
|
|
56
|
+
<DropdownMenuTrigger
|
|
57
|
+
data-slot="menubar-trigger"
|
|
58
|
+
className={cn(
|
|
59
|
+
"flex items-center rounded-sm px-1.5 py-[2px] text-sm font-medium outline-hidden select-none hover:bg-muted aria-expanded:bg-muted",
|
|
60
|
+
className,
|
|
61
|
+
)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function MenubarContent({
|
|
68
|
+
className,
|
|
69
|
+
align = "start",
|
|
70
|
+
alignOffset = -4,
|
|
71
|
+
sideOffset = 8,
|
|
72
|
+
...props
|
|
73
|
+
}: React.ComponentProps<typeof DropdownMenuContent>) {
|
|
74
|
+
return (
|
|
75
|
+
<DropdownMenuContent
|
|
76
|
+
data-slot="menubar-content"
|
|
77
|
+
align={align}
|
|
78
|
+
alignOffset={alignOffset}
|
|
79
|
+
sideOffset={sideOffset}
|
|
80
|
+
className={cn("min-w-36", className)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function MenubarItem({
|
|
87
|
+
className,
|
|
88
|
+
...props
|
|
89
|
+
}: React.ComponentProps<typeof DropdownMenuItem>) {
|
|
90
|
+
return (
|
|
91
|
+
<DropdownMenuItem
|
|
92
|
+
data-slot="menubar-item"
|
|
93
|
+
className={className}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function MenubarCheckboxItem({
|
|
100
|
+
className,
|
|
101
|
+
...props
|
|
102
|
+
}: React.ComponentProps<typeof DropdownMenuCheckboxItem>) {
|
|
103
|
+
return (
|
|
104
|
+
<DropdownMenuCheckboxItem
|
|
105
|
+
data-slot="menubar-checkbox-item"
|
|
106
|
+
className={className}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function MenubarRadioGroup({
|
|
113
|
+
...props
|
|
114
|
+
}: React.ComponentProps<typeof DropdownMenuRadioGroup>) {
|
|
115
|
+
return <DropdownMenuRadioGroup data-slot="menubar-radio-group" {...props} />;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function MenubarRadioItem({
|
|
119
|
+
className,
|
|
120
|
+
...props
|
|
121
|
+
}: React.ComponentProps<typeof DropdownMenuRadioItem>) {
|
|
122
|
+
return (
|
|
123
|
+
<DropdownMenuRadioItem
|
|
124
|
+
data-slot="menubar-radio-item"
|
|
125
|
+
className={className}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function MenubarLabel({
|
|
132
|
+
className,
|
|
133
|
+
inset,
|
|
134
|
+
...props
|
|
135
|
+
}: React.ComponentProps<typeof DropdownMenuLabel> & {
|
|
136
|
+
inset?: boolean;
|
|
137
|
+
}) {
|
|
138
|
+
return (
|
|
139
|
+
<DropdownMenuLabel
|
|
140
|
+
data-slot="menubar-label"
|
|
141
|
+
data-inset={inset}
|
|
142
|
+
className={cn(
|
|
143
|
+
"px-1.5 py-1 text-sm font-medium data-inset:pl-7",
|
|
144
|
+
className,
|
|
145
|
+
)}
|
|
146
|
+
{...props}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function MenubarSeparator({
|
|
152
|
+
className,
|
|
153
|
+
...props
|
|
154
|
+
}: React.ComponentProps<typeof DropdownMenuSeparator>) {
|
|
155
|
+
return (
|
|
156
|
+
<DropdownMenuSeparator
|
|
157
|
+
data-slot="menubar-separator"
|
|
158
|
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
159
|
+
{...props}
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function MenubarShortcut({
|
|
165
|
+
className,
|
|
166
|
+
...props
|
|
167
|
+
}: React.ComponentProps<typeof DropdownMenuShortcut>) {
|
|
168
|
+
return (
|
|
169
|
+
<DropdownMenuShortcut
|
|
170
|
+
data-slot="menubar-shortcut"
|
|
171
|
+
className={className}
|
|
172
|
+
{...props}
|
|
173
|
+
/>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function MenubarSub({
|
|
178
|
+
...props
|
|
179
|
+
}: React.ComponentProps<typeof DropdownMenuSub>) {
|
|
180
|
+
return <DropdownMenuSub data-slot="menubar-sub" {...props} />;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function MenubarSubTrigger({
|
|
184
|
+
className,
|
|
185
|
+
...props
|
|
186
|
+
}: React.ComponentProps<typeof DropdownMenuSubTrigger>) {
|
|
187
|
+
return (
|
|
188
|
+
<DropdownMenuSubTrigger
|
|
189
|
+
data-slot="menubar-sub-trigger"
|
|
190
|
+
className={className}
|
|
191
|
+
{...props}
|
|
192
|
+
/>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function MenubarSubContent({
|
|
197
|
+
className,
|
|
198
|
+
...props
|
|
199
|
+
}: React.ComponentProps<typeof DropdownMenuSubContent>) {
|
|
200
|
+
return (
|
|
201
|
+
<DropdownMenuSubContent
|
|
202
|
+
data-slot="menubar-sub-content"
|
|
203
|
+
className={cn("min-w-32", className)}
|
|
204
|
+
{...props}
|
|
205
|
+
/>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export {
|
|
210
|
+
Menubar,
|
|
211
|
+
MenubarCheckboxItem,
|
|
212
|
+
MenubarContent,
|
|
213
|
+
MenubarGroup,
|
|
214
|
+
MenubarItem,
|
|
215
|
+
MenubarLabel,
|
|
216
|
+
MenubarMenu,
|
|
217
|
+
MenubarPortal,
|
|
218
|
+
MenubarRadioGroup,
|
|
219
|
+
MenubarRadioItem,
|
|
220
|
+
MenubarSeparator,
|
|
221
|
+
MenubarShortcut,
|
|
222
|
+
MenubarSub,
|
|
223
|
+
MenubarSubContent,
|
|
224
|
+
MenubarSubTrigger,
|
|
225
|
+
MenubarTrigger,
|
|
226
|
+
};
|