@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,79 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '../lib/utils.js';
|
|
6
|
+
import { Label } from './label.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Figma: Components · Base › Form Field (2083:229), code FormItem / FormLabel /
|
|
10
|
+
* FormDescription / FormMessage. Label (Body/Small Medium) · control · helper (Body/Small),
|
|
11
|
+
* 6 px apart. Not tied to a form library: pass `invalid` / `disabled` and wire the control
|
|
12
|
+
* with `aria-invalid` (the control turns its border red). FieldError replaces the helper.
|
|
13
|
+
*
|
|
14
|
+
* <Field invalid={!!error}>
|
|
15
|
+
* <FieldLabel htmlFor="name">Project name</FieldLabel>
|
|
16
|
+
* <Input id="name" aria-invalid={!!error} aria-describedby="name-help" />
|
|
17
|
+
* {error ? <FieldError id="name-help">{error}</FieldError>
|
|
18
|
+
* : <FieldDescription id="name-help">Shown on the sidebar.</FieldDescription>}
|
|
19
|
+
* </Field>
|
|
20
|
+
*/
|
|
21
|
+
function Field({
|
|
22
|
+
className,
|
|
23
|
+
invalid,
|
|
24
|
+
disabled,
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<'div'> & { invalid?: boolean; disabled?: boolean }) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
role="group"
|
|
30
|
+
data-slot="field"
|
|
31
|
+
data-invalid={invalid || undefined}
|
|
32
|
+
data-disabled={disabled || undefined}
|
|
33
|
+
className={cn(
|
|
34
|
+
'group/field flex w-full flex-col gap-1.5 data-[disabled]:[&_[data-slot=input]]:opacity-50',
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
|
|
43
|
+
return (
|
|
44
|
+
<Label
|
|
45
|
+
data-slot="field-label"
|
|
46
|
+
className={cn('group-data-[disabled]/field:text-muted-foreground', className)}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
|
53
|
+
return (
|
|
54
|
+
<p
|
|
55
|
+
data-slot="field-description"
|
|
56
|
+
className={cn(
|
|
57
|
+
'text-body-sm text-muted-foreground group-data-[invalid]/field:text-destructive-strong',
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function FieldError({ className, children, ...props }: React.ComponentProps<'p'>) {
|
|
66
|
+
if (!children) return null;
|
|
67
|
+
return (
|
|
68
|
+
<p
|
|
69
|
+
data-slot="field-error"
|
|
70
|
+
role="alert"
|
|
71
|
+
className={cn('text-body-sm text-destructive-strong', className)}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
</p>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { Field, FieldLabel, FieldDescription, FieldError };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../lib/utils.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The shared look of text controls (Input, Select trigger, Date Picker, Textarea).
|
|
7
|
+
* Figma: 32 px, 6 px radius, `input` border (3:1), Shadow/xs, Body/Default; Focus = 1.5 px
|
|
8
|
+
* `ring` border; Disabled = 50 % opacity; Error = `destructive` border.
|
|
9
|
+
*/
|
|
10
|
+
const controlClassName =
|
|
11
|
+
'w-full min-w-0 rounded-md border border-input bg-background text-body text-foreground shadow-xs transition-[color,border-color,box-shadow] outline-none placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground focus-visible:border-ring focus-visible:ring-[0.5px] focus-visible:ring-ring disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20';
|
|
12
|
+
|
|
13
|
+
type InputProps = React.ComponentProps<'input'> & {
|
|
14
|
+
/** An icon before the text (16 px, muted), as Figma's Input "Show icon". */
|
|
15
|
+
startIcon?: React.ReactNode;
|
|
16
|
+
/** Classes for the wrapper that holds the icon (only with startIcon). */
|
|
17
|
+
wrapperClassName?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Figma: Components · Base › Input (2007:32). */
|
|
21
|
+
function Input({ className, type, startIcon, wrapperClassName, ...props }: InputProps) {
|
|
22
|
+
const input = (
|
|
23
|
+
<input
|
|
24
|
+
type={type}
|
|
25
|
+
data-slot="input"
|
|
26
|
+
className={cn(
|
|
27
|
+
controlClassName,
|
|
28
|
+
'h-8 px-2.5 py-1 file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-body file:font-medium',
|
|
29
|
+
startIcon && 'pl-[34px]',
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
if (!startIcon) return input;
|
|
36
|
+
return (
|
|
37
|
+
<div data-slot="input-wrapper" className={cn('relative w-full', wrapperClassName)}>
|
|
38
|
+
<span
|
|
39
|
+
aria-hidden
|
|
40
|
+
className="pointer-events-none absolute inset-y-0 left-[11px] flex items-center text-muted-foreground [&_svg]:size-4"
|
|
41
|
+
>
|
|
42
|
+
{startIcon}
|
|
43
|
+
</span>
|
|
44
|
+
{input}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { Input, controlClassName, type InputProps };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
|
|
4
|
+
import { cn } from '../lib/utils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Figma: Components · Base › Key Chip (2006:69), code `<Kbd>` (custom).
|
|
8
|
+
* highlight: the lime square of a space key (20 px / 5 px radius or 24 px / 6 px radius).
|
|
9
|
+
* muted: a keyboard hint or ID in Mono/Small on muted (20 px, 4 px radius).
|
|
10
|
+
*/
|
|
11
|
+
const kbdVariants = cva(
|
|
12
|
+
'inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap',
|
|
13
|
+
{
|
|
14
|
+
variants: {
|
|
15
|
+
variant: {
|
|
16
|
+
highlight: 'bg-highlight font-sans font-semibold tracking-[0.15px] text-highlight-foreground',
|
|
17
|
+
muted: 'h-5 min-w-5 rounded-xs bg-muted px-1.5 font-mono text-mono-sm text-muted-foreground',
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
20: '',
|
|
21
|
+
24: '',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
compoundVariants: [
|
|
25
|
+
{ variant: 'highlight', size: 20, className: 'size-5 rounded-sm text-[8.4px] leading-none' },
|
|
26
|
+
{ variant: 'highlight', size: 24, className: 'size-6 rounded-md text-[10px] leading-none' },
|
|
27
|
+
],
|
|
28
|
+
defaultVariants: { variant: 'muted', size: 20 },
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
function Kbd({
|
|
33
|
+
className,
|
|
34
|
+
variant = 'muted',
|
|
35
|
+
size = 20,
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<'kbd'> & VariantProps<typeof kbdVariants>) {
|
|
38
|
+
return (
|
|
39
|
+
<kbd
|
|
40
|
+
data-slot="kbd"
|
|
41
|
+
data-variant={variant}
|
|
42
|
+
className={cn(kbdVariants({ variant, size }), className)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { Kbd, kbdVariants };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Label as LabelPrimitive } from 'radix-ui';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils.js';
|
|
7
|
+
|
|
8
|
+
/** Figma: Form Field label, Body/Small Medium. */
|
|
9
|
+
function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
10
|
+
return (
|
|
11
|
+
<LabelPrimitive.Root
|
|
12
|
+
data-slot="label"
|
|
13
|
+
className={cn(
|
|
14
|
+
'flex select-none items-center gap-2 text-body-sm font-medium text-foreground group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:text-muted-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { Label };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
import { cn } from '../lib/utils.js';
|
|
5
|
+
import { buttonVariants } from './button.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Figma: Components · Base › Pagination (2085:245) and Pagination Item (2085:244).
|
|
9
|
+
* 32 px squares, 6 px radius, Body/Default; active = bordered on background + Medium.
|
|
10
|
+
* Items are links: pass href (or asChild with the router's Link).
|
|
11
|
+
*/
|
|
12
|
+
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
|
|
13
|
+
return (
|
|
14
|
+
<nav
|
|
15
|
+
role="navigation"
|
|
16
|
+
aria-label="pagination"
|
|
17
|
+
data-slot="pagination"
|
|
18
|
+
className={cn('mx-auto flex w-full justify-center', className)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function PaginationContent({ className, ...props }: React.ComponentProps<'ul'>) {
|
|
25
|
+
return <ul data-slot="pagination-content" className={cn('flex items-center gap-1', className)} {...props} />;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function PaginationItem(props: React.ComponentProps<'li'>) {
|
|
29
|
+
return <li data-slot="pagination-item" {...props} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type PaginationLinkProps = { isActive?: boolean } & React.ComponentProps<'a'>;
|
|
33
|
+
|
|
34
|
+
function PaginationLink({ className, isActive, ...props }: PaginationLinkProps) {
|
|
35
|
+
return (
|
|
36
|
+
<a
|
|
37
|
+
aria-current={isActive ? 'page' : undefined}
|
|
38
|
+
data-slot="pagination-link"
|
|
39
|
+
data-active={isActive}
|
|
40
|
+
className={cn(
|
|
41
|
+
buttonVariants({ variant: isActive ? 'outline' : 'ghost' }),
|
|
42
|
+
'size-8 rounded-md px-0 font-normal data-[active=true]:font-medium data-[active=true]:shadow-none',
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function PaginationPrevious({ className, children = 'Previous', ...props }: React.ComponentProps<typeof PaginationLink>) {
|
|
51
|
+
return (
|
|
52
|
+
<PaginationLink
|
|
53
|
+
aria-label="Go to previous page"
|
|
54
|
+
className={cn('w-auto gap-1 px-2.5', className)}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
<ChevronLeftIcon />
|
|
58
|
+
<span>{children}</span>
|
|
59
|
+
</PaginationLink>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function PaginationNext({ className, children = 'Next', ...props }: React.ComponentProps<typeof PaginationLink>) {
|
|
64
|
+
return (
|
|
65
|
+
<PaginationLink aria-label="Go to next page" className={cn('w-auto gap-1 px-2.5', className)} {...props}>
|
|
66
|
+
<span>{children}</span>
|
|
67
|
+
<ChevronRightIcon />
|
|
68
|
+
</PaginationLink>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function PaginationEllipsis({ className, ...props }: React.ComponentProps<'span'>) {
|
|
73
|
+
return (
|
|
74
|
+
<span
|
|
75
|
+
aria-hidden
|
|
76
|
+
data-slot="pagination-ellipsis"
|
|
77
|
+
className={cn('flex size-8 items-center justify-center text-muted-foreground', className)}
|
|
78
|
+
{...props}
|
|
79
|
+
>
|
|
80
|
+
<MoreHorizontalIcon className="size-4" />
|
|
81
|
+
<span className="sr-only">More pages</span>
|
|
82
|
+
</span>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
Pagination,
|
|
88
|
+
PaginationContent,
|
|
89
|
+
PaginationLink,
|
|
90
|
+
PaginationItem,
|
|
91
|
+
PaginationPrevious,
|
|
92
|
+
PaginationNext,
|
|
93
|
+
PaginationEllipsis,
|
|
94
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Popover as PopoverPrimitive } from 'radix-ui';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils.js';
|
|
7
|
+
|
|
8
|
+
/** Figma: Components · Base › Popover (2084:117). 300 px, 16 px padding, 8 px radius, Shadow/md. */
|
|
9
|
+
function Popover(props: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
|
10
|
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function PopoverTrigger(props: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
14
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function PopoverAnchor(props: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
|
18
|
+
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function PopoverContent({
|
|
22
|
+
className,
|
|
23
|
+
align = 'center',
|
|
24
|
+
sideOffset = 4,
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
|
27
|
+
return (
|
|
28
|
+
<PopoverPrimitive.Portal>
|
|
29
|
+
<PopoverPrimitive.Content
|
|
30
|
+
data-slot="popover-content"
|
|
31
|
+
align={align}
|
|
32
|
+
sideOffset={sideOffset}
|
|
33
|
+
className={cn(
|
|
34
|
+
'z-50 flex w-[300px] origin-(--radix-popover-content-transform-origin) flex-col gap-3 rounded-lg border border-border bg-popover p-4 text-body text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]: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',
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
</PopoverPrimitive.Portal>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
44
|
+
return <div data-slot="popover-header" className={cn('flex flex-col gap-1', className)} {...props} />;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function PopoverTitle({ className, ...props }: React.ComponentProps<'h2'>) {
|
|
48
|
+
return <h2 data-slot="popover-title" className={cn('text-card-title', className)} {...props} />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function PopoverDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
|
52
|
+
return (
|
|
53
|
+
<p data-slot="popover-description" className={cn('text-body text-muted-foreground', className)} {...props} />
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
Popover,
|
|
59
|
+
PopoverTrigger,
|
|
60
|
+
PopoverAnchor,
|
|
61
|
+
PopoverContent,
|
|
62
|
+
PopoverHeader,
|
|
63
|
+
PopoverTitle,
|
|
64
|
+
PopoverDescription,
|
|
65
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
5
|
+
import { Progress as ProgressPrimitive } from 'radix-ui';
|
|
6
|
+
|
|
7
|
+
import { resolveTone } from '../lib/tone.js';
|
|
8
|
+
import { cn } from '../lib/utils.js';
|
|
9
|
+
|
|
10
|
+
/** Figma: Components · Base › Progress (2085:210). 8 px muted track, full radius, toned bar. */
|
|
11
|
+
const progressIndicatorVariants = cva('h-full w-full flex-1 rounded-full transition-transform', {
|
|
12
|
+
variants: {
|
|
13
|
+
tone: {
|
|
14
|
+
primary: 'bg-primary',
|
|
15
|
+
success: 'bg-success',
|
|
16
|
+
warning: 'bg-warning',
|
|
17
|
+
destructive: 'bg-destructive',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: { tone: 'primary' },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
type ProgressTone = NonNullable<VariantProps<typeof progressIndicatorVariants>['tone']>;
|
|
24
|
+
|
|
25
|
+
type ProgressProps = React.ComponentProps<typeof ProgressPrimitive.Root> & {
|
|
26
|
+
/** Figma "Tone". Default `primary`. */
|
|
27
|
+
tone?: ProgressTone | null;
|
|
28
|
+
/** Same as `tone` (shadcn-style name). */
|
|
29
|
+
variant?: ProgressTone | null;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function Progress({ className, value, tone: toneProp, variant, ...props }: ProgressProps) {
|
|
33
|
+
const tone = resolveTone(toneProp, variant, {}, 'primary');
|
|
34
|
+
const pct = Math.min(100, Math.max(0, value ?? 0));
|
|
35
|
+
return (
|
|
36
|
+
<ProgressPrimitive.Root
|
|
37
|
+
data-slot="progress"
|
|
38
|
+
value={value}
|
|
39
|
+
className={cn('relative h-2 w-full overflow-hidden rounded-full bg-muted', className)}
|
|
40
|
+
{...props}
|
|
41
|
+
>
|
|
42
|
+
<ProgressPrimitive.Indicator
|
|
43
|
+
data-slot="progress-indicator"
|
|
44
|
+
data-tone={tone}
|
|
45
|
+
className={progressIndicatorVariants({ tone })}
|
|
46
|
+
style={{ transform: `translateX(-${100 - pct}%)` }}
|
|
47
|
+
/>
|
|
48
|
+
</ProgressPrimitive.Root>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { Progress, progressIndicatorVariants, type ProgressProps, type ProgressTone };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { RadioGroup as RadioGroupPrimitive } from 'radix-ui';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils.js';
|
|
7
|
+
|
|
8
|
+
/** Figma: Components · Base › Radio (2083:207). 16 px ring; checked = violet ring + 8 px dot. */
|
|
9
|
+
function RadioGroup({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
|
|
10
|
+
return (
|
|
11
|
+
<RadioGroupPrimitive.Root
|
|
12
|
+
data-slot="radio-group"
|
|
13
|
+
className={cn('grid gap-3', className)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function RadioGroupItem({
|
|
20
|
+
className,
|
|
21
|
+
...props
|
|
22
|
+
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
|
|
23
|
+
return (
|
|
24
|
+
<RadioGroupPrimitive.Item
|
|
25
|
+
data-slot="radio-group-item"
|
|
26
|
+
className={cn(
|
|
27
|
+
'peer aspect-square size-4 shrink-0 rounded-full border border-input bg-background shadow-xs transition-[border-color,box-shadow] outline-none focus-visible:ring-3 focus-visible:ring-ring/40 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-[state=checked]:border-primary',
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
<RadioGroupPrimitive.Indicator
|
|
33
|
+
data-slot="radio-group-indicator"
|
|
34
|
+
className="flex items-center justify-center"
|
|
35
|
+
>
|
|
36
|
+
<span className="size-2 rounded-full bg-primary" />
|
|
37
|
+
</RadioGroupPrimitive.Indicator>
|
|
38
|
+
</RadioGroupPrimitive.Item>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { RadioGroup, RadioGroupItem };
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Select as SelectPrimitive } from 'radix-ui';
|
|
5
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../lib/utils.js';
|
|
8
|
+
import { controlClassName } from './input.js';
|
|
9
|
+
import { menuContentClassName, menuItemClassName } from './dropdown-menu.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Figma: Components · Base › Select (2083:40). The trigger looks like Input (32 px,
|
|
13
|
+
* `input` border, ring on focus) with a muted chevron; the list is a Dropdown Menu panel.
|
|
14
|
+
*/
|
|
15
|
+
function Select(props: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
|
16
|
+
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function SelectGroup(props: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
|
20
|
+
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function SelectValue(props: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
|
24
|
+
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function SelectTrigger({
|
|
28
|
+
className,
|
|
29
|
+
size = 'default',
|
|
30
|
+
children,
|
|
31
|
+
...props
|
|
32
|
+
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & { size?: 'sm' | 'default' }) {
|
|
33
|
+
return (
|
|
34
|
+
<SelectPrimitive.Trigger
|
|
35
|
+
data-slot="select-trigger"
|
|
36
|
+
data-size={size}
|
|
37
|
+
className={cn(
|
|
38
|
+
controlClassName,
|
|
39
|
+
"flex h-8 items-center justify-between gap-2 whitespace-nowrap px-2.5 data-[size=sm]:h-7 data-[placeholder]:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
{children}
|
|
45
|
+
<SelectPrimitive.Icon asChild>
|
|
46
|
+
<ChevronDownIcon className="size-4 opacity-100" />
|
|
47
|
+
</SelectPrimitive.Icon>
|
|
48
|
+
</SelectPrimitive.Trigger>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function SelectContent({
|
|
53
|
+
className,
|
|
54
|
+
children,
|
|
55
|
+
position = 'popper',
|
|
56
|
+
align = 'start',
|
|
57
|
+
...props
|
|
58
|
+
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
|
59
|
+
return (
|
|
60
|
+
<SelectPrimitive.Portal>
|
|
61
|
+
<SelectPrimitive.Content
|
|
62
|
+
data-slot="select-content"
|
|
63
|
+
className={cn(
|
|
64
|
+
menuContentClassName,
|
|
65
|
+
'relative max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto',
|
|
66
|
+
position === 'popper' &&
|
|
67
|
+
'w-full min-w-(--radix-select-trigger-width) data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
|
68
|
+
className,
|
|
69
|
+
)}
|
|
70
|
+
position={position}
|
|
71
|
+
align={align}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
<SelectScrollUpButton />
|
|
75
|
+
<SelectPrimitive.Viewport
|
|
76
|
+
className={cn(
|
|
77
|
+
position === 'popper' &&
|
|
78
|
+
'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width) scroll-my-1',
|
|
79
|
+
)}
|
|
80
|
+
>
|
|
81
|
+
{children}
|
|
82
|
+
</SelectPrimitive.Viewport>
|
|
83
|
+
<SelectScrollDownButton />
|
|
84
|
+
</SelectPrimitive.Content>
|
|
85
|
+
</SelectPrimitive.Portal>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
|
90
|
+
return (
|
|
91
|
+
<SelectPrimitive.Label
|
|
92
|
+
data-slot="select-label"
|
|
93
|
+
className={cn('px-2 py-1.5 text-body-sm font-medium text-muted-foreground', className)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function SelectItem({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
100
|
+
return (
|
|
101
|
+
<SelectPrimitive.Item
|
|
102
|
+
data-slot="select-item"
|
|
103
|
+
className={cn(menuItemClassName, 'w-full pr-8', className)}
|
|
104
|
+
{...props}
|
|
105
|
+
>
|
|
106
|
+
<span className="absolute right-2 flex size-4 items-center justify-center">
|
|
107
|
+
<SelectPrimitive.ItemIndicator>
|
|
108
|
+
<CheckIcon className="size-4 !text-foreground" />
|
|
109
|
+
</SelectPrimitive.ItemIndicator>
|
|
110
|
+
</span>
|
|
111
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
112
|
+
</SelectPrimitive.Item>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function SelectSeparator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
|
117
|
+
return (
|
|
118
|
+
<SelectPrimitive.Separator
|
|
119
|
+
data-slot="select-separator"
|
|
120
|
+
className={cn('pointer-events-none -mx-1 my-1 h-px bg-border', className)}
|
|
121
|
+
{...props}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function SelectScrollUpButton({
|
|
127
|
+
className,
|
|
128
|
+
...props
|
|
129
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
|
130
|
+
return (
|
|
131
|
+
<SelectPrimitive.ScrollUpButton
|
|
132
|
+
data-slot="select-scroll-up-button"
|
|
133
|
+
className={cn('flex cursor-default items-center justify-center py-1 text-muted-foreground', className)}
|
|
134
|
+
{...props}
|
|
135
|
+
>
|
|
136
|
+
<ChevronUpIcon className="size-4" />
|
|
137
|
+
</SelectPrimitive.ScrollUpButton>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function SelectScrollDownButton({
|
|
142
|
+
className,
|
|
143
|
+
...props
|
|
144
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
|
145
|
+
return (
|
|
146
|
+
<SelectPrimitive.ScrollDownButton
|
|
147
|
+
data-slot="select-scroll-down-button"
|
|
148
|
+
className={cn('flex cursor-default items-center justify-center py-1 text-muted-foreground', className)}
|
|
149
|
+
{...props}
|
|
150
|
+
>
|
|
151
|
+
<ChevronDownIcon className="size-4" />
|
|
152
|
+
</SelectPrimitive.ScrollDownButton>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export {
|
|
157
|
+
Select,
|
|
158
|
+
SelectContent,
|
|
159
|
+
SelectGroup,
|
|
160
|
+
SelectItem,
|
|
161
|
+
SelectLabel,
|
|
162
|
+
SelectScrollDownButton,
|
|
163
|
+
SelectScrollUpButton,
|
|
164
|
+
SelectSeparator,
|
|
165
|
+
SelectTrigger,
|
|
166
|
+
SelectValue,
|
|
167
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Separator as SeparatorPrimitive } from 'radix-ui';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils.js';
|
|
7
|
+
|
|
8
|
+
/** Figma: Components · Base › Separator (2085:227). 1 px `border` line. */
|
|
9
|
+
function Separator({
|
|
10
|
+
className,
|
|
11
|
+
orientation = 'horizontal',
|
|
12
|
+
decorative = true,
|
|
13
|
+
...props
|
|
14
|
+
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
|
15
|
+
return (
|
|
16
|
+
<SeparatorPrimitive.Root
|
|
17
|
+
data-slot="separator"
|
|
18
|
+
decorative={decorative}
|
|
19
|
+
orientation={orientation}
|
|
20
|
+
className={cn(
|
|
21
|
+
'shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
|
|
22
|
+
className,
|
|
23
|
+
)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { Separator };
|