@octanejs/shadcn 0.0.1
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/LICENSE +21 -0
- package/README.md +43 -0
- package/package.json +63 -0
- package/src/hooks/use-mobile.ts +23 -0
- package/src/index.ts +263 -0
- package/src/lib/types.ts +10 -0
- package/src/lib/utils.ts +8 -0
- package/src/styles/theme.css +104 -0
- package/src/ui/accordion.tsrx +72 -0
- package/src/ui/alert-dialog.tsrx +161 -0
- package/src/ui/alert.tsrx +69 -0
- package/src/ui/aspect-ratio.tsrx +10 -0
- package/src/ui/avatar.tsrx +79 -0
- package/src/ui/badge.tsrx +50 -0
- package/src/ui/breadcrumb.tsrx +89 -0
- package/src/ui/button.tsrx +71 -0
- package/src/ui/card.tsrx +75 -0
- package/src/ui/checkbox.tsrx +31 -0
- package/src/ui/collapsible.tsrx +21 -0
- package/src/ui/context-menu.tsrx +179 -0
- package/src/ui/dialog.tsrx +133 -0
- package/src/ui/dropdown-menu.tsrx +189 -0
- package/src/ui/empty.tsrx +86 -0
- package/src/ui/field.tsrx +188 -0
- package/src/ui/hover-card.tsrx +48 -0
- package/src/ui/input.tsrx +18 -0
- package/src/ui/item.tsrx +171 -0
- package/src/ui/kbd.tsrx +26 -0
- package/src/ui/label.tsrx +21 -0
- package/src/ui/menubar.tsrx +198 -0
- package/src/ui/native-select.tsrx +55 -0
- package/src/ui/navigation-menu.tsrx +116 -0
- package/src/ui/pagination.tsrx +113 -0
- package/src/ui/popover.tsrx +73 -0
- package/src/ui/progress.tsrx +27 -0
- package/src/ui/radio-group.tsrx +37 -0
- package/src/ui/scroll-area.tsrx +43 -0
- package/src/ui/select.tsrx +165 -0
- package/src/ui/separator.tsrx +30 -0
- package/src/ui/sheet.tsrx +114 -0
- package/src/ui/sidebar.tsrx +661 -0
- package/src/ui/skeleton.tsrx +14 -0
- package/src/ui/slider.tsrx +69 -0
- package/src/ui/sonner.tsrx +45 -0
- package/src/ui/spinner.tsrx +23 -0
- package/src/ui/switch.tsrx +30 -0
- package/src/ui/table.tsrx +80 -0
- package/src/ui/tabs.tsrx +69 -0
- package/src/ui/textarea.tsrx +20 -0
- package/src/ui/toggle-group.tsrx +100 -0
- package/src/ui/toggle.tsrx +47 -0
- package/src/ui/tooltip.tsrx +65 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Field — maintainer-supplied default-Tailwind flavor, class strings verbatim.
|
|
2
|
+
// Octane adaptations: "use client" dropped; Label/Separator come from the
|
|
3
|
+
// package's own ports; FieldSeparator and FieldError compose their conditional
|
|
4
|
+
// children with createElement (a `children && …` branch beside a sibling
|
|
5
|
+
// descriptor), everything else is template JSX.
|
|
6
|
+
import { createElement, useMemo, type OctaneNode } from 'octane';
|
|
7
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
8
|
+
|
|
9
|
+
import { cn } from '../lib/utils';
|
|
10
|
+
import { Label } from './label.tsrx';
|
|
11
|
+
import { Separator } from './separator.tsrx';
|
|
12
|
+
|
|
13
|
+
type Props = { className?: string; children?: OctaneNode } & Record<string, unknown>;
|
|
14
|
+
|
|
15
|
+
export function FieldSet({ className, ...props }: Props) @{
|
|
16
|
+
<fieldset
|
|
17
|
+
data-slot="field-set"
|
|
18
|
+
className={cn(
|
|
19
|
+
'flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function FieldLegend({ className, variant = 'legend', ...props }: Props & {
|
|
27
|
+
variant?: 'legend' | 'label';
|
|
28
|
+
}) @{
|
|
29
|
+
<legend
|
|
30
|
+
data-slot="field-legend"
|
|
31
|
+
data-variant={variant}
|
|
32
|
+
className={cn(
|
|
33
|
+
'mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function FieldGroup({ className, ...props }: Props) @{
|
|
41
|
+
<div
|
|
42
|
+
data-slot="field-group"
|
|
43
|
+
className={cn(
|
|
44
|
+
'group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4',
|
|
45
|
+
className,
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const fieldVariants = cva(
|
|
52
|
+
'group/field flex w-full gap-2 data-[invalid=true]:text-destructive',
|
|
53
|
+
{
|
|
54
|
+
variants: {
|
|
55
|
+
orientation: {
|
|
56
|
+
vertical: 'flex-col *:w-full [&>.sr-only]:w-auto',
|
|
57
|
+
horizontal: 'flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
|
|
58
|
+
responsive: 'flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
defaultVariants: {
|
|
62
|
+
orientation: 'vertical',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
export function Field({ className, orientation = 'vertical', ...props }: Props & {
|
|
68
|
+
orientation?: VariantProps<typeof fieldVariants>['orientation'];
|
|
69
|
+
}) @{
|
|
70
|
+
<div
|
|
71
|
+
role="group"
|
|
72
|
+
data-slot="field"
|
|
73
|
+
data-orientation={orientation}
|
|
74
|
+
className={cn(fieldVariants({ orientation }), className)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function FieldContent({ className, ...props }: Props) @{
|
|
80
|
+
<div
|
|
81
|
+
data-slot="field-content"
|
|
82
|
+
className={cn('group/field-content flex flex-1 flex-col gap-0.5 leading-snug', className)}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function FieldLabel({ className, ...props }: Props) @{
|
|
88
|
+
<Label
|
|
89
|
+
data-slot="field-label"
|
|
90
|
+
className={cn(
|
|
91
|
+
'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10',
|
|
92
|
+
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',
|
|
93
|
+
className,
|
|
94
|
+
)}
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function FieldTitle({ className, ...props }: Props) @{
|
|
100
|
+
<div
|
|
101
|
+
data-slot="field-label"
|
|
102
|
+
className={cn(
|
|
103
|
+
'flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50',
|
|
104
|
+
className,
|
|
105
|
+
)}
|
|
106
|
+
{...props}
|
|
107
|
+
/>
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function FieldDescription({ className, ...props }: Props) @{
|
|
111
|
+
<p
|
|
112
|
+
data-slot="field-description"
|
|
113
|
+
className={cn(
|
|
114
|
+
'text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5',
|
|
115
|
+
'last:mt-0 nth-last-2:-mt-1',
|
|
116
|
+
'[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary',
|
|
117
|
+
className,
|
|
118
|
+
)}
|
|
119
|
+
{...props}
|
|
120
|
+
/>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function FieldSeparator({ children, className, ...props }: Props) {
|
|
124
|
+
return createElement(
|
|
125
|
+
'div',
|
|
126
|
+
{
|
|
127
|
+
'data-slot': 'field-separator',
|
|
128
|
+
'data-content': !!children,
|
|
129
|
+
className: cn(
|
|
130
|
+
'relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2',
|
|
131
|
+
className,
|
|
132
|
+
),
|
|
133
|
+
...props,
|
|
134
|
+
},
|
|
135
|
+
createElement(Separator, { key: 'rule', className: 'absolute inset-0 top-1/2' }),
|
|
136
|
+
children
|
|
137
|
+
? createElement(
|
|
138
|
+
'span',
|
|
139
|
+
{
|
|
140
|
+
key: 'content',
|
|
141
|
+
className: 'relative mx-auto block w-fit bg-background px-2 text-muted-foreground',
|
|
142
|
+
'data-slot': 'field-separator-content',
|
|
143
|
+
},
|
|
144
|
+
children,
|
|
145
|
+
)
|
|
146
|
+
: null,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function FieldError(props: Props & { errors?: Array<{ message?: string } | undefined> }) {
|
|
151
|
+
const { className, children: _children, errors: _errors, ...rest } = props;
|
|
152
|
+
// Read through `props` inside the callback: octane's destructured-parameter
|
|
153
|
+
// locals are not the value channel the compiler threads into a closure.
|
|
154
|
+
const content = useMemo(() => {
|
|
155
|
+
if (props.children) return props.children;
|
|
156
|
+
const errors = props.errors;
|
|
157
|
+
if (!errors?.length) return null;
|
|
158
|
+
|
|
159
|
+
const uniqueErrors = [
|
|
160
|
+
...new Map(errors.map((error) => [error?.message, error])).values(),
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
if (uniqueErrors?.length == 1) return uniqueErrors[0]?.message;
|
|
164
|
+
|
|
165
|
+
return createElement(
|
|
166
|
+
'ul',
|
|
167
|
+
{ className: 'ml-4 flex list-disc flex-col gap-1' },
|
|
168
|
+
uniqueErrors.map(
|
|
169
|
+
(error, index) => error?.message
|
|
170
|
+
? createElement('li', { key: index }, error.message)
|
|
171
|
+
: null,
|
|
172
|
+
),
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (!content) return null;
|
|
177
|
+
|
|
178
|
+
return createElement(
|
|
179
|
+
'div',
|
|
180
|
+
{
|
|
181
|
+
role: 'alert',
|
|
182
|
+
'data-slot': 'field-error',
|
|
183
|
+
className: cn('text-sm font-normal text-destructive', className),
|
|
184
|
+
...rest,
|
|
185
|
+
},
|
|
186
|
+
content,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Ported from shadcn-ui/ui@4baadbc6517070ae8f8feb2c97037adc2b305544
|
|
2
|
+
// apps/v4/registry/bases/radix/ui/hover-card.tsx (the `"use client"` directive
|
|
3
|
+
// is dropped — octane has no RSC axis).
|
|
4
|
+
//
|
|
5
|
+
// HoverCardContent renders Portal > Content: the Portal's child must be a real
|
|
6
|
+
// element DESCRIPTOR (radix portals cannot enumerate opaque compiled .tsrx
|
|
7
|
+
// children), so the pair is composed with createElement; the consumer's
|
|
8
|
+
// children flow into the Content primitive through the ordinary props.children
|
|
9
|
+
// channel.
|
|
10
|
+
import { createElement } from 'octane';
|
|
11
|
+
import { HoverCard as HoverCardPrimitive } from '@octanejs/radix';
|
|
12
|
+
|
|
13
|
+
import { cn } from '../lib/utils';
|
|
14
|
+
|
|
15
|
+
export function HoverCard(props: Record<string, unknown>) @{
|
|
16
|
+
<HoverCardPrimitive.Root data-slot="hover-card" {...props} />
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function HoverCardTrigger(props: Record<string, unknown>) @{
|
|
20
|
+
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface HoverCardContentProps extends Record<string, unknown> {
|
|
24
|
+
className?: string;
|
|
25
|
+
align?: 'start' | 'center' | 'end';
|
|
26
|
+
sideOffset?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function HoverCardContent({
|
|
30
|
+
className,
|
|
31
|
+
align = 'center',
|
|
32
|
+
sideOffset = 4,
|
|
33
|
+
...props
|
|
34
|
+
}: HoverCardContentProps) {
|
|
35
|
+
return createElement(HoverCardPrimitive.Portal, {
|
|
36
|
+
'data-slot': 'hover-card-portal',
|
|
37
|
+
children: createElement(HoverCardPrimitive.Content, {
|
|
38
|
+
'data-slot': 'hover-card-content',
|
|
39
|
+
align,
|
|
40
|
+
sideOffset,
|
|
41
|
+
className: cn(
|
|
42
|
+
'z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
43
|
+
className,
|
|
44
|
+
),
|
|
45
|
+
...props,
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Default-Tailwind (package canonical) input — user-supplied flavor, class string verbatim.
|
|
2
|
+
// OCTANE DIVERGENCE: per-keystroke handling is the native onInput event.
|
|
3
|
+
import { cn } from '../lib/utils';
|
|
4
|
+
import type { HostProps } from '../lib/types';
|
|
5
|
+
|
|
6
|
+
export interface InputProps extends HostProps<'input'> {}
|
|
7
|
+
|
|
8
|
+
export function Input({ className, type, ...props }: InputProps) @{
|
|
9
|
+
<input
|
|
10
|
+
type={type}
|
|
11
|
+
data-slot="input"
|
|
12
|
+
className={cn(
|
|
13
|
+
'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none 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',
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
}
|
package/src/ui/item.tsrx
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Item — maintainer-supplied default-Tailwind flavor, class strings verbatim.
|
|
2
|
+
// Octane adaptations: radix-ui Slot → @octanejs/radix (asChild via the @if/Slot
|
|
3
|
+
// descriptor contract); Separator comes from the package's own port.
|
|
4
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
5
|
+
import { Slot } from '@octanejs/radix';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../lib/utils';
|
|
8
|
+
import { Separator } from './separator.tsrx';
|
|
9
|
+
|
|
10
|
+
type DivProps = { className?: string } & Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
export function ItemGroup({ className, ...props }: DivProps) @{
|
|
13
|
+
<div
|
|
14
|
+
role="list"
|
|
15
|
+
data-slot="item-group"
|
|
16
|
+
className={cn(
|
|
17
|
+
'group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2',
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ItemSeparator({ className, ...props }: DivProps) @{
|
|
25
|
+
<Separator
|
|
26
|
+
data-slot="item-separator"
|
|
27
|
+
orientation="horizontal"
|
|
28
|
+
className={cn('my-2', className)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const itemVariants = cva(
|
|
34
|
+
'group/item flex w-full flex-wrap items-center rounded-lg border text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-muted',
|
|
35
|
+
{
|
|
36
|
+
variants: {
|
|
37
|
+
variant: {
|
|
38
|
+
default: 'border-transparent',
|
|
39
|
+
outline: 'border-border',
|
|
40
|
+
muted: 'border-transparent bg-muted/50',
|
|
41
|
+
},
|
|
42
|
+
size: {
|
|
43
|
+
default: 'gap-2.5 px-3 py-2.5',
|
|
44
|
+
sm: 'gap-2.5 px-3 py-2.5',
|
|
45
|
+
xs: 'gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
defaultVariants: {
|
|
49
|
+
variant: 'default',
|
|
50
|
+
size: 'default',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export interface ItemProps extends Record<string, unknown> {
|
|
56
|
+
className?: string;
|
|
57
|
+
variant?: VariantProps<typeof itemVariants>['variant'];
|
|
58
|
+
size?: VariantProps<typeof itemVariants>['size'];
|
|
59
|
+
// OCTANE DIVERGENCE: asChild composes element descriptors (createElement),
|
|
60
|
+
// not opaque compiled .tsrx children — the documented radix Slot contract.
|
|
61
|
+
asChild?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function Item({
|
|
65
|
+
className,
|
|
66
|
+
variant = 'default',
|
|
67
|
+
size = 'default',
|
|
68
|
+
asChild = false,
|
|
69
|
+
...props
|
|
70
|
+
}: ItemProps) @{
|
|
71
|
+
@if (asChild) {
|
|
72
|
+
<Slot
|
|
73
|
+
data-slot="item"
|
|
74
|
+
data-variant={variant}
|
|
75
|
+
data-size={size}
|
|
76
|
+
className={cn(itemVariants({ variant, size, className }))}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
} @else {
|
|
80
|
+
<div
|
|
81
|
+
data-slot="item"
|
|
82
|
+
data-variant={variant}
|
|
83
|
+
data-size={size}
|
|
84
|
+
className={cn(itemVariants({ variant, size, className }))}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const itemMediaVariants = cva(
|
|
91
|
+
'flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none',
|
|
92
|
+
{
|
|
93
|
+
variants: {
|
|
94
|
+
variant: {
|
|
95
|
+
default: 'bg-transparent',
|
|
96
|
+
icon: '[&_svg:not([class*=\'size-\'])]:size-4',
|
|
97
|
+
image: 'size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
defaultVariants: {
|
|
101
|
+
variant: 'default',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
export interface ItemMediaProps extends Record<string, unknown> {
|
|
107
|
+
className?: string;
|
|
108
|
+
variant?: VariantProps<typeof itemMediaVariants>['variant'];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function ItemMedia({ className, variant = 'default', ...props }: ItemMediaProps) @{
|
|
112
|
+
<div
|
|
113
|
+
data-slot="item-media"
|
|
114
|
+
data-variant={variant}
|
|
115
|
+
className={cn(itemMediaVariants({ variant, className }))}
|
|
116
|
+
{...props}
|
|
117
|
+
/>
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function ItemContent({ className, ...props }: DivProps) @{
|
|
121
|
+
<div
|
|
122
|
+
data-slot="item-content"
|
|
123
|
+
className={cn(
|
|
124
|
+
'flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none',
|
|
125
|
+
className,
|
|
126
|
+
)}
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function ItemTitle({ className, ...props }: DivProps) @{
|
|
132
|
+
<div
|
|
133
|
+
data-slot="item-title"
|
|
134
|
+
className={cn(
|
|
135
|
+
'line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium underline-offset-4',
|
|
136
|
+
className,
|
|
137
|
+
)}
|
|
138
|
+
{...props}
|
|
139
|
+
/>
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function ItemDescription({ className, ...props }: DivProps) @{
|
|
143
|
+
<p
|
|
144
|
+
data-slot="item-description"
|
|
145
|
+
className={cn(
|
|
146
|
+
'line-clamp-2 text-left text-sm leading-normal font-normal text-muted-foreground group-data-[size=xs]/item:text-xs [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary',
|
|
147
|
+
className,
|
|
148
|
+
)}
|
|
149
|
+
{...props}
|
|
150
|
+
/>
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function ItemActions({ className, ...props }: DivProps) @{
|
|
154
|
+
<div data-slot="item-actions" className={cn('flex items-center gap-2', className)} {...props} />
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function ItemHeader({ className, ...props }: DivProps) @{
|
|
158
|
+
<div
|
|
159
|
+
data-slot="item-header"
|
|
160
|
+
className={cn('flex basis-full items-center justify-between gap-2', className)}
|
|
161
|
+
{...props}
|
|
162
|
+
/>
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function ItemFooter({ className, ...props }: DivProps) @{
|
|
166
|
+
<div
|
|
167
|
+
data-slot="item-footer"
|
|
168
|
+
className={cn('flex basis-full items-center justify-between gap-2', className)}
|
|
169
|
+
{...props}
|
|
170
|
+
/>
|
|
171
|
+
}
|
package/src/ui/kbd.tsrx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Ported from shadcn-ui/ui@4baadbc6517070ae8f8feb2c97037adc2b305544
|
|
2
|
+
// apps/v4/registry/bases/radix/ui/kbd.tsx. Note: upstream renders KbdGroup as
|
|
3
|
+
// a <kbd> host too — preserved as-is.
|
|
4
|
+
import { cn } from '../lib/utils';
|
|
5
|
+
import type { HostProps } from '../lib/types';
|
|
6
|
+
|
|
7
|
+
export interface KbdProps extends HostProps<'kbd'> {}
|
|
8
|
+
|
|
9
|
+
export function Kbd({ className, ...props }: KbdProps) @{
|
|
10
|
+
<kbd
|
|
11
|
+
data-slot="kbd"
|
|
12
|
+
className={cn(
|
|
13
|
+
'pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none',
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function KbdGroup({ className, ...props }: KbdProps) @{
|
|
21
|
+
<kbd
|
|
22
|
+
data-slot="kbd-group"
|
|
23
|
+
className={cn('inline-flex items-center gap-1', className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Ported from shadcn-ui/ui@4baadbc6517070ae8f8feb2c97037adc2b305544
|
|
2
|
+
// apps/v4/registry/bases/radix/ui/label.tsx (the `"use client"` directive is
|
|
3
|
+
// dropped — octane has no RSC axis).
|
|
4
|
+
import { Label as LabelPrimitive } from '@octanejs/radix';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils';
|
|
7
|
+
|
|
8
|
+
export interface LabelProps extends Record<string, unknown> {
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Label({ className, ...props }: LabelProps) @{
|
|
13
|
+
<LabelPrimitive.Root
|
|
14
|
+
data-slot="label"
|
|
15
|
+
className={cn(
|
|
16
|
+
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
|
|
17
|
+
className,
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// Menubar — maintainer-supplied default-Tailwind flavor, class strings
|
|
2
|
+
// verbatim. Octane adaptations: "use client" dropped, lucide-react →
|
|
3
|
+
// @octanejs/lucide, radix-ui → @octanejs/radix. Content composes through the
|
|
4
|
+
// local MenubarPortal wrapper with createElement (radix's Portal slots its
|
|
5
|
+
// child); parts interleaving consumer children with sibling descriptors use
|
|
6
|
+
// the {props.children} lowering in template JSX.
|
|
7
|
+
import { createElement, type OctaneNode } from 'octane';
|
|
8
|
+
import { Menubar as MenubarPrimitive } from '@octanejs/radix';
|
|
9
|
+
import { CheckIcon, ChevronRightIcon } from '@octanejs/lucide';
|
|
10
|
+
|
|
11
|
+
import { cn } from '../lib/utils';
|
|
12
|
+
|
|
13
|
+
type Props = { className?: string; children?: OctaneNode } & Record<string, unknown>;
|
|
14
|
+
|
|
15
|
+
export function Menubar({ className, ...props }: Props) @{
|
|
16
|
+
<MenubarPrimitive.Root
|
|
17
|
+
data-slot="menubar"
|
|
18
|
+
className={cn('flex h-8 items-center gap-0.5 rounded-lg border p-[3px]', className)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function MenubarMenu(props: Record<string, unknown>) @{
|
|
24
|
+
<MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function MenubarGroup(props: Record<string, unknown>) @{
|
|
28
|
+
<MenubarPrimitive.Group data-slot="menubar-group" {...props} />
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function MenubarPortal(props: Record<string, unknown>) @{
|
|
32
|
+
<MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function MenubarRadioGroup(props: Record<string, unknown>) @{
|
|
36
|
+
<MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function MenubarTrigger({ className, ...props }: Props) @{
|
|
40
|
+
<MenubarPrimitive.Trigger
|
|
41
|
+
data-slot="menubar-trigger"
|
|
42
|
+
className={cn(
|
|
43
|
+
'flex items-center rounded-sm px-1.5 py-[2px] text-sm font-medium outline-hidden select-none hover:bg-muted aria-expanded:bg-muted',
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function MenubarContent({
|
|
51
|
+
className,
|
|
52
|
+
align = 'start',
|
|
53
|
+
alignOffset = -4,
|
|
54
|
+
sideOffset = 8,
|
|
55
|
+
...props
|
|
56
|
+
}: Props & { align?: string; alignOffset?: number; sideOffset?: number }) {
|
|
57
|
+
return createElement(
|
|
58
|
+
MenubarPortal,
|
|
59
|
+
{},
|
|
60
|
+
createElement(MenubarPrimitive.Content, {
|
|
61
|
+
'data-slot': 'menubar-content',
|
|
62
|
+
align,
|
|
63
|
+
alignOffset,
|
|
64
|
+
sideOffset,
|
|
65
|
+
className: cn(
|
|
66
|
+
'z-50 min-w-36 origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95',
|
|
67
|
+
className,
|
|
68
|
+
),
|
|
69
|
+
...props,
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function MenubarItem({ className, inset, variant = 'default', ...props }: Props & {
|
|
75
|
+
inset?: boolean;
|
|
76
|
+
variant?: 'default' | 'destructive';
|
|
77
|
+
}) @{
|
|
78
|
+
<MenubarPrimitive.Item
|
|
79
|
+
data-slot="menubar-item"
|
|
80
|
+
data-inset={inset}
|
|
81
|
+
data-variant={variant}
|
|
82
|
+
className={cn(
|
|
83
|
+
'group/menubar-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive!',
|
|
84
|
+
className,
|
|
85
|
+
)}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function MenubarCheckboxItem(props: Props & {
|
|
91
|
+
checked?: boolean | 'indeterminate';
|
|
92
|
+
inset?: boolean;
|
|
93
|
+
}) @{
|
|
94
|
+
const { className, children: _children, checked, inset, ...rest } = props;
|
|
95
|
+
|
|
96
|
+
<MenubarPrimitive.CheckboxItem
|
|
97
|
+
data-slot="menubar-checkbox-item"
|
|
98
|
+
data-inset={inset}
|
|
99
|
+
className={cn(
|
|
100
|
+
'relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
|
101
|
+
className,
|
|
102
|
+
)}
|
|
103
|
+
checked={checked}
|
|
104
|
+
{...rest}
|
|
105
|
+
>
|
|
106
|
+
<span
|
|
107
|
+
className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4"
|
|
108
|
+
>
|
|
109
|
+
<MenubarPrimitive.ItemIndicator>
|
|
110
|
+
<CheckIcon />
|
|
111
|
+
</MenubarPrimitive.ItemIndicator>
|
|
112
|
+
</span>
|
|
113
|
+
{props.children}
|
|
114
|
+
</MenubarPrimitive.CheckboxItem>
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function MenubarRadioItem(props: Props & { inset?: boolean }) @{
|
|
118
|
+
const { className, children: _children, inset, ...rest } = props;
|
|
119
|
+
|
|
120
|
+
<MenubarPrimitive.RadioItem
|
|
121
|
+
data-slot="menubar-radio-item"
|
|
122
|
+
data-inset={inset}
|
|
123
|
+
className={cn(
|
|
124
|
+
'relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
|
125
|
+
className,
|
|
126
|
+
)}
|
|
127
|
+
{...rest}
|
|
128
|
+
>
|
|
129
|
+
<span
|
|
130
|
+
className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4"
|
|
131
|
+
>
|
|
132
|
+
<MenubarPrimitive.ItemIndicator>
|
|
133
|
+
<CheckIcon />
|
|
134
|
+
</MenubarPrimitive.ItemIndicator>
|
|
135
|
+
</span>
|
|
136
|
+
{props.children}
|
|
137
|
+
</MenubarPrimitive.RadioItem>
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function MenubarLabel({ className, inset, ...props }: Props & { inset?: boolean }) @{
|
|
141
|
+
<MenubarPrimitive.Label
|
|
142
|
+
data-slot="menubar-label"
|
|
143
|
+
data-inset={inset}
|
|
144
|
+
className={cn('px-1.5 py-1 text-sm font-medium data-inset:pl-7', className)}
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function MenubarSeparator({ className, ...props }: Props) @{
|
|
150
|
+
<MenubarPrimitive.Separator
|
|
151
|
+
data-slot="menubar-separator"
|
|
152
|
+
className={cn('-mx-1 my-1 h-px bg-border', className)}
|
|
153
|
+
{...props}
|
|
154
|
+
/>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function MenubarShortcut({ className, ...props }: Props) @{
|
|
158
|
+
<span
|
|
159
|
+
data-slot="menubar-shortcut"
|
|
160
|
+
className={cn(
|
|
161
|
+
'ml-auto text-xs tracking-widest text-muted-foreground group-focus/menubar-item:text-accent-foreground',
|
|
162
|
+
className,
|
|
163
|
+
)}
|
|
164
|
+
{...props}
|
|
165
|
+
/>
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function MenubarSub(props: Record<string, unknown>) @{
|
|
169
|
+
<MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function MenubarSubTrigger(props: Props & { inset?: boolean }) @{
|
|
173
|
+
const { className, children: _children, inset, ...rest } = props;
|
|
174
|
+
|
|
175
|
+
<MenubarPrimitive.SubTrigger
|
|
176
|
+
data-slot="menubar-sub-trigger"
|
|
177
|
+
data-inset={inset}
|
|
178
|
+
className={cn(
|
|
179
|
+
'flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-open:bg-accent data-open:text-accent-foreground [&_svg:not([class*=\'size-\'])]:size-4',
|
|
180
|
+
className,
|
|
181
|
+
)}
|
|
182
|
+
{...rest}
|
|
183
|
+
>
|
|
184
|
+
{props.children}
|
|
185
|
+
<ChevronRightIcon className="cn-rtl-flip ml-auto size-4" />
|
|
186
|
+
</MenubarPrimitive.SubTrigger>
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function MenubarSubContent({ className, ...props }: Props) @{
|
|
190
|
+
<MenubarPrimitive.SubContent
|
|
191
|
+
data-slot="menubar-sub-content"
|
|
192
|
+
className={cn(
|
|
193
|
+
'z-50 min-w-32 origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 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',
|
|
194
|
+
className,
|
|
195
|
+
)}
|
|
196
|
+
{...props}
|
|
197
|
+
/>
|
|
198
|
+
}
|