@ponti-studios/ui 0.1.2 → 0.3.0
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/LICENSE +21 -0
- package/README.md +7 -7
- package/THIRD_PARTY_NOTICES.md +17 -0
- package/dist/index.js +1028 -638
- package/dist/index.js.map +1 -1
- package/package.json +30 -32
- package/src/animations.css +16 -434
- package/src/components/data-display/accordion.tsx +10 -7
- package/src/components/data-display/filter-chip.tsx +2 -2
- package/src/components/data-display/index.ts +5 -0
- package/src/components/data-display/metric-card.stories.tsx +48 -0
- package/src/components/data-display/metric-card.tsx +20 -0
- package/src/components/data-display/sort-controls.stories.tsx +45 -0
- package/src/components/data-display/sort-controls.tsx +99 -0
- package/src/components/data-display/sort-row.tsx +74 -0
- package/src/components/data-display/table.tsx +5 -5
- package/src/components/feedback/alert.tsx +4 -4
- package/src/components/feedback/empty-state.tsx +6 -6
- package/src/components/feedback/file-upload-status-badge.stories.tsx +28 -0
- package/src/components/feedback/file-upload-status-badge.tsx +33 -0
- package/src/components/feedback/file-upload-status.stories.tsx +57 -0
- package/src/components/feedback/file-upload-status.tsx +152 -0
- package/src/components/feedback/index.ts +9 -0
- package/src/components/feedback/progress-bar.stories.tsx +39 -0
- package/src/components/feedback/progress-bar.tsx +21 -0
- package/src/components/feedback/progress.stories.tsx +8 -6
- package/src/components/feedback/progress.tsx +1 -1
- package/src/components/feedback/skeleton.stories.tsx +36 -0
- package/src/components/feedback/skeleton.tsx +7 -0
- package/src/components/feedback/spinner.tsx +2 -2
- package/src/components/feedback/update-guard.stories.tsx +32 -0
- package/src/components/feedback/update-guard.tsx +280 -0
- package/src/components/forms/calendar.tsx +11 -11
- package/src/components/forms/date-month-select.stories.tsx +26 -0
- package/src/components/forms/date-month-select.tsx +70 -0
- package/src/components/forms/date-picker.tsx +3 -3
- package/src/components/forms/drop-zone.stories.tsx +56 -0
- package/src/components/forms/drop-zone.tsx +146 -0
- package/src/components/forms/entity-select.stories.tsx +65 -0
- package/src/components/forms/entity-select.tsx +79 -0
- package/src/components/forms/field.tsx +3 -3
- package/src/components/forms/group-by-select.stories.tsx +26 -0
- package/src/components/forms/group-by-select.tsx +47 -0
- package/src/components/forms/index.ts +10 -0
- package/src/components/forms/input.stories.tsx +4 -4
- package/src/components/forms/input.tsx +1 -1
- package/src/components/forms/passkey-management.stories.tsx +81 -0
- package/src/components/forms/passkey-management.tsx +138 -0
- package/src/components/forms/radio-group.tsx +14 -13
- package/src/components/forms/select.stories.tsx +4 -4
- package/src/components/forms/select.tsx +45 -53
- package/src/components/forms/slider.stories.tsx +5 -2
- package/src/components/forms/slider.tsx +27 -17
- package/src/components/forms/stepper.tsx +4 -4
- package/src/components/forms/switch.stories.tsx +1 -1
- package/src/components/forms/switch.tsx +2 -2
- package/src/components/forms/textarea.stories.tsx +2 -2
- package/src/components/forms/textarea.tsx +1 -1
- package/src/components/layout/index.ts +3 -0
- package/src/components/layout/scroll-area.stories.tsx +4 -4
- package/src/components/layout/scroll-area.tsx +23 -21
- package/src/components/layout/section-intro.stories.tsx +29 -0
- package/src/components/layout/section-intro.tsx +31 -0
- package/src/components/layout/surface-panel.stories.tsx +32 -0
- package/src/components/layout/surface-panel.tsx +15 -0
- package/src/components/navigation/app-navigation.stories.tsx +2 -2
- package/src/components/navigation/app-navigation.tsx +13 -13
- package/src/components/navigation/index.ts +4 -0
- package/src/components/navigation/pagination-controls.stories.tsx +41 -0
- package/src/components/navigation/pagination-controls.tsx +50 -0
- package/src/components/navigation/route-link.stories.tsx +30 -0
- package/src/components/navigation/route-link.tsx +42 -0
- package/src/components/navigation/tabs.stories.tsx +5 -5
- package/src/components/navigation/tabs.tsx +7 -7
- package/src/components/overlays/alert-dialog.tsx +21 -46
- package/src/components/overlays/command.tsx +51 -82
- package/src/components/overlays/dialog.tsx +5 -5
- package/src/components/overlays/dropdown-menu.stories.tsx +0 -5
- package/src/components/overlays/dropdown-menu.tsx +44 -142
- package/src/components/overlays/popover.stories.tsx +2 -2
- package/src/components/overlays/popover.tsx +34 -16
- package/src/components/overlays/sheet.stories.tsx +2 -2
- package/src/components/overlays/sheet.tsx +35 -17
- package/src/components/primitives/avatar.tsx +3 -3
- package/src/components/primitives/badge.tsx +7 -11
- package/src/components/primitives/button.tsx +41 -13
- package/src/components/primitives/card.stories.tsx +2 -2
- package/src/components/primitives/card.tsx +2 -2
- package/src/components/primitives/index.ts +2 -0
- package/src/components/primitives/label.stories.tsx +1 -1
- package/src/components/primitives/label.tsx +5 -6
- package/src/components/primitives/status-badge.stories.tsx +52 -0
- package/src/components/primitives/status-badge.tsx +33 -0
- package/src/constants/chart-colors.ts +5 -5
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-sort.ts +20 -0
- package/src/index.ts +30 -11
- package/src/styles.css +76 -1040
- package/src/tokens/color-systems.stories.tsx +9 -119
- package/src/tokens/foundations.stories.tsx +21 -113
- package/src/tokens/generated/dark.colors.generated.ts +56 -0
- package/src/tokens/generated/dark.css +54 -0
- package/src/tokens/generated/foundations.css +148 -0
- package/src/tokens/generated/foundations.generated.ts +443 -0
- package/src/tokens/generated/light.colors.generated.ts +56 -0
- package/src/tokens/generated/light.css +54 -0
- package/src/tokens/index.ts +74 -16
- package/src/tokens/layout.stories.tsx +3 -108
- package/src/tokens/colors.ts +0 -156
- package/src/tokens/motion.ts +0 -51
- package/src/tokens/radii.ts +0 -10
- package/src/tokens/shadows.ts +0 -34
- package/src/tokens/spacing.ts +0 -30
- package/src/tokens/typography.ts +0 -42
|
@@ -1,38 +1,39 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
|
|
4
|
+
import { Radio as RadioPrimitive } from "@base-ui/react/radio";
|
|
4
5
|
import { Circle } from "lucide-react";
|
|
5
6
|
import * as React from "react";
|
|
6
7
|
|
|
7
8
|
import { cn } from "../../lib/utils";
|
|
8
9
|
|
|
9
10
|
const RadioGroup = React.forwardRef<
|
|
10
|
-
React.ComponentRef<typeof RadioGroupPrimitive
|
|
11
|
-
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive
|
|
11
|
+
React.ComponentRef<typeof RadioGroupPrimitive>,
|
|
12
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive>
|
|
12
13
|
>(({ className, ...props }, ref) => {
|
|
13
|
-
return <RadioGroupPrimitive
|
|
14
|
+
return <RadioGroupPrimitive className={cn("grid gap-2", className)} {...props} ref={ref} />;
|
|
14
15
|
});
|
|
15
|
-
RadioGroup.displayName =
|
|
16
|
+
RadioGroup.displayName = "RadioGroup";
|
|
16
17
|
|
|
17
18
|
const RadioGroupItem = React.forwardRef<
|
|
18
|
-
React.ComponentRef<typeof
|
|
19
|
-
React.ComponentPropsWithoutRef<typeof
|
|
19
|
+
React.ComponentRef<typeof RadioPrimitive.Root>,
|
|
20
|
+
React.ComponentPropsWithoutRef<typeof RadioPrimitive.Root>
|
|
20
21
|
>(({ className, ...props }, ref) => {
|
|
21
22
|
return (
|
|
22
|
-
<
|
|
23
|
+
<RadioPrimitive.Root
|
|
23
24
|
ref={ref}
|
|
24
25
|
className={cn(
|
|
25
|
-
"border-
|
|
26
|
+
"border-primary bg-background text-foreground focus-visible:border-ring focus-visible:bg-accent/25 focus-visible:ring-ring aspect-square h-6 w-6 rounded-full border focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
26
27
|
className,
|
|
27
28
|
)}
|
|
28
29
|
{...props}
|
|
29
30
|
>
|
|
30
|
-
<
|
|
31
|
+
<RadioPrimitive.Indicator className="flex items-center justify-center">
|
|
31
32
|
<Circle className="fill-primary h-4 w-4" />
|
|
32
|
-
</
|
|
33
|
-
</
|
|
33
|
+
</RadioPrimitive.Indicator>
|
|
34
|
+
</RadioPrimitive.Root>
|
|
34
35
|
);
|
|
35
36
|
});
|
|
36
|
-
RadioGroupItem.displayName =
|
|
37
|
+
RadioGroupItem.displayName = "Radio";
|
|
37
38
|
|
|
38
39
|
export { RadioGroup, RadioGroupItem };
|
|
@@ -22,7 +22,7 @@ type Story = StoryObj<typeof meta>;
|
|
|
22
22
|
export const Default: Story = {
|
|
23
23
|
render: () => (
|
|
24
24
|
<Select>
|
|
25
|
-
<SelectTrigger className="w-[180px]">
|
|
25
|
+
<SelectTrigger className="w-[180px]" aria-label="Select a fruit">
|
|
26
26
|
<SelectValue placeholder="Select a fruit" />
|
|
27
27
|
</SelectTrigger>
|
|
28
28
|
<SelectContent>
|
|
@@ -39,7 +39,7 @@ export const Default: Story = {
|
|
|
39
39
|
export const WithGroups: Story = {
|
|
40
40
|
render: () => (
|
|
41
41
|
<Select>
|
|
42
|
-
<SelectTrigger className="w-[200px]">
|
|
42
|
+
<SelectTrigger className="w-[200px]" aria-label="Select timezone">
|
|
43
43
|
<SelectValue placeholder="Select timezone" />
|
|
44
44
|
</SelectTrigger>
|
|
45
45
|
<SelectContent>
|
|
@@ -62,7 +62,7 @@ export const WithGroups: Story = {
|
|
|
62
62
|
export const Disabled: Story = {
|
|
63
63
|
render: () => (
|
|
64
64
|
<Select disabled>
|
|
65
|
-
<SelectTrigger className="w-[180px]">
|
|
65
|
+
<SelectTrigger className="w-[180px]" aria-label="Select a fruit">
|
|
66
66
|
<SelectValue placeholder="Select a fruit" />
|
|
67
67
|
</SelectTrigger>
|
|
68
68
|
<SelectContent>
|
|
@@ -75,7 +75,7 @@ export const Disabled: Story = {
|
|
|
75
75
|
export const Small: Story = {
|
|
76
76
|
render: () => (
|
|
77
77
|
<Select>
|
|
78
|
-
<SelectTrigger size="sm" className="w-[160px]">
|
|
78
|
+
<SelectTrigger size="sm" className="w-[160px]" aria-label="Select an option">
|
|
79
79
|
<SelectValue placeholder="Select" />
|
|
80
80
|
</SelectTrigger>
|
|
81
81
|
<SelectContent>
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Select as SelectPrimitive } from "@base-ui/react/select";
|
|
2
2
|
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
5
|
import { cn } from "../../lib/utils";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
type SelectProps = Omit<React.ComponentProps<typeof SelectPrimitive.Root<string>>, "onValueChange" | "value" | "defaultValue"> & {
|
|
8
|
+
value?: string;
|
|
9
|
+
defaultValue?: string;
|
|
10
|
+
onValueChange?: (value: string) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function Select({ onValueChange, ...props }: SelectProps) {
|
|
14
|
+
return (
|
|
15
|
+
<SelectPrimitive.Root
|
|
16
|
+
data-slot="select"
|
|
17
|
+
{...props}
|
|
18
|
+
onValueChange={(value) => {
|
|
19
|
+
if (value !== null) onValueChange?.(value);
|
|
20
|
+
}}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
9
23
|
}
|
|
10
24
|
|
|
11
25
|
function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
|
@@ -21,21 +35,19 @@ function SelectTrigger({
|
|
|
21
35
|
size = "default",
|
|
22
36
|
children,
|
|
23
37
|
...props
|
|
24
|
-
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
|
25
|
-
size?: "sm" | "default";
|
|
26
|
-
}) {
|
|
38
|
+
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & { size?: "sm" | "default" }) {
|
|
27
39
|
return (
|
|
28
40
|
<SelectPrimitive.Trigger
|
|
29
41
|
data-slot="select-trigger"
|
|
30
42
|
data-size={size}
|
|
31
43
|
className={cn(
|
|
32
|
-
"data-placeholder:text-
|
|
44
|
+
"data-placeholder:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 aria-invalid:border-destructive flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-inset disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *: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",
|
|
33
45
|
className,
|
|
34
46
|
)}
|
|
35
47
|
{...props}
|
|
36
48
|
>
|
|
37
49
|
{children}
|
|
38
|
-
<SelectPrimitive.Icon
|
|
50
|
+
<SelectPrimitive.Icon>
|
|
39
51
|
<ChevronDownIcon className="size-4 opacity-50" />
|
|
40
52
|
</SelectPrimitive.Icon>
|
|
41
53
|
</SelectPrimitive.Trigger>
|
|
@@ -45,34 +57,27 @@ function SelectTrigger({
|
|
|
45
57
|
function SelectContent({
|
|
46
58
|
className,
|
|
47
59
|
children,
|
|
48
|
-
position = "item-aligned",
|
|
60
|
+
position: _position = "item-aligned",
|
|
49
61
|
...props
|
|
50
|
-
}: React.ComponentProps<typeof SelectPrimitive.
|
|
62
|
+
}: React.ComponentProps<typeof SelectPrimitive.Popup> & {
|
|
63
|
+
position?: "item-aligned" | "popper";
|
|
64
|
+
}) {
|
|
51
65
|
return (
|
|
52
66
|
<SelectPrimitive.Portal>
|
|
53
|
-
<SelectPrimitive.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"bg-raised text-primary data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 relative z-50 max-h-(--radix-select-content-available-height) min-w-32 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border",
|
|
57
|
-
position === "popper" &&
|
|
58
|
-
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
59
|
-
className,
|
|
60
|
-
)}
|
|
61
|
-
position={position}
|
|
62
|
-
{...props}
|
|
63
|
-
>
|
|
64
|
-
<SelectScrollUpButton />
|
|
65
|
-
<SelectPrimitive.Viewport
|
|
67
|
+
<SelectPrimitive.Positioner sideOffset={4}>
|
|
68
|
+
<SelectPrimitive.Popup
|
|
69
|
+
data-slot="select-content"
|
|
66
70
|
className={cn(
|
|
67
|
-
"p-1",
|
|
68
|
-
|
|
69
|
-
"h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width) scroll-my-1",
|
|
71
|
+
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 relative z-50 max-h-(--available-height) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 outline-none",
|
|
72
|
+
className,
|
|
70
73
|
)}
|
|
74
|
+
{...props}
|
|
71
75
|
>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
<SelectScrollUpButton />
|
|
77
|
+
<SelectPrimitive.List>{children}</SelectPrimitive.List>
|
|
78
|
+
<SelectScrollDownButton />
|
|
79
|
+
</SelectPrimitive.Popup>
|
|
80
|
+
</SelectPrimitive.Positioner>
|
|
76
81
|
</SelectPrimitive.Portal>
|
|
77
82
|
);
|
|
78
83
|
}
|
|
@@ -81,30 +86,23 @@ function SelectLabel({ className, ...props }: React.ComponentProps<typeof Select
|
|
|
81
86
|
return (
|
|
82
87
|
<SelectPrimitive.Label
|
|
83
88
|
data-slot="select-label"
|
|
84
|
-
className={cn("text-
|
|
89
|
+
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
|
85
90
|
{...props}
|
|
86
91
|
/>
|
|
87
92
|
);
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
function SelectItem({
|
|
91
|
-
className,
|
|
92
|
-
children,
|
|
93
|
-
...props
|
|
94
|
-
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
95
|
+
function SelectItem({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
95
96
|
return (
|
|
96
97
|
<SelectPrimitive.Item
|
|
97
98
|
data-slot="select-item"
|
|
98
99
|
className={cn(
|
|
99
|
-
"
|
|
100
|
+
"data-highlighted:bg-accent data-highlighted:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50",
|
|
100
101
|
className,
|
|
101
102
|
)}
|
|
102
103
|
{...props}
|
|
103
104
|
>
|
|
104
|
-
<span
|
|
105
|
-
data-slot="select-item-indicator"
|
|
106
|
-
className="absolute right-2 flex size-3.5 items-center justify-center"
|
|
107
|
-
>
|
|
105
|
+
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
|
108
106
|
<SelectPrimitive.ItemIndicator>
|
|
109
107
|
<CheckIcon className="size-4" />
|
|
110
108
|
</SelectPrimitive.ItemIndicator>
|
|
@@ -114,33 +112,27 @@ function SelectItem({
|
|
|
114
112
|
);
|
|
115
113
|
}
|
|
116
114
|
|
|
117
|
-
function SelectScrollUpButton({
|
|
118
|
-
className,
|
|
119
|
-
...props
|
|
120
|
-
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
|
115
|
+
function SelectScrollUpButton({ className, ...props }: React.ComponentProps<"div">) {
|
|
121
116
|
return (
|
|
122
|
-
<SelectPrimitive.
|
|
117
|
+
<SelectPrimitive.ScrollUpArrow
|
|
123
118
|
data-slot="select-scroll-up-button"
|
|
124
119
|
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
|
125
120
|
{...props}
|
|
126
121
|
>
|
|
127
122
|
<ChevronUpIcon className="size-4" />
|
|
128
|
-
</SelectPrimitive.
|
|
123
|
+
</SelectPrimitive.ScrollUpArrow>
|
|
129
124
|
);
|
|
130
125
|
}
|
|
131
126
|
|
|
132
|
-
function SelectScrollDownButton({
|
|
133
|
-
className,
|
|
134
|
-
...props
|
|
135
|
-
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
|
127
|
+
function SelectScrollDownButton({ className, ...props }: React.ComponentProps<"div">) {
|
|
136
128
|
return (
|
|
137
|
-
<SelectPrimitive.
|
|
129
|
+
<SelectPrimitive.ScrollDownArrow
|
|
138
130
|
data-slot="select-scroll-down-button"
|
|
139
131
|
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
|
140
132
|
{...props}
|
|
141
133
|
>
|
|
142
134
|
<ChevronDownIcon className="size-4" />
|
|
143
|
-
</SelectPrimitive.
|
|
135
|
+
</SelectPrimitive.ScrollDownArrow>
|
|
144
136
|
);
|
|
145
137
|
}
|
|
146
138
|
|
|
@@ -30,6 +30,7 @@ export const Default: Story = {
|
|
|
30
30
|
defaultValue: [50],
|
|
31
31
|
min: 0,
|
|
32
32
|
max: 100,
|
|
33
|
+
"aria-label": "Volume",
|
|
33
34
|
},
|
|
34
35
|
};
|
|
35
36
|
|
|
@@ -39,6 +40,7 @@ export const WithStep: Story = {
|
|
|
39
40
|
min: 0,
|
|
40
41
|
max: 100,
|
|
41
42
|
step: 10,
|
|
43
|
+
"aria-label": "Volume",
|
|
42
44
|
},
|
|
43
45
|
};
|
|
44
46
|
|
|
@@ -47,9 +49,9 @@ export const Controlled: Story = {
|
|
|
47
49
|
const [value, setValue] = React.useState([33]);
|
|
48
50
|
return (
|
|
49
51
|
<div className="w-full max-w-sm space-y-4 px-1">
|
|
50
|
-
<Slider value={value} onValueChange={setValue} min={0} max={100} />
|
|
52
|
+
<Slider aria-label="Volume" value={value} onValueChange={setValue} min={0} max={100} />
|
|
51
53
|
<div className="flex justify-between text-sm">
|
|
52
|
-
<span className="text-
|
|
54
|
+
<span className="text-muted-foreground">Volume</span>
|
|
53
55
|
<span className="font-medium tabular-nums">{value[0]}%</span>
|
|
54
56
|
</div>
|
|
55
57
|
</div>
|
|
@@ -63,5 +65,6 @@ export const Disabled: Story = {
|
|
|
63
65
|
disabled: true,
|
|
64
66
|
min: 0,
|
|
65
67
|
max: 100,
|
|
68
|
+
"aria-label": "Volume",
|
|
66
69
|
},
|
|
67
70
|
};
|
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { Slider as SliderPrimitive } from "@base-ui/react/slider";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
|
|
6
6
|
import { cn } from "../../lib/utils";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
8
|
+
type SliderProps = Omit<React.ComponentProps<typeof SliderPrimitive.Root>, "onValueChange" | "value" | "defaultValue"> & {
|
|
9
|
+
value?: number[];
|
|
10
|
+
defaultValue?: number[];
|
|
11
|
+
onValueChange?: (value: number[]) => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
|
15
|
+
({ className, onValueChange, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props }, ref) => (
|
|
16
|
+
<SliderPrimitive.Root
|
|
17
|
+
ref={ref}
|
|
18
|
+
className={cn("relative flex w-full touch-none items-center select-none", className)}
|
|
19
|
+
{...props}
|
|
20
|
+
onValueChange={(value) => onValueChange?.(Array.isArray(value) ? [...value] : [value])}
|
|
21
|
+
>
|
|
22
|
+
<SliderPrimitive.Track className="bg-muted relative h-2 w-full grow overflow-hidden rounded-full">
|
|
23
|
+
<SliderPrimitive.Indicator className="bg-accent absolute h-full" />
|
|
24
|
+
</SliderPrimitive.Track>
|
|
25
|
+
<SliderPrimitive.Thumb
|
|
26
|
+
aria-label={ariaLabel ?? "Slider"}
|
|
27
|
+
aria-labelledby={ariaLabelledby}
|
|
28
|
+
className="border-border focus-visible:border-ring focus-visible:bg-accent focus-visible:ring-ring block h-6 w-6 rounded-full border bg-background shadow-sm ring-1 ring-black/5 transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50"
|
|
29
|
+
/>
|
|
30
|
+
</SliderPrimitive.Root>
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
Slider.displayName = "Slider";
|
|
24
34
|
|
|
25
35
|
export { Slider };
|
|
@@ -32,19 +32,19 @@ function Stepper({
|
|
|
32
32
|
const atMax = value >= max;
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
|
-
<div className={cn("
|
|
35
|
+
<div className={cn("flex items-center rounded-lg border border-input bg-muted", className)}>
|
|
36
36
|
<Button
|
|
37
37
|
type="button"
|
|
38
38
|
variant="ghost"
|
|
39
39
|
onClick={() => onChange(Math.max(min, value - step))}
|
|
40
40
|
disabled={disabled || atMin}
|
|
41
|
-
className="
|
|
41
|
+
className="text-muted-foreground rounded-none rounded-l-lg border-r"
|
|
42
42
|
aria-label={decreaseLabel}
|
|
43
43
|
>
|
|
44
44
|
<Minus className="size-3.5" />
|
|
45
45
|
</Button>
|
|
46
46
|
<div className="flex min-w-[3ch] flex-1 items-center justify-center px-3">
|
|
47
|
-
<span className="text-
|
|
47
|
+
<span className="text-foreground text-sm font-semibold tabular-nums">
|
|
48
48
|
{format ? format(value) : value}
|
|
49
49
|
</span>
|
|
50
50
|
</div>
|
|
@@ -53,7 +53,7 @@ function Stepper({
|
|
|
53
53
|
variant="ghost"
|
|
54
54
|
onClick={() => onChange(Math.min(max, value + step))}
|
|
55
55
|
disabled={disabled || atMax}
|
|
56
|
-
className="
|
|
56
|
+
className="text-muted-foreground rounded-none rounded-r-lg border-l"
|
|
57
57
|
aria-label={increaseLabel}
|
|
58
58
|
>
|
|
59
59
|
<Plus className="size-3.5" />
|
|
@@ -49,7 +49,7 @@ function SwitchPreview({
|
|
|
49
49
|
onCheckedChange={setCurrentChecked}
|
|
50
50
|
size={size}
|
|
51
51
|
/>
|
|
52
|
-
<label htmlFor={id} className="text-
|
|
52
|
+
<label htmlFor={id} className="text-foreground text-sm leading-none font-medium">
|
|
53
53
|
{label}
|
|
54
54
|
</label>
|
|
55
55
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
|
|
4
4
|
import { cn } from "../../lib/utils";
|
|
@@ -15,7 +15,7 @@ function Switch({
|
|
|
15
15
|
data-slot="switch"
|
|
16
16
|
data-size={size}
|
|
17
17
|
className={cn(
|
|
18
|
-
"peer data-
|
|
18
|
+
"peer data-checked:bg-primary data-unchecked:bg-input focus-visible:ring-ring inline-flex shrink-0 items-center rounded-full border border-transparent p-px focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-checked:justify-end data-unchecked:justify-start",
|
|
19
19
|
size === "default" ? "h-[1.15rem] w-8" : "h-3.5 w-6",
|
|
20
20
|
className,
|
|
21
21
|
)}
|
|
@@ -22,7 +22,7 @@ function TextareaField({
|
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
24
|
<div className="grid gap-2">
|
|
25
|
-
<label htmlFor={id} className="text-
|
|
25
|
+
<label htmlFor={id} className="text-foreground text-sm leading-none font-medium">
|
|
26
26
|
{label}
|
|
27
27
|
</label>
|
|
28
28
|
<Textarea
|
|
@@ -32,7 +32,7 @@ function TextareaField({
|
|
|
32
32
|
{...props}
|
|
33
33
|
/>
|
|
34
34
|
{helpText ? (
|
|
35
|
-
<p id={helpId} className="text-
|
|
35
|
+
<p id={helpId} className="text-muted-foreground text-sm">
|
|
36
36
|
{helpText}
|
|
37
37
|
</p>
|
|
38
38
|
) : null}
|
|
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
|
7
7
|
<textarea
|
|
8
8
|
data-slot="textarea"
|
|
9
9
|
className={cn(
|
|
10
|
-
"placeholder:text-
|
|
10
|
+
"placeholder:text-muted-foreground aria-invalid:ring-destructive/20 aria-invalid:border-destructive focus-visible:ring-ring flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
11
11
|
className,
|
|
12
12
|
)}
|
|
13
13
|
{...props}
|
|
@@ -25,7 +25,7 @@ export const Default: Story = {
|
|
|
25
25
|
className="flex size-40 shrink-0 items-center justify-center rounded-lg"
|
|
26
26
|
style={{ backgroundColor: item.bg }}
|
|
27
27
|
>
|
|
28
|
-
<span className="text-
|
|
28
|
+
<span className="text-foreground font-medium">{item.label}</span>
|
|
29
29
|
</div>
|
|
30
30
|
)),
|
|
31
31
|
},
|
|
@@ -41,7 +41,7 @@ export const WithSnap: Story = {
|
|
|
41
41
|
className="flex size-48 shrink-0 items-center justify-center rounded-lg"
|
|
42
42
|
style={{ backgroundColor: item.bg }}
|
|
43
43
|
>
|
|
44
|
-
<span className="text-
|
|
44
|
+
<span className="text-foreground font-medium">{item.label}</span>
|
|
45
45
|
</div>
|
|
46
46
|
)),
|
|
47
47
|
},
|
|
@@ -57,7 +57,7 @@ export const Vertical: Story = {
|
|
|
57
57
|
className="flex h-24 shrink-0 items-center justify-center rounded-lg"
|
|
58
58
|
style={{ backgroundColor: item.bg }}
|
|
59
59
|
>
|
|
60
|
-
<span className="text-
|
|
60
|
+
<span className="text-foreground font-medium">{item.label}</span>
|
|
61
61
|
</div>
|
|
62
62
|
)),
|
|
63
63
|
},
|
|
@@ -74,7 +74,7 @@ export const ScreenshotsGallery: Story = {
|
|
|
74
74
|
className="flex aspect-video w-72 shrink-0 items-center justify-center overflow-hidden rounded-xl border"
|
|
75
75
|
style={{ backgroundColor: item.bg }}
|
|
76
76
|
>
|
|
77
|
-
<span className="text-
|
|
77
|
+
<span className="text-foreground/70 text-sm">{item.label}</span>
|
|
78
78
|
</div>
|
|
79
79
|
))}
|
|
80
80
|
</ScrollArea>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
|
|
1
2
|
import * as React from "react";
|
|
3
|
+
|
|
2
4
|
import { cn } from "../../lib/utils";
|
|
3
5
|
|
|
4
|
-
interface ScrollAreaProps extends React.
|
|
6
|
+
interface ScrollAreaProps extends React.ComponentProps<typeof ScrollAreaPrimitive.Root> {
|
|
5
7
|
orientation?: "horizontal" | "vertical";
|
|
6
8
|
snap?: "start" | "center" | "none";
|
|
7
9
|
}
|
|
@@ -9,31 +11,31 @@ interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
9
11
|
const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
|
|
10
12
|
({ className, children, orientation = "horizontal", snap = "none", ...props }, ref) => {
|
|
11
13
|
return (
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
>
|
|
24
|
-
{React.Children.map(children, (child) => {
|
|
25
|
-
if (!React.isValidElement(child)) return child;
|
|
26
|
-
if (snap !== "none") {
|
|
14
|
+
<ScrollAreaPrimitive.Root ref={ref} className={cn("relative", className)} {...props}>
|
|
15
|
+
<ScrollAreaPrimitive.Viewport
|
|
16
|
+
className={cn(
|
|
17
|
+
"scrollbar-hide flex overflow-auto",
|
|
18
|
+
orientation === "horizontal" ? "flex-row" : "flex-col",
|
|
19
|
+
snap !== "none" && (orientation === "horizontal" ? "snap-x" : "snap-y"),
|
|
20
|
+
snap !== "none" && "snap-mandatory",
|
|
21
|
+
)}
|
|
22
|
+
>
|
|
23
|
+
{React.Children.map(children, (child) => {
|
|
24
|
+
if (!React.isValidElement(child)) return child;
|
|
27
25
|
return React.cloneElement(child as React.ReactElement<{ className?: string }>, {
|
|
28
|
-
className: cn(
|
|
26
|
+
className: cn(
|
|
27
|
+
snap !== "none" && orientation === "horizontal" && `snap-${snap}`,
|
|
28
|
+
snap !== "none" && orientation === "vertical" && `snap-${snap}`,
|
|
29
|
+
(child.props as { className?: string }).className,
|
|
30
|
+
),
|
|
29
31
|
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
</div>
|
|
32
|
+
})}
|
|
33
|
+
</ScrollAreaPrimitive.Viewport>
|
|
34
|
+
</ScrollAreaPrimitive.Root>
|
|
34
35
|
);
|
|
35
36
|
},
|
|
36
37
|
);
|
|
38
|
+
|
|
37
39
|
ScrollArea.displayName = "ScrollArea";
|
|
38
40
|
|
|
39
41
|
export { ScrollArea };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { Button } from "../primitives/button";
|
|
4
|
+
import { SectionIntro } from "./section-intro";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Layout/SectionIntro",
|
|
8
|
+
component: SectionIntro,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
} satisfies Meta<typeof SectionIntro>;
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof meta>;
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
args: {
|
|
17
|
+
title: "Accounts",
|
|
18
|
+
description: "Connect and manage the accounts you track.",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const WithEyebrowAndActions: Story = {
|
|
23
|
+
args: {
|
|
24
|
+
eyebrow: "Settings",
|
|
25
|
+
title: "Security",
|
|
26
|
+
description: "Manage how you sign in and keep your account secure.",
|
|
27
|
+
actions: <Button variant="outline">Add passkey</Button>,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export interface SectionIntroProps {
|
|
4
|
+
eyebrow?: ReactNode;
|
|
5
|
+
title: ReactNode;
|
|
6
|
+
description?: ReactNode;
|
|
7
|
+
actions?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Page-level heading composition: eyebrow label, title, description, and an
|
|
12
|
+
* optional trailing actions slot.
|
|
13
|
+
*/
|
|
14
|
+
export function SectionIntro({ eyebrow, title, description, actions }: SectionIntroProps) {
|
|
15
|
+
return (
|
|
16
|
+
<div className="flex items-start justify-between gap-6">
|
|
17
|
+
<div className="min-w-0 flex-1">
|
|
18
|
+
{eyebrow ? (
|
|
19
|
+
<div className="text-muted-foreground mb-3 text-[11px] font-medium tracking-[0.18em] uppercase">
|
|
20
|
+
{eyebrow}
|
|
21
|
+
</div>
|
|
22
|
+
) : null}
|
|
23
|
+
<div className="space-y-3">
|
|
24
|
+
<h1 className="text-title-2 text-foreground font-semibold tracking-tight">{title}</h1>
|
|
25
|
+
{description ? <p className="text-sm text-muted-foreground">{description}</p> : null}
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
{actions ? <div className="shrink-0">{actions}</div> : null}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { SurfacePanel } from "./surface-panel";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Layout/SurfacePanel",
|
|
7
|
+
component: SurfacePanel,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
} satisfies Meta<typeof SurfacePanel>;
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<div className="w-80">
|
|
17
|
+
<SurfacePanel>
|
|
18
|
+
<p className="text-muted-foreground text-sm">Panel content goes here.</p>
|
|
19
|
+
</SurfacePanel>
|
|
20
|
+
</div>
|
|
21
|
+
),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const AsSection: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<div className="w-80">
|
|
27
|
+
<SurfacePanel as="section" aria-label="Example panel">
|
|
28
|
+
<p className="text-muted-foreground text-sm">Rendered as a semantic <section>.</p>
|
|
29
|
+
</SurfacePanel>
|
|
30
|
+
</div>
|
|
31
|
+
),
|
|
32
|
+
};
|