@brokenrubik/ui 0.0.0-stage → 0.1.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/README.md +264 -2
- package/dist/components/accordion.d.ts +13 -0
- package/dist/components/accordion.d.ts.map +1 -0
- package/dist/components/accordion.js +25 -0
- package/dist/components/accordion.js.map +1 -0
- package/dist/components/alert-dialog.d.ts +21 -0
- package/dist/components/alert-dialog.d.ts.map +1 -0
- package/dist/components/alert-dialog.js +46 -0
- package/dist/components/alert-dialog.js.map +1 -0
- package/dist/components/alert.d.ts +24 -0
- package/dist/components/alert.d.ts.map +1 -0
- package/dist/components/alert.js +42 -0
- package/dist/components/alert.js.map +1 -0
- package/dist/components/avatar.d.ts +27 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.js +53 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +21 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.js +40 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/button.d.ts +18 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.js +39 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/calendar.d.ts +13 -0
- package/dist/components/calendar.d.ts.map +1 -0
- package/dist/components/calendar.js +68 -0
- package/dist/components/calendar.js.map +1 -0
- package/dist/components/card.d.ts +15 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/card.js +31 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/checkbox.d.ts +6 -0
- package/dist/components/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox.js +12 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/command.d.ts +24 -0
- package/dist/components/command.d.ts.map +1 -0
- package/dist/components/command.js +41 -0
- package/dist/components/command.js.map +1 -0
- package/dist/components/date-picker.d.ts +24 -0
- package/dist/components/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker.js +18 -0
- package/dist/components/date-picker.js.map +1 -0
- package/dist/components/dialog.d.ts +23 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.js +45 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +34 -0
- package/dist/components/dropdown-menu.d.ts.map +1 -0
- package/dist/components/dropdown-menu.js +61 -0
- package/dist/components/dropdown-menu.js.map +1 -0
- package/dist/components/empty-state.d.ts +13 -0
- package/dist/components/empty-state.d.ts.map +1 -0
- package/dist/components/empty-state.js +25 -0
- package/dist/components/empty-state.js.map +1 -0
- package/dist/components/field.d.ts +24 -0
- package/dist/components/field.d.ts.map +1 -0
- package/dist/components/field.js +34 -0
- package/dist/components/field.js.map +1 -0
- package/dist/components/input.d.ts +17 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +18 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/kbd.d.ts +14 -0
- package/dist/components/kbd.d.ts.map +1 -0
- package/dist/components/kbd.js +31 -0
- package/dist/components/kbd.js.map +1 -0
- package/dist/components/label.d.ts +6 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/label.js +11 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/pagination.d.ts +18 -0
- package/dist/components/pagination.d.ts.map +1 -0
- package/dist/components/pagination.js +33 -0
- package/dist/components/pagination.js.map +1 -0
- package/dist/components/popover.d.ts +12 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.js +29 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/progress.d.ts +17 -0
- package/dist/components/progress.d.ts.map +1 -0
- package/dist/components/progress.js +26 -0
- package/dist/components/progress.js.map +1 -0
- package/dist/components/radio-group.d.ts +7 -0
- package/dist/components/radio-group.d.ts.map +1 -0
- package/dist/components/radio-group.js +14 -0
- package/dist/components/radio-group.js.map +1 -0
- package/dist/components/select.d.ts +20 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.js +46 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +6 -0
- package/dist/components/separator.d.ts.map +1 -0
- package/dist/components/separator.js +11 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sheet.d.ts +19 -0
- package/dist/components/sheet.d.ts.map +1 -0
- package/dist/components/sheet.js +47 -0
- package/dist/components/sheet.js.map +1 -0
- package/dist/components/skeleton.d.ts +5 -0
- package/dist/components/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton.js +9 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/sonner.d.ts +13 -0
- package/dist/components/sonner.d.ts.map +1 -0
- package/dist/components/sonner.js +40 -0
- package/dist/components/sonner.js.map +1 -0
- package/dist/components/switch.d.ts +6 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.js +11 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/tabs.d.ts +13 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +24 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +5 -0
- package/dist/components/textarea.d.ts.map +1 -0
- package/dist/components/textarea.js +10 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/toggle-group.d.ts +11 -0
- package/dist/components/toggle-group.d.ts.map +1 -0
- package/dist/components/toggle-group.js +18 -0
- package/dist/components/toggle-group.js.map +1 -0
- package/dist/components/tooltip.d.ts +13 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.js +24 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/tone.d.ts +8 -0
- package/dist/lib/tone.d.ts.map +1 -0
- package/dist/lib/tone.js +14 -0
- package/dist/lib/tone.js.map +1 -0
- package/dist/lib/utils.d.ts +20 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +40 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/styles/base.css +37 -0
- package/dist/styles/fonts.css +16 -0
- package/dist/styles/styles.css +14 -0
- package/dist/styles/theme.css +170 -0
- package/dist/styles/tokens.css +228 -0
- package/docs/design-handoff.md +112 -0
- package/docs/figma-vs-tailwind.md +135 -0
- package/docs/visual-testing.md +156 -0
- package/package.json +97 -4
- package/src/components/accordion.tsx +66 -0
- package/src/components/alert-dialog.tsx +139 -0
- package/src/components/alert.tsx +87 -0
- package/src/components/avatar.tsx +105 -0
- package/src/components/badge.tsx +63 -0
- package/src/components/button.tsx +70 -0
- package/src/components/calendar.tsx +131 -0
- package/src/components/card.tsx +72 -0
- package/src/components/checkbox.tsx +31 -0
- package/src/components/command.tsx +149 -0
- package/src/components/date-picker.tsx +86 -0
- package/src/components/dialog.tsx +126 -0
- package/src/components/dropdown-menu.tsx +225 -0
- package/src/components/empty-state.tsx +63 -0
- package/src/components/field.tsx +79 -0
- package/src/components/input.tsx +49 -0
- package/src/components/kbd.tsx +48 -0
- package/src/components/label.tsx +22 -0
- package/src/components/pagination.tsx +94 -0
- package/src/components/popover.tsx +65 -0
- package/src/components/progress.tsx +52 -0
- package/src/components/radio-group.tsx +42 -0
- package/src/components/select.tsx +167 -0
- package/src/components/separator.tsx +29 -0
- package/src/components/sheet.tsx +112 -0
- package/src/components/skeleton.tsx +16 -0
- package/src/components/sonner.tsx +58 -0
- package/src/components/switch.tsx +27 -0
- package/src/components/tabs.tsx +56 -0
- package/src/components/textarea.tsx +17 -0
- package/src/components/toggle-group.tsx +42 -0
- package/src/components/tooltip.tsx +55 -0
- package/src/index.ts +35 -0
- package/src/lib/tone.ts +16 -0
- package/src/lib/utils.ts +46 -0
- package/src/styles/base.css +37 -0
- package/src/styles/fonts.css +16 -0
- package/src/styles/styles.css +14 -0
- package/src/styles/theme.css +170 -0
- package/src/styles/tokens.css +228 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Dialog as SheetPrimitive } from 'radix-ui';
|
|
5
|
+
import { XIcon } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../lib/utils.js';
|
|
8
|
+
import { closeButtonClassName, overlayClassName } from './dialog.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Figma: Components · Base › Sheet (2084:161), code `<Sheet side="right">`. 400 px panel,
|
|
12
|
+
* 24 px padding, 16 px gap, border on the open side, Shadow/lg. Header like Dialog.
|
|
13
|
+
*/
|
|
14
|
+
function Sheet(props: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
|
15
|
+
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function SheetTrigger(props: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
|
19
|
+
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function SheetClose(props: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
|
23
|
+
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function SheetPortal(props: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
|
27
|
+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
|
31
|
+
return <SheetPrimitive.Overlay data-slot="sheet-overlay" className={cn(overlayClassName, className)} {...props} />;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function SheetContent({
|
|
35
|
+
className,
|
|
36
|
+
children,
|
|
37
|
+
side = 'right',
|
|
38
|
+
showCloseButton = true,
|
|
39
|
+
...props
|
|
40
|
+
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
|
41
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
42
|
+
showCloseButton?: boolean;
|
|
43
|
+
}) {
|
|
44
|
+
return (
|
|
45
|
+
<SheetPortal>
|
|
46
|
+
<SheetOverlay />
|
|
47
|
+
<SheetPrimitive.Content
|
|
48
|
+
data-slot="sheet-content"
|
|
49
|
+
data-side={side}
|
|
50
|
+
className={cn(
|
|
51
|
+
'fixed z-50 flex flex-col gap-4 overflow-y-auto bg-background p-6 text-body text-foreground shadow-lg outline-none transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-200 data-[state=open]:animate-in data-[state=open]:duration-300',
|
|
52
|
+
side === 'right' &&
|
|
53
|
+
'inset-y-0 right-0 h-full w-full border-l border-border data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:w-[400px]',
|
|
54
|
+
side === 'left' &&
|
|
55
|
+
'inset-y-0 left-0 h-full w-full border-r border-border data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:w-[400px]',
|
|
56
|
+
side === 'top' &&
|
|
57
|
+
'inset-x-0 top-0 h-auto border-b border-border data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
|
58
|
+
side === 'bottom' &&
|
|
59
|
+
'inset-x-0 bottom-0 h-auto border-t border-border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
|
60
|
+
className,
|
|
61
|
+
)}
|
|
62
|
+
{...props}
|
|
63
|
+
>
|
|
64
|
+
{children}
|
|
65
|
+
{showCloseButton && (
|
|
66
|
+
<SheetPrimitive.Close data-slot="sheet-close" className={cn(closeButtonClassName, 'absolute top-6 right-6')}>
|
|
67
|
+
<XIcon />
|
|
68
|
+
<span className="sr-only">Close</span>
|
|
69
|
+
</SheetPrimitive.Close>
|
|
70
|
+
)}
|
|
71
|
+
</SheetPrimitive.Content>
|
|
72
|
+
</SheetPortal>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
77
|
+
return <div data-slot="sheet-header" className={cn('flex flex-col gap-1.5 pr-9', className)} {...props} />;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
|
81
|
+
return (
|
|
82
|
+
<div data-slot="sheet-footer" className={cn('mt-auto flex justify-end gap-2', className)} {...props} />
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
|
87
|
+
return <SheetPrimitive.Title data-slot="sheet-title" className={cn('text-page-title', className)} {...props} />;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function SheetDescription({
|
|
91
|
+
className,
|
|
92
|
+
...props
|
|
93
|
+
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
|
94
|
+
return (
|
|
95
|
+
<SheetPrimitive.Description
|
|
96
|
+
data-slot="sheet-description"
|
|
97
|
+
className={cn('text-body text-muted-foreground', className)}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
Sheet,
|
|
105
|
+
SheetTrigger,
|
|
106
|
+
SheetClose,
|
|
107
|
+
SheetContent,
|
|
108
|
+
SheetHeader,
|
|
109
|
+
SheetFooter,
|
|
110
|
+
SheetTitle,
|
|
111
|
+
SheetDescription,
|
|
112
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../lib/utils.js';
|
|
4
|
+
|
|
5
|
+
/** Figma: Components · Base › Skeleton (2085:214). Muted, 6 px radius (rounded-full for circles). */
|
|
6
|
+
function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
data-slot="skeleton"
|
|
10
|
+
className={cn('animate-pulse rounded-md bg-muted motion-reduce:animate-none', className)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { Skeleton };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Toaster as Sonner, toast, type ToasterProps } from 'sonner';
|
|
5
|
+
import {
|
|
6
|
+
CircleAlertIcon,
|
|
7
|
+
CircleCheckIcon,
|
|
8
|
+
InfoIcon,
|
|
9
|
+
Loader2Icon,
|
|
10
|
+
TriangleAlertIcon,
|
|
11
|
+
} from 'lucide-react';
|
|
12
|
+
|
|
13
|
+
import { cn } from '../lib/utils.js';
|
|
14
|
+
import { buttonVariants } from './button.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Figma: Components · Base › Toast (2085:201), code `<Toaster>` (sonner). 360 px popover,
|
|
18
|
+
* 8 px radius, 12/12/12/16 px padding, Shadow/lg. Title Body/Default Medium, description
|
|
19
|
+
* Body/Small muted, action = small outline button. Success and error add a -strong icon.
|
|
20
|
+
*
|
|
21
|
+
* Mount <Toaster /> once. Call toast('Task moved', { description, action }) anywhere.
|
|
22
|
+
* `theme` follows the `.dark` class by default; pass theme="dark" | "light" to force it.
|
|
23
|
+
*/
|
|
24
|
+
function Toaster({ className, toastOptions, ...props }: ToasterProps) {
|
|
25
|
+
return (
|
|
26
|
+
<Sonner
|
|
27
|
+
className={cn('toaster group', className)}
|
|
28
|
+
icons={{
|
|
29
|
+
success: <CircleCheckIcon className="size-4 text-success-strong" />,
|
|
30
|
+
info: <InfoIcon className="size-4 text-primary-strong" />,
|
|
31
|
+
warning: <TriangleAlertIcon className="size-4 text-warning-strong" />,
|
|
32
|
+
error: <CircleAlertIcon className="size-4 text-destructive-strong" />,
|
|
33
|
+
loading: <Loader2Icon className="size-4 animate-spin text-muted-foreground" />,
|
|
34
|
+
}}
|
|
35
|
+
toastOptions={{
|
|
36
|
+
unstyled: true,
|
|
37
|
+
...toastOptions,
|
|
38
|
+
classNames: {
|
|
39
|
+
toast:
|
|
40
|
+
'group/toast flex w-[var(--width)] items-center gap-3 rounded-lg border border-border bg-popover py-3 pr-3 pl-4 text-popover-foreground shadow-lg font-sans',
|
|
41
|
+
content: 'flex min-w-0 flex-1 flex-col gap-0.5',
|
|
42
|
+
title: 'text-body font-medium text-foreground',
|
|
43
|
+
description: 'text-body-sm text-muted-foreground',
|
|
44
|
+
icon: 'flex size-4 shrink-0 items-center justify-center',
|
|
45
|
+
actionButton: cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'shrink-0'),
|
|
46
|
+
cancelButton: cn(buttonVariants({ variant: 'ghost', size: 'sm' }), 'shrink-0'),
|
|
47
|
+
closeButton:
|
|
48
|
+
'absolute -top-2 -left-2 flex size-5 items-center justify-center rounded-full border border-border bg-popover text-muted-foreground hover:text-foreground',
|
|
49
|
+
...toastOptions?.classNames,
|
|
50
|
+
},
|
|
51
|
+
}}
|
|
52
|
+
style={{ '--width': '360px' } as React.CSSProperties}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { Toaster, toast };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Switch as SwitchPrimitive } from 'radix-ui';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils.js';
|
|
7
|
+
|
|
8
|
+
/** Figma: Components · Base › Switch (2007:47). 32×18 track (`input` off, `primary` on), 14 px thumb. */
|
|
9
|
+
function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
|
|
10
|
+
return (
|
|
11
|
+
<SwitchPrimitive.Root
|
|
12
|
+
data-slot="switch"
|
|
13
|
+
className={cn(
|
|
14
|
+
'peer inline-flex h-[18px] w-8 shrink-0 items-center rounded-full px-0.5 transition-colors outline-none focus-visible:ring-3 focus-visible:ring-ring/40 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
>
|
|
19
|
+
<SwitchPrimitive.Thumb
|
|
20
|
+
data-slot="switch-thumb"
|
|
21
|
+
className="pointer-events-none block size-3.5 rounded-full bg-background shadow-xs ring-0 transition-transform data-[state=checked]:translate-x-3.5 data-[state=unchecked]:translate-x-0"
|
|
22
|
+
/>
|
|
23
|
+
</SwitchPrimitive.Root>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { Switch };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Tabs as TabsPrimitive } from 'radix-ui';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Figma: Components · Base › Tab (2007:42). Underline tabs: 40 px high, 10 px side
|
|
10
|
+
* padding; active = foreground + Medium + 2 px violet underline; inactive = muted.
|
|
11
|
+
* For segmented controls (List / Board) use ToggleGroup.
|
|
12
|
+
*/
|
|
13
|
+
function Tabs({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
|
14
|
+
return (
|
|
15
|
+
<TabsPrimitive.Root
|
|
16
|
+
data-slot="tabs"
|
|
17
|
+
className={cn('flex flex-col gap-4', className)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function TabsList({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.List>) {
|
|
24
|
+
return (
|
|
25
|
+
<TabsPrimitive.List
|
|
26
|
+
data-slot="tabs-list"
|
|
27
|
+
className={cn('flex items-center border-b border-border', className)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
34
|
+
return (
|
|
35
|
+
<TabsPrimitive.Trigger
|
|
36
|
+
data-slot="tabs-trigger"
|
|
37
|
+
className={cn(
|
|
38
|
+
"relative -mb-px inline-flex h-10 items-center justify-center gap-1.5 whitespace-nowrap px-2.5 text-body text-muted-foreground transition-colors outline-none after:absolute after:inset-x-0 after:bottom-0 after:h-0.5 after:bg-transparent hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:font-medium data-[state=active]:text-foreground data-[state=active]:after:bg-primary [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
47
|
+
return (
|
|
48
|
+
<TabsPrimitive.Content
|
|
49
|
+
data-slot="tabs-content"
|
|
50
|
+
className={cn('flex-1 outline-none', className)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../lib/utils.js';
|
|
4
|
+
import { controlClassName } from './input.js';
|
|
5
|
+
|
|
6
|
+
/** Figma: Components · Base › Textarea (2083:56). 80 px minimum, 8/10 px padding. */
|
|
7
|
+
function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
|
|
8
|
+
return (
|
|
9
|
+
<textarea
|
|
10
|
+
data-slot="textarea"
|
|
11
|
+
className={cn(controlClassName, 'field-sizing-content flex min-h-20 px-2.5 py-2', className)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { Textarea };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { ToggleGroup as ToggleGroupPrimitive } from 'radix-ui';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Figma: Components · Base › Toggle Group (2085:233) and Toggle Group Item (2085:232).
|
|
10
|
+
* A segmented control: 28 px muted track, 2 px padding; items 24 px, 5 px radius,
|
|
11
|
+
* Body/Small; "on" = background + Shadow/xs + Medium.
|
|
12
|
+
*/
|
|
13
|
+
function ToggleGroup({
|
|
14
|
+
className,
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root>) {
|
|
17
|
+
return (
|
|
18
|
+
<ToggleGroupPrimitive.Root
|
|
19
|
+
data-slot="toggle-group"
|
|
20
|
+
className={cn('inline-flex h-7 w-fit items-center gap-0.5 rounded-md bg-muted p-0.5', className)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ToggleGroupItem({
|
|
27
|
+
className,
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item>) {
|
|
30
|
+
return (
|
|
31
|
+
<ToggleGroupPrimitive.Item
|
|
32
|
+
data-slot="toggle-group-item"
|
|
33
|
+
className={cn(
|
|
34
|
+
"inline-flex h-6 items-center justify-center gap-1.5 whitespace-nowrap rounded-sm px-2.5 text-body-sm text-muted-foreground transition-[color,background-color,box-shadow] outline-none hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-background data-[state=on]:font-medium data-[state=on]:text-foreground data-[state=on]:shadow-xs [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { ToggleGroup, ToggleGroupItem };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Tooltip as TooltipPrimitive } from 'radix-ui';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Figma: Components · Base › Tooltip (2007:61). Inverted (foreground fill, background
|
|
10
|
+
* text), Body/Small, 4/8 px padding, 6 px radius, Shadow/md, no arrow.
|
|
11
|
+
* Put one <TooltipProvider> near the app root; <Tooltip> works without it too.
|
|
12
|
+
*/
|
|
13
|
+
function TooltipProvider({
|
|
14
|
+
delayDuration = 300,
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
17
|
+
return <TooltipPrimitive.Provider data-slot="tooltip-provider" delayDuration={delayDuration} {...props} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function Tooltip(props: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
21
|
+
return (
|
|
22
|
+
<TooltipProvider>
|
|
23
|
+
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
24
|
+
</TooltipProvider>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function TooltipTrigger(props: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
29
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function TooltipContent({
|
|
33
|
+
className,
|
|
34
|
+
sideOffset = 6,
|
|
35
|
+
children,
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
38
|
+
return (
|
|
39
|
+
<TooltipPrimitive.Portal>
|
|
40
|
+
<TooltipPrimitive.Content
|
|
41
|
+
data-slot="tooltip-content"
|
|
42
|
+
sideOffset={sideOffset}
|
|
43
|
+
className={cn(
|
|
44
|
+
'z-50 w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) rounded-md border border-sidebar-border bg-foreground px-2 py-1 text-body-sm text-background shadow-md text-balance animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 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',
|
|
45
|
+
className,
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
>
|
|
49
|
+
{children}
|
|
50
|
+
</TooltipPrimitive.Content>
|
|
51
|
+
</TooltipPrimitive.Portal>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// @brokenrubik/ui — every component. Per-component imports also work:
|
|
2
|
+
// import { Button } from '@brokenrubik/ui/components/button';
|
|
3
|
+
export * from './lib/utils.js';
|
|
4
|
+
export * from './components/accordion.js';
|
|
5
|
+
export * from './components/alert.js';
|
|
6
|
+
export * from './components/alert-dialog.js';
|
|
7
|
+
export * from './components/avatar.js';
|
|
8
|
+
export * from './components/badge.js';
|
|
9
|
+
export * from './components/button.js';
|
|
10
|
+
export * from './components/calendar.js';
|
|
11
|
+
export * from './components/card.js';
|
|
12
|
+
export * from './components/checkbox.js';
|
|
13
|
+
export * from './components/command.js';
|
|
14
|
+
export * from './components/date-picker.js';
|
|
15
|
+
export * from './components/dialog.js';
|
|
16
|
+
export * from './components/dropdown-menu.js';
|
|
17
|
+
export * from './components/empty-state.js';
|
|
18
|
+
export * from './components/field.js';
|
|
19
|
+
export * from './components/input.js';
|
|
20
|
+
export * from './components/kbd.js';
|
|
21
|
+
export * from './components/label.js';
|
|
22
|
+
export * from './components/pagination.js';
|
|
23
|
+
export * from './components/popover.js';
|
|
24
|
+
export * from './components/progress.js';
|
|
25
|
+
export * from './components/radio-group.js';
|
|
26
|
+
export * from './components/select.js';
|
|
27
|
+
export * from './components/separator.js';
|
|
28
|
+
export * from './components/sheet.js';
|
|
29
|
+
export * from './components/skeleton.js';
|
|
30
|
+
export * from './components/sonner.js';
|
|
31
|
+
export * from './components/switch.js';
|
|
32
|
+
export * from './components/tabs.js';
|
|
33
|
+
export * from './components/textarea.js';
|
|
34
|
+
export * from './components/toggle-group.js';
|
|
35
|
+
export * from './components/tooltip.js';
|
package/src/lib/tone.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tone` and `variant` are two names for the same prop on Badge, Alert, Progress and
|
|
3
|
+
* AvatarFallback. `tone` is the Figma name; `variant` is the shadcn name, so shadcn code
|
|
4
|
+
* (`<Badge variant="secondary">`) works without edits. When both are set, `tone` wins.
|
|
5
|
+
* `aliases` maps shadcn-only values (`default`, `secondary`, `outline`) to a BR tone.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveTone<T extends string>(
|
|
8
|
+
tone: T | null | undefined,
|
|
9
|
+
variant: string | null | undefined,
|
|
10
|
+
aliases: Readonly<Record<string, T>>,
|
|
11
|
+
fallback: T,
|
|
12
|
+
): T {
|
|
13
|
+
if (tone) return tone;
|
|
14
|
+
if (!variant) return fallback;
|
|
15
|
+
return aliases[variant] ?? (variant as T);
|
|
16
|
+
}
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from 'clsx';
|
|
2
|
+
import { extendTailwindMerge, mergeConfigs, type ConfigExtension } from 'tailwind-merge';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The BR text styles registered as `text-*` font sizes in theme.css. tailwind-merge must
|
|
6
|
+
* know them: by default it reads an unknown `text-foo` as a color, so `cn('text-body',
|
|
7
|
+
* 'text-muted-foreground')` would drop `text-body`.
|
|
8
|
+
*/
|
|
9
|
+
export const brTextSizes = [
|
|
10
|
+
'display',
|
|
11
|
+
'page-title',
|
|
12
|
+
'section',
|
|
13
|
+
'card-title',
|
|
14
|
+
'body-lg',
|
|
15
|
+
'body',
|
|
16
|
+
'body-sm',
|
|
17
|
+
'caption',
|
|
18
|
+
'overline',
|
|
19
|
+
'nav',
|
|
20
|
+
'nav-label',
|
|
21
|
+
'initials',
|
|
22
|
+
'initials-sm',
|
|
23
|
+
'mono',
|
|
24
|
+
'mono-sm',
|
|
25
|
+
'doc-title',
|
|
26
|
+
'doc-h2',
|
|
27
|
+
'doc-body',
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
type MergeConfig = ConfigExtension<string, string>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Builds a `cn` for an app that adds its own scales on top of the BR theme (for example
|
|
34
|
+
* extra `text-*` sizes or `shadow-*` names), so tailwind-merge groups them right.
|
|
35
|
+
*
|
|
36
|
+
* export const cn = createCn({ extend: { theme: { text: ['hero'] } } });
|
|
37
|
+
*/
|
|
38
|
+
export function createCn(config?: MergeConfig) {
|
|
39
|
+
const merge = extendTailwindMerge({ extend: { theme: { text: [...brTextSizes] } } }, (base) =>
|
|
40
|
+
config ? mergeConfigs(base, config) : base,
|
|
41
|
+
);
|
|
42
|
+
return (...inputs: ClassValue[]) => merge(clsx(inputs));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Joins class names and resolves Tailwind conflicts (the last class wins). */
|
|
46
|
+
export const cn = createCn();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @brokenrubik/ui — base styles (optional). Page defaults from the BR Admin Library:
|
|
3
|
+
* Body/Default (13/20 IBM Plex Sans) on --background, slate borders, balanced headings.
|
|
4
|
+
* Skip this file if the app sets its own base; tokens.css and theme.css work without it.
|
|
5
|
+
*/
|
|
6
|
+
@layer base {
|
|
7
|
+
*,
|
|
8
|
+
::before,
|
|
9
|
+
::after {
|
|
10
|
+
border-color: var(--color-border);
|
|
11
|
+
outline-color: color-mix(in oklab, var(--color-ring) 50%, transparent);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
html {
|
|
15
|
+
font-family: var(--font-sans);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
body {
|
|
19
|
+
background-color: var(--color-background);
|
|
20
|
+
color: var(--color-foreground);
|
|
21
|
+
font-size: var(--text-body);
|
|
22
|
+
line-height: var(--text-body--line-height);
|
|
23
|
+
-webkit-font-smoothing: antialiased;
|
|
24
|
+
-moz-osx-font-smoothing: grayscale;
|
|
25
|
+
text-rendering: optimizeLegibility;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
h1,
|
|
29
|
+
h2,
|
|
30
|
+
h3 {
|
|
31
|
+
text-wrap: balance;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
p {
|
|
35
|
+
text-wrap: pretty;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @brokenrubik/ui — fonts: IBM Plex Sans 400/500/600 and IBM Plex Mono 400/500 (the weights
|
|
3
|
+
* the BR Admin Library uses). styles.css imports this file, so apps get the fonts with no
|
|
4
|
+
* extra setup. The font files come from the @fontsource packages (dependencies of this
|
|
5
|
+
* package); the app's bundler copies them. Each @font-face has a unicode-range, so the
|
|
6
|
+
* browser downloads only the subsets (latin, latin-ext…) that the page uses.
|
|
7
|
+
*
|
|
8
|
+
* The families are "IBM Plex Sans" and "IBM Plex Mono", the default of --font-sans and
|
|
9
|
+
* --font-mono in theme.css. An app that loads the fonts in a different way (next/font) can
|
|
10
|
+
* set --font-plex-sans / --font-plex-mono; these faces are then never downloaded.
|
|
11
|
+
*/
|
|
12
|
+
@import "@fontsource/ibm-plex-sans/400.css";
|
|
13
|
+
@import "@fontsource/ibm-plex-sans/500.css";
|
|
14
|
+
@import "@fontsource/ibm-plex-sans/600.css";
|
|
15
|
+
@import "@fontsource/ibm-plex-mono/400.css";
|
|
16
|
+
@import "@fontsource/ibm-plex-mono/500.css";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @brokenrubik/ui — everything: fonts, tokens, Tailwind theme, animations, base styles.
|
|
3
|
+
*
|
|
4
|
+
* @import "tailwindcss";
|
|
5
|
+
* @import "@brokenrubik/ui/styles.css";
|
|
6
|
+
*
|
|
7
|
+
* Need less? Import the parts: fonts.css (IBM Plex), tokens.css (CSS variables only),
|
|
8
|
+
* theme.css (Tailwind mapping), base.css (page defaults). Components need tokens.css + theme.css + tw-animate-css.
|
|
9
|
+
*/
|
|
10
|
+
@import "./fonts.css";
|
|
11
|
+
@import "tw-animate-css";
|
|
12
|
+
@import "./tokens.css";
|
|
13
|
+
@import "./theme.css";
|
|
14
|
+
@import "./base.css";
|