@octanejs/shadcn 0.0.37 → 0.0.38

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 CHANGED
@@ -1,9 +1,7 @@
1
1
  # @octanejs/shadcn
2
2
 
3
- shadcn/ui for the [octane](https://github.com/octanejs/octane) renderer — a port
4
- of the **Radix base** of [shadcn-ui/ui](https://github.com/shadcn-ui/ui) onto
5
- octane's hooks and native events, built on
6
- [`@octanejs/radix`](../radix).
3
+ shadcn/ui for the [octane](https://github.com/octanejs/octane) renderer, with
4
+ Base UI, Radix, and React Aria bases adapted to octane's hooks and native events.
7
5
 
8
6
  ## Installation
9
7
 
@@ -16,7 +14,7 @@ Upstream pin: `shadcn-ui/ui@4baadbc6` + CLI `shadcn@4.14.1`.
16
14
 
17
15
  ## What ships
18
16
 
19
- - **40 component families (~185 exports)**: the full Tier-1 static set, all 24
17
+ - **44 component families across three bases**: the full Tier-1 static set, all 24
20
18
  radix-backed Tier-2 components, and the first Tier-3 composites (Sidebar with
21
19
  `useSidebar`/`useIsMobile`, Field), plus `cn()` and the default neutral theme
22
20
  tokens (`@octanejs/shadcn/theme.css`).
@@ -34,10 +32,14 @@ Upstream pin: `shadcn-ui/ui@4baadbc6` + CLI `shadcn@4.14.1`.
34
32
 
35
33
  ## Component coverage
36
34
 
37
- Each primitive base ships the families its primitives support, exactly as upstream's do.
35
+ The coverage table tracks the families ported for each primitive base.
38
36
  Switching `style` changes which primitive a component is built on — the `data-slot` contract and
39
37
  component names stay identical.
40
38
 
39
+ The Base UI wrappers target `@octanejs/base-ui`'s Base UI 1.8.0 API. Select,
40
+ Combobox, Navigation Menu, and Scroll Area primitives are available from
41
+ `@octanejs/base-ui`; their shadcn Base UI wrappers have not been ported yet.
42
+
41
43
  <!-- BEGIN COVERAGE -->
42
44
 
43
45
  **44 families** — Radix 44/44 · React Aria 33/44 · Base UI 40/44
@@ -69,13 +71,13 @@ component names stay identical.
69
71
  | `label` | ✅ | ✅ | ✅ |
70
72
  | `menubar` | ✅ | ⛔ | ✅ |
71
73
  | `native-select` | ✅ | ✅ | ✅ |
72
- | `navigation-menu` | ✅ | ⛔ | |
74
+ | `navigation-menu` | ✅ | ⛔ | |
73
75
  | `pagination` | ✅ | ✅ | ✅ |
74
76
  | `popover` | ✅ | ✅ | ✅ |
75
77
  | `progress` | ✅ | ⛔ | ✅ |
76
78
  | `radio-group` | ✅ | ✅ | ✅ |
77
- | `scroll-area` | ✅ | ✅ | |
78
- | `select` | ✅ | ⛔ | |
79
+ | `scroll-area` | ✅ | ✅ | |
80
+ | `select` | ✅ | ⛔ | |
79
81
  | `separator` | ✅ | ✅ | ✅ |
80
82
  | `sheet` | ✅ | ✅ | ✅ |
81
83
  | `sidebar` | ✅ | ⛔ | ✅ |
@@ -95,9 +97,6 @@ component names stay identical.
95
97
 
96
98
  | Base | Family | Reason |
97
99
  | --- | --- | --- |
98
- | Base UI | `navigation-menu` | @octanejs/base-ui has no navigation-menu primitive |
99
- | Base UI | `scroll-area` | @octanejs/base-ui has no scroll-area primitive |
100
- | Base UI | `select` | @octanejs/base-ui has no select primitive |
101
100
  | Base UI | `sonner` | needs next-themes, which has no octane binding |
102
101
  | React Aria | `context-menu` | no counterpart in upstream’s aria base |
103
102
  | React Aria | `dropdown-menu` | no counterpart in upstream’s aria base |
@@ -143,12 +142,12 @@ same mechanism shadcn uses upstream, where its own registry is
143
142
 
144
143
  | `style` | Primitives | Families |
145
144
  | --- | --- | --- |
146
- | `base-nova` *(default)* | `@octanejs/base-ui` | 21 |
145
+ | `base-nova` *(default)* | `@octanejs/base-ui` | 40 |
147
146
  | `radix-nova` | `@octanejs/radix` | 44 |
148
147
  | `aria-nova` | `@octanejs/aria` | 33 |
149
148
 
150
- The bases deliberately ship different family counts, exactly as upstream's do a base only
151
- has the families its primitives support. Switching `style` changes which primitive a component
149
+ The bases currently ship different family counts; see the coverage table for remaining ports.
150
+ Switching `style` changes which primitive a component
152
151
  is built on; the `data-slot` contract and component names stay identical, so your markup and
153
152
  styling do not change.
154
153
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/shadcn",
3
- "version": "0.0.37",
3
+ "version": "0.0.38",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -154,23 +154,23 @@
154
154
  "clsx": "^2.1.1",
155
155
  "tailwind-merge": "3.6.0",
156
156
  "@octanejs/aria": "0.0.46",
157
- "@octanejs/base-ui": "0.1.50",
157
+ "@octanejs/base-ui": "0.1.51",
158
158
  "@octanejs/lucide": "0.1.47",
159
- "@octanejs/radix": "0.1.51",
159
+ "@octanejs/radix": "0.1.52",
160
160
  "@octanejs/sonner": "0.1.47"
161
161
  },
162
162
  "peerDependencies": {
163
- "octane": "^0.1.51 || ^0.2.0"
163
+ "octane": "^0.2.5"
164
164
  },
165
165
  "devDependencies": {
166
- "@tsrx/react": "^0.2.63",
166
+ "@tsrx/react": "^0.2.67",
167
167
  "esbuild": "^0.28.1",
168
168
  "lucide-react": "1.24.0",
169
169
  "radix-ui": "^1.6.4",
170
- "react": "^19.2.7",
171
- "react-dom": "^19.2.7",
170
+ "react": "19.2.7",
171
+ "react-dom": "19.2.7",
172
172
  "vitest": "^4.1.10",
173
- "octane": "0.2.0"
173
+ "octane": "0.2.5"
174
174
  },
175
175
  "scripts": {
176
176
  "test": "vitest run",
@@ -30,21 +30,23 @@ import { cn } from '../../../lib/utils';
30
30
 
31
31
  type Props = { className?: string; children?: OctaneNode } & Record<string, unknown>;
32
32
 
33
- export interface BaseUiPositioningProps {
34
- /** Forwarded to the Positioner; see the note at the top of this file. */
35
- side?: 'top' | 'right' | 'bottom' | 'left';
36
- sideOffset?: number;
37
- align?: 'start' | 'center' | 'end';
38
- alignOffset?: number;
39
- anchor?: unknown;
40
- positionMethod?: 'absolute' | 'fixed';
41
- collisionBoundary?: unknown;
42
- collisionPadding?: unknown;
43
- collisionAvoidance?: unknown;
44
- arrowPadding?: number;
45
- sticky?: boolean;
46
- disableAnchorTracking?: boolean;
47
- }
33
+ /** Props routed to the Positioner rather than the popup. */
34
+ export type BaseUiPositioningProps =
35
+ Pick<
36
+ ContextMenuPrimitive.Positioner.Props,
37
+ | 'side'
38
+ | 'sideOffset'
39
+ | 'align'
40
+ | 'alignOffset'
41
+ | 'anchor'
42
+ | 'positionMethod'
43
+ | 'collisionBoundary'
44
+ | 'collisionPadding'
45
+ | 'collisionAvoidance'
46
+ | 'arrowPadding'
47
+ | 'sticky'
48
+ | 'disableAnchorTracking'
49
+ >;
48
50
 
49
51
  export function ContextMenu(props: Record<string, unknown>) @{
50
52
  <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
@@ -221,7 +223,10 @@ export function ContextMenuCheckboxItem(props: Props & {
221
223
  </ContextMenuPrimitive.CheckboxItem>
222
224
  }
223
225
 
224
- export function ContextMenuRadioItem(props: Props & { inset?: boolean }) @{
226
+ export function ContextMenuRadioItem(props: Omit<
227
+ ContextMenuPrimitive.RadioItem.Props,
228
+ 'className'
229
+ > & { className?: string; inset?: boolean }) @{
225
230
  const { className, children: _children, inset, ...rest } = props;
226
231
 
227
232
  <ContextMenuPrimitive.RadioItem
@@ -10,7 +10,7 @@
10
10
  //
11
11
  // UNVERIFIED PROVENANCE: class strings come from this package's radix base rather than
12
12
  // transcribed from upstream's Base UI source.
13
- import { createElement, type OctaneNode } from 'octane';
13
+ import { type OctaneNode } from 'octane';
14
14
  import { Dialog as DialogPrimitive } from '@octanejs/base-ui/dialog';
15
15
  import { XIcon } from '@octanejs/lucide';
16
16
 
@@ -46,7 +46,8 @@ export function DialogOverlay({ className, ...props }: Props) @{
46
46
  />
47
47
  }
48
48
 
49
- export interface DialogContentProps extends Props {
49
+ export interface DialogContentProps extends Omit<DialogPrimitive.Popup.Props, 'className'> {
50
+ className?: string;
50
51
  showCloseButton?: boolean;
51
52
  }
52
53
 
@@ -55,38 +56,30 @@ export function DialogContent({
55
56
  children,
56
57
  showCloseButton = true,
57
58
  ...props
58
- }: DialogContentProps) {
59
- return createElement(
60
- DialogPrimitive.Portal,
61
- { 'data-slot': 'dialog-portal' },
62
- createElement(DialogOverlay, { key: 'overlay' }),
63
- createElement(
64
- DialogPrimitive.Popup,
65
- {
66
- key: 'content',
67
- 'data-slot': 'dialog-content',
68
- className: cn(
69
- 'fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
70
- className,
71
- ),
72
- ...props,
73
- },
74
- children,
75
- showCloseButton
76
- ? createElement(DialogPrimitive.Close, {
77
- key: 'close',
78
- 'data-slot': 'dialog-close',
79
- className: 'absolute top-2 right-2',
80
- render: createElement(
81
- Button,
82
- { variant: 'ghost', size: 'icon-sm' },
83
- createElement(XIcon, { key: 'icon' }),
84
- createElement('span', { key: 'sr', className: 'sr-only' }, 'Close'),
85
- ),
86
- })
87
- : null,
88
- ),
89
- );
59
+ }: DialogContentProps) @{
60
+ <DialogPrimitive.Portal data-slot="dialog-portal">
61
+ <DialogOverlay />
62
+ <DialogPrimitive.Popup
63
+ data-slot="dialog-content"
64
+ className={cn(
65
+ 'fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
66
+ className,
67
+ )}
68
+ {...props}
69
+ >
70
+ {children}
71
+ @if (showCloseButton) {
72
+ <DialogPrimitive.Close
73
+ data-slot="dialog-close"
74
+ className="absolute top-2 right-2"
75
+ render={<Button variant="ghost" size="icon-sm">
76
+ <XIcon />
77
+ <span className="sr-only">Close</span>
78
+ </Button>}
79
+ />
80
+ }
81
+ </DialogPrimitive.Popup>
82
+ </DialogPrimitive.Portal>
90
83
  }
91
84
 
92
85
  export function DialogHeader({ className, ...props }: Props) @{
@@ -109,11 +102,9 @@ export function DialogFooter(props: DialogFooterProps) @{
109
102
  {...rest}
110
103
  >
111
104
  {props.children}
112
- {showCloseButton
113
- ? createElement(DialogPrimitive.Close, {
114
- render: createElement(Button, { variant: 'outline' }, 'Close'),
115
- })
116
- : null}
105
+ @if (showCloseButton) {
106
+ <DialogPrimitive.Close render={<Button variant="outline">Close</Button>} />
107
+ }
117
108
  </div>
118
109
  }
119
110
 
@@ -57,21 +57,23 @@ export function DropdownMenuTrigger(props: Record<string, unknown>) @{
57
57
  <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
58
58
  }
59
59
 
60
- export interface BaseUiPositioningProps {
61
- /** Forwarded to the Positioner; see the note at the top of this file. */
62
- side?: 'top' | 'right' | 'bottom' | 'left';
63
- sideOffset?: number;
64
- align?: 'start' | 'center' | 'end';
65
- alignOffset?: number;
66
- anchor?: unknown;
67
- positionMethod?: 'absolute' | 'fixed';
68
- collisionBoundary?: unknown;
69
- collisionPadding?: unknown;
70
- collisionAvoidance?: unknown;
71
- arrowPadding?: number;
72
- sticky?: boolean;
73
- disableAnchorTracking?: boolean;
74
- }
60
+ /** Props routed to the Positioner rather than the popup. */
61
+ export type BaseUiPositioningProps =
62
+ Pick<
63
+ MenuPrimitive.Positioner.Props,
64
+ | 'side'
65
+ | 'sideOffset'
66
+ | 'align'
67
+ | 'alignOffset'
68
+ | 'anchor'
69
+ | 'positionMethod'
70
+ | 'collisionBoundary'
71
+ | 'collisionPadding'
72
+ | 'collisionAvoidance'
73
+ | 'arrowPadding'
74
+ | 'sticky'
75
+ | 'disableAnchorTracking'
76
+ >;
75
77
 
76
78
  export interface DropdownMenuContentProps extends Props, BaseUiPositioningProps {}
77
79
 
@@ -170,7 +172,10 @@ export function DropdownMenuRadioGroup(props: Record<string, unknown>) @{
170
172
  <MenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />
171
173
  }
172
174
 
173
- export function DropdownMenuRadioItem(props: Props & { inset?: boolean }) @{
175
+ export function DropdownMenuRadioItem(props: Omit<MenuPrimitive.RadioItem.Props, 'className'> & {
176
+ className?: string;
177
+ inset?: boolean;
178
+ }) @{
174
179
  const { className, children: _children, inset, ...rest } = props;
175
180
 
176
181
  <MenuPrimitive.RadioItem
@@ -28,21 +28,23 @@ import { PreviewCard as PreviewCardPrimitive } from '@octanejs/base-ui/preview-c
28
28
 
29
29
  import { cn } from '../../../lib/utils';
30
30
 
31
- export interface BaseUiPositioningProps {
32
- /** Forwarded to the Positioner; see the note at the top of this file. */
33
- side?: 'top' | 'right' | 'bottom' | 'left';
34
- sideOffset?: number;
35
- align?: 'start' | 'center' | 'end';
36
- alignOffset?: number;
37
- anchor?: unknown;
38
- positionMethod?: 'absolute' | 'fixed';
39
- collisionBoundary?: unknown;
40
- collisionPadding?: unknown;
41
- collisionAvoidance?: unknown;
42
- arrowPadding?: number;
43
- sticky?: boolean;
44
- disableAnchorTracking?: boolean;
45
- }
31
+ /** Props routed to the Positioner rather than the popup. */
32
+ export type BaseUiPositioningProps =
33
+ Pick<
34
+ PreviewCardPrimitive.Positioner.Props,
35
+ | 'side'
36
+ | 'sideOffset'
37
+ | 'align'
38
+ | 'alignOffset'
39
+ | 'anchor'
40
+ | 'positionMethod'
41
+ | 'collisionBoundary'
42
+ | 'collisionPadding'
43
+ | 'collisionAvoidance'
44
+ | 'arrowPadding'
45
+ | 'sticky'
46
+ | 'disableAnchorTracking'
47
+ >;
46
48
 
47
49
  export function HoverCard(props: Record<string, unknown>) @{
48
50
  <PreviewCardPrimitive.Root data-slot="hover-card" {...props} />
@@ -33,21 +33,23 @@ import { cn } from '../../../lib/utils';
33
33
 
34
34
  type Props = { className?: string; children?: OctaneNode } & Record<string, unknown>;
35
35
 
36
- export interface BaseUiPositioningProps {
37
- /** Forwarded to the Positioner; see the note at the top of this file. */
38
- side?: 'top' | 'right' | 'bottom' | 'left';
39
- sideOffset?: number;
40
- align?: 'start' | 'center' | 'end';
41
- alignOffset?: number;
42
- anchor?: unknown;
43
- positionMethod?: 'absolute' | 'fixed';
44
- collisionBoundary?: unknown;
45
- collisionPadding?: unknown;
46
- collisionAvoidance?: unknown;
47
- arrowPadding?: number;
48
- sticky?: boolean;
49
- disableAnchorTracking?: boolean;
50
- }
36
+ /** Props routed to the Positioner rather than the popup. */
37
+ export type BaseUiPositioningProps =
38
+ Pick<
39
+ MenuPrimitive.Positioner.Props,
40
+ | 'side'
41
+ | 'sideOffset'
42
+ | 'align'
43
+ | 'alignOffset'
44
+ | 'anchor'
45
+ | 'positionMethod'
46
+ | 'collisionBoundary'
47
+ | 'collisionPadding'
48
+ | 'collisionAvoidance'
49
+ | 'arrowPadding'
50
+ | 'sticky'
51
+ | 'disableAnchorTracking'
52
+ >;
51
53
 
52
54
  export function Menubar({ className, ...props }: Props) @{
53
55
  <MenubarPrimitive
@@ -170,7 +172,10 @@ export function MenubarCheckboxItem(props: Props & {
170
172
  </MenuPrimitive.CheckboxItem>
171
173
  }
172
174
 
173
- export function MenubarRadioItem(props: Props & { inset?: boolean }) @{
175
+ export function MenubarRadioItem(props: Omit<MenuPrimitive.RadioItem.Props, 'className'> & {
176
+ className?: string;
177
+ inset?: boolean;
178
+ }) @{
174
179
  const { className, children: _children, inset, ...rest } = props;
175
180
 
176
181
  <MenuPrimitive.RadioItem
@@ -41,21 +41,23 @@ export function PopoverTrigger(props: Record<string, unknown>) @{
41
41
  <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
42
42
  }
43
43
 
44
- export interface BaseUiPositioningProps {
45
- /** Forwarded to the Positioner; see the note at the top of this file. */
46
- side?: 'top' | 'right' | 'bottom' | 'left';
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
- }
44
+ /** Props routed to the Positioner rather than the popup. */
45
+ export type BaseUiPositioningProps =
46
+ Pick<
47
+ PopoverPrimitive.Positioner.Props,
48
+ | 'side'
49
+ | 'sideOffset'
50
+ | 'align'
51
+ | 'alignOffset'
52
+ | 'anchor'
53
+ | 'positionMethod'
54
+ | 'collisionBoundary'
55
+ | 'collisionPadding'
56
+ | 'collisionAvoidance'
57
+ | 'arrowPadding'
58
+ | 'sticky'
59
+ | 'disableAnchorTracking'
60
+ >;
59
61
 
60
62
  export interface PopoverContentProps extends Record<string, unknown>, BaseUiPositioningProps {
61
63
  className?: string;
@@ -23,29 +23,26 @@
23
23
  // `thumbAlignment="edge"` is forwarded as upstream does: it insets the thumb so the two extremes
24
24
  // sit flush with the ends of the track instead of overhanging them.
25
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';
26
+ // One keyed thumb is rendered for each value.
27
+ import { useMemo } from 'octane';
29
28
  import { Slider as SliderPrimitive } from '@octanejs/base-ui/slider';
30
29
 
31
30
  import { cn } from '../../../lib/utils';
32
31
 
33
- export interface SliderProps extends Record<string, unknown> {
32
+ export interface SliderProps<
33
+ Value extends number | readonly number[] = number | readonly number[],
34
+ > extends Omit<SliderPrimitive.Root.Props<Value>, 'className'> {
34
35
  className?: string;
35
- defaultValue?: number | number[];
36
- value?: number | number[];
37
- min?: number;
38
- max?: number;
39
36
  }
40
37
 
41
- export function Slider({
38
+ export function Slider<Value extends number | readonly number[]>({
42
39
  className,
43
40
  defaultValue,
44
41
  value,
45
42
  min = 0,
46
43
  max = 100,
47
44
  ...props
48
- }: SliderProps) {
45
+ }: SliderProps<Value>) @{
49
46
  // This drives HOW MANY THUMBS get rendered, so a value shape it fails to recognise is a visible
50
47
  // bug rather than a type nicety.
51
48
  //
@@ -67,45 +64,37 @@ export function Slider({
67
64
  return [min, max];
68
65
  }, [value, defaultValue, min, max]);
69
66
 
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
- );
67
+ <SliderPrimitive.Root
68
+ className={cn(
69
+ 'data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full',
70
+ className,
71
+ )}
72
+ data-slot="slider"
73
+ defaultValue={defaultValue}
74
+ value={value}
75
+ min={min}
76
+ max={max}
77
+ thumbAlignment="edge"
78
+ {...props}
79
+ >
80
+ <SliderPrimitive.Control
81
+ 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"
82
+ >
83
+ <SliderPrimitive.Track
84
+ data-slot="slider-track"
85
+ 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"
86
+ >
87
+ <SliderPrimitive.Indicator
88
+ data-slot="slider-range"
89
+ className="bg-primary select-none data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
90
+ />
91
+ </SliderPrimitive.Track>
92
+ @for (const index of _values.keys(); key index) {
93
+ <SliderPrimitive.Thumb
94
+ data-slot="slider-thumb"
95
+ 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"
96
+ />
97
+ }
98
+ </SliderPrimitive.Control>
99
+ </SliderPrimitive.Root>
111
100
  }
@@ -2,17 +2,8 @@
2
2
  // class strings verbatim. Runs on @octanejs/base-ui's `Tabs`, which was ported for this family:
3
3
  // Root / List / Tab / Panel.
4
4
  //
5
- // ONE DEPARTURE FROM THE SOURCE, FORCED BY A VERSION SKEW. Upstream writes its orientation variants
6
- // as `data-horizontal:` / `data-vertical:` bare attributes. The pinned @octanejs/base-ui (v1.6.0)
7
- // converts the orientation state to `data-orientation="horizontal" | "vertical"` instead, so those
8
- // utilities would match nothing: the root would never switch to a column, and every
9
- // `group-data-horizontal/tabs:` and `group-data-vertical/tabs:` selector on the trigger would be
10
- // dead. They are written as `data-[orientation=…]:` here.
11
- //
12
- // This is the SAME skew the slider hit, and it is not a porting mistake in either layer — shadcn's
13
- // Base UI sources target a newer Base UI than the pin this package binds. The primitive stays
14
- // faithful to its pin; the adaptation belongs here. When the binding moves to a release that emits
15
- // the bare attributes, these revert to upstream's spelling.
5
+ // Base UI 1.8 emits `data-orientation="horizontal" | "vertical"`, so orientation
6
+ // selectors target the attribute value on the root, list, trigger, and panel.
16
7
  //
17
8
  // `data-active` on the trigger needs no such treatment: the pin emits it exactly as upstream's
18
9
  // classes expect (verified by rendering).
@@ -23,9 +14,9 @@ import { Tabs as TabsPrimitive } from '@octanejs/base-ui/tabs';
23
14
 
24
15
  import { cn } from '../../../lib/utils';
25
16
 
26
- type Props = { className?: string } & Record<string, unknown>;
17
+ type StyledProps<P> = Omit<P, 'className'> & { className?: string };
27
18
 
28
- export interface TabsProps extends Props {
19
+ export interface TabsProps extends StyledProps<TabsPrimitive.Root.Props> {
29
20
  orientation?: 'horizontal' | 'vertical';
30
21
  }
31
22
 
@@ -54,7 +45,7 @@ export const tabsListVariants = cva(
54
45
  },
55
46
  );
56
47
 
57
- export interface TabsListProps extends Props {
48
+ export interface TabsListProps extends StyledProps<TabsPrimitive.List.Props> {
58
49
  variant?: VariantProps<typeof tabsListVariants>['variant'];
59
50
  }
60
51
 
@@ -67,7 +58,7 @@ export function TabsList({ className, variant = 'default', ...props }: TabsListP
67
58
  />
68
59
  }
69
60
 
70
- export function TabsTrigger({ className, ...props }: Props) @{
61
+ export function TabsTrigger({ className, ...props }: StyledProps<TabsPrimitive.Tab.Props>) @{
71
62
  <TabsPrimitive.Tab
72
63
  data-slot="tabs-trigger"
73
64
  className={cn(
@@ -81,7 +72,7 @@ export function TabsTrigger({ className, ...props }: Props) @{
81
72
  />
82
73
  }
83
74
 
84
- export function TabsContent({ className, ...props }: Props) @{
75
+ export function TabsContent({ className, ...props }: StyledProps<TabsPrimitive.Panel.Props>) @{
85
76
  <TabsPrimitive.Panel
86
77
  data-slot="tabs-content"
87
78
  className={cn('flex-1 text-sm outline-none', className)}
@@ -20,13 +20,9 @@
20
20
  // The pressed dialect is `data-pressed`, not radix's `data-[state=on]` — see toggle.tsrx, whose
21
21
  // `toggleVariants` this file reuses, so the two bases cannot drift apart.
22
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
23
  // UNVERIFIED PROVENANCE: class strings come from this package's radix base rather than
28
24
  // transcribed from upstream's Base UI source.
29
- import { createContext, createElement, useContext, type OctaneNode } from 'octane';
25
+ import { createContext, useContext, type CSSProperties, type OctaneNode } from 'octane';
30
26
  import { type VariantProps } from 'class-variance-authority';
31
27
  import { ToggleGroup as ToggleGroupPrimitive } from '@octanejs/base-ui/toggle-group';
32
28
  import { Toggle as TogglePrimitive } from '@octanejs/base-ui/toggle';
@@ -65,32 +61,26 @@ export function ToggleGroup({
65
61
  type,
66
62
  children,
67
63
  ...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
- );
64
+ }: ToggleGroupProps) @{
65
+ const style: CSSProperties & { '--gap': number } = { '--gap': spacing };
66
+ <ToggleGroupPrimitive
67
+ data-slot="toggle-group"
68
+ data-variant={variant}
69
+ data-size={size}
70
+ data-spacing={spacing}
71
+ orientation={orientation}
72
+ multiple={type === 'multiple'}
73
+ style={style}
74
+ className={cn(
75
+ 'group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs',
76
+ className,
77
+ )}
78
+ {...props}
79
+ >
80
+ <ToggleGroupContext.Provider
81
+ value={{ variant, size, spacing, orientation }}
82
+ >{children}</ToggleGroupContext.Provider>
83
+ </ToggleGroupPrimitive>
94
84
  }
95
85
 
96
86
  export interface ToggleGroupItemProps extends Record<string, unknown> {
@@ -39,21 +39,23 @@ export interface TooltipContentProps extends Record<string, unknown> {
39
39
  children?: OctaneNode;
40
40
  }
41
41
 
42
- export interface BaseUiPositioningProps {
43
- /** Forwarded to the Positioner; see the note at the top of this file. */
44
- side?: 'top' | 'right' | 'bottom' | 'left';
45
- sideOffset?: number;
46
- align?: 'start' | 'center' | 'end';
47
- alignOffset?: number;
48
- anchor?: unknown;
49
- positionMethod?: 'absolute' | 'fixed';
50
- collisionBoundary?: unknown;
51
- collisionPadding?: unknown;
52
- collisionAvoidance?: unknown;
53
- arrowPadding?: number;
54
- sticky?: boolean;
55
- disableAnchorTracking?: boolean;
56
- }
42
+ /** Props routed to the Positioner rather than the popup. */
43
+ export type BaseUiPositioningProps =
44
+ Pick<
45
+ TooltipPrimitive.Positioner.Props,
46
+ | 'side'
47
+ | 'sideOffset'
48
+ | 'align'
49
+ | 'alignOffset'
50
+ | 'anchor'
51
+ | 'positionMethod'
52
+ | 'collisionBoundary'
53
+ | 'collisionPadding'
54
+ | 'collisionAvoidance'
55
+ | 'arrowPadding'
56
+ | 'sticky'
57
+ | 'disableAnchorTracking'
58
+ >;
57
59
 
58
60
  export function TooltipContent({
59
61
  className,