@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,225 @@
1
+ import * as React from "react";
2
+ import {
3
+ Drawer,
4
+ DrawerContent,
5
+ DrawerDescription,
6
+ DrawerFooter,
7
+ DrawerHeader,
8
+ DrawerTitle,
9
+ DrawerTrigger,
10
+ } from "#/components/ui/drawer.tsx";
11
+ import {
12
+ Sheet,
13
+ SheetContent,
14
+ type SheetContentSize,
15
+ SheetDescription,
16
+ SheetFooter,
17
+ SheetHeader,
18
+ SheetTitle,
19
+ SheetTrigger,
20
+ } from "#/components/ui/sheet.tsx";
21
+ import { useIsMobile } from "#/hooks/use-mobile.ts";
22
+ import { cn } from "#/lib/utils.ts";
23
+
24
+ // One API, two surfaces: a side sheet on desktop and a vaul bottom drawer under
25
+ // the `useIsMobile` breakpoint (768px) — the same split `ResponsiveDialog` makes
26
+ // for centered dialogs. The root owns the open state so it survives crossing the
27
+ // breakpoint while open: the two halves are different component trees, so
28
+ // uncontrolled state would reset.
29
+ //
30
+ // The halves disagree on composition (Base UI `render` vs Radix `asChild`); the
31
+ // trigger bridges that by accepting the element form of `render` only.
32
+
33
+ const ResponsiveSheetContext = React.createContext<boolean | null>(null);
34
+
35
+ function useResponsiveSheetIsMobile(part: string): boolean {
36
+ const isMobile = React.use(ResponsiveSheetContext);
37
+ if (isMobile === null) {
38
+ throw new Error(`${part} must be used within <ResponsiveSheet>`);
39
+ }
40
+ return isMobile;
41
+ }
42
+
43
+ function ResponsiveSheet({
44
+ open,
45
+ defaultOpen = false,
46
+ onOpenChange,
47
+ children,
48
+ }: {
49
+ open?: boolean;
50
+ defaultOpen?: boolean;
51
+ onOpenChange?: (open: boolean) => void;
52
+ children?: React.ReactNode;
53
+ }) {
54
+ const isMobile = useIsMobile();
55
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
56
+ const isOpen = open ?? uncontrolledOpen;
57
+ const handleOpenChange = (nextOpen: boolean) => {
58
+ setUncontrolledOpen(nextOpen);
59
+ onOpenChange?.(nextOpen);
60
+ };
61
+
62
+ return (
63
+ <ResponsiveSheetContext.Provider value={isMobile}>
64
+ {isMobile ? (
65
+ <Drawer open={isOpen} onOpenChange={handleOpenChange}>
66
+ {children}
67
+ </Drawer>
68
+ ) : (
69
+ <Sheet open={isOpen} onOpenChange={handleOpenChange}>
70
+ {children}
71
+ </Sheet>
72
+ )}
73
+ </ResponsiveSheetContext.Provider>
74
+ );
75
+ }
76
+
77
+ function ResponsiveSheetTrigger({
78
+ render,
79
+ children,
80
+ ...props
81
+ }: Omit<React.ComponentProps<typeof DrawerTrigger>, "asChild"> & {
82
+ /** Element form only — the drawer half clones it, so render functions are unsupported. */
83
+ render?: React.ReactElement;
84
+ }) {
85
+ const isMobile = useResponsiveSheetIsMobile("ResponsiveSheetTrigger");
86
+ if (isMobile) {
87
+ if (render) {
88
+ return (
89
+ <DrawerTrigger asChild {...props}>
90
+ {children === undefined
91
+ ? render
92
+ : React.cloneElement(render, undefined, children)}
93
+ </DrawerTrigger>
94
+ );
95
+ }
96
+ return <DrawerTrigger {...props}>{children}</DrawerTrigger>;
97
+ }
98
+ return (
99
+ <SheetTrigger render={render} {...props}>
100
+ {children}
101
+ </SheetTrigger>
102
+ );
103
+ }
104
+
105
+ function ResponsiveSheetContent({
106
+ className,
107
+ children,
108
+ size = "default",
109
+ showCloseButton = true,
110
+ closeButtonLabel = "Close",
111
+ }: {
112
+ className?: string;
113
+ children?: React.ReactNode;
114
+ /** Desktop panel width — the mobile drawer is always full-width. */
115
+ size?: SheetContentSize;
116
+ showCloseButton?: boolean;
117
+ closeButtonLabel?: string;
118
+ }) {
119
+ const isMobile = useResponsiveSheetIsMobile("ResponsiveSheetContent");
120
+ if (isMobile) {
121
+ return (
122
+ <DrawerContent
123
+ className={className}
124
+ showCloseButton={showCloseButton}
125
+ closeButtonLabel={closeButtonLabel}
126
+ >
127
+ {children}
128
+ </DrawerContent>
129
+ );
130
+ }
131
+ return (
132
+ <SheetContent
133
+ className={className}
134
+ size={size}
135
+ showCloseButton={showCloseButton}
136
+ closeButtonLabel={closeButtonLabel}
137
+ >
138
+ {children}
139
+ </SheetContent>
140
+ );
141
+ }
142
+
143
+ function ResponsiveSheetHeader({
144
+ className,
145
+ ...props
146
+ }: React.ComponentProps<"div">) {
147
+ const isMobile = useResponsiveSheetIsMobile("ResponsiveSheetHeader");
148
+ return isMobile ? (
149
+ // The bottom drawer centers its header by default; a detail panel reads
150
+ // the same on both surfaces only if the mobile half stays left-aligned.
151
+ <DrawerHeader
152
+ className={cn(
153
+ "group-data-[vaul-drawer-direction=bottom]/drawer-content:text-left",
154
+ className,
155
+ )}
156
+ {...props}
157
+ />
158
+ ) : (
159
+ <SheetHeader className={className} {...props} />
160
+ );
161
+ }
162
+
163
+ /** The scrolling region between header and footer, gutters included. */
164
+ function ResponsiveSheetBody({
165
+ className,
166
+ ...props
167
+ }: React.ComponentProps<"div">) {
168
+ return (
169
+ <div
170
+ data-slot="responsive-sheet-body"
171
+ className={cn(
172
+ "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto px-4 pb-4",
173
+ className,
174
+ )}
175
+ {...props}
176
+ />
177
+ );
178
+ }
179
+
180
+ function ResponsiveSheetFooter({
181
+ className,
182
+ ...props
183
+ }: React.ComponentProps<"div">) {
184
+ const isMobile = useResponsiveSheetIsMobile("ResponsiveSheetFooter");
185
+ return isMobile ? (
186
+ <DrawerFooter className={className} {...props} />
187
+ ) : (
188
+ <SheetFooter className={className} {...props} />
189
+ );
190
+ }
191
+
192
+ function ResponsiveSheetTitle({
193
+ className,
194
+ ...props
195
+ }: React.ComponentProps<typeof DrawerTitle>) {
196
+ const isMobile = useResponsiveSheetIsMobile("ResponsiveSheetTitle");
197
+ return isMobile ? (
198
+ <DrawerTitle className={className} {...props} />
199
+ ) : (
200
+ <SheetTitle className={className} {...props} />
201
+ );
202
+ }
203
+
204
+ function ResponsiveSheetDescription({
205
+ className,
206
+ ...props
207
+ }: React.ComponentProps<typeof DrawerDescription>) {
208
+ const isMobile = useResponsiveSheetIsMobile("ResponsiveSheetDescription");
209
+ return isMobile ? (
210
+ <DrawerDescription className={className} {...props} />
211
+ ) : (
212
+ <SheetDescription className={className} {...props} />
213
+ );
214
+ }
215
+
216
+ export {
217
+ ResponsiveSheet,
218
+ ResponsiveSheetBody,
219
+ ResponsiveSheetContent,
220
+ ResponsiveSheetDescription,
221
+ ResponsiveSheetFooter,
222
+ ResponsiveSheetHeader,
223
+ ResponsiveSheetTitle,
224
+ ResponsiveSheetTrigger,
225
+ };
@@ -0,0 +1,51 @@
1
+ import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
2
+
3
+ import { cn } from "#/lib/utils.ts";
4
+
5
+ function ScrollArea({
6
+ className,
7
+ children,
8
+ ...props
9
+ }: ScrollAreaPrimitive.Root.Props) {
10
+ return (
11
+ <ScrollAreaPrimitive.Root
12
+ data-slot="scroll-area"
13
+ className={cn("relative", className)}
14
+ {...props}
15
+ >
16
+ <ScrollAreaPrimitive.Viewport
17
+ data-slot="scroll-area-viewport"
18
+ className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
19
+ >
20
+ {children}
21
+ </ScrollAreaPrimitive.Viewport>
22
+ <ScrollBar />
23
+ <ScrollAreaPrimitive.Corner />
24
+ </ScrollAreaPrimitive.Root>
25
+ );
26
+ }
27
+
28
+ function ScrollBar({
29
+ className,
30
+ orientation = "vertical",
31
+ ...props
32
+ }: ScrollAreaPrimitive.Scrollbar.Props) {
33
+ return (
34
+ <ScrollAreaPrimitive.Scrollbar
35
+ data-slot="scroll-area-scrollbar"
36
+ orientation={orientation}
37
+ className={cn(
38
+ "flex touch-none p-px transition-colors select-none data-[orientation=horizontal]:h-2.5 data-[orientation=horizontal]:flex-col data-[orientation=horizontal]:border-t data-[orientation=horizontal]:border-t-transparent data-[orientation=vertical]:h-full data-[orientation=vertical]:w-2.5 data-[orientation=vertical]:border-l data-[orientation=vertical]:border-l-transparent",
39
+ className,
40
+ )}
41
+ {...props}
42
+ >
43
+ <ScrollAreaPrimitive.Thumb
44
+ data-slot="scroll-area-thumb"
45
+ className="relative flex-1 rounded-full bg-border"
46
+ />
47
+ </ScrollAreaPrimitive.Scrollbar>
48
+ );
49
+ }
50
+
51
+ export { ScrollArea, ScrollBar };
@@ -0,0 +1,106 @@
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
+ /**
7
+ * Vertical rhythm wrapper for an in-page section - heading row + content with
8
+ * consistent spacing, the `PageHeader` anatomy one level down. Renders a
9
+ * `section` by default - pass `render` for a plain `div` when the landmark
10
+ * is unwanted.
11
+ */
12
+ function Section({
13
+ className,
14
+ render,
15
+ ...props
16
+ }: useRender.ComponentProps<"section">) {
17
+ return useRender({
18
+ defaultTagName: "section",
19
+ props: mergeProps<"section">(
20
+ {
21
+ className: cn("flex flex-col gap-4", className),
22
+ },
23
+ props,
24
+ ),
25
+ render,
26
+ state: {
27
+ slot: "section",
28
+ },
29
+ });
30
+ }
31
+
32
+ function SectionHeader({ className, ...props }: React.ComponentProps<"div">) {
33
+ return (
34
+ <div
35
+ data-slot="section-header"
36
+ className={cn("flex items-center justify-between gap-4", className)}
37
+ {...props}
38
+ />
39
+ );
40
+ }
41
+
42
+ function SectionHeading({ className, ...props }: React.ComponentProps<"div">) {
43
+ return (
44
+ <div
45
+ data-slot="section-heading"
46
+ className={cn("flex min-w-0 flex-col gap-1", className)}
47
+ {...props}
48
+ />
49
+ );
50
+ }
51
+
52
+ /** Renders an `h2` - pass `render` to fit the page's heading outline. */
53
+ function SectionTitle({
54
+ className,
55
+ render,
56
+ ...props
57
+ }: useRender.ComponentProps<"h2">) {
58
+ return useRender({
59
+ defaultTagName: "h2",
60
+ props: mergeProps<"h2">(
61
+ {
62
+ className: cn("text-lg font-semibold tracking-tight", className),
63
+ },
64
+ props,
65
+ ),
66
+ render,
67
+ state: {
68
+ slot: "section-title",
69
+ },
70
+ });
71
+ }
72
+
73
+ function SectionDescription({
74
+ className,
75
+ ...props
76
+ }: React.ComponentProps<"p">) {
77
+ return (
78
+ <p
79
+ data-slot="section-description"
80
+ className={cn(
81
+ "text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
82
+ className,
83
+ )}
84
+ {...props}
85
+ />
86
+ );
87
+ }
88
+
89
+ function SectionActions({ className, ...props }: React.ComponentProps<"div">) {
90
+ return (
91
+ <div
92
+ data-slot="section-actions"
93
+ className={cn("flex shrink-0 items-center gap-2", className)}
94
+ {...props}
95
+ />
96
+ );
97
+ }
98
+
99
+ export {
100
+ Section,
101
+ SectionActions,
102
+ SectionDescription,
103
+ SectionHeader,
104
+ SectionHeading,
105
+ SectionTitle,
106
+ };
@@ -0,0 +1,148 @@
1
+ import { Radio as RadioPrimitive } from "@base-ui/react/radio";
2
+ import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
3
+ import * as React from "react";
4
+ import { cva, type VariantProps } from "#/lib/cva.ts";
5
+ import { cn } from "#/lib/utils.ts";
6
+
7
+ /**
8
+ * Single-select switcher (Day/Week/Month) with an animated selection thumb.
9
+ * Built on radio semantics - exactly one segment is always selected - which
10
+ * is what separates it from `ToggleGroup`'s pressable toolbar buttons.
11
+ */
12
+ const segmentedControlVariants = cva({
13
+ base: "group/segmented-control relative isolate inline-flex w-fit items-center justify-center rounded-lg bg-muted p-[3px] text-muted-foreground",
14
+ variants: {
15
+ size: {
16
+ default: "h-8",
17
+ sm: "h-7 rounded-[min(var(--radius-md),12px)]",
18
+ lg: "h-9",
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ size: "default",
23
+ },
24
+ });
25
+
26
+ const SegmentedControlContext = React.createContext<
27
+ VariantProps<typeof segmentedControlVariants>
28
+ >({ size: "default" });
29
+
30
+ type SegmentedControlProps = RadioGroupPrimitive.Props &
31
+ VariantProps<typeof segmentedControlVariants>;
32
+
33
+ function SegmentedControl({
34
+ className,
35
+ size = "default",
36
+ children,
37
+ onValueChange,
38
+ ...props
39
+ }: SegmentedControlProps) {
40
+ const rootRef = React.useRef<HTMLDivElement | null>(null);
41
+ const thumbRef = React.useRef<HTMLSpanElement | null>(null);
42
+ const hasMeasuredRef = React.useRef(false);
43
+ // Re-rendering on uncontrolled selection changes keeps the layout effect
44
+ // below in sync with Base UI's internal state (the parent component does
45
+ // not re-render when only the primitive's state moves).
46
+ const [, forceMeasure] = React.useReducer((count: number) => count + 1, 0);
47
+
48
+ const measure = React.useCallback(() => {
49
+ const root = rootRef.current;
50
+ const thumb = thumbRef.current;
51
+ if (!root || !thumb) {
52
+ return;
53
+ }
54
+ const checked = root.querySelector<HTMLElement>(
55
+ "[data-slot='segmented-control-item'][data-checked]",
56
+ );
57
+ if (!checked) {
58
+ thumb.style.opacity = "0";
59
+ return;
60
+ }
61
+ // Skip the slide-in animation on the very first paint - the thumb
62
+ // should appear on the selected segment, not travel from the edge.
63
+ const isFirstMeasure = !hasMeasuredRef.current;
64
+ if (isFirstMeasure) {
65
+ thumb.style.transitionDuration = "0s";
66
+ }
67
+ thumb.style.opacity = "1";
68
+ thumb.style.width = `${checked.offsetWidth}px`;
69
+ thumb.style.transform = `translateX(${checked.offsetLeft}px)`;
70
+ if (isFirstMeasure) {
71
+ hasMeasuredRef.current = true;
72
+ requestAnimationFrame(() => {
73
+ thumb.style.transitionDuration = "";
74
+ });
75
+ }
76
+ }, []);
77
+
78
+ React.useLayoutEffect(() => {
79
+ measure();
80
+ });
81
+
82
+ React.useLayoutEffect(() => {
83
+ const root = rootRef.current;
84
+ if (!root || typeof ResizeObserver === "undefined") {
85
+ return;
86
+ }
87
+ const observer = new ResizeObserver(measure);
88
+ observer.observe(root);
89
+ for (const item of root.querySelectorAll(
90
+ "[data-slot='segmented-control-item']",
91
+ )) {
92
+ observer.observe(item);
93
+ }
94
+ return () => observer.disconnect();
95
+ }, [measure]);
96
+
97
+ return (
98
+ <RadioGroupPrimitive
99
+ ref={rootRef}
100
+ data-slot="segmented-control"
101
+ data-size={size}
102
+ className={cn(segmentedControlVariants({ size }), className)}
103
+ onValueChange={(value, eventDetails) => {
104
+ onValueChange?.(value, eventDetails);
105
+ forceMeasure();
106
+ }}
107
+ {...props}
108
+ >
109
+ <span
110
+ ref={thumbRef}
111
+ aria-hidden
112
+ data-slot="segmented-control-thumb"
113
+ className="absolute inset-y-[3px] left-0 -z-10 rounded-md border border-transparent bg-background opacity-0 shadow-sm transition-[transform,width] duration-200 ease-out group-data-[size=sm]/segmented-control:rounded-[min(var(--radius-sm),10px)] motion-reduce:transition-none dark:border-input dark:bg-input/30"
114
+ />
115
+ <SegmentedControlContext.Provider value={{ size }}>
116
+ {children}
117
+ </SegmentedControlContext.Provider>
118
+ </RadioGroupPrimitive>
119
+ );
120
+ }
121
+
122
+ function SegmentedControlItem({
123
+ className,
124
+ ...props
125
+ }: RadioPrimitive.Root.Props) {
126
+ const { size } = React.useContext(SegmentedControlContext);
127
+
128
+ return (
129
+ <RadioPrimitive.Root
130
+ data-slot="segmented-control-item"
131
+ data-size={size}
132
+ className={cn(
133
+ // The thumb slides behind the items, so items keep a transparent
134
+ // background and only transition their text color.
135
+ "inline-flex h-full min-w-0 items-center justify-center gap-1.5 rounded-md border border-transparent px-2.5 text-sm font-medium whitespace-nowrap text-foreground/60 outline-none transition-colors hover:text-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 data-checked:text-foreground data-disabled:pointer-events-none data-disabled:opacity-50 data-[size=sm]:px-2 data-[size=sm]:text-[0.8rem] dark:text-muted-foreground dark:hover:text-foreground dark:data-checked:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[size=sm]:[&_svg:not([class*='size-'])]:size-3.5",
136
+ className,
137
+ )}
138
+ {...props}
139
+ />
140
+ );
141
+ }
142
+
143
+ export {
144
+ SegmentedControl,
145
+ SegmentedControlItem,
146
+ type SegmentedControlProps,
147
+ segmentedControlVariants,
148
+ };