@octanejs/shadcn 0.0.6 → 0.0.7

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 (79) hide show
  1. package/package.json +87 -10
  2. package/src/{ui → bases/radix/ui}/accordion.tsrx +1 -1
  3. package/src/{ui → bases/radix/ui}/alert-dialog.tsrx +1 -1
  4. package/src/{ui → bases/radix/ui}/alert.tsrx +2 -2
  5. package/src/{ui → bases/radix/ui}/avatar.tsrx +1 -1
  6. package/src/{ui → bases/radix/ui}/badge.tsrx +2 -2
  7. package/src/{ui → bases/radix/ui}/breadcrumb.tsrx +1 -1
  8. package/src/{ui → bases/radix/ui}/button.tsrx +2 -2
  9. package/src/{ui → bases/radix/ui}/card.tsrx +2 -2
  10. package/src/{ui → bases/radix/ui}/checkbox.tsrx +1 -1
  11. package/src/{ui → bases/radix/ui}/context-menu.tsrx +1 -1
  12. package/src/{ui → bases/radix/ui}/dialog.tsrx +1 -1
  13. package/src/{ui → bases/radix/ui}/dropdown-menu.tsrx +1 -1
  14. package/src/{ui → bases/radix/ui}/empty.tsrx +1 -1
  15. package/src/{ui → bases/radix/ui}/field.tsrx +1 -1
  16. package/src/{ui → bases/radix/ui}/hover-card.tsrx +1 -1
  17. package/src/{ui → bases/radix/ui}/input.tsrx +2 -2
  18. package/src/{ui → bases/radix/ui}/item.tsrx +1 -1
  19. package/src/{ui → bases/radix/ui}/kbd.tsrx +2 -2
  20. package/src/{ui → bases/radix/ui}/label.tsrx +1 -1
  21. package/src/{ui → bases/radix/ui}/menubar.tsrx +1 -1
  22. package/src/{ui → bases/radix/ui}/native-select.tsrx +1 -1
  23. package/src/{ui → bases/radix/ui}/navigation-menu.tsrx +1 -1
  24. package/src/{ui → bases/radix/ui}/pagination.tsrx +1 -1
  25. package/src/{ui → bases/radix/ui}/popover.tsrx +1 -1
  26. package/src/{ui → bases/radix/ui}/progress.tsrx +1 -1
  27. package/src/{ui → bases/radix/ui}/radio-group.tsrx +1 -1
  28. package/src/{ui → bases/radix/ui}/scroll-area.tsrx +1 -1
  29. package/src/{ui → bases/radix/ui}/select.tsrx +1 -1
  30. package/src/{ui → bases/radix/ui}/separator.tsrx +1 -1
  31. package/src/{ui → bases/radix/ui}/sheet.tsrx +1 -1
  32. package/src/{ui → bases/radix/ui}/sidebar.tsrx +2 -2
  33. package/src/{ui → bases/radix/ui}/skeleton.tsrx +2 -2
  34. package/src/{ui → bases/radix/ui}/slider.tsrx +1 -1
  35. package/src/{ui → bases/radix/ui}/spinner.tsrx +1 -1
  36. package/src/{ui → bases/radix/ui}/switch.tsrx +1 -1
  37. package/src/{ui → bases/radix/ui}/table.tsrx +1 -1
  38. package/src/{ui → bases/radix/ui}/tabs.tsrx +1 -1
  39. package/src/{ui → bases/radix/ui}/textarea.tsrx +2 -2
  40. package/src/{ui → bases/radix/ui}/toggle-group.tsrx +1 -1
  41. package/src/{ui → bases/radix/ui}/toggle.tsrx +1 -1
  42. package/src/{ui → bases/radix/ui}/tooltip.tsrx +1 -1
  43. package/src/bases/react-aria/ui/accordion.tsrx +73 -0
  44. package/src/bases/react-aria/ui/alert-dialog.tsrx +154 -0
  45. package/src/bases/react-aria/ui/alert.tsrx +60 -0
  46. package/src/bases/react-aria/ui/aspect-ratio.tsrx +13 -0
  47. package/src/bases/react-aria/ui/avatar.tsrx +83 -0
  48. package/src/bases/react-aria/ui/badge.tsrx +48 -0
  49. package/src/bases/react-aria/ui/breadcrumb.tsrx +100 -0
  50. package/src/bases/react-aria/ui/button.tsrx +96 -0
  51. package/src/bases/react-aria/ui/card.tsrx +73 -0
  52. package/src/bases/react-aria/ui/checkbox.tsrx +37 -0
  53. package/src/bases/react-aria/ui/collapsible.tsrx +20 -0
  54. package/src/bases/react-aria/ui/dialog.tsrx +131 -0
  55. package/src/bases/react-aria/ui/empty.tsrx +79 -0
  56. package/src/bases/react-aria/ui/field.tsrx +170 -0
  57. package/src/bases/react-aria/ui/input.tsrx +28 -0
  58. package/src/bases/react-aria/ui/item.tsrx +148 -0
  59. package/src/bases/react-aria/ui/kbd.tsrx +30 -0
  60. package/src/bases/react-aria/ui/label.tsrx +22 -0
  61. package/src/bases/react-aria/ui/native-select.tsrx +44 -0
  62. package/src/bases/react-aria/ui/pagination.tsrx +92 -0
  63. package/src/bases/react-aria/ui/popover.tsrx +57 -0
  64. package/src/bases/react-aria/ui/radio-group.tsrx +46 -0
  65. package/src/bases/react-aria/ui/scroll-area.tsrx +14 -0
  66. package/src/bases/react-aria/ui/separator.tsrx +28 -0
  67. package/src/bases/react-aria/ui/sheet.tsrx +143 -0
  68. package/src/bases/react-aria/ui/skeleton.tsrx +10 -0
  69. package/src/bases/react-aria/ui/slider.tsrx +56 -0
  70. package/src/bases/react-aria/ui/spinner.tsrx +28 -0
  71. package/src/bases/react-aria/ui/switch.tsrx +38 -0
  72. package/src/bases/react-aria/ui/table.tsrx +98 -0
  73. package/src/bases/react-aria/ui/tabs.tsrx +67 -0
  74. package/src/bases/react-aria/ui/textarea.tsrx +26 -0
  75. package/src/bases/react-aria/ui/toggle.tsrx +38 -0
  76. package/src/index.ts +0 -263
  77. /package/src/{ui → bases/radix/ui}/aspect-ratio.tsrx +0 -0
  78. /package/src/{ui → bases/radix/ui}/collapsible.tsrx +0 -0
  79. /package/src/{ui → bases/radix/ui}/sonner.tsrx +0 -0
@@ -0,0 +1,73 @@
1
+ import { type OctaneNode } from 'octane';
2
+ import {
3
+ DisclosurePanel as AccordionContentPrimitive,
4
+ Heading as AccordionHeaderPrimitive,
5
+ Disclosure as AccordionItemPrimitive,
6
+ DisclosureGroup as AccordionPrimitive,
7
+ Button as AccordionTriggerPrimitive,
8
+ type ButtonProps,
9
+ type DisclosureGroupProps,
10
+ type DisclosurePanelProps,
11
+ type DisclosureProps,
12
+ } from '@octanejs/aria/components';
13
+
14
+ import { cn } from '../../../lib/utils';
15
+ import { ChevronDownIcon, ChevronUpIcon } from '@octanejs/lucide';
16
+
17
+ export function Accordion({ className, ...props }: DisclosureGroupProps) {
18
+ return <AccordionPrimitive
19
+ data-slot="accordion"
20
+ className={cn('flex w-full flex-col', className)}
21
+ {...props}
22
+ />;
23
+ }
24
+
25
+ export function AccordionItem({ className, ...props }: DisclosureProps) {
26
+ return <AccordionItemPrimitive
27
+ data-slot="accordion-item"
28
+ className={cn('not-last:border-b', className)}
29
+ {...props}
30
+ />;
31
+ }
32
+
33
+ export function AccordionTrigger({ className, children, ...props }: Omit<
34
+ ButtonProps,
35
+ 'children'
36
+ > & { children: OctaneNode }) {
37
+ return <AccordionHeaderPrimitive className="flex">
38
+ <AccordionTriggerPrimitive
39
+ slot="trigger"
40
+ data-slot="accordion-trigger"
41
+ className={cn(
42
+ 'group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring disabled:pointer-events-none disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground',
43
+ className,
44
+ )}
45
+ {...props}
46
+ >
47
+ {children}
48
+ <ChevronDownIcon
49
+ data-slot="accordion-trigger-icon"
50
+ className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
51
+ />
52
+ <ChevronUpIcon
53
+ data-slot="accordion-trigger-icon"
54
+ className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"
55
+ />
56
+ </AccordionTriggerPrimitive>
57
+ </AccordionHeaderPrimitive>;
58
+ }
59
+
60
+ export function AccordionContent({ className, children, ...props }: DisclosurePanelProps) {
61
+ return <AccordionContentPrimitive
62
+ data-slot="accordion-content"
63
+ className="h-(--disclosure-panel-height) overflow-clip text-sm transition-[height] data-open:animate-accordion-down data-closed:animate-accordion-up"
64
+ {...props}
65
+ >
66
+ <div
67
+ className={cn(
68
+ 'pt-0 pb-2.5 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4',
69
+ className,
70
+ )}
71
+ >{children}</div>
72
+ </AccordionContentPrimitive>;
73
+ }
@@ -0,0 +1,154 @@
1
+ // OCTANE ADAPTATION: RAC does not publish a `ModalProps` type (upstream writes
2
+ // `React.ComponentProps<typeof Modal>` for exactly that reason), so the one
3
+ // prop borrowed from Modal is spelled out instead of picked.
4
+ import { type OctaneNode } from 'octane';
5
+ import {
6
+ Dialog as AlertDialogPrimitive,
7
+ DialogTrigger as AlertDialogTriggerPrimitive,
8
+ Heading,
9
+ ModalOverlay as ModalOverlayPrimitive,
10
+ Modal as ModalPrimitive,
11
+ type DialogProps as AlertDialogPrimitiveProps,
12
+ type DialogTriggerProps as AlertDialogTriggerPrimitiveProps,
13
+ type ModalOverlayProps as ModalOverlayPrimitiveProps,
14
+ } from '@octanejs/aria/components';
15
+ import type { HeadingProps } from '@octanejs/aria/components';
16
+
17
+ import { cn } from '../../../lib/utils';
18
+ import type { HostProps } from '../../../lib/types';
19
+ import { Button } from './button.tsrx';
20
+
21
+ export function AlertDialogTrigger({ ...props }: AlertDialogTriggerPrimitiveProps) {
22
+ return <AlertDialogTriggerPrimitive data-slot="alert-dialog-trigger" {...props} />;
23
+ }
24
+
25
+ export function AlertDialogOverlay({ className, children, ...props }: Omit<
26
+ ModalOverlayPrimitiveProps,
27
+ 'className' | 'children'
28
+ > & {
29
+ className?: string;
30
+ children: OctaneNode;
31
+ }) {
32
+ return <ModalOverlayPrimitive
33
+ data-slot="alert-dialog-overlay"
34
+ className={cn(
35
+ 'fixed inset-0 isolate z-50 bg-black/10 duration-100 data-entering:animate-in data-entering:fade-in-0 data-exiting:animate-out data-exiting:fade-out-0 supports-backdrop-filter:backdrop-blur-xs',
36
+ className,
37
+ )}
38
+ {...props}
39
+ >{children}</ModalOverlayPrimitive>;
40
+ }
41
+
42
+ export function AlertDialog({ className, size = 'default', children, ...props }: Omit<
43
+ ModalOverlayPrimitiveProps,
44
+ 'className' | 'children'
45
+ > & { isDismissable?: boolean } & {
46
+ className?: string;
47
+ size?: 'default' | 'sm';
48
+ children: OctaneNode;
49
+ }) {
50
+ return <AlertDialogOverlay {...props}>
51
+ <ModalPrimitive
52
+ data-slot="alert-dialog-content"
53
+ data-size={size}
54
+ className={cn(
55
+ 'group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-entering:animate-in data-entering:fade-in-0 data-entering:zoom-in-95 data-exiting:animate-out data-exiting:fade-out-0 data-exiting:zoom-out-95 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm',
56
+ className,
57
+ )}
58
+ >
59
+ <AlertDialogPrimitive
60
+ data-slot="alert-dialog"
61
+ role="alertdialog"
62
+ className="[display:inherit] [gap:inherit] outline-none"
63
+ >{children}</AlertDialogPrimitive>
64
+ </ModalPrimitive>
65
+ </AlertDialogOverlay>;
66
+ }
67
+
68
+ export function AlertDialogContent({ className, size = 'default', children, ...props }: Record<
69
+ string,
70
+ any
71
+ >) {
72
+ return <AlertDialog className={className} size={size} {...props}>{children}</AlertDialog>;
73
+ }
74
+
75
+ export function AlertDialogHeader({ className, ...props }: HostProps<'div'>) {
76
+ return <div
77
+ data-slot="alert-dialog-header"
78
+ className={cn(
79
+ 'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',
80
+ className,
81
+ )}
82
+ {...props}
83
+ />;
84
+ }
85
+
86
+ export function AlertDialogFooter({ className, ...props }: HostProps<'div'>) {
87
+ return <div
88
+ data-slot="alert-dialog-footer"
89
+ className={cn(
90
+ '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',
91
+ className,
92
+ )}
93
+ {...props}
94
+ />;
95
+ }
96
+
97
+ export function AlertDialogMedia({ className, ...props }: HostProps<'div'>) {
98
+ return <div
99
+ data-slot="alert-dialog-media"
100
+ className={cn(
101
+ 'mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*=\'size-\'])]:size-6',
102
+ className,
103
+ )}
104
+ {...props}
105
+ />;
106
+ }
107
+
108
+ export function AlertDialogTitle({ className, ...props }: Omit<HeadingProps, 'slot'>) {
109
+ return <Heading
110
+ slot="title"
111
+ data-slot="alert-dialog-title"
112
+ className={cn(
113
+ 'text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',
114
+ className,
115
+ )}
116
+ {...props}
117
+ />;
118
+ }
119
+
120
+ export function AlertDialogDescription({ className, ...props }: Omit<HostProps<'div'>, 'slot'>) {
121
+ return <div
122
+ data-slot="alert-dialog-description"
123
+ className={cn(
124
+ 'text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',
125
+ className,
126
+ )}
127
+ {...props}
128
+ />;
129
+ }
130
+
131
+ export function AlertDialogAction({ className, ...props }: Record<string, any>) {
132
+ return <Button
133
+ slot="close"
134
+ data-slot="alert-dialog-action"
135
+ className={cn(className)}
136
+ {...props}
137
+ />;
138
+ }
139
+
140
+ export function AlertDialogCancel({
141
+ className,
142
+ variant = 'outline',
143
+ size = 'default',
144
+ ...props
145
+ }: Record<string, any>) {
146
+ return <Button
147
+ slot="close"
148
+ data-slot="alert-dialog-cancel"
149
+ className={cn(className)}
150
+ variant={variant}
151
+ size={size}
152
+ {...props}
153
+ />;
154
+ }
@@ -0,0 +1,60 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority';
2
+
3
+ import { cn } from '../../../lib/utils';
4
+ import type { HostProps } from '../../../lib/types';
5
+
6
+ const alertVariants = cva(
7
+ 'group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*=\'size-\'])]:size-4',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'bg-card text-card-foreground',
12
+ destructive: 'bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current',
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ variant: 'default',
17
+ },
18
+ },
19
+ );
20
+
21
+ export function Alert({ className, variant, ...props }: HostProps<'div'> & VariantProps<
22
+ typeof alertVariants
23
+ >) {
24
+ return <div
25
+ data-slot="alert"
26
+ role="alert"
27
+ className={cn(alertVariants({ variant }), className)}
28
+ {...props}
29
+ />;
30
+ }
31
+
32
+ export function AlertTitle({ className, ...props }: HostProps<'div'>) {
33
+ return <div
34
+ data-slot="alert-title"
35
+ className={cn(
36
+ 'font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground',
37
+ className,
38
+ )}
39
+ {...props}
40
+ />;
41
+ }
42
+
43
+ export function AlertDescription({ className, ...props }: HostProps<'div'>) {
44
+ return <div
45
+ data-slot="alert-description"
46
+ className={cn(
47
+ 'text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4',
48
+ className,
49
+ )}
50
+ {...props}
51
+ />;
52
+ }
53
+
54
+ export function AlertAction({ className, ...props }: HostProps<'div'>) {
55
+ return <div
56
+ data-slot="alert-action"
57
+ className={cn('absolute top-2 right-2', className)}
58
+ {...props}
59
+ />;
60
+ }
@@ -0,0 +1,13 @@
1
+ import { cn } from '../../../lib/utils';
2
+ import type { HostProps } from '../../../lib/types';
3
+
4
+ export function AspectRatio({ ratio, className, ...props }: HostProps<'div'> & { ratio: number }) {
5
+ return <div
6
+ data-slot="aspect-ratio"
7
+ style={{
8
+ '--ratio': ratio,
9
+ } as Record<string, string | number>}
10
+ className={cn('relative aspect-(--ratio)', className)}
11
+ {...props}
12
+ />;
13
+ }
@@ -0,0 +1,83 @@
1
+ import { useState } from 'octane';
2
+
3
+ import { cn } from '../../../lib/utils';
4
+ import type { HostProps } from '../../../lib/types';
5
+
6
+ export function Avatar({ className, size = 'default', ...props }: HostProps<'div'> & {
7
+ size?: 'default' | 'sm' | 'lg';
8
+ }) {
9
+ return <div
10
+ data-slot="avatar"
11
+ data-size={size}
12
+ className={cn(
13
+ 'group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',
14
+ className,
15
+ )}
16
+ {...props}
17
+ />;
18
+ }
19
+
20
+ type ImageState = 'loading' | 'loaded' | 'error';
21
+
22
+ export function AvatarImage({ className, ...props }: HostProps<'img'>) {
23
+ const [state, setState] = useState<ImageState>(props.src ? 'loading' : 'error');
24
+ return <img
25
+ data-slot="avatar-image"
26
+ alt={props.alt || ''}
27
+ data-state={state}
28
+ onLoad={() => setState('loaded')}
29
+ onError={() => setState('error')}
30
+ className={cn(
31
+ 'peer aspect-square size-full rounded-full object-cover data-[state=error]:hidden',
32
+ className,
33
+ )}
34
+ {...props}
35
+ />;
36
+ }
37
+
38
+ export function AvatarFallback({ className, ...props }: HostProps<'div'>) {
39
+ return <div
40
+ data-slot="avatar-fallback"
41
+ className={cn(
42
+ 'flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs peer-data-[state=error]:flex peer-[*]:hidden',
43
+ className,
44
+ )}
45
+ {...props}
46
+ />;
47
+ }
48
+
49
+ export function AvatarBadge({ className, ...props }: HostProps<'span'>) {
50
+ return <span
51
+ data-slot="avatar-badge"
52
+ className={cn(
53
+ 'absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none',
54
+ 'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',
55
+ 'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',
56
+ 'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',
57
+ className,
58
+ )}
59
+ {...props}
60
+ />;
61
+ }
62
+
63
+ export function AvatarGroup({ className, ...props }: HostProps<'div'>) {
64
+ return <div
65
+ data-slot="avatar-group"
66
+ className={cn(
67
+ 'group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',
68
+ className,
69
+ )}
70
+ {...props}
71
+ />;
72
+ }
73
+
74
+ export function AvatarGroupCount({ className, ...props }: HostProps<'div'>) {
75
+ return <div
76
+ data-slot="avatar-group-count"
77
+ className={cn(
78
+ 'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',
79
+ className,
80
+ )}
81
+ {...props}
82
+ />;
83
+ }
@@ -0,0 +1,48 @@
1
+ import { type OctaneNode } from 'octane';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+
4
+ import { cn } from '../../../lib/utils';
5
+ import type { HostProps } from '../../../lib/types';
6
+
7
+ export const badgeVariants = cva(
8
+ 'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
13
+ secondary: 'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',
14
+ destructive: 'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',
15
+ outline: 'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',
16
+ ghost: 'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',
17
+ link: 'text-primary underline-offset-4 hover:underline',
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: 'default',
22
+ },
23
+ },
24
+ );
25
+
26
+ export function Badge({ className, variant = 'default', render, ...props }: HostProps<
27
+ 'span'
28
+ > & VariantProps<typeof badgeVariants> & {
29
+ render?: (props: Record<string, unknown>) => OctaneNode;
30
+ }) {
31
+ if (render) {
32
+ const renderProps = {
33
+ 'data-slot': 'badge',
34
+ 'data-variant': variant,
35
+ className: cn(badgeVariants({ variant }), className),
36
+ ...props,
37
+ };
38
+
39
+ return render(renderProps);
40
+ }
41
+
42
+ return <span
43
+ data-slot="badge"
44
+ data-variant={variant}
45
+ className={cn(badgeVariants({ variant }), className)}
46
+ {...props}
47
+ />;
48
+ }
@@ -0,0 +1,100 @@
1
+ // OCTANE ADAPTATION: octane's renderable type is `OctaneNode = unknown`, and a
2
+ // union containing `unknown` collapses to `unknown` — so RAC's
3
+ // `ChildrenOrFunction` cannot keep its render-prop arm distinguishable the way
4
+ // React's precise `ReactNode` union does, and composeRenderProps has nothing to
5
+ // infer its render values from. Annotating them here restores the upstream
6
+ // parameter types without weakening the binding.
7
+ import {
8
+ Breadcrumb as BreadcrumbPrimitive,
9
+ Breadcrumbs as BreadcrumbsPrimitive,
10
+ composeRenderProps,
11
+ Link as LinkPrimitive,
12
+ type BreadcrumbProps,
13
+ type BreadcrumbRenderProps,
14
+ type BreadcrumbsProps,
15
+ type LinkProps,
16
+ } from '@octanejs/aria/components';
17
+
18
+ import { cn } from '../../../lib/utils';
19
+ import type { HostProps } from '../../../lib/types';
20
+ import { ChevronRightIcon, MoreHorizontalIcon } from '@octanejs/lucide';
21
+
22
+ export function Breadcrumb({ className, ...props }: HostProps<'nav'>) {
23
+ return <nav
24
+ aria-label="breadcrumb"
25
+ data-slot="breadcrumb"
26
+ className={cn(className)}
27
+ {...props}
28
+ />;
29
+ }
30
+
31
+ export function BreadcrumbList<T extends object>({ className, ...props }: BreadcrumbsProps<T>) {
32
+ return <BreadcrumbsPrimitive
33
+ data-slot="breadcrumb-list"
34
+ className={cn(
35
+ 'flex flex-wrap items-center gap-1.5 text-sm wrap-break-word text-muted-foreground',
36
+ className,
37
+ )}
38
+ {...props}
39
+ />;
40
+ }
41
+
42
+ export function BreadcrumbItem({
43
+ className,
44
+ children,
45
+ separatorClassName,
46
+ ...props
47
+ }: BreadcrumbProps & { separatorClassName?: string }) {
48
+ return <BreadcrumbPrimitive
49
+ data-slot="breadcrumb-item"
50
+ className={cn('inline-flex items-center gap-1', className)}
51
+ {...props}
52
+ >{composeRenderProps(
53
+ children,
54
+ (children, { isCurrent }: BreadcrumbRenderProps) => <>
55
+ {children}
56
+ {!isCurrent &&
57
+ <span
58
+ data-slot="breadcrumb-separator"
59
+ role="presentation"
60
+ aria-hidden="true"
61
+ className={cn('[&>svg]:size-3.5', separatorClassName)}
62
+ >
63
+ <ChevronRightIcon />
64
+ </span>}
65
+ </>,
66
+ )}</BreadcrumbPrimitive>;
67
+ }
68
+
69
+ export function BreadcrumbLink({ className, render, ...props }: LinkProps) {
70
+ return <LinkPrimitive
71
+ data-slot="breadcrumb-link"
72
+ className={cn('transition-colors hover:text-foreground', className)}
73
+ render={render}
74
+ {...props}
75
+ />;
76
+ }
77
+
78
+ export function BreadcrumbPage({ className, ...props }: HostProps<'span'>) {
79
+ return <span
80
+ data-slot="breadcrumb-page"
81
+ role="link"
82
+ aria-disabled="true"
83
+ aria-current="page"
84
+ className={cn('font-normal text-foreground', className)}
85
+ {...props}
86
+ />;
87
+ }
88
+
89
+ export function BreadcrumbEllipsis({ className, ...props }: HostProps<'span'>) {
90
+ return <span
91
+ data-slot="breadcrumb-ellipsis"
92
+ role="presentation"
93
+ aria-hidden="true"
94
+ className={cn('flex size-5 items-center justify-center [&>svg]:size-4', className)}
95
+ {...props}
96
+ >
97
+ <MoreHorizontalIcon />
98
+ <span className="sr-only">More</span>
99
+ </span>;
100
+ }
@@ -0,0 +1,96 @@
1
+ // React Aria base button — structure from upstream `bases/aria/ui/button.tsx`
2
+ // (RAC Button + Link primitives, both slotted as `button`, and the aria base's
3
+ // data-variant/data-size attributes, which the Base UI base omits).
4
+ //
5
+ // STYLING: the cva strings are this package's Radix-base (default-Tailwind,
6
+ // utilities-inlined) flavor rather than upstream's `cn-*` semantic hooks, so
7
+ // this component styles against a plain Tailwind v4 build like the migrated
8
+ // Radix families do. Upstream's own bases share button's class strings exactly,
9
+ // so nothing is lost here — but that is NOT a general rule: where a selector
10
+ // keys off a primitive-emitted attribute the bases legitimately diverge (the
11
+ // Radix separator styles `data-horizontal:`, the aria one
12
+ // `aria-[orientation=horizontal]:`). `tests/cross-base.test.ts` therefore pins
13
+ // the structural contract — data-slot names and exported components — not class
14
+ // equality.
15
+ import { cva, type VariantProps } from 'class-variance-authority';
16
+ import { Button as ButtonPrimitive, Link as LinkPrimitive } from '@octanejs/aria/components';
17
+ import type {
18
+ ButtonProps as ButtonPrimitiveProps,
19
+ LinkProps as LinkPrimitiveProps,
20
+ } from '@octanejs/aria/components';
21
+
22
+ import { cn } from '../../../lib/utils';
23
+
24
+ export const buttonVariants = cva(
25
+ 'group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
26
+ {
27
+ variants: {
28
+ variant: {
29
+ default: 'bg-primary text-primary-foreground hover:bg-primary/80',
30
+ outline: 'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',
31
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
32
+ ghost: 'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',
33
+ destructive: 'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40',
34
+ link: 'text-primary underline-offset-4 hover:underline',
35
+ },
36
+ size: {
37
+ default: 'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
38
+ xs: 'h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*=\'size-\'])]:size-3',
39
+ sm: 'h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*=\'size-\'])]:size-3.5',
40
+ lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
41
+ icon: 'size-8',
42
+ 'icon-xs': 'size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*=\'size-\'])]:size-3',
43
+ 'icon-sm': 'size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg',
44
+ 'icon-lg': 'size-9',
45
+ },
46
+ },
47
+ defaultVariants: {
48
+ variant: 'default',
49
+ size: 'default',
50
+ },
51
+ },
52
+ );
53
+
54
+ type ButtonVariants = VariantProps<typeof buttonVariants>;
55
+
56
+ export interface ButtonProps extends Omit<ButtonPrimitiveProps, 'className'> {
57
+ variant?: ButtonVariants['variant'];
58
+ size?: ButtonVariants['size'];
59
+ className?: string;
60
+ }
61
+
62
+ export interface LinkButtonProps extends Omit<LinkPrimitiveProps, 'className'> {
63
+ variant?: ButtonVariants['variant'];
64
+ size?: ButtonVariants['size'];
65
+ className?: string;
66
+ }
67
+
68
+ export function Button({
69
+ className,
70
+ variant = 'default',
71
+ size = 'default',
72
+ ...props
73
+ }: ButtonProps) @{
74
+ <ButtonPrimitive
75
+ data-slot="button"
76
+ data-variant={variant}
77
+ data-size={size}
78
+ className={cn(buttonVariants({ variant, size, className }))}
79
+ {...props}
80
+ />
81
+ }
82
+
83
+ export function LinkButton({
84
+ className,
85
+ variant = 'default',
86
+ size = 'default',
87
+ ...props
88
+ }: LinkButtonProps) @{
89
+ <LinkPrimitive
90
+ data-slot="button"
91
+ data-variant={variant}
92
+ data-size={size}
93
+ className={cn(buttonVariants({ variant, size, className }))}
94
+ {...props}
95
+ />
96
+ }