@octanejs/shadcn 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +170 -3
  2. package/package.json +46 -7
  3. package/src/bases/base-ui/ui/accordion.tsrx +71 -0
  4. package/src/bases/base-ui/ui/alert-dialog.tsrx +149 -0
  5. package/src/bases/base-ui/ui/alert.tsrx +68 -0
  6. package/src/bases/base-ui/ui/aspect-ratio.tsrx +24 -0
  7. package/src/bases/base-ui/ui/avatar.tsrx +89 -0
  8. package/src/bases/base-ui/ui/badge.tsrx +56 -0
  9. package/src/bases/base-ui/ui/breadcrumb.tsrx +109 -0
  10. package/src/bases/base-ui/ui/button.tsrx +69 -0
  11. package/src/bases/base-ui/ui/card.tsrx +76 -0
  12. package/src/bases/base-ui/ui/checkbox.tsrx +43 -0
  13. package/src/bases/base-ui/ui/collapsible.tsrx +25 -0
  14. package/src/bases/base-ui/ui/dialog.tsrx +137 -0
  15. package/src/bases/base-ui/ui/dropdown-menu.tsrx +292 -0
  16. package/src/bases/base-ui/ui/empty.tsrx +88 -0
  17. package/src/bases/base-ui/ui/field.tsrx +201 -0
  18. package/src/bases/base-ui/ui/input.tsrx +28 -0
  19. package/src/bases/base-ui/ui/item.tsrx +163 -0
  20. package/src/bases/base-ui/ui/kbd.tsrx +27 -0
  21. package/src/bases/base-ui/ui/label.tsrx +31 -0
  22. package/src/bases/base-ui/ui/native-select.tsrx +54 -0
  23. package/src/bases/base-ui/ui/pagination.tsrx +125 -0
  24. package/src/bases/base-ui/ui/popover.tsrx +124 -0
  25. package/src/bases/base-ui/ui/progress.tsrx +46 -0
  26. package/src/bases/base-ui/ui/radio-group.tsrx +62 -0
  27. package/src/bases/base-ui/ui/separator.tsrx +34 -0
  28. package/src/bases/base-ui/ui/sheet.tsrx +121 -0
  29. package/src/bases/base-ui/ui/skeleton.tsrx +17 -0
  30. package/src/bases/base-ui/ui/slider.tsrx +111 -0
  31. package/src/bases/base-ui/ui/spinner.tsrx +30 -0
  32. package/src/bases/base-ui/ui/switch.tsrx +43 -0
  33. package/src/bases/base-ui/ui/table.tsrx +85 -0
  34. package/src/bases/base-ui/ui/textarea.tsrx +22 -0
  35. package/src/bases/base-ui/ui/toggle-group.tsrx +124 -0
  36. package/src/bases/base-ui/ui/toggle.tsrx +57 -0
  37. package/src/bases/base-ui/ui/tooltip.tsrx +111 -0
@@ -0,0 +1,89 @@
1
+ // Base UI base avatar — runs on @octanejs/base-ui's Avatar (Root/Image/Fallback), a direct
2
+ // one-to-one mapping with the radix base.
3
+ //
4
+ // `AvatarBadge`, `AvatarGroup` and `AvatarGroupCount` are plain host elements in every base —
5
+ // upstream defines no primitive for them — so they are unchanged from the radix source.
6
+ //
7
+ // Nothing here keys off a primitive-emitted attribute: the `data-[size=…]` variants read the
8
+ // `data-size` this component writes itself, and the primitive emits no state attributes at all
9
+ // (verified by rendering it — Root and Fallback are bare spans). So unlike the overlays and the
10
+ // form controls, there is no dialect to get wrong.
11
+ //
12
+ // UNVERIFIED PROVENANCE: class strings come from this package's radix base rather than
13
+ // transcribed from upstream's Base UI source.
14
+ import { Avatar as AvatarPrimitive } from '@octanejs/base-ui/avatar';
15
+
16
+ import { cn } from '../../../lib/utils';
17
+
18
+ type Props = { className?: string } & Record<string, unknown>;
19
+
20
+ export interface AvatarProps extends Props {
21
+ size?: 'default' | 'sm' | 'lg';
22
+ }
23
+
24
+ export function Avatar({ className, size = 'default', ...props }: AvatarProps) @{
25
+ <AvatarPrimitive.Root
26
+ data-slot="avatar"
27
+ data-size={size}
28
+ className={cn(
29
+ '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',
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ }
35
+
36
+ export function AvatarImage({ className, ...props }: Props) @{
37
+ <AvatarPrimitive.Image
38
+ data-slot="avatar-image"
39
+ className={cn('aspect-square size-full rounded-full object-cover', className)}
40
+ {...props}
41
+ />
42
+ }
43
+
44
+ export function AvatarFallback({ className, ...props }: Props) @{
45
+ <AvatarPrimitive.Fallback
46
+ data-slot="avatar-fallback"
47
+ className={cn(
48
+ 'flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs',
49
+ className,
50
+ )}
51
+ {...props}
52
+ />
53
+ }
54
+
55
+ export function AvatarBadge({ className, ...props }: Props) @{
56
+ <span
57
+ data-slot="avatar-badge"
58
+ className={cn(
59
+ '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',
60
+ 'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',
61
+ 'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',
62
+ 'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',
63
+ className,
64
+ )}
65
+ {...props}
66
+ />
67
+ }
68
+
69
+ export function AvatarGroup({ className, ...props }: Props) @{
70
+ <div
71
+ data-slot="avatar-group"
72
+ className={cn(
73
+ 'group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',
74
+ className,
75
+ )}
76
+ {...props}
77
+ />
78
+ }
79
+
80
+ export function AvatarGroupCount({ className, ...props }: Props) @{
81
+ <div
82
+ data-slot="avatar-group-count"
83
+ className={cn(
84
+ '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',
85
+ className,
86
+ )}
87
+ {...props}
88
+ />
89
+ }
@@ -0,0 +1,56 @@
1
+ // Base UI base badge — a plain host `<span>` carrying the shared cva strings.
2
+ //
3
+ // The cva block is byte-identical to the radix and React Aria bases (checked, not assumed), and no
4
+ // primitive is involved: badge is a span with `data-slot` and `data-variant`, and nothing
5
+ // underneath emits state. So there is no dialect here to get wrong, and no motion to mistranslate.
6
+ //
7
+ // NO ESCAPE HATCH, DELIBERATELY. Upstream's other bases each let a badge render as a different
8
+ // element, and each does it differently: radix takes `asChild` and swaps in `Slot`; React Aria
9
+ // takes a `render` FUNCTION and calls it with the computed props. Base UI has no Slot, and its own
10
+ // convention is a `render` ELEMENT prop backed by `useRender` — but badge is not a Base UI
11
+ // primitive, so nothing here decides which of those shapes upstream's Base UI badge actually
12
+ // exposes, or whether it exposes one at all.
13
+ //
14
+ // Guessing would publish an API this base may not have, and an API shape is not something a later
15
+ // commit can quietly correct. Adding the hatch once the upstream source is available is additive
16
+ // and non-breaking; shipping the wrong one is not. This follows button.tsrx in the same base,
17
+ // which omits `LinkButton` for the same reason. A consumer needing a different element today can
18
+ // spread `badgeVariants({ variant })` onto it directly, which is what the class export is for.
19
+ import { cva, type VariantProps } from 'class-variance-authority';
20
+
21
+ import { cn } from '../../../lib/utils';
22
+ import type { HostProps } from '../../../lib/types';
23
+
24
+ export const badgeVariants = cva(
25
+ '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!',
26
+ {
27
+ variants: {
28
+ variant: {
29
+ default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
30
+ secondary: 'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',
31
+ 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',
32
+ outline: 'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',
33
+ ghost: 'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',
34
+ link: 'text-primary underline-offset-4 hover:underline',
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: 'default',
39
+ },
40
+ },
41
+ );
42
+
43
+ export interface BadgeProps extends HostProps<'span'> {
44
+ variant?: VariantProps<typeof badgeVariants>['variant'];
45
+ /** Not supported in this base — see the header. Declared so it fails to compile, not silently. */
46
+ asChild?: never;
47
+ }
48
+
49
+ export function Badge({ className, variant = 'default', ...props }: BadgeProps) @{
50
+ <span
51
+ data-slot="badge"
52
+ data-variant={variant}
53
+ className={cn(badgeVariants({ variant }), className)}
54
+ {...props}
55
+ />
56
+ }
@@ -0,0 +1,109 @@
1
+ // Base UI base breadcrumb — class strings from the maintainer-supplied radix source, verbatim.
2
+ // Six of the seven parts are plain host elements with no primitive underneath and no state
3
+ // attributes, so they carry across unchanged and there is no dialect to get wrong.
4
+ //
5
+ // `BreadcrumbLink` IS MISSING ITS ESCAPE HATCH, and unlike badge that costs something real: the
6
+ // `asChild` branch is how a router's Link gets plugged in, which is most of what this family is
7
+ // for. It is still left out, for the same reason:
8
+ //
9
+ // radix `asChild` swaps in `Slot`, a pure prop-merger
10
+ // React Aria routes through RAC's own `Link` primitive with a `render` prop
11
+ // Base UI has neither — no Slot, and no Link primitive
12
+ //
13
+ // Base UI's house convention is a `render` ELEMENT prop backed by `useRender`, and this base
14
+ // already relies on it where a real primitive offers it (see pagination). But breadcrumb has no
15
+ // primitive, so nothing here settles whether upstream's Base UI breadcrumb implements `render`
16
+ // itself, and an API shape is not something a later commit can quietly correct — adding it is
17
+ // additive, shipping the wrong spelling is breaking.
18
+ //
19
+ // Until then a router link still works, it just has to carry the classes itself:
20
+ //
21
+ // <BreadcrumbItem>
22
+ // <RouterLink className="transition-colors hover:text-foreground">Home</RouterLink>
23
+ // </BreadcrumbItem>
24
+ //
25
+ // which loses only the `data-slot="breadcrumb-link"` hook.
26
+ import { ChevronRightIcon, MoreHorizontalIcon } from '@octanejs/lucide';
27
+ import { type OctaneNode } from 'octane';
28
+
29
+ import { cn } from '../../../lib/utils';
30
+
31
+ type Props = { className?: string } & Record<string, unknown>;
32
+
33
+ export function Breadcrumb({ className, ...props }: Props) @{
34
+ <nav aria-label="breadcrumb" data-slot="breadcrumb" className={cn(className)} {...props} />
35
+ }
36
+
37
+ export function BreadcrumbList({ className, ...props }: Props) @{
38
+ <ol
39
+ data-slot="breadcrumb-list"
40
+ className={cn(
41
+ 'flex flex-wrap items-center gap-1.5 text-sm wrap-break-word text-muted-foreground',
42
+ className,
43
+ )}
44
+ {...props}
45
+ />
46
+ }
47
+
48
+ export function BreadcrumbItem({ className, ...props }: Props) @{
49
+ <li
50
+ data-slot="breadcrumb-item"
51
+ className={cn('inline-flex items-center gap-1', className)}
52
+ {...props}
53
+ />
54
+ }
55
+
56
+ export interface BreadcrumbLinkProps extends Props {
57
+ /**
58
+ * NOT SUPPORTED in this base — see the header. Declared as `never` so markup carrying it over
59
+ * from the radix base fails to compile instead of silently rendering the anchor AND the child,
60
+ * which nests one link inside another.
61
+ */
62
+ asChild?: never;
63
+ }
64
+
65
+ export function BreadcrumbLink({ className, ...props }: BreadcrumbLinkProps) @{
66
+ <a
67
+ data-slot="breadcrumb-link"
68
+ className={cn('transition-colors hover:text-foreground', className)}
69
+ {...props}
70
+ />
71
+ }
72
+
73
+ export function BreadcrumbPage({ className, ...props }: Props) @{
74
+ <span
75
+ data-slot="breadcrumb-page"
76
+ role="link"
77
+ aria-disabled="true"
78
+ aria-current="page"
79
+ className={cn('font-normal text-foreground', className)}
80
+ {...props}
81
+ />
82
+ }
83
+
84
+ export function BreadcrumbSeparator(props: Props & { children?: OctaneNode }) @{
85
+ const { className, children: _children, ...rest } = props;
86
+
87
+ <li
88
+ data-slot="breadcrumb-separator"
89
+ role="presentation"
90
+ aria-hidden="true"
91
+ className={cn('[&>svg]:size-3.5', className)}
92
+ {...rest}
93
+ >
94
+ {props.children ?? <ChevronRightIcon className="cn-rtl-flip" />}
95
+ </li>
96
+ }
97
+
98
+ export function BreadcrumbEllipsis({ className, ...props }: Props) @{
99
+ <span
100
+ data-slot="breadcrumb-ellipsis"
101
+ role="presentation"
102
+ aria-hidden="true"
103
+ className={cn('flex size-5 items-center justify-center [&>svg]:size-4', className)}
104
+ {...props}
105
+ >
106
+ <MoreHorizontalIcon />
107
+ <span className="sr-only">More</span>
108
+ </span>
109
+ }
@@ -0,0 +1,69 @@
1
+ // Base UI base button — runs on the @octanejs/base-ui `Button` primitive.
2
+ //
3
+ // TWO THINGS HERE ARE RECORDED FACTS, not guesses, both from the React Aria base's own
4
+ // provenance note:
5
+ // 1. Upstream's bases SHARE button's cva strings exactly, so the class strings below are the
6
+ // package's shipped utilities-inlined flavor and match the other bases.
7
+ // 2. The Base UI base OMITS the `data-variant`/`data-size` attributes the aria base sets. They
8
+ // are deliberately absent rather than forgotten — no selector in these strings keys off
9
+ // them, so nothing styles differently for their absence.
10
+ //
11
+ // NO LinkButton. The aria base has one because RAC ships a `Link` primitive slotted as a button;
12
+ // Base UI has no such primitive, and upstream's Base UI base composes links through the `render`
13
+ // prop instead. Inventing one here would be an API this base does not have — `pagination`, which
14
+ // consumes it in the aria base, therefore stays unported until the upstream source says how.
15
+ import { cva, type VariantProps } from 'class-variance-authority';
16
+ import { Button as ButtonPrimitive } from '@octanejs/base-ui/button';
17
+
18
+ import { cn } from '../../../lib/utils';
19
+
20
+ export const buttonVariants = cva(
21
+ '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',
22
+ {
23
+ variants: {
24
+ variant: {
25
+ default: 'bg-primary text-primary-foreground hover:bg-primary/80',
26
+ 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',
27
+ 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',
28
+ ghost: 'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',
29
+ 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',
30
+ link: 'text-primary underline-offset-4 hover:underline',
31
+ },
32
+ size: {
33
+ default: 'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
34
+ 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',
35
+ 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',
36
+ lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
37
+ icon: 'size-8',
38
+ 'icon-xs': 'size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*=\'size-\'])]:size-3',
39
+ 'icon-sm': 'size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg',
40
+ 'icon-lg': 'size-9',
41
+ },
42
+ },
43
+ defaultVariants: {
44
+ variant: 'default',
45
+ size: 'default',
46
+ },
47
+ },
48
+ );
49
+
50
+ type ButtonVariants = VariantProps<typeof buttonVariants>;
51
+
52
+ export interface ButtonProps extends Record<string, any> {
53
+ variant?: ButtonVariants['variant'];
54
+ size?: ButtonVariants['size'];
55
+ className?: string;
56
+ }
57
+
58
+ export function Button({
59
+ className,
60
+ variant = 'default',
61
+ size = 'default',
62
+ ...props
63
+ }: ButtonProps) @{
64
+ <ButtonPrimitive
65
+ data-slot="button"
66
+ className={cn(buttonVariants({ variant, size, className }))}
67
+ {...props}
68
+ />
69
+ }
@@ -0,0 +1,76 @@
1
+ // Base UI base card — presentational, no headless primitive in any base.
2
+ //
3
+ // UNVERIFIED PROVENANCE: derived from this package's React Aria base rather than transcribed
4
+ // from upstream's `bases/base-ui/ui/card.tsx`, which was not available at port time.
5
+ // Diff the class strings against the upstream source before treating this as ported.
6
+ import { cn } from '../../../lib/utils';
7
+ import type { DivProps } from '../../../lib/types';
8
+
9
+ export interface CardProps extends DivProps {
10
+ size?: 'default' | 'sm';
11
+ }
12
+
13
+ export function Card({ className, size = 'default', ...props }: CardProps) @{
14
+ <div
15
+ data-slot="card"
16
+ data-size={size}
17
+ className={cn(
18
+ 'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',
19
+ className,
20
+ )}
21
+ {...props}
22
+ />
23
+ }
24
+
25
+ export function CardHeader({ className, ...props }: DivProps) @{
26
+ <div
27
+ data-slot="card-header"
28
+ className={cn(
29
+ 'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ }
35
+
36
+ export function CardTitle({ className, ...props }: DivProps) @{
37
+ <div
38
+ data-slot="card-title"
39
+ className={cn(
40
+ 'text-base leading-snug font-medium group-data-[size=sm]/card:text-sm',
41
+ className,
42
+ )}
43
+ {...props}
44
+ />
45
+ }
46
+
47
+ export function CardDescription({ className, ...props }: DivProps) @{
48
+ <div
49
+ data-slot="card-description"
50
+ className={cn('text-sm text-muted-foreground', className)}
51
+ {...props}
52
+ />
53
+ }
54
+
55
+ export function CardAction({ className, ...props }: DivProps) @{
56
+ <div
57
+ data-slot="card-action"
58
+ className={cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', className)}
59
+ {...props}
60
+ />
61
+ }
62
+
63
+ export function CardContent({ className, ...props }: DivProps) @{
64
+ <div data-slot="card-content" className={cn('px-(--card-spacing)', className)} {...props} />
65
+ }
66
+
67
+ export function CardFooter({ className, ...props }: DivProps) @{
68
+ <div
69
+ data-slot="card-footer"
70
+ className={cn(
71
+ 'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',
72
+ className,
73
+ )}
74
+ {...props}
75
+ />
76
+ }
@@ -0,0 +1,43 @@
1
+ // Base UI base checkbox — runs on the @octanejs/base-ui `Checkbox` primitive.
2
+ //
3
+ // THE CONDITIONAL UTILITIES ARE ADAPTED, NOT COPIED, and the reason is structural rather than
4
+ // stylistic. Base UI's Checkbox.Root renders a `<span role="checkbox">` — verified by rendering
5
+ // it, not assumed — with a visually-hidden native `<input>` alongside it for form submission.
6
+ // A `<span>` is never `:disabled`, so Tailwind's `disabled:` variant (which compiles to the
7
+ // `:disabled` pseudo-class) can NEVER match. The primitive publishes `data-disabled` instead:
8
+ //
9
+ // radix base: `disabled:opacity-50` -> matches, Root is a real button
10
+ // base-ui base: `data-disabled:opacity-50` <- what this file uses
11
+ //
12
+ // `data-checked:` needs no change — the radix base already uses that exact form, and Base UI
13
+ // emits `data-checked`/`data-unchecked` too. `focus-visible:` also stays: the span carries
14
+ // tabindex, so the native pseudo-class applies.
15
+ //
16
+ // UNVERIFIED PROVENANCE: the non-conditional utilities are taken from this package's radix base
17
+ // rather than transcribed from upstream's Base UI source.
18
+ import { Checkbox as CheckboxPrimitive } from '@octanejs/base-ui/checkbox';
19
+ import { CheckIcon } from '@octanejs/lucide';
20
+
21
+ import { cn } from '../../../lib/utils';
22
+
23
+ export interface CheckboxProps extends Record<string, unknown> {
24
+ className?: string;
25
+ }
26
+
27
+ export function Checkbox({ className, ...props }: CheckboxProps) @{
28
+ <CheckboxPrimitive.Root
29
+ data-slot="checkbox"
30
+ className={cn(
31
+ 'peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 data-disabled:cursor-not-allowed data-disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary',
32
+ className,
33
+ )}
34
+ {...props}
35
+ >
36
+ <CheckboxPrimitive.Indicator
37
+ data-slot="checkbox-indicator"
38
+ className="grid place-content-center text-current transition-none [&>svg]:size-3.5"
39
+ >
40
+ <CheckIcon />
41
+ </CheckboxPrimitive.Indicator>
42
+ </CheckboxPrimitive.Root>
43
+ }
@@ -0,0 +1,25 @@
1
+ // Base UI base collapsible — runs on @octanejs/base-ui's Collapsible.
2
+ //
3
+ // PART MAPPING: shadcn names the content part `CollapsibleContent` while the Base UI part
4
+ // underneath is `Collapsible.Panel` — the same rename this base already makes for `accordion`.
5
+ // The exported names and all three `data-slot` values stay as shadcn defines them, so consumers
6
+ // see no difference between bases.
7
+ //
8
+ // No class strings: upstream ships this family unstyled in every base, leaving layout to the
9
+ // consumer. There is correspondingly no styling risk here, and nothing to verify against a
10
+ // class-string source.
11
+ import { Collapsible as CollapsiblePrimitive } from '@octanejs/base-ui/collapsible';
12
+
13
+ type Props = Record<string, unknown>;
14
+
15
+ export function Collapsible(props: Props) @{
16
+ <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
17
+ }
18
+
19
+ export function CollapsibleTrigger(props: Props) @{
20
+ <CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
21
+ }
22
+
23
+ export function CollapsibleContent(props: Props) @{
24
+ <CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
25
+ }
@@ -0,0 +1,137 @@
1
+ // Base UI base dialog — runs on @octanejs/base-ui's Dialog.
2
+ // Part mapping: Overlay -> Backdrop, Content -> Popup, and the close affordance composes a
3
+ // Button through Base UI's render-as-ELEMENT contract rather than radix's `asChild`.
4
+ //
5
+ // `data-open:`/`data-closed:` need no adaptation — the radix base already uses that exact form
6
+ // and Base UI emits the same attributes.
7
+ //
8
+ // Drops upstream's `cn-font-heading` on the title, matching the react-aria base and this base's
9
+ // alert-dialog: the package ships the utilities-inlined flavor and nothing defines that hook.
10
+ //
11
+ // UNVERIFIED PROVENANCE: class strings come from this package's radix base rather than
12
+ // transcribed from upstream's Base UI source.
13
+ import { createElement, type OctaneNode } from 'octane';
14
+ import { Dialog as DialogPrimitive } from '@octanejs/base-ui/dialog';
15
+ import { XIcon } from '@octanejs/lucide';
16
+
17
+ import { cn } from '../../../lib/utils';
18
+ import { Button } from './button.tsrx';
19
+
20
+ type Props = { className?: string; children?: OctaneNode } & Record<string, unknown>;
21
+
22
+ export function Dialog(props: Record<string, unknown>) @{
23
+ <DialogPrimitive.Root data-slot="dialog" {...props} />
24
+ }
25
+
26
+ export function DialogTrigger(props: Record<string, unknown>) @{
27
+ <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
28
+ }
29
+
30
+ export function DialogPortal(props: Record<string, unknown>) @{
31
+ <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
32
+ }
33
+
34
+ export function DialogClose(props: Record<string, unknown>) @{
35
+ <DialogPrimitive.Close data-slot="dialog-close" {...props} />
36
+ }
37
+
38
+ export function DialogOverlay({ className, ...props }: Props) @{
39
+ <DialogPrimitive.Backdrop
40
+ data-slot="dialog-overlay"
41
+ className={cn(
42
+ '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',
43
+ className,
44
+ )}
45
+ {...props}
46
+ />
47
+ }
48
+
49
+ export interface DialogContentProps extends Props {
50
+ showCloseButton?: boolean;
51
+ }
52
+
53
+ export function DialogContent({
54
+ className,
55
+ children,
56
+ showCloseButton = true,
57
+ ...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
+ );
90
+ }
91
+
92
+ export function DialogHeader({ className, ...props }: Props) @{
93
+ <div data-slot="dialog-header" className={cn('flex flex-col gap-2', className)} {...props} />
94
+ }
95
+
96
+ export interface DialogFooterProps extends Props {
97
+ showCloseButton?: boolean;
98
+ }
99
+
100
+ export function DialogFooter(props: DialogFooterProps) @{
101
+ const { className, showCloseButton = false, children: _children, ...rest } = props;
102
+
103
+ <div
104
+ data-slot="dialog-footer"
105
+ className={cn(
106
+ '-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',
107
+ className,
108
+ )}
109
+ {...rest}
110
+ >
111
+ {props.children}
112
+ {showCloseButton
113
+ ? createElement(DialogPrimitive.Close, {
114
+ render: createElement(Button, { variant: 'outline' }, 'Close'),
115
+ })
116
+ : null}
117
+ </div>
118
+ }
119
+
120
+ export function DialogTitle({ className, ...props }: Props) @{
121
+ <DialogPrimitive.Title
122
+ data-slot="dialog-title"
123
+ className={cn('text-base leading-none font-medium', className)}
124
+ {...props}
125
+ />
126
+ }
127
+
128
+ export function DialogDescription({ className, ...props }: Props) @{
129
+ <DialogPrimitive.Description
130
+ data-slot="dialog-description"
131
+ className={cn(
132
+ 'text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',
133
+ className,
134
+ )}
135
+ {...props}
136
+ />
137
+ }