@spunto/design-system 0.2.1 → 0.4.0

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.
@@ -0,0 +1,45 @@
1
+ import type { ComponentProps } from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "../utils"
5
+
6
+ const spinnerVariants = cva("animate-spin text-current", {
7
+ variants: {
8
+ size: {
9
+ xs: "size-3",
10
+ sm: "size-4",
11
+ default: "size-5",
12
+ lg: "size-6",
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ size: "default",
17
+ },
18
+ })
19
+
20
+ /**
21
+ * An indeterminate loading spinner. Inherits the current text color, so drop it
22
+ * inside a `Button` or set `text-*` to tint it.
23
+ */
24
+ function Spinner({
25
+ className,
26
+ size,
27
+ ...props
28
+ }: ComponentProps<"svg"> & VariantProps<typeof spinnerVariants>) {
29
+ return (
30
+ <svg
31
+ data-slot="spinner"
32
+ role="status"
33
+ aria-label="Chargement"
34
+ viewBox="0 0 24 24"
35
+ fill="none"
36
+ className={cn(spinnerVariants({ size }), className)}
37
+ {...props}
38
+ >
39
+ <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth={2.5} className="opacity-20" />
40
+ <path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" strokeWidth={2.5} strokeLinecap="round" />
41
+ </svg>
42
+ )
43
+ }
44
+
45
+ export { Spinner, spinnerVariants }
@@ -0,0 +1,121 @@
1
+ "use client"
2
+
3
+ import { createContext, useContext, useState, type ReactNode } from "react"
4
+ import { Toast as ToastPrimitive } from "@base-ui/react/toast"
5
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
6
+
7
+ import { ToastList, toastManager, type ToastSwipeDirection } from "./toast"
8
+
9
+ export type ToastPosition =
10
+ | "top-left"
11
+ | "top-center"
12
+ | "top-right"
13
+ | "bottom-left"
14
+ | "bottom-center"
15
+ | "bottom-right"
16
+
17
+ export interface SpuntoProviderProps {
18
+ /** App tree. */
19
+ children?: ReactNode
20
+ /**
21
+ * Where the toast stack docks.
22
+ * @default "top-right"
23
+ */
24
+ toastPosition?: ToastPosition
25
+ /**
26
+ * Default auto-dismiss delay (ms) for toasts that don't set their own.
27
+ * `0` keeps toasts until dismissed.
28
+ * @default 5000
29
+ */
30
+ toastDuration?: number
31
+ /**
32
+ * Maximum number of toasts stacked at once. Older toasts collapse behind the
33
+ * newest and animate out past this count.
34
+ * @default 3
35
+ */
36
+ toastLimit?: number
37
+ /**
38
+ * Delay (ms) before a hovered `Tooltip` opens. Shared across every tooltip via
39
+ * Base UI's delay group, so once one is showing its neighbours open instantly.
40
+ * @default 600
41
+ */
42
+ tooltipDelay?: number
43
+ /**
44
+ * Delay (ms) before a tooltip closes once the pointer leaves.
45
+ * @default 0
46
+ */
47
+ tooltipCloseDelay?: number
48
+ }
49
+
50
+ // Toasts swipe toward the nearest screen edge for the current position.
51
+ const SWIPE_DIRECTIONS: Record<ToastPosition, ToastSwipeDirection[]> = {
52
+ "top-left": ["up", "left"],
53
+ "top-center": ["up"],
54
+ "top-right": ["up", "right"],
55
+ "bottom-left": ["down", "left"],
56
+ "bottom-center": ["down"],
57
+ "bottom-right": ["down", "right"],
58
+ }
59
+
60
+ /**
61
+ * Shared DOM node the overlay components (`Dialog`, `AlertDialog`, `Select`,
62
+ * `Tooltip`) portal into. `SpuntoProvider` renders it as the last child of its
63
+ * subtree so every popup lands *inside* the themed tree — the `.dark` class and
64
+ * the token CSS vars cascade to portalled content for free. When no provider is
65
+ * mounted the value is `null`, and the primitives fall back to Base UI's default
66
+ * (`document.body`), so the components keep working standalone.
67
+ * @internal
68
+ */
69
+ const OverlayContainerContext = createContext<HTMLElement | null>(null)
70
+
71
+ /**
72
+ * Container element the design-system overlays portal into, provided by the
73
+ * nearest `SpuntoProvider` (or `null` → Base UI's `document.body` default).
74
+ * @internal
75
+ */
76
+ export function useOverlayContainer(): HTMLElement | null {
77
+ return useContext(OverlayContainerContext)
78
+ }
79
+
80
+ /**
81
+ * Client-side umbrella provider for the Spunto design system. Wrap your app once
82
+ * (in a Next.js `app/layout.tsx`, it's a client component — render it around
83
+ * `{children}` inside the server layout, no `"use client"` needed on the layout
84
+ * itself). It mounts the toast viewport, the shared tooltip delay group, and the
85
+ * overlay portal container that dialogs/selects render into — the single seam
86
+ * every overlay feature plugs into without a breaking change.
87
+ *
88
+ * ```tsx
89
+ * <SpuntoProvider toastPosition="bottom-right">
90
+ * {children}
91
+ * </SpuntoProvider>
92
+ * ```
93
+ */
94
+ export function SpuntoProvider({
95
+ children,
96
+ toastPosition = "top-right",
97
+ toastDuration = 5000,
98
+ toastLimit = 3,
99
+ tooltipDelay = 600,
100
+ tooltipCloseDelay = 0,
101
+ }: SpuntoProviderProps) {
102
+ const [overlayContainer, setOverlayContainer] = useState<HTMLElement | null>(null)
103
+
104
+ return (
105
+ <ToastPrimitive.Provider toastManager={toastManager} timeout={toastDuration} limit={toastLimit}>
106
+ <TooltipPrimitive.Provider delay={tooltipDelay} closeDelay={tooltipCloseDelay}>
107
+ <OverlayContainerContext.Provider value={overlayContainer}>
108
+ {children}
109
+ <ToastPrimitive.Portal>
110
+ <ToastPrimitive.Viewport data-slot="toast-viewport" data-position={toastPosition}>
111
+ <ToastList swipeDirection={SWIPE_DIRECTIONS[toastPosition]} />
112
+ </ToastPrimitive.Viewport>
113
+ </ToastPrimitive.Portal>
114
+ {/* Overlays (Dialog/Select/Tooltip) portal into this node — kept last so
115
+ popups stack above the app, and inside the provider so tokens cascade. */}
116
+ <div ref={setOverlayContainer} data-slot="spunto-overlay-root" />
117
+ </OverlayContainerContext.Provider>
118
+ </TooltipPrimitive.Provider>
119
+ </ToastPrimitive.Provider>
120
+ )
121
+ }
@@ -0,0 +1,68 @@
1
+ "use client"
2
+
3
+ import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"
4
+
5
+ import { cn } from "../utils"
6
+
7
+ /** Groups the tab parts. Controlled via `value`/`onValueChange`, or uncontrolled. */
8
+ function Tabs({ className, ...props }: TabsPrimitive.Root.Props) {
9
+ return <TabsPrimitive.Root data-slot="tabs" className={cn("flex flex-col gap-3", className)} {...props} />
10
+ }
11
+
12
+ /**
13
+ * The row of tabs. Contains `TabsTab`s and an optional `TabsIndicator`. Styled as
14
+ * a segmented control with a muted track.
15
+ */
16
+ function TabsList({ className, ...props }: TabsPrimitive.List.Props) {
17
+ return (
18
+ <TabsPrimitive.List
19
+ data-slot="tabs-list"
20
+ className={cn(
21
+ "relative isolate flex h-9 w-fit items-center gap-1 rounded-lg bg-muted p-1 text-muted-foreground",
22
+ className
23
+ )}
24
+ {...props}
25
+ />
26
+ )
27
+ }
28
+
29
+ /** The sliding highlight behind the active tab. Optional; place inside `TabsList`. */
30
+ function TabsIndicator({ className, ...props }: TabsPrimitive.Indicator.Props) {
31
+ return (
32
+ <TabsPrimitive.Indicator
33
+ data-slot="tabs-indicator"
34
+ className={cn(
35
+ "absolute top-1/2 left-0 z-[-1] h-[calc(100%-0.5rem)] w-[var(--active-tab-width)] -translate-y-1/2 translate-x-[var(--active-tab-left)] rounded-md bg-background shadow-sm transition-[width,transform] duration-200 ease-out",
36
+ className
37
+ )}
38
+ {...props}
39
+ />
40
+ )
41
+ }
42
+
43
+ /** A single tab button. `value` ties it to the matching `TabsPanel`. */
44
+ function TabsTab({ className, ...props }: TabsPrimitive.Tab.Props) {
45
+ return (
46
+ <TabsPrimitive.Tab
47
+ data-slot="tabs-tab"
48
+ className={cn(
49
+ "inline-flex h-full cursor-default items-center justify-center rounded-md px-3 text-sm font-medium whitespace-nowrap transition-colors outline-none select-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 data-disabled:pointer-events-none data-disabled:opacity-50 data-selected:text-foreground",
50
+ className
51
+ )}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+
57
+ /** The content shown for the tab with the matching `value`. */
58
+ function TabsPanel({ className, ...props }: TabsPrimitive.Panel.Props) {
59
+ return (
60
+ <TabsPrimitive.Panel
61
+ data-slot="tabs-panel"
62
+ className={cn("outline-none focus-visible:ring-2 focus-visible:ring-ring/50", className)}
63
+ {...props}
64
+ />
65
+ )
66
+ }
67
+
68
+ export { Tabs, TabsList, TabsIndicator, TabsTab, TabsPanel }
@@ -0,0 +1,199 @@
1
+ "use client"
2
+
3
+ import type { ReactNode } from "react"
4
+ import { Toast as ToastPrimitive } from "@base-ui/react/toast"
5
+
6
+ import { cn } from "../utils"
7
+
8
+ export type ToastVariant = "success" | "error" | "warning" | "info"
9
+
10
+ /** Optional call-to-action rendered as a button inside the toast. */
11
+ export interface ToastAction {
12
+ label: string
13
+ onClick: () => void
14
+ }
15
+
16
+ export interface ToastOptions {
17
+ /** Overrides the first positional argument as the toast's heading. */
18
+ title?: ReactNode
19
+ /** Secondary line under the title. */
20
+ description?: ReactNode
21
+ /** Colour + icon. Defaults to a neutral toast when omitted. */
22
+ variant?: ToastVariant
23
+ /**
24
+ * Auto-dismiss delay in ms. `0` keeps the toast until dismissed.
25
+ * Falls back to the `SpuntoProvider` `toastDuration` when omitted.
26
+ */
27
+ duration?: number
28
+ /** Optional action button. Clicking it also dismisses the toast. */
29
+ action?: ToastAction
30
+ }
31
+
32
+ /** Payload we stash on each Base UI toast so the renderer can style it. */
33
+ export interface SpuntoToastData {
34
+ variant?: ToastVariant
35
+ action?: ToastAction
36
+ }
37
+
38
+ /**
39
+ * Singleton manager shared by the imperative `toast()` API and the
40
+ * `SpuntoProvider` that renders it. Created with Base UI's `createToastManager`
41
+ * so `toast()` is callable from anywhere — event handlers, async code, even
42
+ * outside the React tree.
43
+ */
44
+ export const toastManager = ToastPrimitive.createToastManager<SpuntoToastData>()
45
+
46
+ function emit(message: ReactNode, options: ToastOptions = {}, variant?: ToastVariant): string {
47
+ const resolved = variant ?? options.variant
48
+ return toastManager.add({
49
+ title: options.title ?? message,
50
+ description: options.description,
51
+ // `undefined` lets the provider's default timeout apply.
52
+ timeout: options.duration,
53
+ type: resolved,
54
+ data: { variant: resolved, action: options.action },
55
+ })
56
+ }
57
+
58
+ export interface ToastFn {
59
+ (message: ReactNode, options?: ToastOptions): string
60
+ success: (message: ReactNode, options?: ToastOptions) => string
61
+ error: (message: ReactNode, options?: ToastOptions) => string
62
+ warning: (message: ReactNode, options?: ToastOptions) => string
63
+ info: (message: ReactNode, options?: ToastOptions) => string
64
+ /** Dismiss a specific toast by id, or all toasts when called without one. */
65
+ dismiss: (id?: string) => void
66
+ }
67
+
68
+ /**
69
+ * Imperative, sonner-style toast API. Import and call from anywhere.
70
+ *
71
+ * ```ts
72
+ * toast.success("Worker ready", { description: "spunto-abc is up" })
73
+ * const id = toast("Deploying…", { duration: 0 })
74
+ * toast.dismiss(id)
75
+ * ```
76
+ */
77
+ export const toast: ToastFn = Object.assign(
78
+ (message: ReactNode, options?: ToastOptions) => emit(message, options),
79
+ {
80
+ success: (message: ReactNode, options?: ToastOptions) => emit(message, options, "success"),
81
+ error: (message: ReactNode, options?: ToastOptions) => emit(message, options, "error"),
82
+ warning: (message: ReactNode, options?: ToastOptions) => emit(message, options, "warning"),
83
+ info: (message: ReactNode, options?: ToastOptions) => emit(message, options, "info"),
84
+ dismiss: (id?: string) => toastManager.close(id),
85
+ }
86
+ )
87
+
88
+ /** Per-variant accent (icon + left border). Derived from the status tokens. */
89
+ const VARIANT_ACCENT: Record<ToastVariant, string> = {
90
+ success: "text-success border-l-success",
91
+ error: "text-destructive border-l-destructive",
92
+ warning: "text-warning border-l-warning",
93
+ info: "text-info border-l-info",
94
+ }
95
+
96
+ function VariantIcon({ variant }: { variant?: ToastVariant }) {
97
+ const common = { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", "aria-hidden": true } as const
98
+ switch (variant) {
99
+ case "success":
100
+ return (
101
+ <svg {...common} stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
102
+ <circle cx="12" cy="12" r="9" />
103
+ <path d="m8.5 12 2.5 2.5 4.5-5" />
104
+ </svg>
105
+ )
106
+ case "error":
107
+ return (
108
+ <svg {...common} stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
109
+ <circle cx="12" cy="12" r="9" />
110
+ <path d="M15 9l-6 6M9 9l6 6" />
111
+ </svg>
112
+ )
113
+ case "warning":
114
+ return (
115
+ <svg {...common} stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
116
+ <path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z" />
117
+ <path d="M12 9v4M12 17h.01" />
118
+ </svg>
119
+ )
120
+ case "info":
121
+ return (
122
+ <svg {...common} stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
123
+ <circle cx="12" cy="12" r="9" />
124
+ <path d="M12 11v5M12 8h.01" />
125
+ </svg>
126
+ )
127
+ default:
128
+ return (
129
+ <svg {...common} stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
130
+ <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
131
+ <path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" />
132
+ </svg>
133
+ )
134
+ }
135
+ }
136
+
137
+ function CloseIcon() {
138
+ return (
139
+ <svg width={14} height={14} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden>
140
+ <path d="M18 6 6 18M6 6l12 12" />
141
+ </svg>
142
+ )
143
+ }
144
+
145
+ /**
146
+ * Renders the live toast stack. Consumes the manager via `useToastManager`, so
147
+ * it must live inside a `Toast.Provider` — `SpuntoProvider` handles that.
148
+ * @internal
149
+ */
150
+ export type ToastSwipeDirection = "up" | "down" | "left" | "right"
151
+
152
+ export function ToastList({ swipeDirection }: { swipeDirection?: ToastSwipeDirection[] }) {
153
+ const { toasts } = ToastPrimitive.useToastManager<SpuntoToastData>()
154
+
155
+ return toasts.map((item) => {
156
+ const variant = item.data?.variant
157
+ const action = item.data?.action
158
+ return (
159
+ <ToastPrimitive.Root
160
+ key={item.id}
161
+ toast={item}
162
+ swipeDirection={swipeDirection}
163
+ data-slot="toast"
164
+ className={cn(
165
+ "pointer-events-auto rounded-lg border border-border border-l-4 bg-popover text-popover-foreground shadow-lg shadow-black/[0.06] outline-none",
166
+ "focus-visible:ring-2 focus-visible:ring-ring/50",
167
+ variant ? VARIANT_ACCENT[variant] : "border-l-muted-foreground/40"
168
+ )}
169
+ >
170
+ <ToastPrimitive.Content className="flex h-full items-start gap-3 p-3.5 transition-opacity duration-200 data-behind:opacity-0 data-expanded:opacity-100">
171
+ <span className={cn("mt-px shrink-0", variant ? VARIANT_ACCENT[variant] : "text-muted-foreground")}>
172
+ <VariantIcon variant={variant} />
173
+ </span>
174
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
175
+ <ToastPrimitive.Title className="text-sm leading-snug font-semibold text-foreground" />
176
+ <ToastPrimitive.Description className="text-sm leading-snug text-muted-foreground" />
177
+ {action && (
178
+ <ToastPrimitive.Action
179
+ className="mt-1.5 inline-flex h-7 w-fit items-center rounded-md border border-border bg-background px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring/50 outline-none"
180
+ onClick={() => {
181
+ action.onClick()
182
+ toastManager.close(item.id)
183
+ }}
184
+ >
185
+ {action.label}
186
+ </ToastPrimitive.Action>
187
+ )}
188
+ </div>
189
+ <ToastPrimitive.Close
190
+ aria-label="Fermer"
191
+ className="-mt-1 -mr-1 flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 outline-none"
192
+ >
193
+ <CloseIcon />
194
+ </ToastPrimitive.Close>
195
+ </ToastPrimitive.Content>
196
+ </ToastPrimitive.Root>
197
+ )
198
+ })
199
+ }
@@ -0,0 +1,90 @@
1
+ "use client"
2
+
3
+ import type { ReactElement, ReactNode } from "react"
4
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
5
+
6
+ import { cn } from "../utils"
7
+ import { useOverlayContainer } from "./spunto-provider"
8
+
9
+ export interface TooltipProps {
10
+ /** The tooltip bubble content. When omitted, the child renders with no tooltip. */
11
+ content?: ReactNode
12
+ /** The element the tooltip is anchored to (used as the trigger). */
13
+ children: ReactElement<Record<string, unknown>>
14
+ /**
15
+ * Preferred side of the trigger to place the bubble.
16
+ * @default "top"
17
+ */
18
+ side?: TooltipPrimitive.Positioner.Props["side"]
19
+ /**
20
+ * Alignment along the chosen side.
21
+ * @default "center"
22
+ */
23
+ align?: TooltipPrimitive.Positioner.Props["align"]
24
+ /** Gap (px) between the trigger and the bubble. @default 6 */
25
+ sideOffset?: number
26
+ /** Show the little pointer triangle. @default true */
27
+ arrow?: boolean
28
+ /** Controlled open state. */
29
+ open?: boolean
30
+ /** Open-state change handler (controlled mode). */
31
+ onOpenChange?: TooltipPrimitive.Root.Props["onOpenChange"]
32
+ }
33
+
34
+ /**
35
+ * Hover/focus tooltip built on Base UI. Wrap any focusable element:
36
+ *
37
+ * ```tsx
38
+ * <Tooltip content="Copier l'ID">
39
+ * <Button size="icon" variant="ghost"><CopyIcon /></Button>
40
+ * </Tooltip>
41
+ * ```
42
+ *
43
+ * The shared open delay + "instant on neighbour" grouping comes from the
44
+ * `Tooltip.Provider` that `SpuntoProvider` mounts; it also works standalone.
45
+ */
46
+ function Tooltip({
47
+ content,
48
+ children,
49
+ side = "top",
50
+ align = "center",
51
+ sideOffset = 6,
52
+ arrow = true,
53
+ open,
54
+ onOpenChange,
55
+ }: TooltipProps) {
56
+ const container = useOverlayContainer()
57
+
58
+ if (content == null || content === false) return children
59
+
60
+ return (
61
+ <TooltipPrimitive.Root open={open} onOpenChange={onOpenChange}>
62
+ <TooltipPrimitive.Trigger data-slot="tooltip-trigger" render={children} />
63
+ <TooltipPrimitive.Portal container={container ?? undefined}>
64
+ <TooltipPrimitive.Positioner data-slot="tooltip-positioner" side={side} align={align} sideOffset={sideOffset}>
65
+ <TooltipPrimitive.Popup
66
+ data-slot="tooltip"
67
+ className={cn(
68
+ "z-50 max-w-xs rounded-md bg-foreground px-2 py-1 text-xs font-medium text-background shadow-md shadow-black/10",
69
+ "origin-[var(--transform-origin)] transition-[transform,opacity] duration-150 data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0"
70
+ )}
71
+ >
72
+ {arrow && (
73
+ <TooltipPrimitive.Arrow
74
+ data-slot="tooltip-arrow"
75
+ className="data-[side=bottom]:top-[-6px] data-[side=left]:right-[-6px] data-[side=right]:left-[-6px] data-[side=top]:bottom-[-6px] data-[side=left]:rotate-90 data-[side=right]:-rotate-90 data-[side=bottom]:rotate-180"
76
+ >
77
+ <svg width={12} height={6} viewBox="0 0 12 6" fill="none" aria-hidden>
78
+ <path d="M0 0L6 6L12 0" className="fill-foreground" />
79
+ </svg>
80
+ </TooltipPrimitive.Arrow>
81
+ )}
82
+ {content}
83
+ </TooltipPrimitive.Popup>
84
+ </TooltipPrimitive.Positioner>
85
+ </TooltipPrimitive.Portal>
86
+ </TooltipPrimitive.Root>
87
+ )
88
+ }
89
+
90
+ export { Tooltip }
package/src/index.ts CHANGED
@@ -14,3 +14,62 @@ export { Textarea } from "./components/textarea"
14
14
  export { Label } from "./components/label"
15
15
  export { Badge, badgeVariants } from "./components/badge"
16
16
  export { Switch } from "./components/switch"
17
+ export { Checkbox } from "./components/checkbox"
18
+ export { RadioGroup, RadioGroupItem } from "./components/radio-group"
19
+ export { Separator } from "./components/separator"
20
+ export { Skeleton } from "./components/skeleton"
21
+ export { Spinner, spinnerVariants } from "./components/spinner"
22
+ export { Avatar, AvatarImage, AvatarFallback } from "./components/avatar"
23
+
24
+ // Inline, declarative status banner (persistent) — the counterpart to `toast()`.
25
+ // `alertVariants` ships from this directive-free module so it stays RSC-callable.
26
+ export { Alert, AlertTitle, AlertDescription, alertVariants } from "./components/alert"
27
+ export type { AlertVariant } from "./components/alert"
28
+
29
+ // Tabs — list + sliding indicator + tab + panel, on Base UI.
30
+ export { Tabs, TabsList, TabsIndicator, TabsTab, TabsPanel } from "./components/tabs"
31
+
32
+ // Select — dropdown listbox with items, groups, and separators, on Base UI.
33
+ export {
34
+ Select,
35
+ SelectTrigger,
36
+ SelectValue,
37
+ SelectContent,
38
+ SelectItem,
39
+ SelectGroup,
40
+ SelectGroupLabel,
41
+ SelectSeparator,
42
+ } from "./components/select"
43
+
44
+ // Overlays that plug into the `SpuntoProvider` umbrella (portal container +
45
+ // tooltip delay group).
46
+ export { Tooltip } from "./components/tooltip"
47
+ export type { TooltipProps } from "./components/tooltip"
48
+ export {
49
+ Dialog,
50
+ DialogTrigger,
51
+ DialogClose,
52
+ DialogContent,
53
+ DialogHeader,
54
+ DialogFooter,
55
+ DialogTitle,
56
+ DialogDescription,
57
+ } from "./components/dialog"
58
+ export {
59
+ AlertDialog,
60
+ AlertDialogTrigger,
61
+ AlertDialogClose,
62
+ AlertDialogContent,
63
+ AlertDialogHeader,
64
+ AlertDialogFooter,
65
+ AlertDialogTitle,
66
+ AlertDialogDescription,
67
+ } from "./components/alert-dialog"
68
+
69
+ // Toasts — imperative `toast()` API + the umbrella client provider that renders
70
+ // them. `SpuntoProvider` mounts the toast viewport, the tooltip delay group, and
71
+ // the overlay portal container that dialogs/selects render into.
72
+ export { SpuntoProvider } from "./components/spunto-provider"
73
+ export type { SpuntoProviderProps, ToastPosition } from "./components/spunto-provider"
74
+ export { toast } from "./components/toast"
75
+ export type { ToastFn, ToastOptions, ToastVariant, ToastAction } from "./components/toast"