@octanejs/shadcn 0.0.7 → 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.
- package/README.md +170 -3
- package/package.json +46 -7
- package/src/bases/base-ui/ui/accordion.tsrx +71 -0
- package/src/bases/base-ui/ui/alert-dialog.tsrx +149 -0
- package/src/bases/base-ui/ui/alert.tsrx +68 -0
- package/src/bases/base-ui/ui/aspect-ratio.tsrx +24 -0
- package/src/bases/base-ui/ui/avatar.tsrx +89 -0
- package/src/bases/base-ui/ui/badge.tsrx +56 -0
- package/src/bases/base-ui/ui/breadcrumb.tsrx +109 -0
- package/src/bases/base-ui/ui/button.tsrx +69 -0
- package/src/bases/base-ui/ui/card.tsrx +76 -0
- package/src/bases/base-ui/ui/checkbox.tsrx +43 -0
- package/src/bases/base-ui/ui/collapsible.tsrx +25 -0
- package/src/bases/base-ui/ui/dialog.tsrx +137 -0
- package/src/bases/base-ui/ui/dropdown-menu.tsrx +292 -0
- package/src/bases/base-ui/ui/empty.tsrx +88 -0
- package/src/bases/base-ui/ui/field.tsrx +201 -0
- package/src/bases/base-ui/ui/input.tsrx +28 -0
- package/src/bases/base-ui/ui/item.tsrx +163 -0
- package/src/bases/base-ui/ui/kbd.tsrx +27 -0
- package/src/bases/base-ui/ui/label.tsrx +31 -0
- package/src/bases/base-ui/ui/native-select.tsrx +54 -0
- package/src/bases/base-ui/ui/pagination.tsrx +125 -0
- package/src/bases/base-ui/ui/popover.tsrx +124 -0
- package/src/bases/base-ui/ui/progress.tsrx +46 -0
- package/src/bases/base-ui/ui/radio-group.tsrx +62 -0
- package/src/bases/base-ui/ui/separator.tsrx +34 -0
- package/src/bases/base-ui/ui/sheet.tsrx +121 -0
- package/src/bases/base-ui/ui/skeleton.tsrx +17 -0
- package/src/bases/base-ui/ui/slider.tsrx +111 -0
- package/src/bases/base-ui/ui/spinner.tsrx +30 -0
- package/src/bases/base-ui/ui/switch.tsrx +43 -0
- package/src/bases/base-ui/ui/table.tsrx +85 -0
- package/src/bases/base-ui/ui/textarea.tsrx +22 -0
- package/src/bases/base-ui/ui/toggle-group.tsrx +124 -0
- package/src/bases/base-ui/ui/toggle.tsrx +57 -0
- package/src/bases/base-ui/ui/tooltip.tsrx +111 -0
|
@@ -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,17 @@
|
|
|
1
|
+
// Base UI base skeleton — presentational, no headless primitive in any base.
|
|
2
|
+
//
|
|
3
|
+
// UNVERIFIED PROVENANCE: derived from this package's React Aria base rather than transcribed
|
|
4
|
+
// from upstream's `bases/base-ui/ui/skeleton.tsx`, which was not available at port time.
|
|
5
|
+
// Skeleton is the WORST case for this, and the reason the whole batch is labelled: the Radix
|
|
6
|
+
// base renders `bg-accent` where the React Aria base renders `bg-muted`, so the one token that
|
|
7
|
+
// matters here is known to differ ACROSS bases. Verify against the upstream source.
|
|
8
|
+
import { cn } from '../../../lib/utils';
|
|
9
|
+
import type { DivProps } from '../../../lib/types';
|
|
10
|
+
|
|
11
|
+
export function Skeleton({ className, ...props }: DivProps) @{
|
|
12
|
+
<div
|
|
13
|
+
data-slot="skeleton"
|
|
14
|
+
className={cn('animate-pulse rounded-md bg-muted', className)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
}
|
|
@@ -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,30 @@
|
|
|
1
|
+
// Base UI base spinner — presentational, no headless primitive in any base.
|
|
2
|
+
//
|
|
3
|
+
// UNVERIFIED PROVENANCE: derived from this package's React Aria base rather than transcribed
|
|
4
|
+
// from upstream's `bases/base-ui/ui/spinner.tsx`, which was not available at port time.
|
|
5
|
+
// Diff the class strings against the upstream source before treating this as ported.
|
|
6
|
+
//
|
|
7
|
+
// OCTANE DIVERGENCE: upstream's IconPlaceholder (the CLI-resolved iconLibrary axis) is
|
|
8
|
+
// resolved at port time to the default library, lucide, via @octanejs/lucide. Other icon
|
|
9
|
+
// libraries are a registry-emit concern.
|
|
10
|
+
//
|
|
11
|
+
// The props are a permissive record rather than HostProps<'svg'>: these are forwarded to a
|
|
12
|
+
// lucide component, not applied to a host element, so the host SVG attribute types do not
|
|
13
|
+
// describe them.
|
|
14
|
+
import { Loader2Icon } from '@octanejs/lucide';
|
|
15
|
+
|
|
16
|
+
import { cn } from '../../../lib/utils';
|
|
17
|
+
|
|
18
|
+
export interface SpinnerProps extends Record<string, unknown> {
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function Spinner({ className, ...props }: SpinnerProps) @{
|
|
23
|
+
<Loader2Icon
|
|
24
|
+
data-slot="spinner"
|
|
25
|
+
role="status"
|
|
26
|
+
aria-label="Loading"
|
|
27
|
+
className={cn('size-4 animate-spin', className)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Base UI base switch — runs on the @octanejs/base-ui `Switch` primitive.
|
|
2
|
+
//
|
|
3
|
+
// TWO ATTRIBUTE DIALECTS DIFFER FROM THE RADIX BASE, both verified by rendering the primitive:
|
|
4
|
+
//
|
|
5
|
+
// 1. Checked state. Radix publishes `data-state="checked"`, Base UI publishes bare
|
|
6
|
+
// `data-checked` / `data-unchecked` attributes:
|
|
7
|
+
// radix: `data-[state=checked]:bg-primary`
|
|
8
|
+
// base-ui: `data-checked:bg-primary`
|
|
9
|
+
// Copying the radix form would leave the switch permanently showing its unchecked colours
|
|
10
|
+
// and the thumb never translating — the exact symptom of a wrong-base copy.
|
|
11
|
+
//
|
|
12
|
+
// 2. Disabled. Switch.Root renders a `<span role="switch">`, never `:disabled`, so
|
|
13
|
+
// `disabled:` cannot match; the primitive emits `data-disabled` instead.
|
|
14
|
+
//
|
|
15
|
+
// UNVERIFIED PROVENANCE: the non-conditional utilities come from this package's radix base
|
|
16
|
+
// rather than upstream's Base UI source.
|
|
17
|
+
import { Switch as SwitchPrimitive } from '@octanejs/base-ui/switch';
|
|
18
|
+
|
|
19
|
+
import { cn } from '../../../lib/utils';
|
|
20
|
+
|
|
21
|
+
export interface SwitchProps extends Record<string, unknown> {
|
|
22
|
+
className?: string;
|
|
23
|
+
size?: 'sm' | 'default';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function Switch({ className, size = 'default', ...props }: SwitchProps) @{
|
|
27
|
+
<SwitchPrimitive.Root
|
|
28
|
+
data-slot="switch"
|
|
29
|
+
data-size={size}
|
|
30
|
+
className={cn(
|
|
31
|
+
'peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 data-disabled:cursor-not-allowed data-disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80',
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
<SwitchPrimitive.Thumb
|
|
37
|
+
data-slot="switch-thumb"
|
|
38
|
+
className={cn(
|
|
39
|
+
'pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-checked:translate-x-[calc(100%-2px)] data-unchecked:translate-x-0 dark:data-checked:bg-primary-foreground dark:data-unchecked:bg-foreground',
|
|
40
|
+
)}
|
|
41
|
+
/>
|
|
42
|
+
</SwitchPrimitive.Root>
|
|
43
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Base UI base textarea — a plain host `<textarea>`. Base UI ships no textarea primitive (its
|
|
2
|
+
// Input is a native `<input>` via Field.Control), so unlike the React Aria base there is no
|
|
3
|
+
// `TextArea` component to compose and this matches the Radix base's plain-host shape.
|
|
4
|
+
//
|
|
5
|
+
// UNVERIFIED PROVENANCE: class string taken from this package's React Aria base rather than
|
|
6
|
+
// transcribed from upstream's Base UI source.
|
|
7
|
+
//
|
|
8
|
+
// OCTANE DIVERGENCE: per-keystroke handling is the native `onInput` event, not a synthetic
|
|
9
|
+
// `onChange`; native `change` keeps its commit-on-blur meaning.
|
|
10
|
+
import { cn } from '../../../lib/utils';
|
|
11
|
+
import type { HostProps } from '../../../lib/types';
|
|
12
|
+
|
|
13
|
+
export function Textarea({ className, ...props }: HostProps<'textarea'>) @{
|
|
14
|
+
<textarea
|
|
15
|
+
data-slot="textarea"
|
|
16
|
+
className={cn(
|
|
17
|
+
'flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Base UI base toggle-group — runs on @octanejs/base-ui's ToggleGroup.
|
|
2
|
+
//
|
|
3
|
+
// THERE IS NO `Item` PART. Verified by rendering the primitive: Base UI's ToggleGroup is a single
|
|
4
|
+
// component whose children are ordinary `Toggle`s carrying a `value`, where radix pairs
|
|
5
|
+
// `ToggleGroup.Root` with `ToggleGroup.Item`. So `ToggleGroupItem` here wraps `Toggle` — the same
|
|
6
|
+
// primitive `toggle.tsrx` uses — and the exported names and `data-slot` values stay as shadcn
|
|
7
|
+
// defines them, so consumers see no difference between bases.
|
|
8
|
+
//
|
|
9
|
+
// TWO API DIFFERENCES A CONSUMER CAN OBSERVE, both inherent to the primitive:
|
|
10
|
+
//
|
|
11
|
+
// 1. `type="single" | "multiple"` is translated to Base UI's `multiple` boolean, so markup
|
|
12
|
+
// written against the radix base keeps working.
|
|
13
|
+
//
|
|
14
|
+
// 2. VALUES ARE ALWAYS ARRAYS. Base UI's `value` / `defaultValue` / `onValueChange` speak
|
|
15
|
+
// `string[]` in both modes, where radix's single mode speaks a bare `string`. That is not
|
|
16
|
+
// translated: faking a scalar would mean inventing conversion behavior on every change, and
|
|
17
|
+
// silently reshaping the argument to the consumer's handler is worse than a documented
|
|
18
|
+
// difference. Single mode still yields at most one entry.
|
|
19
|
+
//
|
|
20
|
+
// The pressed dialect is `data-pressed`, not radix's `data-[state=on]` — see toggle.tsrx, whose
|
|
21
|
+
// `toggleVariants` this file reuses, so the two bases cannot drift apart.
|
|
22
|
+
//
|
|
23
|
+
// ToggleGroup is composed with createElement: the consumer's children must sit INSIDE the context
|
|
24
|
+
// Provider, and opaque compiled .tsrx children can only flow through the props.children channel
|
|
25
|
+
// (see breadcrumb.tsrx's comment).
|
|
26
|
+
//
|
|
27
|
+
// UNVERIFIED PROVENANCE: class strings come from this package's radix base rather than
|
|
28
|
+
// transcribed from upstream's Base UI source.
|
|
29
|
+
import { createContext, createElement, useContext, type OctaneNode } from 'octane';
|
|
30
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
31
|
+
import { ToggleGroup as ToggleGroupPrimitive } from '@octanejs/base-ui/toggle-group';
|
|
32
|
+
import { Toggle as TogglePrimitive } from '@octanejs/base-ui/toggle';
|
|
33
|
+
|
|
34
|
+
import { cn } from '../../../lib/utils';
|
|
35
|
+
import { toggleVariants } from './toggle.tsrx';
|
|
36
|
+
|
|
37
|
+
const ToggleGroupContext = createContext<
|
|
38
|
+
VariantProps<typeof toggleVariants> & {
|
|
39
|
+
spacing?: number;
|
|
40
|
+
orientation?: 'horizontal' | 'vertical';
|
|
41
|
+
}
|
|
42
|
+
>({
|
|
43
|
+
size: 'default',
|
|
44
|
+
variant: 'default',
|
|
45
|
+
spacing: 2,
|
|
46
|
+
orientation: 'horizontal',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export interface ToggleGroupProps extends Record<string, unknown> {
|
|
50
|
+
className?: string;
|
|
51
|
+
variant?: VariantProps<typeof toggleVariants>['variant'];
|
|
52
|
+
size?: VariantProps<typeof toggleVariants>['size'];
|
|
53
|
+
spacing?: number;
|
|
54
|
+
orientation?: 'horizontal' | 'vertical';
|
|
55
|
+
type?: 'single' | 'multiple';
|
|
56
|
+
children?: OctaneNode;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function ToggleGroup({
|
|
60
|
+
className,
|
|
61
|
+
variant,
|
|
62
|
+
size,
|
|
63
|
+
spacing = 2,
|
|
64
|
+
orientation = 'horizontal',
|
|
65
|
+
type,
|
|
66
|
+
children,
|
|
67
|
+
...props
|
|
68
|
+
}: ToggleGroupProps) {
|
|
69
|
+
return createElement(
|
|
70
|
+
ToggleGroupPrimitive,
|
|
71
|
+
{
|
|
72
|
+
'data-slot': 'toggle-group',
|
|
73
|
+
'data-variant': variant,
|
|
74
|
+
'data-size': size,
|
|
75
|
+
'data-spacing': spacing,
|
|
76
|
+
// Forwarded to the primitive: it drives the arrow-key axis (a vertical group must
|
|
77
|
+
// ignore left/right and use up/down). The primitive also emits `data-orientation`
|
|
78
|
+
// itself, so unlike the radix base this is not set by hand.
|
|
79
|
+
orientation,
|
|
80
|
+
multiple: type === 'multiple',
|
|
81
|
+
style: { '--gap': spacing },
|
|
82
|
+
className: cn(
|
|
83
|
+
'group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs',
|
|
84
|
+
className,
|
|
85
|
+
),
|
|
86
|
+
...props,
|
|
87
|
+
},
|
|
88
|
+
createElement(
|
|
89
|
+
ToggleGroupContext.Provider,
|
|
90
|
+
{ value: { variant, size, spacing, orientation } },
|
|
91
|
+
children,
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface ToggleGroupItemProps extends Record<string, unknown> {
|
|
97
|
+
className?: string;
|
|
98
|
+
variant?: VariantProps<typeof toggleVariants>['variant'];
|
|
99
|
+
size?: VariantProps<typeof toggleVariants>['size'];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function ToggleGroupItem({
|
|
103
|
+
className,
|
|
104
|
+
variant = 'default',
|
|
105
|
+
size = 'default',
|
|
106
|
+
...props
|
|
107
|
+
}: ToggleGroupItemProps) @{
|
|
108
|
+
const context = useContext(ToggleGroupContext);
|
|
109
|
+
<TogglePrimitive
|
|
110
|
+
data-slot="toggle-group-item"
|
|
111
|
+
data-variant={context.variant || variant}
|
|
112
|
+
data-size={context.size || size}
|
|
113
|
+
data-spacing={context.spacing}
|
|
114
|
+
className={cn(
|
|
115
|
+
'min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l',
|
|
116
|
+
toggleVariants({
|
|
117
|
+
variant: context.variant || variant,
|
|
118
|
+
size: context.size || size,
|
|
119
|
+
}),
|
|
120
|
+
className,
|
|
121
|
+
)}
|
|
122
|
+
{...props}
|
|
123
|
+
/>
|
|
124
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Base UI base toggle — runs on @octanejs/base-ui's Toggle.
|
|
2
|
+
//
|
|
3
|
+
// THE PRESSED-STATE DIALECT DIFFERS, verified by rendering the primitive: Base UI emits a bare
|
|
4
|
+
// `data-pressed` attribute (alongside `aria-pressed`), where radix emits `data-state="on"`. So
|
|
5
|
+
// the radix base's `data-[state=on]:` utilities are replaced with `data-pressed:` — copying them
|
|
6
|
+
// unchanged would leave a toggle that never shows its pressed styling, with nothing failing.
|
|
7
|
+
//
|
|
8
|
+
// `disabled:` is kept rather than converted: Base UI's Toggle renders a real `<button>` (again
|
|
9
|
+
// verified by rendering), so the `:disabled` pseudo-class does match here — unlike checkbox,
|
|
10
|
+
// switch and radio, whose Roots are spans.
|
|
11
|
+
//
|
|
12
|
+
// UNVERIFIED PROVENANCE: the non-conditional utilities come from this package's radix base
|
|
13
|
+
// rather than transcribed from upstream's Base UI source.
|
|
14
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
15
|
+
import { Toggle as TogglePrimitive } from '@octanejs/base-ui/toggle';
|
|
16
|
+
|
|
17
|
+
import { cn } from '../../../lib/utils';
|
|
18
|
+
|
|
19
|
+
export const toggleVariants = cva(
|
|
20
|
+
'inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-pressed:bg-accent data-pressed:text-accent-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
|
21
|
+
{
|
|
22
|
+
variants: {
|
|
23
|
+
variant: {
|
|
24
|
+
default: 'bg-transparent',
|
|
25
|
+
outline: 'border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground',
|
|
26
|
+
},
|
|
27
|
+
size: {
|
|
28
|
+
default: 'h-9 min-w-9 px-2',
|
|
29
|
+
sm: 'h-8 min-w-8 px-1.5',
|
|
30
|
+
lg: 'h-10 min-w-10 px-2.5',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: 'default',
|
|
35
|
+
size: 'default',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export interface ToggleProps extends Record<string, unknown> {
|
|
41
|
+
className?: string;
|
|
42
|
+
variant?: VariantProps<typeof toggleVariants>['variant'];
|
|
43
|
+
size?: VariantProps<typeof toggleVariants>['size'];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function Toggle({
|
|
47
|
+
className,
|
|
48
|
+
variant = 'default',
|
|
49
|
+
size = 'default',
|
|
50
|
+
...props
|
|
51
|
+
}: ToggleProps) @{
|
|
52
|
+
<TogglePrimitive
|
|
53
|
+
data-slot="toggle"
|
|
54
|
+
className={cn(toggleVariants({ variant, size, className }))}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
}
|