@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,266 @@
1
+ import {
2
+ CaretDownIcon,
3
+ CaretLeftIcon,
4
+ CaretRightIcon,
5
+ } from "@phosphor-icons/react";
6
+ import * as React from "react";
7
+ import {
8
+ type DayButton,
9
+ DayPicker,
10
+ getDefaultClassNames,
11
+ } from "react-day-picker";
12
+ import { Button, buttonVariants } from "#/components/ui/button.tsx";
13
+ import { cn } from "#/lib/utils.ts";
14
+
15
+ // DayPicker's props are a union over `mode` (single | range | multiple); omitting a
16
+ // key must distribute so each member keeps its own `selected`/`onSelect` shape.
17
+ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
18
+ ? Omit<T, K>
19
+ : never;
20
+
21
+ /**
22
+ * First day of the week for a BCP-47 locale (react-day-picker's `weekStartsOn`,
23
+ * 0 = Sunday), read from the native `Intl.Locale` week info: `en-US` → Sunday,
24
+ * `fr-FR` → Monday. Returns undefined (react-day-picker's default) when the
25
+ * runtime lacks the API or the locale is omitted.
26
+ */
27
+ function weekStartFromLocale(
28
+ locale: string | undefined,
29
+ ): 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined {
30
+ if (!locale) return undefined;
31
+ try {
32
+ const intlLocale = new Intl.Locale(locale) as Intl.Locale & {
33
+ getWeekInfo?: () => { firstDay: number };
34
+ weekInfo?: { firstDay: number };
35
+ };
36
+ const info = intlLocale.getWeekInfo?.() ?? intlLocale.weekInfo;
37
+ // Intl reports 1 = Monday … 7 = Sunday; map Sunday (7) to 0 for the picker.
38
+ if (info) return (info.firstDay % 7) as 0 | 1 | 2 | 3 | 4 | 5 | 6;
39
+ } catch {
40
+ // Older runtimes lack Intl week info; fall back to the picker default.
41
+ }
42
+ return undefined;
43
+ }
44
+
45
+ function Calendar({
46
+ className,
47
+ classNames,
48
+ showOutsideDays = true,
49
+ captionLayout = "label",
50
+ navButtonVariant = "ghost",
51
+ locale,
52
+ weekStartsOn,
53
+ formatters,
54
+ components,
55
+ ...props
56
+ }: DistributiveOmit<React.ComponentProps<typeof DayPicker>, "locale"> & {
57
+ navButtonVariant?: React.ComponentProps<typeof Button>["variant"];
58
+ /** BCP-47 locale (e.g. "fr-FR") applied to the month, weekday, and day labels. */
59
+ locale?: string;
60
+ }) {
61
+ const defaultClassNames = getDefaultClassNames();
62
+
63
+ return (
64
+ <DayPicker
65
+ showOutsideDays={showOutsideDays}
66
+ className={cn(
67
+ "group/calendar bg-background p-2 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
68
+ String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
69
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
70
+ className,
71
+ )}
72
+ captionLayout={captionLayout}
73
+ weekStartsOn={weekStartsOn ?? weekStartFromLocale(locale)}
74
+ formatters={{
75
+ // Localize every visible label through the native Intl formatters so the
76
+ // calendar carries no date-fns locale object.
77
+ formatCaption: (date) =>
78
+ new Intl.DateTimeFormat(locale, {
79
+ month: "long",
80
+ year: "numeric",
81
+ }).format(date),
82
+ formatMonthDropdown: (date) =>
83
+ new Intl.DateTimeFormat(locale, { month: "short" }).format(date),
84
+ formatYearDropdown: (date) =>
85
+ new Intl.DateTimeFormat(locale, { year: "numeric" }).format(date),
86
+ formatWeekdayName: (date) =>
87
+ new Intl.DateTimeFormat(locale, { weekday: "short" }).format(date),
88
+ ...formatters,
89
+ }}
90
+ classNames={{
91
+ root: cn("w-fit", defaultClassNames.root),
92
+ months: cn(
93
+ "relative flex flex-col gap-4 md:flex-row",
94
+ defaultClassNames.months,
95
+ ),
96
+ month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
97
+ nav: cn(
98
+ "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
99
+ defaultClassNames.nav,
100
+ ),
101
+ button_previous: cn(
102
+ buttonVariants({ variant: navButtonVariant }),
103
+ "size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
104
+ defaultClassNames.button_previous,
105
+ ),
106
+ button_next: cn(
107
+ buttonVariants({ variant: navButtonVariant }),
108
+ "size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
109
+ defaultClassNames.button_next,
110
+ ),
111
+ month_caption: cn(
112
+ "flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
113
+ defaultClassNames.month_caption,
114
+ ),
115
+ dropdowns: cn(
116
+ "flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
117
+ defaultClassNames.dropdowns,
118
+ ),
119
+ dropdown_root: cn(
120
+ "relative rounded-(--cell-radius)",
121
+ defaultClassNames.dropdown_root,
122
+ ),
123
+ dropdown: cn(
124
+ "absolute inset-0 bg-popover opacity-0",
125
+ defaultClassNames.dropdown,
126
+ ),
127
+ caption_label: cn(
128
+ "font-medium select-none",
129
+ captionLayout === "label"
130
+ ? "text-sm"
131
+ : "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
132
+ defaultClassNames.caption_label,
133
+ ),
134
+ month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
135
+ weekdays: cn("flex", defaultClassNames.weekdays),
136
+ // text-[0.8rem]: shared with Button `sm` (button-variants.ts); keep in sync.
137
+ weekday: cn(
138
+ "flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none",
139
+ defaultClassNames.weekday,
140
+ ),
141
+ week: cn("mt-2 flex w-full", defaultClassNames.week),
142
+ week_number_header: cn(
143
+ "w-(--cell-size) select-none",
144
+ defaultClassNames.week_number_header,
145
+ ),
146
+ week_number: cn(
147
+ "text-[0.8rem] text-muted-foreground select-none",
148
+ defaultClassNames.week_number,
149
+ ),
150
+ day: cn(
151
+ "group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
152
+ props.showWeekNumber
153
+ ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)"
154
+ : "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",
155
+ defaultClassNames.day,
156
+ ),
157
+ range_start: cn(
158
+ "relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",
159
+ defaultClassNames.range_start,
160
+ ),
161
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
162
+ range_end: cn(
163
+ "relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted",
164
+ defaultClassNames.range_end,
165
+ ),
166
+ today: cn(
167
+ "rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
168
+ defaultClassNames.today,
169
+ ),
170
+ outside: cn(
171
+ "text-muted-foreground aria-selected:text-muted-foreground",
172
+ defaultClassNames.outside,
173
+ ),
174
+ disabled: cn(
175
+ "text-muted-foreground opacity-50",
176
+ defaultClassNames.disabled,
177
+ ),
178
+ hidden: cn("invisible", defaultClassNames.hidden),
179
+ ...classNames,
180
+ }}
181
+ components={{
182
+ Root: ({ className, rootRef, ...props }) => {
183
+ return (
184
+ <div
185
+ data-slot="calendar"
186
+ ref={rootRef}
187
+ className={className}
188
+ {...props}
189
+ />
190
+ );
191
+ },
192
+ Chevron: ({ className, orientation, ...props }) => {
193
+ if (orientation === "left") {
194
+ return (
195
+ <CaretLeftIcon className={cn("size-4", className)} {...props} />
196
+ );
197
+ }
198
+
199
+ if (orientation === "right") {
200
+ return (
201
+ <CaretRightIcon className={cn("size-4", className)} {...props} />
202
+ );
203
+ }
204
+
205
+ return (
206
+ <CaretDownIcon className={cn("size-4", className)} {...props} />
207
+ );
208
+ },
209
+ DayButton: ({ ...props }) => (
210
+ <CalendarDayButton locale={locale} {...props} />
211
+ ),
212
+ WeekNumber: ({ children, ...props }) => {
213
+ return (
214
+ <td {...props}>
215
+ <div className="flex size-(--cell-size) items-center justify-center text-center">
216
+ {children}
217
+ </div>
218
+ </td>
219
+ );
220
+ },
221
+ ...components,
222
+ }}
223
+ {...props}
224
+ />
225
+ );
226
+ }
227
+
228
+ function CalendarDayButton({
229
+ className,
230
+ day,
231
+ modifiers,
232
+ locale,
233
+ ...props
234
+ }: React.ComponentProps<typeof DayButton> & { locale?: string }) {
235
+ const defaultClassNames = getDefaultClassNames();
236
+
237
+ const ref = React.useRef<HTMLButtonElement>(null);
238
+ React.useEffect(() => {
239
+ if (modifiers.focused) ref.current?.focus();
240
+ }, [modifiers.focused]);
241
+
242
+ return (
243
+ <Button
244
+ variant="ghost"
245
+ size="icon"
246
+ data-day={day.date.toLocaleDateString(locale)}
247
+ data-selected-single={
248
+ modifiers.selected &&
249
+ !modifiers.range_start &&
250
+ !modifiers.range_end &&
251
+ !modifiers.range_middle
252
+ }
253
+ data-range-start={modifiers.range_start}
254
+ data-range-end={modifiers.range_end}
255
+ data-range-middle={modifiers.range_middle}
256
+ className={cn(
257
+ "relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-3 group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70",
258
+ defaultClassNames.day,
259
+ className,
260
+ )}
261
+ {...props}
262
+ />
263
+ );
264
+ }
265
+
266
+ export { Calendar, CalendarDayButton };
@@ -0,0 +1,155 @@
1
+ import { mergeProps } from "@base-ui/react/merge-props";
2
+ import { useRender } from "@base-ui/react/use-render";
3
+
4
+ import { cn } from "#/lib/utils.ts";
5
+
6
+ type CardSize = "default" | "sm";
7
+
8
+ function Card({
9
+ className,
10
+ size = "default",
11
+ render,
12
+ ...props
13
+ }: useRender.ComponentProps<"div"> & { size?: CardSize }) {
14
+ return useRender({
15
+ defaultTagName: "div",
16
+ props: mergeProps<"div">(
17
+ {
18
+ className: cn(
19
+ "group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
20
+ className,
21
+ ),
22
+ },
23
+ props,
24
+ ),
25
+ render,
26
+ state: { slot: "card", size },
27
+ });
28
+ }
29
+
30
+ function CardHeader({
31
+ className,
32
+ render,
33
+ ...props
34
+ }: useRender.ComponentProps<"div">) {
35
+ return useRender({
36
+ defaultTagName: "div",
37
+ props: mergeProps<"div">(
38
+ {
39
+ className: cn(
40
+ "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
41
+ className,
42
+ ),
43
+ },
44
+ props,
45
+ ),
46
+ render,
47
+ state: { slot: "card-header" },
48
+ });
49
+ }
50
+
51
+ function CardTitle({
52
+ className,
53
+ render,
54
+ ...props
55
+ }: useRender.ComponentProps<"div">) {
56
+ return useRender({
57
+ defaultTagName: "div",
58
+ props: mergeProps<"div">(
59
+ {
60
+ className: cn(
61
+ "text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
62
+ className,
63
+ ),
64
+ },
65
+ props,
66
+ ),
67
+ render,
68
+ state: { slot: "card-title" },
69
+ });
70
+ }
71
+
72
+ function CardDescription({
73
+ className,
74
+ render,
75
+ ...props
76
+ }: useRender.ComponentProps<"div">) {
77
+ return useRender({
78
+ defaultTagName: "div",
79
+ props: mergeProps<"div">(
80
+ { className: cn("text-sm text-muted-foreground", className) },
81
+ props,
82
+ ),
83
+ render,
84
+ state: { slot: "card-description" },
85
+ });
86
+ }
87
+
88
+ function CardAction({
89
+ className,
90
+ render,
91
+ ...props
92
+ }: useRender.ComponentProps<"div">) {
93
+ return useRender({
94
+ defaultTagName: "div",
95
+ props: mergeProps<"div">(
96
+ {
97
+ className: cn(
98
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
99
+ className,
100
+ ),
101
+ },
102
+ props,
103
+ ),
104
+ render,
105
+ state: { slot: "card-action" },
106
+ });
107
+ }
108
+
109
+ function CardContent({
110
+ className,
111
+ render,
112
+ ...props
113
+ }: useRender.ComponentProps<"div">) {
114
+ return useRender({
115
+ defaultTagName: "div",
116
+ props: mergeProps<"div">(
117
+ { className: cn("px-4 group-data-[size=sm]/card:px-3", className) },
118
+ props,
119
+ ),
120
+ render,
121
+ state: { slot: "card-content" },
122
+ });
123
+ }
124
+
125
+ function CardFooter({
126
+ className,
127
+ render,
128
+ ...props
129
+ }: useRender.ComponentProps<"div">) {
130
+ return useRender({
131
+ defaultTagName: "div",
132
+ props: mergeProps<"div">(
133
+ {
134
+ className: cn(
135
+ "flex items-center rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/card:p-3",
136
+ className,
137
+ ),
138
+ },
139
+ props,
140
+ ),
141
+ render,
142
+ state: { slot: "card-footer" },
143
+ });
144
+ }
145
+
146
+ export {
147
+ Card,
148
+ CardAction,
149
+ CardContent,
150
+ CardDescription,
151
+ CardFooter,
152
+ CardHeader,
153
+ type CardSize,
154
+ CardTitle,
155
+ };