@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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +43 -0
  3. package/package.json +63 -0
  4. package/src/hooks/use-mobile.ts +23 -0
  5. package/src/index.ts +263 -0
  6. package/src/lib/types.ts +10 -0
  7. package/src/lib/utils.ts +8 -0
  8. package/src/styles/theme.css +104 -0
  9. package/src/ui/accordion.tsrx +72 -0
  10. package/src/ui/alert-dialog.tsrx +161 -0
  11. package/src/ui/alert.tsrx +69 -0
  12. package/src/ui/aspect-ratio.tsrx +10 -0
  13. package/src/ui/avatar.tsrx +79 -0
  14. package/src/ui/badge.tsrx +50 -0
  15. package/src/ui/breadcrumb.tsrx +89 -0
  16. package/src/ui/button.tsrx +71 -0
  17. package/src/ui/card.tsrx +75 -0
  18. package/src/ui/checkbox.tsrx +31 -0
  19. package/src/ui/collapsible.tsrx +21 -0
  20. package/src/ui/context-menu.tsrx +179 -0
  21. package/src/ui/dialog.tsrx +133 -0
  22. package/src/ui/dropdown-menu.tsrx +189 -0
  23. package/src/ui/empty.tsrx +86 -0
  24. package/src/ui/field.tsrx +188 -0
  25. package/src/ui/hover-card.tsrx +48 -0
  26. package/src/ui/input.tsrx +18 -0
  27. package/src/ui/item.tsrx +171 -0
  28. package/src/ui/kbd.tsrx +26 -0
  29. package/src/ui/label.tsrx +21 -0
  30. package/src/ui/menubar.tsrx +198 -0
  31. package/src/ui/native-select.tsrx +55 -0
  32. package/src/ui/navigation-menu.tsrx +116 -0
  33. package/src/ui/pagination.tsrx +113 -0
  34. package/src/ui/popover.tsrx +73 -0
  35. package/src/ui/progress.tsrx +27 -0
  36. package/src/ui/radio-group.tsrx +37 -0
  37. package/src/ui/scroll-area.tsrx +43 -0
  38. package/src/ui/select.tsrx +165 -0
  39. package/src/ui/separator.tsrx +30 -0
  40. package/src/ui/sheet.tsrx +114 -0
  41. package/src/ui/sidebar.tsrx +661 -0
  42. package/src/ui/skeleton.tsrx +14 -0
  43. package/src/ui/slider.tsrx +69 -0
  44. package/src/ui/sonner.tsrx +45 -0
  45. package/src/ui/spinner.tsrx +23 -0
  46. package/src/ui/switch.tsrx +30 -0
  47. package/src/ui/table.tsrx +80 -0
  48. package/src/ui/tabs.tsrx +69 -0
  49. package/src/ui/textarea.tsrx +20 -0
  50. package/src/ui/toggle-group.tsrx +100 -0
  51. package/src/ui/toggle.tsrx +47 -0
  52. package/src/ui/tooltip.tsrx +65 -0
@@ -0,0 +1,179 @@
1
+ // ContextMenu — 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 its Portal
4
+ // subtree with createElement (radix's Portal slots its child); parts that
5
+ // interleave consumer children with sibling descriptors use the
6
+ // {props.children} lowering in template JSX.
7
+ import { createElement, type OctaneNode } from 'octane';
8
+ import { ContextMenu as ContextMenuPrimitive } 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 ContextMenu(props: Record<string, unknown>) @{
16
+ <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
17
+ }
18
+
19
+ export function ContextMenuTrigger({ className, ...props }: Props) @{
20
+ <ContextMenuPrimitive.Trigger
21
+ data-slot="context-menu-trigger"
22
+ className={cn('select-none', className)}
23
+ {...props}
24
+ />
25
+ }
26
+
27
+ export function ContextMenuGroup(props: Record<string, unknown>) @{
28
+ <ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
29
+ }
30
+
31
+ export function ContextMenuPortal(props: Record<string, unknown>) @{
32
+ <ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
33
+ }
34
+
35
+ export function ContextMenuSub(props: Record<string, unknown>) @{
36
+ <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
37
+ }
38
+
39
+ export function ContextMenuRadioGroup(props: Record<string, unknown>) @{
40
+ <ContextMenuPrimitive.RadioGroup data-slot="context-menu-radio-group" {...props} />
41
+ }
42
+
43
+ export function ContextMenuContent({ className, ...props }: Props & {
44
+ side?: 'top' | 'right' | 'bottom' | 'left';
45
+ }) {
46
+ return createElement(
47
+ ContextMenuPrimitive.Portal,
48
+ null,
49
+ createElement(ContextMenuPrimitive.Content, {
50
+ 'data-slot': 'context-menu-content',
51
+ className: cn(
52
+ 'z-50 max-h-(--radix-context-menu-content-available-height) min-w-36 origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto 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 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
53
+ className,
54
+ ),
55
+ ...props,
56
+ }),
57
+ );
58
+ }
59
+
60
+ export function ContextMenuItem({ className, inset, variant = 'default', ...props }: Props & {
61
+ inset?: boolean;
62
+ variant?: 'default' | 'destructive';
63
+ }) @{
64
+ <ContextMenuPrimitive.Item
65
+ data-slot="context-menu-item"
66
+ data-inset={inset}
67
+ data-variant={variant}
68
+ className={cn(
69
+ 'group/context-menu-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 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 focus:*:[svg]:text-accent-foreground data-[variant=destructive]:*:[svg]:text-destructive',
70
+ className,
71
+ )}
72
+ {...props}
73
+ />
74
+ }
75
+
76
+ export function ContextMenuSubTrigger(props: Props & { inset?: boolean }) @{
77
+ const { className, children: _children, inset, ...rest } = props;
78
+
79
+ <ContextMenuPrimitive.SubTrigger
80
+ data-slot="context-menu-sub-trigger"
81
+ data-inset={inset}
82
+ className={cn(
83
+ '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 data-inset:pl-7 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
84
+ className,
85
+ )}
86
+ {...rest}
87
+ >
88
+ {props.children}
89
+ <ChevronRightIcon className="cn-rtl-flip ml-auto" />
90
+ </ContextMenuPrimitive.SubTrigger>
91
+ }
92
+
93
+ export function ContextMenuSubContent({ className, ...props }: Props) @{
94
+ <ContextMenuPrimitive.SubContent
95
+ data-slot="context-menu-sub-content"
96
+ className={cn(
97
+ 'z-50 min-w-32 origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-lg border bg-popover p-1 text-popover-foreground shadow-lg 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',
98
+ className,
99
+ )}
100
+ {...props}
101
+ />
102
+ }
103
+
104
+ export function ContextMenuCheckboxItem(props: Props & {
105
+ checked?: boolean | 'indeterminate';
106
+ inset?: boolean;
107
+ }) @{
108
+ const { className, children: _children, checked, inset, ...rest } = props;
109
+
110
+ <ContextMenuPrimitive.CheckboxItem
111
+ data-slot="context-menu-checkbox-item"
112
+ data-inset={inset}
113
+ className={cn(
114
+ 'relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent 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',
115
+ className,
116
+ )}
117
+ checked={checked}
118
+ {...rest}
119
+ >
120
+ <span className="pointer-events-none absolute right-2">
121
+ <ContextMenuPrimitive.ItemIndicator>
122
+ <CheckIcon />
123
+ </ContextMenuPrimitive.ItemIndicator>
124
+ </span>
125
+ {props.children}
126
+ </ContextMenuPrimitive.CheckboxItem>
127
+ }
128
+
129
+ export function ContextMenuRadioItem(props: Props & { inset?: boolean }) @{
130
+ const { className, children: _children, inset, ...rest } = props;
131
+
132
+ <ContextMenuPrimitive.RadioItem
133
+ data-slot="context-menu-radio-item"
134
+ data-inset={inset}
135
+ className={cn(
136
+ 'relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent 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',
137
+ className,
138
+ )}
139
+ {...rest}
140
+ >
141
+ <span className="pointer-events-none absolute right-2">
142
+ <ContextMenuPrimitive.ItemIndicator>
143
+ <CheckIcon />
144
+ </ContextMenuPrimitive.ItemIndicator>
145
+ </span>
146
+ {props.children}
147
+ </ContextMenuPrimitive.RadioItem>
148
+ }
149
+
150
+ export function ContextMenuLabel({ className, inset, ...props }: Props & { inset?: boolean }) @{
151
+ <ContextMenuPrimitive.Label
152
+ data-slot="context-menu-label"
153
+ data-inset={inset}
154
+ className={cn(
155
+ 'px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7',
156
+ className,
157
+ )}
158
+ {...props}
159
+ />
160
+ }
161
+
162
+ export function ContextMenuSeparator({ className, ...props }: Props) @{
163
+ <ContextMenuPrimitive.Separator
164
+ data-slot="context-menu-separator"
165
+ className={cn('-mx-1 my-1 h-px bg-border', className)}
166
+ {...props}
167
+ />
168
+ }
169
+
170
+ export function ContextMenuShortcut({ className, ...props }: Props) @{
171
+ <span
172
+ data-slot="context-menu-shortcut"
173
+ className={cn(
174
+ 'ml-auto text-xs tracking-widest text-muted-foreground group-focus/context-menu-item:text-accent-foreground',
175
+ className,
176
+ )}
177
+ {...props}
178
+ />
179
+ }
@@ -0,0 +1,133 @@
1
+ // Default-Tailwind dialog flavor (package canonical) — user-supplied styling,
2
+ // class strings verbatim. Octane adaptations: "use client" dropped,
3
+ // lucide-react → @octanejs/lucide, radix-ui → @octanejs/radix. DialogContent
4
+ // composes Portal > Overlay + Content with createElement (radix's Portal
5
+ // slots its child — descriptors required); consumer children flow through
6
+ // the props.children channel. Template parts use the {props.children}
7
+ // lowering.
8
+ import { createElement, type OctaneNode } from 'octane';
9
+ import { Dialog as DialogPrimitive } from '@octanejs/radix';
10
+ import { XIcon } from '@octanejs/lucide';
11
+
12
+ import { cn } from '../lib/utils';
13
+ import { Button } from './button.tsrx';
14
+
15
+ type Props = { className?: string; children?: OctaneNode } & Record<string, unknown>;
16
+
17
+ export function Dialog(props: Record<string, unknown>) @{
18
+ <DialogPrimitive.Root data-slot="dialog" {...props} />
19
+ }
20
+
21
+ export function DialogTrigger(props: Record<string, unknown>) @{
22
+ <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
23
+ }
24
+
25
+ export function DialogPortal(props: Record<string, unknown>) @{
26
+ <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
27
+ }
28
+
29
+ export function DialogClose(props: Record<string, unknown>) @{
30
+ <DialogPrimitive.Close data-slot="dialog-close" {...props} />
31
+ }
32
+
33
+ export function DialogOverlay({ className, ...props }: Props) @{
34
+ <DialogPrimitive.Overlay
35
+ data-slot="dialog-overlay"
36
+ className={cn(
37
+ 'fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0',
38
+ className,
39
+ )}
40
+ {...props}
41
+ />
42
+ }
43
+
44
+ export interface DialogContentProps extends Props {
45
+ showCloseButton?: boolean;
46
+ }
47
+
48
+ export function DialogContent({
49
+ className,
50
+ children,
51
+ showCloseButton = true,
52
+ ...props
53
+ }: DialogContentProps) {
54
+ return createElement(
55
+ DialogPrimitive.Portal,
56
+ { 'data-slot': 'dialog-portal' },
57
+ createElement(DialogOverlay, { key: 'overlay' }),
58
+ createElement(
59
+ DialogPrimitive.Content,
60
+ {
61
+ key: 'content',
62
+ 'data-slot': 'dialog-content',
63
+ className: cn(
64
+ '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',
65
+ className,
66
+ ),
67
+ ...props,
68
+ },
69
+ children,
70
+ showCloseButton
71
+ ? createElement(
72
+ DialogPrimitive.Close,
73
+ { key: 'close', 'data-slot': 'dialog-close', asChild: true },
74
+ createElement(
75
+ Button,
76
+ { variant: 'ghost', className: 'absolute top-2 right-2', size: 'icon-sm' },
77
+ createElement(XIcon, { key: 'icon' }),
78
+ createElement('span', { key: 'sr', className: 'sr-only' }, 'Close'),
79
+ ),
80
+ )
81
+ : null,
82
+ ),
83
+ );
84
+ }
85
+
86
+ export function DialogHeader({ className, ...props }: Props) @{
87
+ <div data-slot="dialog-header" className={cn('flex flex-col gap-2', className)} {...props} />
88
+ }
89
+
90
+ export interface DialogFooterProps extends Props {
91
+ showCloseButton?: boolean;
92
+ }
93
+
94
+ export function DialogFooter(props: DialogFooterProps) @{
95
+ const { className, showCloseButton = false, children: _children, ...rest } = props;
96
+
97
+ <div
98
+ data-slot="dialog-footer"
99
+ className={cn(
100
+ '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',
101
+ className,
102
+ )}
103
+ {...rest}
104
+ >
105
+ {props.children}
106
+ {showCloseButton
107
+ ? createElement(
108
+ DialogPrimitive.Close,
109
+ { asChild: true },
110
+ createElement(Button, { variant: 'outline' }, 'Close'),
111
+ )
112
+ : null}
113
+ </div>
114
+ }
115
+
116
+ export function DialogTitle({ className, ...props }: Props) @{
117
+ <DialogPrimitive.Title
118
+ data-slot="dialog-title"
119
+ className={cn('cn-font-heading text-base leading-none font-medium', className)}
120
+ {...props}
121
+ />
122
+ }
123
+
124
+ export function DialogDescription({ className, ...props }: Props) @{
125
+ <DialogPrimitive.Description
126
+ data-slot="dialog-description"
127
+ className={cn(
128
+ 'text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',
129
+ className,
130
+ )}
131
+ {...props}
132
+ />
133
+ }
@@ -0,0 +1,189 @@
1
+ // DropdownMenu — 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 its Portal
4
+ // subtree with createElement (radix's Portal slots its child, so the child must
5
+ // be a real element descriptor); the parts that interleave consumer children
6
+ // with sibling descriptors use the {props.children} lowering in template JSX.
7
+ import { createElement, type OctaneNode } from 'octane';
8
+ import { DropdownMenu as DropdownMenuPrimitive } 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 DropdownMenu(props: Record<string, unknown>) @{
16
+ <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
17
+ }
18
+
19
+ export function DropdownMenuPortal(props: Record<string, unknown>) @{
20
+ <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
21
+ }
22
+
23
+ export function DropdownMenuTrigger(props: Record<string, unknown>) @{
24
+ <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
25
+ }
26
+
27
+ export function DropdownMenuContent({
28
+ className,
29
+ align = 'start',
30
+ sideOffset = 4,
31
+ ...props
32
+ }: Props & {
33
+ align?: string;
34
+ sideOffset?: number;
35
+ }) {
36
+ return createElement(
37
+ DropdownMenuPrimitive.Portal,
38
+ null,
39
+ createElement(DropdownMenuPrimitive.Content, {
40
+ 'data-slot': 'dropdown-menu-content',
41
+ sideOffset,
42
+ align,
43
+ className: cn(
44
+ 'z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto 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-[state=closed]:overflow-hidden 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',
45
+ className,
46
+ ),
47
+ ...props,
48
+ }),
49
+ );
50
+ }
51
+
52
+ export function DropdownMenuGroup(props: Record<string, unknown>) @{
53
+ <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
54
+ }
55
+
56
+ export function DropdownMenuItem({ className, inset, variant = 'default', ...props }: Props & {
57
+ inset?: boolean;
58
+ variant?: 'default' | 'destructive';
59
+ }) @{
60
+ <DropdownMenuPrimitive.Item
61
+ data-slot="dropdown-menu-item"
62
+ data-inset={inset}
63
+ data-variant={variant}
64
+ className={cn(
65
+ 'group/dropdown-menu-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',
66
+ className,
67
+ )}
68
+ {...props}
69
+ />
70
+ }
71
+
72
+ export function DropdownMenuCheckboxItem(props: Props & {
73
+ checked?: boolean | 'indeterminate';
74
+ inset?: boolean;
75
+ }) @{
76
+ const { className, children: _children, checked, inset, ...rest } = props;
77
+
78
+ <DropdownMenuPrimitive.CheckboxItem
79
+ data-slot="dropdown-menu-checkbox-item"
80
+ data-inset={inset}
81
+ className={cn(
82
+ 'relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 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',
83
+ className,
84
+ )}
85
+ checked={checked}
86
+ {...rest}
87
+ >
88
+ <span
89
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
90
+ data-slot="dropdown-menu-checkbox-item-indicator"
91
+ >
92
+ <DropdownMenuPrimitive.ItemIndicator>
93
+ <CheckIcon />
94
+ </DropdownMenuPrimitive.ItemIndicator>
95
+ </span>
96
+ {props.children}
97
+ </DropdownMenuPrimitive.CheckboxItem>
98
+ }
99
+
100
+ export function DropdownMenuRadioGroup(props: Record<string, unknown>) @{
101
+ <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />
102
+ }
103
+
104
+ export function DropdownMenuRadioItem(props: Props & { inset?: boolean }) @{
105
+ const { className, children: _children, inset, ...rest } = props;
106
+
107
+ <DropdownMenuPrimitive.RadioItem
108
+ data-slot="dropdown-menu-radio-item"
109
+ data-inset={inset}
110
+ className={cn(
111
+ 'relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 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',
112
+ className,
113
+ )}
114
+ {...rest}
115
+ >
116
+ <span
117
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
118
+ data-slot="dropdown-menu-radio-item-indicator"
119
+ >
120
+ <DropdownMenuPrimitive.ItemIndicator>
121
+ <CheckIcon />
122
+ </DropdownMenuPrimitive.ItemIndicator>
123
+ </span>
124
+ {props.children}
125
+ </DropdownMenuPrimitive.RadioItem>
126
+ }
127
+
128
+ export function DropdownMenuLabel({ className, inset, ...props }: Props & { inset?: boolean }) @{
129
+ <DropdownMenuPrimitive.Label
130
+ data-slot="dropdown-menu-label"
131
+ data-inset={inset}
132
+ className={cn(
133
+ 'px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7',
134
+ className,
135
+ )}
136
+ {...props}
137
+ />
138
+ }
139
+
140
+ export function DropdownMenuSeparator({ className, ...props }: Props) @{
141
+ <DropdownMenuPrimitive.Separator
142
+ data-slot="dropdown-menu-separator"
143
+ className={cn('-mx-1 my-1 h-px bg-border', className)}
144
+ {...props}
145
+ />
146
+ }
147
+
148
+ export function DropdownMenuShortcut({ className, ...props }: Props) @{
149
+ <span
150
+ data-slot="dropdown-menu-shortcut"
151
+ className={cn(
152
+ 'ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground',
153
+ className,
154
+ )}
155
+ {...props}
156
+ />
157
+ }
158
+
159
+ export function DropdownMenuSub(props: Record<string, unknown>) @{
160
+ <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
161
+ }
162
+
163
+ export function DropdownMenuSubTrigger(props: Props & { inset?: boolean }) @{
164
+ const { className, children: _children, inset, ...rest } = props;
165
+
166
+ <DropdownMenuPrimitive.SubTrigger
167
+ data-slot="dropdown-menu-sub-trigger"
168
+ data-inset={inset}
169
+ className={cn(
170
+ '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-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
171
+ className,
172
+ )}
173
+ {...rest}
174
+ >
175
+ {props.children}
176
+ <ChevronRightIcon className="cn-rtl-flip ml-auto" />
177
+ </DropdownMenuPrimitive.SubTrigger>
178
+ }
179
+
180
+ export function DropdownMenuSubContent({ className, ...props }: Props) @{
181
+ <DropdownMenuPrimitive.SubContent
182
+ data-slot="dropdown-menu-sub-content"
183
+ className={cn(
184
+ 'z-50 min-w-[96px] origin-(--radix-dropdown-menu-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',
185
+ className,
186
+ )}
187
+ {...props}
188
+ />
189
+ }
@@ -0,0 +1,86 @@
1
+ // Empty — maintainer-supplied default-Tailwind flavor, class strings verbatim.
2
+ // Upstream renders EmptyMedia with data-slot="empty-icon" and EmptyDescription
3
+ // as a <div> (typed as "p") — both preserved as-is.
4
+ import { cva, type VariantProps } from 'class-variance-authority';
5
+
6
+ import { cn } from '../lib/utils';
7
+
8
+ type DivProps = { className?: string } & Record<string, unknown>;
9
+
10
+ export function Empty({ className, ...props }: DivProps) @{
11
+ <div
12
+ data-slot="empty"
13
+ className={cn(
14
+ 'flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 text-center text-balance',
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ }
20
+
21
+ export function EmptyHeader({ className, ...props }: DivProps) @{
22
+ <div
23
+ data-slot="empty-header"
24
+ className={cn('flex max-w-sm flex-col items-center gap-2', className)}
25
+ {...props}
26
+ />
27
+ }
28
+
29
+ export const emptyMediaVariants = cva(
30
+ 'mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0',
31
+ {
32
+ variants: {
33
+ variant: {
34
+ default: 'bg-transparent',
35
+ icon: 'flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*=\'size-\'])]:size-4',
36
+ },
37
+ },
38
+ defaultVariants: {
39
+ variant: 'default',
40
+ },
41
+ },
42
+ );
43
+
44
+ export interface EmptyMediaProps extends Record<string, unknown> {
45
+ className?: string;
46
+ variant?: VariantProps<typeof emptyMediaVariants>['variant'];
47
+ }
48
+
49
+ export function EmptyMedia({ className, variant = 'default', ...props }: EmptyMediaProps) @{
50
+ <div
51
+ data-slot="empty-icon"
52
+ data-variant={variant}
53
+ className={cn(emptyMediaVariants({ variant, className }))}
54
+ {...props}
55
+ />
56
+ }
57
+
58
+ export function EmptyTitle({ className, ...props }: DivProps) @{
59
+ <div
60
+ data-slot="empty-title"
61
+ className={cn('cn-font-heading text-sm font-medium tracking-tight', className)}
62
+ {...props}
63
+ />
64
+ }
65
+
66
+ export function EmptyDescription({ className, ...props }: DivProps) @{
67
+ <div
68
+ data-slot="empty-description"
69
+ className={cn(
70
+ 'text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary',
71
+ className,
72
+ )}
73
+ {...props}
74
+ />
75
+ }
76
+
77
+ export function EmptyContent({ className, ...props }: DivProps) @{
78
+ <div
79
+ data-slot="empty-content"
80
+ className={cn(
81
+ 'flex w-full max-w-sm min-w-0 flex-col items-center gap-2.5 text-sm text-balance',
82
+ className,
83
+ )}
84
+ {...props}
85
+ />
86
+ }