@emara/ui 1.1.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/components/ui/.gitkeep +0 -0
- package/components/ui/accordion.stories.tsx +231 -0
- package/components/ui/accordion.tsx +250 -0
- package/components/ui/app-shell.stories.tsx +270 -0
- package/components/ui/app-shell.tsx +491 -0
- package/components/ui/avatar.stories.tsx +174 -0
- package/components/ui/avatar.tsx +257 -0
- package/components/ui/badge.stories.tsx +127 -0
- package/components/ui/badge.tsx +146 -0
- package/components/ui/breadcrumb.stories.tsx +92 -0
- package/components/ui/breadcrumb.tsx +302 -0
- package/components/ui/button.stories.tsx +186 -0
- package/components/ui/button.tsx +128 -0
- package/components/ui/card.stories.tsx +279 -0
- package/components/ui/card.tsx +250 -0
- package/components/ui/checkbox.stories.tsx +93 -0
- package/components/ui/checkbox.tsx +131 -0
- package/components/ui/combobox.stories.tsx +489 -0
- package/components/ui/combobox.tsx +874 -0
- package/components/ui/context-menu.stories.tsx +202 -0
- package/components/ui/context-menu.tsx +309 -0
- package/components/ui/data-table.stories.tsx +227 -0
- package/components/ui/data-table.tsx +539 -0
- package/components/ui/date-picker.stories.tsx +225 -0
- package/components/ui/date-picker.tsx +597 -0
- package/components/ui/dialog.stories.tsx +193 -0
- package/components/ui/dialog.tsx +262 -0
- package/components/ui/divider.stories.tsx +84 -0
- package/components/ui/divider.tsx +135 -0
- package/components/ui/drawer.stories.tsx +218 -0
- package/components/ui/drawer.tsx +329 -0
- package/components/ui/dropdown-menu.stories.tsx +270 -0
- package/components/ui/dropdown-menu.tsx +353 -0
- package/components/ui/empty-state.stories.tsx +121 -0
- package/components/ui/empty-state.tsx +289 -0
- package/components/ui/field-group.stories.tsx +201 -0
- package/components/ui/field-group.tsx +276 -0
- package/components/ui/form.stories.tsx +219 -0
- package/components/ui/form.tsx +542 -0
- package/components/ui/input.stories.tsx +154 -0
- package/components/ui/input.tsx +208 -0
- package/components/ui/label.stories.tsx +84 -0
- package/components/ui/label.tsx +98 -0
- package/components/ui/page-header.stories.tsx +136 -0
- package/components/ui/page-header.tsx +315 -0
- package/components/ui/pagination.stories.tsx +136 -0
- package/components/ui/pagination.tsx +427 -0
- package/components/ui/popover.stories.tsx +212 -0
- package/components/ui/popover.tsx +167 -0
- package/components/ui/radio-group.stories.tsx +96 -0
- package/components/ui/radio-group.tsx +250 -0
- package/components/ui/select.stories.tsx +203 -0
- package/components/ui/select.tsx +318 -0
- package/components/ui/sidebar.stories.tsx +186 -0
- package/components/ui/sidebar.tsx +623 -0
- package/components/ui/skeleton.stories.tsx +131 -0
- package/components/ui/skeleton.tsx +311 -0
- package/components/ui/switch.stories.tsx +74 -0
- package/components/ui/switch.tsx +186 -0
- package/components/ui/table.stories.tsx +107 -0
- package/components/ui/table.tsx +285 -0
- package/components/ui/tabs.stories.tsx +222 -0
- package/components/ui/tabs.tsx +287 -0
- package/components/ui/textarea.stories.tsx +96 -0
- package/components/ui/textarea.tsx +182 -0
- package/components/ui/toast.stories.tsx +169 -0
- package/components/ui/toast.tsx +250 -0
- package/components/ui/tooltip.stories.tsx +146 -0
- package/components/ui/tooltip.tsx +156 -0
- package/components/ui/top-bar.stories.tsx +182 -0
- package/components/ui/top-bar.tsx +155 -0
- package/dist/components/ui/accordion.d.ts +45 -0
- package/dist/components/ui/accordion.d.ts.map +1 -0
- package/dist/components/ui/accordion.js +99 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/app-shell.d.ts +70 -0
- package/dist/components/ui/app-shell.d.ts.map +1 -0
- package/dist/components/ui/app-shell.js +199 -0
- package/dist/components/ui/app-shell.js.map +1 -0
- package/dist/components/ui/avatar.d.ts +41 -0
- package/dist/components/ui/avatar.d.ts.map +1 -0
- package/dist/components/ui/avatar.js +104 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/badge.d.ts +27 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/badge.js +65 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +35 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.js +88 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/button.d.ts +26 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +73 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/card.d.ts +52 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/card.js +96 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +18 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -0
- package/dist/components/ui/checkbox.js +59 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/combobox.d.ts +194 -0
- package/dist/components/ui/combobox.d.ts.map +1 -0
- package/dist/components/ui/combobox.js +361 -0
- package/dist/components/ui/combobox.js.map +1 -0
- package/dist/components/ui/context-menu.d.ts +46 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/context-menu.js +95 -0
- package/dist/components/ui/context-menu.js.map +1 -0
- package/dist/components/ui/data-table.d.ts +53 -0
- package/dist/components/ui/data-table.d.ts.map +1 -0
- package/dist/components/ui/data-table.js +163 -0
- package/dist/components/ui/data-table.js.map +1 -0
- package/dist/components/ui/date-picker.d.ts +103 -0
- package/dist/components/ui/date-picker.d.ts.map +1 -0
- package/dist/components/ui/date-picker.js +306 -0
- package/dist/components/ui/date-picker.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +40 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/dialog.js +110 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/divider.d.ts +30 -0
- package/dist/components/ui/divider.d.ts.map +1 -0
- package/dist/components/ui/divider.js +62 -0
- package/dist/components/ui/divider.js.map +1 -0
- package/dist/components/ui/drawer.d.ts +56 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/drawer.js +147 -0
- package/dist/components/ui/drawer.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +63 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.js +116 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/empty-state.d.ts +43 -0
- package/dist/components/ui/empty-state.d.ts.map +1 -0
- package/dist/components/ui/empty-state.js +128 -0
- package/dist/components/ui/empty-state.js.map +1 -0
- package/dist/components/ui/field-group.d.ts +38 -0
- package/dist/components/ui/field-group.d.ts.map +1 -0
- package/dist/components/ui/field-group.js +107 -0
- package/dist/components/ui/field-group.js.map +1 -0
- package/dist/components/ui/form.d.ts +67 -0
- package/dist/components/ui/form.d.ts.map +1 -0
- package/dist/components/ui/form.js +286 -0
- package/dist/components/ui/form.js.map +1 -0
- package/dist/components/ui/input.d.ts +36 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/input.js +99 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/label.d.ts +37 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +34 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/page-header.d.ts +65 -0
- package/dist/components/ui/page-header.d.ts.map +1 -0
- package/dist/components/ui/page-header.js +140 -0
- package/dist/components/ui/page-header.js.map +1 -0
- package/dist/components/ui/pagination.d.ts +67 -0
- package/dist/components/ui/pagination.d.ts.map +1 -0
- package/dist/components/ui/pagination.js +109 -0
- package/dist/components/ui/pagination.js.map +1 -0
- package/dist/components/ui/popover.d.ts +28 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/popover.js +85 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/radio-group.d.ts +35 -0
- package/dist/components/ui/radio-group.d.ts.map +1 -0
- package/dist/components/ui/radio-group.js +103 -0
- package/dist/components/ui/radio-group.js.map +1 -0
- package/dist/components/ui/select.d.ts +42 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +86 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/sidebar.d.ts +59 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sidebar.js +189 -0
- package/dist/components/ui/sidebar.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +77 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -0
- package/dist/components/ui/skeleton.js +115 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/switch.d.ts +26 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/components/ui/switch.js +84 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/table.d.ts +52 -0
- package/dist/components/ui/table.d.ts.map +1 -0
- package/dist/components/ui/table.js +109 -0
- package/dist/components/ui/table.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +42 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/tabs.js +163 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +26 -0
- package/dist/components/ui/textarea.d.ts.map +1 -0
- package/dist/components/ui/textarea.js +96 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/toast.d.ts +77 -0
- package/dist/components/ui/toast.d.ts.map +1 -0
- package/dist/components/ui/toast.js +141 -0
- package/dist/components/ui/toast.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +31 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip.js +71 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/components/ui/top-bar.d.ts +30 -0
- package/dist/components/ui/top-bar.d.ts.map +1 -0
- package/dist/components/ui/top-bar.js +64 -0
- package/dist/components/ui/top-bar.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/lib/utils.ts +6 -0
- package/package.json +112 -0
- package/styles/globals.css +685 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { RiCodeLine, RiGlobalLine, RiPaletteLine, RiSearchLine } from "@remixicon/react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Select,
|
|
7
|
+
SelectContent,
|
|
8
|
+
SelectGroup,
|
|
9
|
+
SelectItem,
|
|
10
|
+
SelectLabel,
|
|
11
|
+
SelectSeparator,
|
|
12
|
+
SelectTrigger,
|
|
13
|
+
SelectValue,
|
|
14
|
+
} from "./select";
|
|
15
|
+
|
|
16
|
+
const meta: Meta<typeof Select> = {
|
|
17
|
+
title: "Forms/Select",
|
|
18
|
+
component: Select,
|
|
19
|
+
parameters: { layout: "centered" },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj;
|
|
24
|
+
|
|
25
|
+
export const Default: Story = {
|
|
26
|
+
render: () => (
|
|
27
|
+
<Select>
|
|
28
|
+
<SelectTrigger className="w-56">
|
|
29
|
+
<SelectValue placeholder="Pick a framework" />
|
|
30
|
+
</SelectTrigger>
|
|
31
|
+
<SelectContent>
|
|
32
|
+
<SelectItem value="next">Next.js</SelectItem>
|
|
33
|
+
<SelectItem value="remix">Remix</SelectItem>
|
|
34
|
+
<SelectItem value="astro">Astro</SelectItem>
|
|
35
|
+
<SelectItem value="vite">Vite</SelectItem>
|
|
36
|
+
</SelectContent>
|
|
37
|
+
</Select>
|
|
38
|
+
),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const Sizes: Story = {
|
|
42
|
+
render: () => (
|
|
43
|
+
<div className="space-y-2">
|
|
44
|
+
{(["xs", "sm", "md", "lg", "xl"] as const).map((s) => (
|
|
45
|
+
<Select key={s}>
|
|
46
|
+
<SelectTrigger size={s} className="w-56">
|
|
47
|
+
<SelectValue placeholder={`size=${s}`} />
|
|
48
|
+
</SelectTrigger>
|
|
49
|
+
<SelectContent>
|
|
50
|
+
<SelectItem value="a">Apple</SelectItem>
|
|
51
|
+
<SelectItem value="b">Banana</SelectItem>
|
|
52
|
+
<SelectItem value="c">Cherry</SelectItem>
|
|
53
|
+
</SelectContent>
|
|
54
|
+
</Select>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const Grouped: Story = {
|
|
61
|
+
render: () => (
|
|
62
|
+
<Select>
|
|
63
|
+
<SelectTrigger className="w-64">
|
|
64
|
+
<SelectValue placeholder="Pick a fruit" />
|
|
65
|
+
</SelectTrigger>
|
|
66
|
+
<SelectContent>
|
|
67
|
+
<SelectGroup>
|
|
68
|
+
<SelectLabel>Tropical</SelectLabel>
|
|
69
|
+
<SelectItem value="mango">Mango</SelectItem>
|
|
70
|
+
<SelectItem value="pineapple">Pineapple</SelectItem>
|
|
71
|
+
<SelectItem value="papaya">Papaya</SelectItem>
|
|
72
|
+
</SelectGroup>
|
|
73
|
+
<SelectSeparator />
|
|
74
|
+
<SelectGroup>
|
|
75
|
+
<SelectLabel>Berries</SelectLabel>
|
|
76
|
+
<SelectItem value="strawberry">Strawberry</SelectItem>
|
|
77
|
+
<SelectItem value="blueberry">Blueberry</SelectItem>
|
|
78
|
+
</SelectGroup>
|
|
79
|
+
</SelectContent>
|
|
80
|
+
</Select>
|
|
81
|
+
),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const WithIcons: Story = {
|
|
85
|
+
render: () => (
|
|
86
|
+
<Select>
|
|
87
|
+
<SelectTrigger className="w-64" startAdornment={<RiPaletteLine />}>
|
|
88
|
+
<SelectValue placeholder="Pick a theme" />
|
|
89
|
+
</SelectTrigger>
|
|
90
|
+
<SelectContent>
|
|
91
|
+
<SelectItem value="light" icon={<RiGlobalLine />}>
|
|
92
|
+
Light
|
|
93
|
+
</SelectItem>
|
|
94
|
+
<SelectItem value="dark" icon={<RiCodeLine />}>
|
|
95
|
+
Dark
|
|
96
|
+
</SelectItem>
|
|
97
|
+
<SelectItem value="system" icon={<RiSearchLine />}>
|
|
98
|
+
System
|
|
99
|
+
</SelectItem>
|
|
100
|
+
</SelectContent>
|
|
101
|
+
</Select>
|
|
102
|
+
),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const WithDescriptions: Story = {
|
|
106
|
+
render: () => (
|
|
107
|
+
<Select>
|
|
108
|
+
<SelectTrigger className="w-72">
|
|
109
|
+
<SelectValue placeholder="Pick a plan" />
|
|
110
|
+
</SelectTrigger>
|
|
111
|
+
<SelectContent>
|
|
112
|
+
<SelectItem value="starter" description="Up to 3 projects. Free.">
|
|
113
|
+
Starter
|
|
114
|
+
</SelectItem>
|
|
115
|
+
<SelectItem value="pro" description="Unlimited projects. $12/mo.">
|
|
116
|
+
Pro
|
|
117
|
+
</SelectItem>
|
|
118
|
+
<SelectItem value="team" description="Seats and roles. $48/mo.">
|
|
119
|
+
Team
|
|
120
|
+
</SelectItem>
|
|
121
|
+
</SelectContent>
|
|
122
|
+
</Select>
|
|
123
|
+
),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const Invalid: Story = {
|
|
127
|
+
render: () => (
|
|
128
|
+
<Select>
|
|
129
|
+
<SelectTrigger className="w-56" invalid>
|
|
130
|
+
<SelectValue placeholder="Required field" />
|
|
131
|
+
</SelectTrigger>
|
|
132
|
+
<SelectContent>
|
|
133
|
+
<SelectItem value="a">A</SelectItem>
|
|
134
|
+
<SelectItem value="b">B</SelectItem>
|
|
135
|
+
</SelectContent>
|
|
136
|
+
</Select>
|
|
137
|
+
),
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const Loading: Story = {
|
|
141
|
+
render: () => (
|
|
142
|
+
<Select>
|
|
143
|
+
<SelectTrigger className="w-56" loading>
|
|
144
|
+
<SelectValue placeholder="Loading…" />
|
|
145
|
+
</SelectTrigger>
|
|
146
|
+
<SelectContent>
|
|
147
|
+
<SelectItem value="a">A</SelectItem>
|
|
148
|
+
</SelectContent>
|
|
149
|
+
</Select>
|
|
150
|
+
),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const Clearable: Story = {
|
|
154
|
+
render: () => {
|
|
155
|
+
const Wrapper = () => {
|
|
156
|
+
const [value, setValue] = useState("medium");
|
|
157
|
+
return (
|
|
158
|
+
<Select value={value} onValueChange={setValue}>
|
|
159
|
+
<SelectTrigger className="w-56" clearable value={value} onClear={() => setValue("")}>
|
|
160
|
+
<SelectValue placeholder="Pick a size" />
|
|
161
|
+
</SelectTrigger>
|
|
162
|
+
<SelectContent>
|
|
163
|
+
<SelectItem value="small">Small</SelectItem>
|
|
164
|
+
<SelectItem value="medium">Medium</SelectItem>
|
|
165
|
+
<SelectItem value="large">Large</SelectItem>
|
|
166
|
+
</SelectContent>
|
|
167
|
+
</Select>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
return <Wrapper />;
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const Disabled: Story = {
|
|
175
|
+
render: () => (
|
|
176
|
+
<Select disabled defaultValue="a">
|
|
177
|
+
<SelectTrigger className="w-56">
|
|
178
|
+
<SelectValue />
|
|
179
|
+
</SelectTrigger>
|
|
180
|
+
<SelectContent>
|
|
181
|
+
<SelectItem value="a">A</SelectItem>
|
|
182
|
+
<SelectItem value="b">B</SelectItem>
|
|
183
|
+
</SelectContent>
|
|
184
|
+
</Select>
|
|
185
|
+
),
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const DisabledItem: Story = {
|
|
189
|
+
render: () => (
|
|
190
|
+
<Select>
|
|
191
|
+
<SelectTrigger className="w-56">
|
|
192
|
+
<SelectValue placeholder="Pick one" />
|
|
193
|
+
</SelectTrigger>
|
|
194
|
+
<SelectContent>
|
|
195
|
+
<SelectItem value="free">Free</SelectItem>
|
|
196
|
+
<SelectItem value="pro">Pro</SelectItem>
|
|
197
|
+
<SelectItem value="enterprise" disabled>
|
|
198
|
+
Enterprise (contact sales)
|
|
199
|
+
</SelectItem>
|
|
200
|
+
</SelectContent>
|
|
201
|
+
</Select>
|
|
202
|
+
),
|
|
203
|
+
};
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
5
|
+
import {
|
|
6
|
+
RiArrowDownSLine,
|
|
7
|
+
RiArrowUpSLine,
|
|
8
|
+
RiCheckLine,
|
|
9
|
+
RiCloseLine,
|
|
10
|
+
RiLoader2Line,
|
|
11
|
+
} from "@remixicon/react";
|
|
12
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
13
|
+
|
|
14
|
+
import { cn } from "@/lib/utils";
|
|
15
|
+
|
|
16
|
+
// Per docs/emara-ui-phase-2-components.md §4.
|
|
17
|
+
|
|
18
|
+
const Select = SelectPrimitive.Root;
|
|
19
|
+
const SelectGroup = SelectPrimitive.Group;
|
|
20
|
+
const SelectValue = SelectPrimitive.Value;
|
|
21
|
+
|
|
22
|
+
// ----------------------------------------------------------------------------
|
|
23
|
+
// SelectTrigger
|
|
24
|
+
// ----------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
const selectTriggerVariants = cva(
|
|
27
|
+
[
|
|
28
|
+
"flex items-center justify-between w-full gap-2",
|
|
29
|
+
"rounded-md border border-input bg-background text-foreground",
|
|
30
|
+
"transition-colors",
|
|
31
|
+
"hover:border-foreground/40 disabled:hover:border-input",
|
|
32
|
+
"focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
33
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
34
|
+
"data-[placeholder]:text-muted-foreground",
|
|
35
|
+
"[&>span]:line-clamp-1 [&>span]:text-start",
|
|
36
|
+
].join(" "),
|
|
37
|
+
{
|
|
38
|
+
variants: {
|
|
39
|
+
size: {
|
|
40
|
+
xs: "h-7 ps-2.5 pe-2 text-xs",
|
|
41
|
+
sm: "h-8 ps-3 pe-2 text-xs",
|
|
42
|
+
md: "h-9 ps-3 pe-2 text-sm",
|
|
43
|
+
lg: "h-10 ps-3.5 pe-2.5 text-base",
|
|
44
|
+
xl: "h-12 ps-4 pe-3 text-base",
|
|
45
|
+
},
|
|
46
|
+
invalid: {
|
|
47
|
+
true: "border-destructive focus-visible:ring-destructive",
|
|
48
|
+
false: "",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
defaultVariants: { size: "md", invalid: false },
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
type SelectTriggerVariants = VariantProps<typeof selectTriggerVariants>;
|
|
56
|
+
|
|
57
|
+
type SelectTriggerProps = React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> &
|
|
58
|
+
SelectTriggerVariants & {
|
|
59
|
+
clearable?: boolean;
|
|
60
|
+
loading?: boolean;
|
|
61
|
+
startAdornment?: React.ReactNode;
|
|
62
|
+
onClear?: () => void;
|
|
63
|
+
/** Current value — needed for `clearable` to decide whether to show X. */
|
|
64
|
+
value?: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const SelectTrigger = forwardRef<
|
|
68
|
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
69
|
+
SelectTriggerProps
|
|
70
|
+
>(function SelectTrigger(
|
|
71
|
+
{
|
|
72
|
+
className,
|
|
73
|
+
size,
|
|
74
|
+
invalid,
|
|
75
|
+
clearable = false,
|
|
76
|
+
loading = false,
|
|
77
|
+
startAdornment,
|
|
78
|
+
onClear,
|
|
79
|
+
value,
|
|
80
|
+
children,
|
|
81
|
+
...props
|
|
82
|
+
},
|
|
83
|
+
ref,
|
|
84
|
+
) {
|
|
85
|
+
const showClear = clearable && !props.disabled && !loading && Boolean(value);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<SelectPrimitive.Trigger
|
|
89
|
+
ref={ref}
|
|
90
|
+
className={cn(selectTriggerVariants({ size, invalid }), className)}
|
|
91
|
+
{...props}
|
|
92
|
+
>
|
|
93
|
+
{startAdornment ? (
|
|
94
|
+
<span
|
|
95
|
+
aria-hidden="true"
|
|
96
|
+
className="text-muted-foreground me-1 inline-flex shrink-0 items-center [&_svg]:size-4 [&_svg]:shrink-0"
|
|
97
|
+
>
|
|
98
|
+
{startAdornment}
|
|
99
|
+
</span>
|
|
100
|
+
) : null}
|
|
101
|
+
{children}
|
|
102
|
+
<span className="text-muted-foreground ms-auto inline-flex shrink-0 items-center gap-1 [&_svg]:size-4 [&_svg]:shrink-0">
|
|
103
|
+
{showClear ? (
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
aria-label="Clear"
|
|
107
|
+
onClick={(e) => {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
e.stopPropagation();
|
|
110
|
+
onClear?.();
|
|
111
|
+
}}
|
|
112
|
+
className={cn(
|
|
113
|
+
"inline-flex items-center justify-center rounded p-0.5",
|
|
114
|
+
"hover:text-foreground",
|
|
115
|
+
"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
|
|
116
|
+
)}
|
|
117
|
+
>
|
|
118
|
+
<RiCloseLine />
|
|
119
|
+
</button>
|
|
120
|
+
) : null}
|
|
121
|
+
{loading ? (
|
|
122
|
+
<RiLoader2Line className="size-4 animate-spin" />
|
|
123
|
+
) : (
|
|
124
|
+
<SelectPrimitive.Icon asChild>
|
|
125
|
+
<RiArrowDownSLine />
|
|
126
|
+
</SelectPrimitive.Icon>
|
|
127
|
+
)}
|
|
128
|
+
</span>
|
|
129
|
+
</SelectPrimitive.Trigger>
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
SelectTrigger.displayName = "SelectTrigger";
|
|
133
|
+
|
|
134
|
+
// ----------------------------------------------------------------------------
|
|
135
|
+
// SelectContent
|
|
136
|
+
// ----------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
const SelectContent = forwardRef<
|
|
139
|
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
140
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
141
|
+
>(function SelectContent(
|
|
142
|
+
{ className, children, position = "popper", sideOffset = 4, ...props },
|
|
143
|
+
ref,
|
|
144
|
+
) {
|
|
145
|
+
return (
|
|
146
|
+
<SelectPrimitive.Portal>
|
|
147
|
+
<SelectPrimitive.Content
|
|
148
|
+
ref={ref}
|
|
149
|
+
position={position}
|
|
150
|
+
sideOffset={sideOffset}
|
|
151
|
+
className={cn(
|
|
152
|
+
"z-popover relative max-h-(--radix-select-content-available-height) min-w-32 overflow-hidden",
|
|
153
|
+
"border-border bg-popover text-popover-foreground rounded-md border shadow-md",
|
|
154
|
+
"data-[state=open]:animate-[scale-in_var(--duration-fast)_var(--ease-out)]",
|
|
155
|
+
"data-[state=closed]:animate-[scale-out_var(--duration-fast)_var(--ease-in)]",
|
|
156
|
+
position === "popper" && "w-(--radix-select-trigger-width)",
|
|
157
|
+
className,
|
|
158
|
+
)}
|
|
159
|
+
{...props}
|
|
160
|
+
>
|
|
161
|
+
<SelectScrollUpButton />
|
|
162
|
+
<SelectPrimitive.Viewport className="p-1">{children}</SelectPrimitive.Viewport>
|
|
163
|
+
<SelectScrollDownButton />
|
|
164
|
+
</SelectPrimitive.Content>
|
|
165
|
+
</SelectPrimitive.Portal>
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
SelectContent.displayName = "SelectContent";
|
|
169
|
+
|
|
170
|
+
// ----------------------------------------------------------------------------
|
|
171
|
+
// SelectScrollUpButton / SelectScrollDownButton
|
|
172
|
+
// ----------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
const SelectScrollUpButton = forwardRef<
|
|
175
|
+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
|
176
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
|
177
|
+
>(function SelectScrollUpButton({ className, ...props }, ref) {
|
|
178
|
+
return (
|
|
179
|
+
<SelectPrimitive.ScrollUpButton
|
|
180
|
+
ref={ref}
|
|
181
|
+
className={cn(
|
|
182
|
+
"text-muted-foreground flex cursor-default items-center justify-center py-1",
|
|
183
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
184
|
+
className,
|
|
185
|
+
)}
|
|
186
|
+
{...props}
|
|
187
|
+
>
|
|
188
|
+
<RiArrowUpSLine className="size-4" />
|
|
189
|
+
</SelectPrimitive.ScrollUpButton>
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
SelectScrollUpButton.displayName = "SelectScrollUpButton";
|
|
193
|
+
|
|
194
|
+
const SelectScrollDownButton = forwardRef<
|
|
195
|
+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
|
196
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
|
197
|
+
>(function SelectScrollDownButton({ className, ...props }, ref) {
|
|
198
|
+
return (
|
|
199
|
+
<SelectPrimitive.ScrollDownButton
|
|
200
|
+
ref={ref}
|
|
201
|
+
className={cn(
|
|
202
|
+
"text-muted-foreground flex cursor-default items-center justify-center py-1",
|
|
203
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
204
|
+
className,
|
|
205
|
+
)}
|
|
206
|
+
{...props}
|
|
207
|
+
>
|
|
208
|
+
<RiArrowDownSLine className="size-4" />
|
|
209
|
+
</SelectPrimitive.ScrollDownButton>
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
SelectScrollDownButton.displayName = "SelectScrollDownButton";
|
|
213
|
+
|
|
214
|
+
// ----------------------------------------------------------------------------
|
|
215
|
+
// SelectLabel
|
|
216
|
+
// ----------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
const SelectLabel = forwardRef<
|
|
219
|
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
|
220
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
|
221
|
+
>(function SelectLabel({ className, ...props }, ref) {
|
|
222
|
+
return (
|
|
223
|
+
<SelectPrimitive.Label
|
|
224
|
+
ref={ref}
|
|
225
|
+
className={cn(
|
|
226
|
+
"text-muted-foreground px-2 py-1.5 text-xs font-semibold tracking-wide uppercase",
|
|
227
|
+
className,
|
|
228
|
+
)}
|
|
229
|
+
{...props}
|
|
230
|
+
/>
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
SelectLabel.displayName = "SelectLabel";
|
|
234
|
+
|
|
235
|
+
// ----------------------------------------------------------------------------
|
|
236
|
+
// SelectItem (with Emara additions: description, icon)
|
|
237
|
+
// ----------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
type SelectItemProps = React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> & {
|
|
240
|
+
description?: React.ReactNode;
|
|
241
|
+
icon?: React.ReactNode;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const SelectItem = forwardRef<React.ElementRef<typeof SelectPrimitive.Item>, SelectItemProps>(
|
|
245
|
+
function SelectItem({ className, children, description, icon, ...props }, ref) {
|
|
246
|
+
return (
|
|
247
|
+
<SelectPrimitive.Item
|
|
248
|
+
ref={ref}
|
|
249
|
+
className={cn(
|
|
250
|
+
"relative flex w-full cursor-pointer items-start gap-2 rounded-sm py-1.5 ps-8 pe-2 text-sm outline-none select-none",
|
|
251
|
+
"transition-colors",
|
|
252
|
+
"focus:bg-accent focus:text-accent-foreground",
|
|
253
|
+
"data-[state=checked]:font-medium",
|
|
254
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
255
|
+
className,
|
|
256
|
+
)}
|
|
257
|
+
{...props}
|
|
258
|
+
>
|
|
259
|
+
<span className="absolute start-2 inline-flex h-4 w-4 items-center justify-center text-current">
|
|
260
|
+
<SelectPrimitive.ItemIndicator>
|
|
261
|
+
<RiCheckLine className="size-4" />
|
|
262
|
+
</SelectPrimitive.ItemIndicator>
|
|
263
|
+
</span>
|
|
264
|
+
|
|
265
|
+
<span className="flex flex-1 flex-col gap-0.5 leading-none">
|
|
266
|
+
<span className="inline-flex items-center gap-2">
|
|
267
|
+
{icon ? (
|
|
268
|
+
<span
|
|
269
|
+
aria-hidden="true"
|
|
270
|
+
className="text-muted-foreground inline-flex shrink-0 [&_svg]:size-4 [&_svg]:shrink-0"
|
|
271
|
+
>
|
|
272
|
+
{icon}
|
|
273
|
+
</span>
|
|
274
|
+
) : null}
|
|
275
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
276
|
+
</span>
|
|
277
|
+
{description ? (
|
|
278
|
+
<span className="text-muted-foreground text-xs">{description}</span>
|
|
279
|
+
) : null}
|
|
280
|
+
</span>
|
|
281
|
+
</SelectPrimitive.Item>
|
|
282
|
+
);
|
|
283
|
+
},
|
|
284
|
+
);
|
|
285
|
+
SelectItem.displayName = "SelectItem";
|
|
286
|
+
|
|
287
|
+
// ----------------------------------------------------------------------------
|
|
288
|
+
// SelectSeparator
|
|
289
|
+
// ----------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
const SelectSeparator = forwardRef<
|
|
292
|
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
|
293
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
|
294
|
+
>(function SelectSeparator({ className, ...props }, ref) {
|
|
295
|
+
return (
|
|
296
|
+
<SelectPrimitive.Separator
|
|
297
|
+
ref={ref}
|
|
298
|
+
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
|
299
|
+
{...props}
|
|
300
|
+
/>
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
SelectSeparator.displayName = "SelectSeparator";
|
|
304
|
+
|
|
305
|
+
export {
|
|
306
|
+
Select,
|
|
307
|
+
SelectGroup,
|
|
308
|
+
SelectValue,
|
|
309
|
+
SelectTrigger,
|
|
310
|
+
SelectContent,
|
|
311
|
+
SelectScrollUpButton,
|
|
312
|
+
SelectScrollDownButton,
|
|
313
|
+
SelectLabel,
|
|
314
|
+
SelectItem,
|
|
315
|
+
SelectSeparator,
|
|
316
|
+
selectTriggerVariants,
|
|
317
|
+
};
|
|
318
|
+
export type { SelectTriggerProps, SelectItemProps };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
RiBarChart2Line,
|
|
5
|
+
RiDashboardLine,
|
|
6
|
+
RiFolderLine,
|
|
7
|
+
RiSettings4Line,
|
|
8
|
+
RiTeamLine,
|
|
9
|
+
RiUser3Line,
|
|
10
|
+
RiWalletLine,
|
|
11
|
+
} from "@remixicon/react";
|
|
12
|
+
|
|
13
|
+
import { Avatar, AvatarFallback } from "./avatar";
|
|
14
|
+
import { Badge } from "./badge";
|
|
15
|
+
import {
|
|
16
|
+
Sidebar,
|
|
17
|
+
SidebarBody,
|
|
18
|
+
SidebarBrand,
|
|
19
|
+
SidebarCollapseToggle,
|
|
20
|
+
SidebarFooter,
|
|
21
|
+
SidebarGroup,
|
|
22
|
+
SidebarGroupLabel,
|
|
23
|
+
SidebarItem,
|
|
24
|
+
SidebarSubItem,
|
|
25
|
+
} from "./sidebar";
|
|
26
|
+
import { TooltipProvider } from "./tooltip";
|
|
27
|
+
|
|
28
|
+
const meta: Meta<typeof Sidebar> = {
|
|
29
|
+
title: "Layout/Sidebar",
|
|
30
|
+
component: Sidebar,
|
|
31
|
+
parameters: { layout: "fullscreen" },
|
|
32
|
+
argTypes: {
|
|
33
|
+
variant: { control: "select", options: ["filled", "outline", "floating"] },
|
|
34
|
+
collapsed: { control: "boolean" },
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
type Story = StoryObj<typeof Sidebar>;
|
|
40
|
+
|
|
41
|
+
function Brand() {
|
|
42
|
+
return (
|
|
43
|
+
<SidebarBrand>
|
|
44
|
+
<span className="bg-primary text-primary-foreground inline-flex size-6 items-center justify-center rounded text-xs font-bold">
|
|
45
|
+
E
|
|
46
|
+
</span>
|
|
47
|
+
<span>Emara</span>
|
|
48
|
+
</SidebarBrand>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function FooterUser() {
|
|
53
|
+
return (
|
|
54
|
+
<SidebarFooter>
|
|
55
|
+
<div className="flex items-center gap-2 px-2 py-1.5">
|
|
56
|
+
<Avatar size="sm">
|
|
57
|
+
<AvatarFallback>AZ</AvatarFallback>
|
|
58
|
+
</Avatar>
|
|
59
|
+
<div className="flex-1 truncate text-sm">
|
|
60
|
+
<div className="truncate font-medium">Abdelkader</div>
|
|
61
|
+
<div className="text-muted-foreground truncate text-xs">abdel@emara.io</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</SidebarFooter>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function NavContent() {
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
<SidebarGroup>
|
|
72
|
+
<SidebarGroupLabel>Workspace</SidebarGroupLabel>
|
|
73
|
+
<SidebarItem icon={<RiDashboardLine />} label="Dashboard" tooltip="Dashboard" active />
|
|
74
|
+
<SidebarItem
|
|
75
|
+
icon={<RiTeamLine />}
|
|
76
|
+
label="Team"
|
|
77
|
+
tooltip="Team"
|
|
78
|
+
badge={
|
|
79
|
+
<Badge size="xs" variant="info">
|
|
80
|
+
12
|
|
81
|
+
</Badge>
|
|
82
|
+
}
|
|
83
|
+
/>
|
|
84
|
+
<SidebarItem
|
|
85
|
+
icon={<RiFolderLine />}
|
|
86
|
+
label="Projects"
|
|
87
|
+
tooltip="Projects"
|
|
88
|
+
expandable
|
|
89
|
+
defaultExpanded
|
|
90
|
+
>
|
|
91
|
+
<SidebarSubItem label="Apollo" />
|
|
92
|
+
<SidebarSubItem label="Mercury" active />
|
|
93
|
+
<SidebarSubItem label="Gemini" />
|
|
94
|
+
</SidebarItem>
|
|
95
|
+
</SidebarGroup>
|
|
96
|
+
|
|
97
|
+
<SidebarGroup>
|
|
98
|
+
<SidebarGroupLabel>Insights</SidebarGroupLabel>
|
|
99
|
+
<SidebarItem icon={<RiBarChart2Line />} label="Analytics" tooltip="Analytics" />
|
|
100
|
+
<SidebarItem icon={<RiWalletLine />} label="Billing" tooltip="Billing" kbd={["⌘", "B"]} />
|
|
101
|
+
</SidebarGroup>
|
|
102
|
+
|
|
103
|
+
<SidebarGroup>
|
|
104
|
+
<SidebarGroupLabel>Settings</SidebarGroupLabel>
|
|
105
|
+
<SidebarItem icon={<RiUser3Line />} label="Profile" tooltip="Profile" />
|
|
106
|
+
<SidebarItem icon={<RiSettings4Line />} label="Preferences" tooltip="Preferences" />
|
|
107
|
+
</SidebarGroup>
|
|
108
|
+
</>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const Filled: Story = {
|
|
113
|
+
args: { variant: "filled" },
|
|
114
|
+
render: (args) => (
|
|
115
|
+
<TooltipProvider delayDuration={200}>
|
|
116
|
+
<div className="flex h-[32rem]">
|
|
117
|
+
<Sidebar {...args}>
|
|
118
|
+
<Brand />
|
|
119
|
+
<SidebarBody>
|
|
120
|
+
<NavContent />
|
|
121
|
+
</SidebarBody>
|
|
122
|
+
<FooterUser />
|
|
123
|
+
</Sidebar>
|
|
124
|
+
<main className="text-muted-foreground flex-1 p-6">Main content area</main>
|
|
125
|
+
</div>
|
|
126
|
+
</TooltipProvider>
|
|
127
|
+
),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const Outline: Story = {
|
|
131
|
+
...Filled,
|
|
132
|
+
args: { variant: "outline" },
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const Floating: Story = {
|
|
136
|
+
...Filled,
|
|
137
|
+
args: { variant: "floating" },
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const Collapsed: Story = {
|
|
141
|
+
args: { collapsed: true },
|
|
142
|
+
render: (args) => (
|
|
143
|
+
<TooltipProvider delayDuration={200}>
|
|
144
|
+
<div className="flex h-[32rem]">
|
|
145
|
+
<Sidebar {...args}>
|
|
146
|
+
<Brand />
|
|
147
|
+
<SidebarBody>
|
|
148
|
+
<NavContent />
|
|
149
|
+
</SidebarBody>
|
|
150
|
+
<FooterUser />
|
|
151
|
+
</Sidebar>
|
|
152
|
+
<main className="text-muted-foreground flex-1 p-6">
|
|
153
|
+
Main content area (collapsed sidebar — hover items to see tooltips, click Projects for its
|
|
154
|
+
popover)
|
|
155
|
+
</main>
|
|
156
|
+
</div>
|
|
157
|
+
</TooltipProvider>
|
|
158
|
+
),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const Interactive: Story = {
|
|
162
|
+
render: () => {
|
|
163
|
+
function Demo() {
|
|
164
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
165
|
+
return (
|
|
166
|
+
<TooltipProvider delayDuration={200}>
|
|
167
|
+
<div className="flex h-[32rem]">
|
|
168
|
+
<Sidebar collapsed={collapsed}>
|
|
169
|
+
<Brand />
|
|
170
|
+
<SidebarBody>
|
|
171
|
+
<NavContent />
|
|
172
|
+
</SidebarBody>
|
|
173
|
+
<SidebarFooter>
|
|
174
|
+
<SidebarCollapseToggle onClick={() => setCollapsed((v) => !v)} />
|
|
175
|
+
</SidebarFooter>
|
|
176
|
+
</Sidebar>
|
|
177
|
+
<main className="text-muted-foreground flex-1 p-6">
|
|
178
|
+
Click the collapse button at the bottom of the sidebar.
|
|
179
|
+
</main>
|
|
180
|
+
</div>
|
|
181
|
+
</TooltipProvider>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return <Demo />;
|
|
185
|
+
},
|
|
186
|
+
};
|