@octanejs/shadcn 0.0.8 → 0.0.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.
@@ -0,0 +1,125 @@
1
+ // Base UI base pagination — TRANSCRIBED from upstream's `bases/base-ui/ui/pagination.tsx`
2
+ // (maintainer-supplied), class strings verbatim. There is no pagination primitive in any base;
3
+ // the family is host elements plus button styling, and the only base-specific question is how the
4
+ // link acquires that styling.
5
+ //
6
+ // THE LINK GOES THROUGH `Button`, NOT THROUGH `buttonVariants`. The radix base writes
7
+ // `<Button asChild><a/></Button>`, where Slot is a pure prop-merger that layers no button
8
+ // semantics onto the anchor. Base UI has no Slot, so upstream uses the `render` prop plus
9
+ // `nativeButton={false}` — the documented escape for "this Button is not a native `<button>`".
10
+ //
11
+ // THE TWO BASES THEREFORE DIVERGE OBSERVABLY, and that is upstream's call, not a porting bug:
12
+ // this base's links carry `role="button"` and `tabindex="0"` (verified by rendering) where the
13
+ // radix base's carry neither. `nativeButton={false}` is what keeps keyboard activation working on
14
+ // a non-button element, which is the trade upstream chose. Do not "fix" this toward radix by
15
+ // dropping Button for a bare `buttonVariants()` anchor: it diverges from the source, and it also
16
+ // drops the key handling Base UI attaches.
17
+ //
18
+ // Octane adaptations: lucide-react → @octanejs/lucide, and children are passed explicitly rather
19
+ // than riding along in the props spread — React's `ComponentProps<"a">` carries `children`, octane
20
+ // routes it through its own channel.
21
+ import { createElement, type OctaneNode } from 'octane';
22
+ import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from '@octanejs/lucide';
23
+
24
+ import { cn } from '../../../lib/utils';
25
+ import { Button, type ButtonProps } from './button.tsrx';
26
+
27
+ type Props = { className?: string } & Record<string, unknown>;
28
+
29
+ export function Pagination({ className, ...props }: Props) @{
30
+ <nav
31
+ role="navigation"
32
+ aria-label="pagination"
33
+ data-slot="pagination"
34
+ className={cn('mx-auto flex w-full justify-center', className)}
35
+ {...props}
36
+ />
37
+ }
38
+
39
+ export function PaginationContent({ className, ...props }: Props) @{
40
+ <ul
41
+ data-slot="pagination-content"
42
+ className={cn('flex items-center gap-0.5', className)}
43
+ {...props}
44
+ />
45
+ }
46
+
47
+ export function PaginationItem(props: Record<string, unknown>) @{
48
+ <li data-slot="pagination-item" {...props} />
49
+ }
50
+
51
+ export interface PaginationLinkProps extends Record<string, unknown> {
52
+ className?: string;
53
+ isActive?: boolean;
54
+ size?: ButtonProps['size'];
55
+ children?: OctaneNode;
56
+ }
57
+
58
+ export function PaginationLink({
59
+ className,
60
+ isActive,
61
+ size = 'icon',
62
+ children,
63
+ ...props
64
+ }: PaginationLinkProps) {
65
+ return createElement(Button, {
66
+ variant: isActive ? 'outline' : 'ghost',
67
+ size,
68
+ className: cn(className),
69
+ nativeButton: false,
70
+ render: createElement('a', {
71
+ 'aria-current': isActive ? 'page' : undefined,
72
+ 'data-slot': 'pagination-link',
73
+ 'data-active': isActive,
74
+ ...props,
75
+ children,
76
+ }),
77
+ });
78
+ }
79
+
80
+ export function PaginationPrevious({
81
+ className,
82
+ text = 'Previous',
83
+ ...props
84
+ }: PaginationLinkProps & {
85
+ text?: string;
86
+ }) @{
87
+ <PaginationLink
88
+ aria-label="Go to previous page"
89
+ size="default"
90
+ className={cn('pl-1.5!', className)}
91
+ {...props}
92
+ >
93
+ <ChevronLeftIcon data-icon="inline-start" className="cn-rtl-flip" />
94
+ <span className="hidden sm:block">{text as string}</span>
95
+ </PaginationLink>
96
+ }
97
+
98
+ export function PaginationNext({ className, text = 'Next', ...props }: PaginationLinkProps & {
99
+ text?: string;
100
+ }) @{
101
+ <PaginationLink
102
+ aria-label="Go to next page"
103
+ size="default"
104
+ className={cn('pr-1.5!', className)}
105
+ {...props}
106
+ >
107
+ <span className="hidden sm:block">{text as string}</span>
108
+ <ChevronRightIcon data-icon="inline-end" className="cn-rtl-flip" />
109
+ </PaginationLink>
110
+ }
111
+
112
+ export function PaginationEllipsis({ className, ...props }: Props) @{
113
+ <span
114
+ aria-hidden="true"
115
+ data-slot="pagination-ellipsis"
116
+ className={cn(
117
+ 'flex size-8 items-center justify-center [&_svg:not([class*=\'size-\'])]:size-4',
118
+ className,
119
+ )}
120
+ {...props}
121
+ >
122
+ <MoreHorizontalIcon />
123
+ <span className="sr-only">More pages</span>
124
+ </span>
125
+ }
@@ -21,6 +21,11 @@
21
21
  //
22
22
  // UNVERIFIED PROVENANCE: class strings come from this package's radix base rather than
23
23
  // transcribed from upstream's Base UI source.
24
+ // POSITIONING PROPS BELONG TO THE POSITIONER, NOT THE POPUP. Radix has one `Content` element that
25
+ // takes them all; Base UI splits positioning into its own layer, so anything forwarded to `Popup`
26
+ // instead is inert — `side="top"` silently leaves the popup on the default side, and the prop lands
27
+ // on the DOM as an invalid attribute. They are destructured and routed explicitly.
28
+ //
24
29
  import { createElement } from 'octane';
25
30
  import { Popover as PopoverPrimitive } from '@octanejs/base-ui/popover';
26
31
 
@@ -36,22 +41,56 @@ export function PopoverTrigger(props: Record<string, unknown>) @{
36
41
  <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
37
42
  }
38
43
 
39
- export interface PopoverContentProps extends Record<string, unknown> {
40
- className?: string;
41
- align?: 'start' | 'center' | 'end';
44
+ export interface BaseUiPositioningProps {
45
+ /** Forwarded to the Positioner; see the note at the top of this file. */
46
+ side?: 'top' | 'right' | 'bottom' | 'left';
42
47
  sideOffset?: number;
48
+ align?: 'start' | 'center' | 'end';
49
+ alignOffset?: number;
50
+ anchor?: unknown;
51
+ positionMethod?: 'absolute' | 'fixed';
52
+ collisionBoundary?: unknown;
53
+ collisionPadding?: unknown;
54
+ collisionAvoidance?: unknown;
55
+ arrowPadding?: number;
56
+ sticky?: boolean;
57
+ disableAnchorTracking?: boolean;
58
+ }
59
+
60
+ export interface PopoverContentProps extends Record<string, unknown>, BaseUiPositioningProps {
61
+ className?: string;
43
62
  }
44
63
 
45
64
  export function PopoverContent({
46
65
  className,
47
66
  align = 'center',
48
67
  sideOffset = 4,
68
+ side,
69
+ alignOffset,
70
+ anchor,
71
+ positionMethod,
72
+ collisionBoundary,
73
+ collisionPadding,
74
+ collisionAvoidance,
75
+ arrowPadding,
76
+ sticky,
77
+ disableAnchorTracking,
49
78
  ...props
50
79
  }: PopoverContentProps) {
51
80
  return createElement(PopoverPrimitive.Portal, {
52
81
  children: createElement(PopoverPrimitive.Positioner, {
53
- align,
82
+ side,
54
83
  sideOffset,
84
+ align,
85
+ alignOffset,
86
+ anchor,
87
+ positionMethod,
88
+ collisionBoundary,
89
+ collisionPadding,
90
+ collisionAvoidance,
91
+ arrowPadding,
92
+ sticky,
93
+ disableAnchorTracking,
55
94
  children: createElement(PopoverPrimitive.Popup, {
56
95
  'data-slot': 'popover-content',
57
96
  className: cn(
@@ -0,0 +1,46 @@
1
+ // Base UI base progress — runs on @octanejs/base-ui's Progress.
2
+ //
3
+ // TWO STRUCTURAL DIFFERENCES FROM THE RADIX BASE, both verified by rendering the primitive
4
+ // rather than inferred:
5
+ //
6
+ // 1. THERE IS A TRACK PART. Radix goes Root > Indicator; Base UI goes Root > Track > Indicator,
7
+ // and the Track is what carries the rail. Rendering an Indicator directly under Root would
8
+ // put it outside the element the primitive sizes against.
9
+ //
10
+ // 2. THE INDICATOR SIZES ITSELF. Radix leaves the fill entirely to the consumer, which is why
11
+ // its base ships `style={{ transform: translateX(-${100 - value}%) }}`. Base UI's Indicator
12
+ // already emits `width: <value>%` inline. Carrying the radix transform across would shift a
13
+ // correctly-sized bar sideways by the same amount again — a double offset, worst at the
14
+ // midpoint and invisible at 0% and 100%.
15
+ //
16
+ // So `value` is forwarded to the Root (which owns the aria-valuenow/valuetext contract) and NOT
17
+ // turned into a transform.
18
+ //
19
+ // UNVERIFIED PROVENANCE: class strings come from this package's radix base rather than
20
+ // transcribed from upstream's Base UI source; the rail/fill classes are split across Track and
21
+ // Indicator to match the extra part.
22
+ import { Progress as ProgressPrimitive } from '@octanejs/base-ui/progress';
23
+
24
+ import { cn } from '../../../lib/utils';
25
+
26
+ export interface ProgressProps extends Record<string, unknown> {
27
+ className?: string;
28
+ value?: number | null;
29
+ }
30
+
31
+ export function Progress({ className, value, ...props }: ProgressProps) @{
32
+ // Base UI's Root types `value` as `number | null` where shadcn's prop is optional, and null is
33
+ // its documented indeterminate state — so an omitted value maps to null rather than being
34
+ // forwarded as undefined.
35
+ <ProgressPrimitive.Root data-slot="progress" value={value ?? null} {...props}>
36
+ <ProgressPrimitive.Track
37
+ data-slot="progress-track"
38
+ className={cn('relative h-2 w-full overflow-hidden rounded-full bg-primary/20', className)}
39
+ >
40
+ <ProgressPrimitive.Indicator
41
+ data-slot="progress-indicator"
42
+ className="h-full bg-primary transition-all"
43
+ />
44
+ </ProgressPrimitive.Track>
45
+ </ProgressPrimitive.Root>
46
+ }
@@ -10,6 +10,18 @@
10
10
  // renders a `<span role="radio">`, which is never `:disabled`, so the pseudo-class variant could
11
11
  // never match. The primitive publishes `data-disabled` instead.
12
12
  //
13
+ // THE ROOT MUST CARRY ITS OWN DISPLAY, and this is the second consequence of that same `<span>`.
14
+ // Radix's radio root is a `<button>`, which is `display: inline-block` by default, so its class
15
+ // string never needed one and `size-4` just worked. A bare `<span>` is `display: inline`, where
16
+ // width and height are IGNORED — the box collapses and only `border` paints, rendering the control
17
+ // as a thin vertical bar beside its label rather than a circle. The React Aria base hit the same
18
+ // thing (its root is not a button either) and carries `relative flex` for it; this base copied the
19
+ // radix string and lost it. Hence `relative flex … items-center justify-center` here.
20
+ //
21
+ // The indicator is `size-full` rather than `relative` so the dot, which positions itself with
22
+ // `absolute … -translate-1/2`, anchors to the ROOT's 16px box — matching the React Aria base. Left
23
+ // `relative`, it would anchor to a zero-sized flex item and sit off-centre.
24
+ //
13
25
  // UNVERIFIED PROVENANCE: the non-conditional utilities come from this package's radix base
14
26
  // rather than upstream's Base UI source.
15
27
  import { RadioGroup as RadioGroupPrimitive } from '@octanejs/base-ui/radio-group';
@@ -33,14 +45,14 @@ export function RadioGroupItem({ className, ...props }: ItemProps) @{
33
45
  <RadioPrimitive.Root
34
46
  data-slot="radio-group-item"
35
47
  className={cn(
36
- 'aspect-square size-4 shrink-0 rounded-full border border-input text-primary shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 data-disabled:cursor-not-allowed data-disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:ring-destructive/40',
48
+ 'relative flex aspect-square size-4 shrink-0 items-center justify-center rounded-full border border-input text-primary shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 data-disabled:cursor-not-allowed data-disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:ring-destructive/40',
37
49
  className,
38
50
  )}
39
51
  {...props}
40
52
  >
41
53
  <RadioPrimitive.Indicator
42
54
  data-slot="radio-group-indicator"
43
- className="relative flex items-center justify-center"
55
+ className="flex size-full items-center justify-center"
44
56
  >
45
57
  <CircleIcon
46
58
  className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 fill-primary"
@@ -0,0 +1,121 @@
1
+ // Base UI base sheet — TRANSCRIBED from upstream's `bases/base-ui/ui/sheet.tsx`
2
+ // (maintainer-supplied), class strings verbatim. Runs on @octanejs/base-ui's Dialog:
3
+ // Overlay -> Backdrop, Content -> Popup.
4
+ //
5
+ // UPSTREAM ANIMATES THIS WITH CSS TRANSITIONS, NOT KEYFRAMES, which is why the file is transcribed
6
+ // rather than derived. Base UI publishes `data-starting-style` and `data-ending-style` (see
7
+ // utils/popupStateMapping.ts) and this source drives motion off them with a plain `transition`,
8
+ // where the radix base uses `animate-in` / `animate-out` keyframe utilities.
9
+ //
10
+ // A derived version of this file did jump visibly on close, which is what prompted the
11
+ // transcription. Do NOT infer from that a general rule that keyframes cannot work on Base UI: the
12
+ // same mapping also emits `data-open` and `data-closed`, so `data-closed:animate-out` does match,
13
+ // and the precise cause of that jump was never isolated. What is established here is only what
14
+ // upstream ships, which is this.
15
+ //
16
+ // So the motion here reads: `transition duration-200` plus per-side
17
+ // `data-starting-style:translate-*` / `data-ending-style:translate-*` and an opacity pair —
18
+ // the element sits offset-and-transparent before open and after close, and transitions to its
19
+ // resting position in between.
20
+ //
21
+ // ONE DEPARTURE FROM THE SOURCE: upstream's title carries `cn-font-heading`, a semantic hook from
22
+ // the pinned cn-* system. This package ships the utilities-inlined flavor, the react-aria base
23
+ // drops the same class, and nothing defines it here (0 occurrences in the built CSS).
24
+ //
25
+ // Octane adaptations: no `"use client"`; refs are props.
26
+ import { type OctaneNode } from 'octane';
27
+ import { Dialog as SheetPrimitive } from '@octanejs/base-ui/dialog';
28
+ import { XIcon } from '@octanejs/lucide';
29
+
30
+ import { cn } from '../../../lib/utils';
31
+ import { Button } from './button.tsrx';
32
+
33
+ type Props = { className?: string; children?: OctaneNode } & Record<string, unknown>;
34
+
35
+ export function Sheet(props: Record<string, unknown>) @{
36
+ <SheetPrimitive.Root data-slot="sheet" {...props} />
37
+ }
38
+
39
+ export function SheetTrigger(props: Record<string, unknown>) @{
40
+ <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
41
+ }
42
+
43
+ export function SheetClose(props: Record<string, unknown>) @{
44
+ <SheetPrimitive.Close data-slot="sheet-close" {...props} />
45
+ }
46
+
47
+ export function SheetPortal(props: Record<string, unknown>) @{
48
+ <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
49
+ }
50
+
51
+ export function SheetOverlay({ className, ...props }: Props) @{
52
+ <SheetPrimitive.Backdrop
53
+ data-slot="sheet-overlay"
54
+ className={cn(
55
+ 'fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs',
56
+ className,
57
+ )}
58
+ {...props}
59
+ />
60
+ }
61
+
62
+ export interface SheetContentProps extends Props {
63
+ side?: 'top' | 'right' | 'bottom' | 'left';
64
+ showCloseButton?: boolean;
65
+ }
66
+
67
+ export function SheetContent(props: SheetContentProps) @{
68
+ const { className, side = 'right', showCloseButton = true, children: _children, ...rest } = props;
69
+
70
+ <SheetPortal>
71
+ <SheetOverlay />
72
+ <SheetPrimitive.Popup
73
+ data-slot="sheet-content"
74
+ data-side={side}
75
+ className={cn(
76
+ 'fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',
77
+ className,
78
+ )}
79
+ {...rest}
80
+ >
81
+ {props.children}
82
+ @if (showCloseButton) {
83
+ <SheetPrimitive.Close
84
+ data-slot="sheet-close"
85
+ render={<Button variant="ghost" className="absolute top-3 right-3" size="icon-sm">
86
+ <XIcon />
87
+ <span className="sr-only">Close</span>
88
+ </Button>}
89
+ />
90
+ }
91
+ </SheetPrimitive.Popup>
92
+ </SheetPortal>
93
+ }
94
+
95
+ export function SheetHeader({ className, ...props }: Props) @{
96
+ <div data-slot="sheet-header" className={cn('flex flex-col gap-0.5 p-4', className)} {...props} />
97
+ }
98
+
99
+ export function SheetFooter({ className, ...props }: Props) @{
100
+ <div
101
+ data-slot="sheet-footer"
102
+ className={cn('mt-auto flex flex-col gap-2 p-4', className)}
103
+ {...props}
104
+ />
105
+ }
106
+
107
+ export function SheetTitle({ className, ...props }: Props) @{
108
+ <SheetPrimitive.Title
109
+ data-slot="sheet-title"
110
+ className={cn('text-base font-medium text-foreground', className)}
111
+ {...props}
112
+ />
113
+ }
114
+
115
+ export function SheetDescription({ className, ...props }: Props) @{
116
+ <SheetPrimitive.Description
117
+ data-slot="sheet-description"
118
+ className={cn('text-sm text-muted-foreground', className)}
119
+ {...props}
120
+ />
121
+ }
@@ -0,0 +1,111 @@
1
+ // Base UI base slider — TRANSCRIBED from upstream's `bases/base-ui/ui/slider.tsx`
2
+ // (maintainer-supplied). Structure, part tree and class strings follow that source.
3
+ //
4
+ // THE PART TREE IS NOT THE RADIX ONE:
5
+ //
6
+ // radix Root > Track > Range, Thumb a SIBLING of Track (under Root)
7
+ // Base UI Root > Control > Track > Indicator, Thumb a SIBLING of Track (under Control)
8
+ //
9
+ // The thumb sits OUTSIDE the track in both. That matters: the track carries `overflow-hidden` —
10
+ // which is what gives the fill its rounded ends — and is only `h-1` tall, so a round thumb nested
11
+ // inside it is clipped to a 4px sliver that reads as a missing thumb. Base UI positions the thumb
12
+ // from slider context rather than from its DOM parent (`inset-inline-start: <pct>` plus a
13
+ // centering `translate`), so Control is the box those percentages resolve against.
14
+ //
15
+ // ONE DEPARTURE FROM THE SOURCE, AND IT IS DELIBERATE: upstream writes its orientation variants as
16
+ // `data-horizontal:` / `data-vertical:`. No primitive in @octanejs/base-ui emits those attributes —
17
+ // this one publishes `data-orientation="horizontal" | "vertical"` on Root, Control, Track,
18
+ // Indicator and Thumb (verified by rendering it), so the variants are written as
19
+ // `data-[orientation=…]:` instead. Copied verbatim, upstream's spelling would match nothing and the
20
+ // track would render with no height at all: an invisible rail. Whether the primitive should be
21
+ // emitting the newer attribute names is a question about @octanejs/base-ui, not about this file.
22
+ //
23
+ // `thumbAlignment="edge"` is forwarded as upstream does: it insets the thumb so the two extremes
24
+ // sit flush with the ends of the track instead of overhanging them.
25
+ //
26
+ // Thumbs are composed with createElement for the same reason as the radix base: one per entry of
27
+ // `_values`, and mapped descriptors are octane's channel for computed children.
28
+ import { createElement, useMemo } from 'octane';
29
+ import { Slider as SliderPrimitive } from '@octanejs/base-ui/slider';
30
+
31
+ import { cn } from '../../../lib/utils';
32
+
33
+ export interface SliderProps extends Record<string, unknown> {
34
+ className?: string;
35
+ defaultValue?: number | number[];
36
+ value?: number | number[];
37
+ min?: number;
38
+ max?: number;
39
+ }
40
+
41
+ export function Slider({
42
+ className,
43
+ defaultValue,
44
+ value,
45
+ min = 0,
46
+ max = 100,
47
+ ...props
48
+ }: SliderProps) {
49
+ // This drives HOW MANY THUMBS get rendered, so a value shape it fails to recognise is a visible
50
+ // bug rather than a type nicety.
51
+ //
52
+ // DEPARTS FROM THE TRANSCRIBED SOURCE, deliberately. Upstream only tests `Array.isArray` and
53
+ // otherwise falls back to `[min, max]` — but Base UI's Slider accepts a SCALAR for a
54
+ // single-value slider, and upstream types these props as `SliderPrimitive.Root.Props`, which
55
+ // permits one. So `defaultValue={30}` renders two thumbs against a one-value slider: the range
56
+ // fallback fires, and the second thumb has no value behind it.
57
+ const _values = useMemo(() => {
58
+ // A controlled `value` wins when present; `??` rather than `||` so a legitimate 0 counts.
59
+ const provided = value ?? defaultValue;
60
+ if (Array.isArray(provided)) {
61
+ return provided;
62
+ }
63
+ if (typeof provided === 'number') {
64
+ return [provided];
65
+ }
66
+ // Neither given: upstream's range-across-the-track default, kept as-is in both bases.
67
+ return [min, max];
68
+ }, [value, defaultValue, min, max]);
69
+
70
+ return createElement(
71
+ SliderPrimitive.Root,
72
+ {
73
+ className: cn(
74
+ 'data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full',
75
+ className,
76
+ ),
77
+ 'data-slot': 'slider',
78
+ defaultValue,
79
+ value,
80
+ min,
81
+ max,
82
+ thumbAlignment: 'edge',
83
+ ...props,
84
+ },
85
+ createElement(
86
+ SliderPrimitive.Control,
87
+ {
88
+ className: 'relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',
89
+ },
90
+ createElement(
91
+ SliderPrimitive.Track,
92
+ {
93
+ 'data-slot': 'slider-track',
94
+ className: 'relative grow overflow-hidden rounded-full bg-muted select-none data-[orientation=horizontal]:h-1 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1',
95
+ },
96
+ createElement(SliderPrimitive.Indicator, {
97
+ 'data-slot': 'slider-range',
98
+ className: 'bg-primary select-none data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full',
99
+ }),
100
+ ),
101
+ ...Array.from(
102
+ { length: _values.length },
103
+ (_, index) => createElement(SliderPrimitive.Thumb, {
104
+ 'data-slot': 'slider-thumb',
105
+ key: index,
106
+ className: 'relative block size-3 shrink-0 rounded-full border border-ring bg-white ring-ring/50 transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden active:ring-3 disabled:pointer-events-none disabled:opacity-50',
107
+ }),
108
+ ),
109
+ ),
110
+ );
111
+ }
@@ -0,0 +1,85 @@
1
+ // Base UI base table — THIS FAMILY IS BASE-INDEPENDENT. Neither Radix nor Base UI publishes a
2
+ // table primitive, so upstream ships the same plain host elements in both bases and this file is
3
+ // the radix base's content unchanged. (The React Aria base is the odd one out: RAC does have a
4
+ // table, so that base runs on real collection components with generic item types.)
5
+ //
6
+ // There is consequently no dialect to get wrong here and nothing to verify against a primitive:
7
+ // `data-[state=selected]` on the row is written by the CONSUMER — a data-table library setting
8
+ // selection state — not emitted by anything underneath, so unlike checkbox or toggle it is not a
9
+ // Radix-ism that needs translating.
10
+ import { cn } from '../../../lib/utils';
11
+
12
+ // Multi-host family (div/table/thead/tbody/tr/th/td/caption): no single host
13
+ // prop table applies, so the shared alias stays structural.
14
+ type Props = { className?: string } & Record<string, unknown>;
15
+
16
+ export function Table({ className, ...props }: Props) @{
17
+ <div data-slot="table-container" className="relative w-full overflow-x-auto">
18
+ <table
19
+ data-slot="table"
20
+ className={cn('w-full caption-bottom text-sm', className)}
21
+ {...props}
22
+ />
23
+ </div>
24
+ }
25
+
26
+ export function TableHeader({ className, ...props }: Props) @{
27
+ <thead data-slot="table-header" className={cn('[&_tr]:border-b', className)} {...props} />
28
+ }
29
+
30
+ export function TableBody({ className, ...props }: Props) @{
31
+ <tbody
32
+ data-slot="table-body"
33
+ className={cn('[&_tr:last-child]:border-0', className)}
34
+ {...props}
35
+ />
36
+ }
37
+
38
+ export function TableFooter({ className, ...props }: Props) @{
39
+ <tfoot
40
+ data-slot="table-footer"
41
+ className={cn('bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', className)}
42
+ {...props}
43
+ />
44
+ }
45
+
46
+ export function TableRow({ className, ...props }: Props) @{
47
+ <tr
48
+ data-slot="table-row"
49
+ className={cn(
50
+ 'border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',
51
+ className,
52
+ )}
53
+ {...props}
54
+ />
55
+ }
56
+
57
+ export function TableHead({ className, ...props }: Props) @{
58
+ <th
59
+ data-slot="table-head"
60
+ className={cn(
61
+ 'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
62
+ className,
63
+ )}
64
+ {...props}
65
+ />
66
+ }
67
+
68
+ export function TableCell({ className, ...props }: Props) @{
69
+ <td
70
+ data-slot="table-cell"
71
+ className={cn(
72
+ 'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
73
+ className,
74
+ )}
75
+ {...props}
76
+ />
77
+ }
78
+
79
+ export function TableCaption({ className, ...props }: Props) @{
80
+ <caption
81
+ data-slot="table-caption"
82
+ className={cn('text-muted-foreground mt-4 text-sm', className)}
83
+ {...props}
84
+ />
85
+ }