appos 0.4.3-0 → 0.5.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/dist/exports/api/index.d.mts +3 -3
- package/dist/exports/api/index.mjs +34 -9
- package/dist/exports/api/workflows/index.d.mts +2 -2
- package/dist/exports/api/workflows/index.mjs +2 -2
- package/dist/exports/{auth-By0xx0MI.d.mts → auth-BCOIpGDO.d.mts} +325 -214
- package/dist/exports/cli/index.d.mts +105 -106
- package/dist/exports/cli/index.mjs +87 -13
- package/dist/exports/{constants-BePPc_yF.mjs → constants-BicCnEiJ.mjs} +7 -1
- package/dist/exports/{index-Bpo5QE7k.d.mts → index-CiuTp085.d.mts} +580 -614
- package/dist/exports/{openapi-crG3j4xx.mjs → openapi-uisUTLq7.mjs} +45 -102
- package/dist/exports/{send-email-Bgcdjy-e.mjs → send-email-CXxlkfFL.mjs} +1 -1
- package/dist/exports/{server-BlNxgHUc.mjs → server-CA4aI0U6.mjs} +6 -2
- package/dist/exports/storybook/index.d.ts +40 -0
- package/dist/exports/storybook/index.js +43 -0
- package/dist/exports/tests/api.d.mts +2 -2
- package/dist/exports/tests/api.mjs +3 -3
- package/dist/exports/vite/index.d.mts +19 -3
- package/dist/exports/vite/index.mjs +82 -6
- package/dist/exports/web/index.d.ts +2247 -700
- package/dist/exports/web/index.js +2445 -754
- package/dist/exports/web/routes.d.mts +41 -43
- package/dist/exports/web/ssr.d.mts +2 -2
- package/package.json +48 -31
- package/src/storybook/.storybook/main.ts +119 -0
- package/src/storybook/.storybook/manager.ts +75 -0
- package/src/storybook/.storybook/preview.tsx +76 -0
- package/src/storybook/components/ColorGrid.tsx +324 -0
- package/src/storybook/components/IconGallery.tsx +268 -0
- package/src/storybook/components/SpacingScale.tsx +236 -0
- package/src/storybook/components/TypographyScale.tsx +277 -0
- package/src/storybook/components/index.ts +3 -0
- package/src/storybook/docs/Introduction.mdx +29 -0
- package/src/storybook/docs/foundations/Colors.mdx +27 -0
- package/src/storybook/docs/foundations/Icons.mdx +26 -0
- package/src/storybook/docs/foundations/Spacing.mdx +23 -0
- package/src/storybook/docs/foundations/Typography.mdx +39 -0
- package/src/storybook/index.ts +147 -0
- package/src/storybook/shims/void-elements.js +18 -0
- package/src/web/client/auth-paths.ts +148 -0
- package/src/web/client/auth.ts +218 -0
- package/src/web/client/i18n.d.ts +9 -0
- package/src/web/client/i18n.ts +127 -0
- package/src/web/client/react-router.ts +84 -0
- package/src/web/hooks/index.ts +1 -0
- package/src/web/hooks/use-mobile.ts +19 -0
- package/src/web/index.ts +42 -0
- package/src/web/lib/colors.ts +220 -0
- package/src/web/lib/utils.ts +52 -0
- package/src/web/routes/auth/_layout.tsx +5 -11
- package/src/web/routes.ts +101 -0
- package/src/web/server/context.ts +14 -0
- package/src/web/server/render.ts +11 -0
- package/src/web/ssr.ts +17 -0
- package/src/web/ui/accordion.stories.tsx +183 -0
- package/src/web/ui/accordion.tsx +134 -0
- package/src/web/ui/alert-dialog.stories.tsx +215 -0
- package/src/web/ui/alert-dialog.tsx +328 -0
- package/src/web/ui/alert.stories.tsx +224 -0
- package/src/web/ui/alert.tsx +136 -0
- package/src/web/ui/animated-check.stories.tsx +200 -0
- package/src/web/ui/animated-check.tsx +98 -0
- package/src/web/ui/aspect-ratio.stories.tsx +204 -0
- package/src/web/ui/aspect-ratio.tsx +36 -0
- package/src/web/ui/auth/__mocks__/auth-context.tsx +273 -0
- package/src/web/ui/auth/auth-card.stories.tsx +295 -0
- package/src/web/ui/auth/auth-card.tsx +124 -0
- package/src/web/ui/auth/auth-layout.stories.tsx +330 -0
- package/src/web/ui/auth/auth-layout.tsx +269 -0
- package/src/web/ui/auth/captcha-widget.stories.tsx +200 -0
- package/src/web/ui/auth/captcha-widget.tsx +197 -0
- package/src/web/ui/auth/context.tsx +87 -0
- package/src/web/ui/auth/email-input.stories.tsx +167 -0
- package/src/web/ui/auth/email-input.tsx +84 -0
- package/src/web/ui/auth/forms/email-otp-form.tsx +377 -0
- package/src/web/ui/auth/forms/forgot-password-form.tsx +240 -0
- package/src/web/ui/auth/forms/index.ts +40 -0
- package/src/web/ui/auth/forms/login-form.tsx +486 -0
- package/src/web/ui/auth/forms/phone-otp-form.tsx +388 -0
- package/src/web/ui/auth/forms/reset-password-form.tsx +331 -0
- package/src/web/ui/auth/forms/signup-form.tsx +499 -0
- package/src/web/ui/auth/forms/two-factor-form.tsx +410 -0
- package/src/web/ui/auth/forms/verify-email-form.tsx +373 -0
- package/src/web/ui/auth/hooks.ts +266 -0
- package/src/web/ui/auth/index.ts +31 -0
- package/src/web/ui/auth/magic-link-form.tsx +191 -0
- package/src/web/ui/auth/otp-input.stories.tsx +199 -0
- package/src/web/ui/auth/otp-input.tsx +157 -0
- package/src/web/ui/auth/passkey-button.stories.tsx +115 -0
- package/src/web/ui/auth/passkey-button.tsx +113 -0
- package/src/web/ui/auth/passkey-register-button.stories.tsx +109 -0
- package/src/web/ui/auth/passkey-register-button.tsx +106 -0
- package/src/web/ui/auth/password-input.stories.tsx +287 -0
- package/src/web/ui/auth/password-input.tsx +306 -0
- package/src/web/ui/auth/phone-input.stories.tsx +160 -0
- package/src/web/ui/auth/phone-input.tsx +89 -0
- package/src/web/ui/auth/social-buttons.stories.tsx +217 -0
- package/src/web/ui/auth/social-buttons.tsx +196 -0
- package/src/web/ui/auth/sso-button.stories.tsx +160 -0
- package/src/web/ui/auth/sso-button.tsx +108 -0
- package/src/web/ui/auth/trust-signals.stories.tsx +208 -0
- package/src/web/ui/auth/trust-signals.tsx +260 -0
- package/src/web/ui/avatar.stories.tsx +302 -0
- package/src/web/ui/avatar.tsx +189 -0
- package/src/web/ui/badge.stories.tsx +225 -0
- package/src/web/ui/badge.tsx +66 -0
- package/src/web/ui/brand.stories.tsx +266 -0
- package/src/web/ui/brand.tsx +162 -0
- package/src/web/ui/breadcrumb.stories.tsx +271 -0
- package/src/web/ui/breadcrumb.tsx +214 -0
- package/src/web/ui/button-group.stories.tsx +251 -0
- package/src/web/ui/button-group.tsx +87 -0
- package/src/web/ui/button.stories.tsx +264 -0
- package/src/web/ui/button.tsx +120 -0
- package/src/web/ui/calendar.stories.tsx +235 -0
- package/src/web/ui/calendar.tsx +221 -0
- package/src/web/ui/card.stories.tsx +262 -0
- package/src/web/ui/card.tsx +199 -0
- package/src/web/ui/carousel.stories.tsx +244 -0
- package/src/web/ui/carousel.tsx +241 -0
- package/src/web/ui/chart.stories.tsx +833 -0
- package/src/web/ui/chart.tsx +390 -0
- package/src/web/ui/checkbox.stories.tsx +208 -0
- package/src/web/ui/checkbox.tsx +39 -0
- package/src/web/ui/collapsible.stories.tsx +239 -0
- package/src/web/ui/collapsible.tsx +21 -0
- package/src/web/ui/command.stories.tsx +291 -0
- package/src/web/ui/command.tsx +189 -0
- package/src/web/ui/context-menu.stories.tsx +255 -0
- package/src/web/ui/context-menu.tsx +263 -0
- package/src/web/ui/dialog.stories.tsx +263 -0
- package/src/web/ui/dialog.tsx +273 -0
- package/src/web/ui/drawer.stories.tsx +299 -0
- package/src/web/ui/drawer.tsx +130 -0
- package/src/web/ui/dropdown-menu.stories.tsx +320 -0
- package/src/web/ui/dropdown-menu.tsx +263 -0
- package/src/web/ui/empty.stories.tsx +204 -0
- package/src/web/ui/empty.tsx +101 -0
- package/src/web/ui/error-page.stories.tsx +181 -0
- package/src/web/ui/error-page.tsx +136 -0
- package/src/web/ui/field.stories.tsx +364 -0
- package/src/web/ui/field.tsx +252 -0
- package/src/web/ui/globals.css +3 -0
- package/src/web/ui/hover-card.stories.tsx +261 -0
- package/src/web/ui/hover-card.tsx +50 -0
- package/src/web/ui/index.ts +55 -0
- package/src/web/ui/input-group.stories.tsx +290 -0
- package/src/web/ui/input-group.tsx +157 -0
- package/src/web/ui/input-otp.stories.tsx +235 -0
- package/src/web/ui/input-otp.tsx +86 -0
- package/src/web/ui/input.stories.tsx +205 -0
- package/src/web/ui/input.tsx +30 -0
- package/src/web/ui/item.stories.tsx +348 -0
- package/src/web/ui/item.tsx +200 -0
- package/src/web/ui/kbd.stories.tsx +236 -0
- package/src/web/ui/kbd.tsx +27 -0
- package/src/web/ui/label.stories.tsx +180 -0
- package/src/web/ui/label.tsx +32 -0
- package/src/web/ui/locales/en/ui.json +13 -30
- package/src/web/ui/locales/zh-CN/ui.json +17 -34
- package/src/web/ui/locales/zh-TW/ui.json +17 -34
- package/src/web/ui/menubar.stories.tsx +385 -0
- package/src/web/ui/menubar.tsx +273 -0
- package/src/web/ui/navigation-menu.stories.tsx +361 -0
- package/src/web/ui/navigation-menu.tsx +168 -0
- package/src/web/ui/pagination.stories.tsx +312 -0
- package/src/web/ui/pagination.tsx +221 -0
- package/src/web/ui/popover.stories.tsx +315 -0
- package/src/web/ui/popover.tsx +89 -0
- package/src/web/ui/progress.stories.tsx +239 -0
- package/src/web/ui/progress.tsx +128 -0
- package/src/web/ui/radio-group.stories.tsx +315 -0
- package/src/web/ui/radio-group.tsx +38 -0
- package/src/web/ui/resizable.stories.tsx +304 -0
- package/src/web/ui/resizable.tsx +53 -0
- package/src/web/ui/scroll-area.stories.tsx +276 -0
- package/src/web/ui/scroll-area.tsx +53 -0
- package/src/web/ui/select.stories.tsx +314 -0
- package/src/web/ui/select.tsx +312 -0
- package/src/web/ui/separator.stories.tsx +246 -0
- package/src/web/ui/separator.tsx +36 -0
- package/src/web/ui/sheet.stories.tsx +417 -0
- package/src/web/ui/sheet.tsx +133 -0
- package/src/web/ui/sidebar.stories.tsx +402 -0
- package/src/web/ui/sidebar.tsx +723 -0
- package/src/web/ui/skeleton.stories.tsx +235 -0
- package/src/web/ui/skeleton.tsx +26 -0
- package/src/web/ui/slider.stories.tsx +294 -0
- package/src/web/ui/slider.tsx +75 -0
- package/src/web/ui/sonner.stories.tsx +279 -0
- package/src/web/ui/sonner.tsx +46 -0
- package/src/web/ui/spinner.stories.tsx +204 -0
- package/src/web/ui/spinner.tsx +28 -0
- package/src/web/ui/switch.stories.tsx +285 -0
- package/src/web/ui/switch.tsx +43 -0
- package/src/web/ui/table.stories.tsx +362 -0
- package/src/web/ui/table.tsx +115 -0
- package/src/web/ui/tabs.stories.tsx +401 -0
- package/src/web/ui/tabs.tsx +136 -0
- package/src/web/ui/textarea.stories.tsx +272 -0
- package/src/web/ui/textarea.tsx +28 -0
- package/src/web/ui/toggle-group.stories.tsx +334 -0
- package/src/web/ui/toggle-group.tsx +88 -0
- package/src/web/ui/toggle.stories.tsx +264 -0
- package/src/web/ui/toggle.tsx +57 -0
- package/src/web/ui/tooltip.stories.tsx +342 -0
- package/src/web/ui/tooltip.tsx +116 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import {
|
|
3
|
+
Checkbox,
|
|
4
|
+
Field,
|
|
5
|
+
FieldContent,
|
|
6
|
+
FieldDescription,
|
|
7
|
+
FieldError,
|
|
8
|
+
FieldGroup,
|
|
9
|
+
FieldLabel,
|
|
10
|
+
FieldLegend,
|
|
11
|
+
FieldSeparator,
|
|
12
|
+
FieldSet,
|
|
13
|
+
FieldTitle,
|
|
14
|
+
Input,
|
|
15
|
+
RadioGroup,
|
|
16
|
+
RadioGroupItem,
|
|
17
|
+
Switch,
|
|
18
|
+
} from "appos/web";
|
|
19
|
+
|
|
20
|
+
const meta = {
|
|
21
|
+
title: "Base/Field",
|
|
22
|
+
component: Field,
|
|
23
|
+
tags: ["autodocs"],
|
|
24
|
+
parameters: {
|
|
25
|
+
layout: "centered",
|
|
26
|
+
docs: {
|
|
27
|
+
description: {
|
|
28
|
+
component:
|
|
29
|
+
"A comprehensive field component system for building accessible forms. Supports vertical, horizontal, and responsive orientations with labels, descriptions, and error states.",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
argTypes: {
|
|
34
|
+
orientation: {
|
|
35
|
+
control: "select",
|
|
36
|
+
options: ["vertical", "horizontal", "responsive"],
|
|
37
|
+
description: "Layout orientation of the field",
|
|
38
|
+
table: { defaultValue: { summary: "vertical" } },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
} satisfies Meta<typeof Field>;
|
|
42
|
+
|
|
43
|
+
export default meta;
|
|
44
|
+
type Story = StoryObj<typeof meta>;
|
|
45
|
+
|
|
46
|
+
export const Default: Story = {
|
|
47
|
+
render: () => (
|
|
48
|
+
<Field className="w-75">
|
|
49
|
+
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
50
|
+
<Input id="email" type="email" placeholder="Enter your email" />
|
|
51
|
+
</Field>
|
|
52
|
+
),
|
|
53
|
+
parameters: {
|
|
54
|
+
docs: {
|
|
55
|
+
description: {
|
|
56
|
+
story: "Default vertical field layout with label and input.",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const WithDescription: Story = {
|
|
63
|
+
render: () => (
|
|
64
|
+
<Field className="w-75">
|
|
65
|
+
<FieldLabel htmlFor="username">Username</FieldLabel>
|
|
66
|
+
<Input id="username" placeholder="Enter username" />
|
|
67
|
+
<FieldDescription>
|
|
68
|
+
This will be your public display name.
|
|
69
|
+
</FieldDescription>
|
|
70
|
+
</Field>
|
|
71
|
+
),
|
|
72
|
+
parameters: {
|
|
73
|
+
docs: {
|
|
74
|
+
description: {
|
|
75
|
+
story:
|
|
76
|
+
"Field with description text below the input for additional context.",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const WithError: Story = {
|
|
83
|
+
render: () => (
|
|
84
|
+
<Field className="w-75" data-invalid="true">
|
|
85
|
+
<FieldLabel htmlFor="password">Password</FieldLabel>
|
|
86
|
+
<Input id="password" type="password" placeholder="Enter password" />
|
|
87
|
+
<FieldError>Password must be at least 8 characters.</FieldError>
|
|
88
|
+
</Field>
|
|
89
|
+
),
|
|
90
|
+
parameters: {
|
|
91
|
+
docs: {
|
|
92
|
+
description: {
|
|
93
|
+
story:
|
|
94
|
+
"Field with error state and error message. Use data-invalid for styling.",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const WithMultipleErrors: Story = {
|
|
101
|
+
render: () => (
|
|
102
|
+
<Field className="w-75" data-invalid="true">
|
|
103
|
+
<FieldLabel htmlFor="pass">Password</FieldLabel>
|
|
104
|
+
<Input id="pass" type="password" placeholder="Enter password" />
|
|
105
|
+
<FieldError
|
|
106
|
+
errors={[
|
|
107
|
+
{ message: "Must be at least 8 characters" },
|
|
108
|
+
{ message: "Must contain a number" },
|
|
109
|
+
{ message: "Must contain a special character" },
|
|
110
|
+
]}
|
|
111
|
+
/>
|
|
112
|
+
</Field>
|
|
113
|
+
),
|
|
114
|
+
parameters: {
|
|
115
|
+
docs: {
|
|
116
|
+
description: {
|
|
117
|
+
story: "Field with multiple error messages displayed as a list.",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const ErrorWithoutIcon: Story = {
|
|
124
|
+
render: () => (
|
|
125
|
+
<Field className="w-75" data-invalid="true">
|
|
126
|
+
<FieldLabel htmlFor="name">Name</FieldLabel>
|
|
127
|
+
<Input id="name" placeholder="Enter your name" />
|
|
128
|
+
<FieldError showIcon={false}>Name is required.</FieldError>
|
|
129
|
+
</Field>
|
|
130
|
+
),
|
|
131
|
+
parameters: {
|
|
132
|
+
docs: {
|
|
133
|
+
description: {
|
|
134
|
+
story: "Error message without the alert icon for a cleaner look.",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const HorizontalOrientation: Story = {
|
|
141
|
+
render: () => (
|
|
142
|
+
<Field orientation="horizontal" className="w-100">
|
|
143
|
+
<FieldLabel htmlFor="notify">Notifications</FieldLabel>
|
|
144
|
+
<Switch id="notify" />
|
|
145
|
+
</Field>
|
|
146
|
+
),
|
|
147
|
+
parameters: {
|
|
148
|
+
docs: {
|
|
149
|
+
description: {
|
|
150
|
+
story: "Horizontal field layout for switches and checkboxes.",
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const HorizontalWithContent: Story = {
|
|
157
|
+
render: () => (
|
|
158
|
+
<Field orientation="horizontal" className="w-100">
|
|
159
|
+
<Checkbox id="terms" />
|
|
160
|
+
<FieldContent>
|
|
161
|
+
<FieldTitle>Accept terms</FieldTitle>
|
|
162
|
+
<FieldDescription>
|
|
163
|
+
I agree to the terms of service and privacy policy.
|
|
164
|
+
</FieldDescription>
|
|
165
|
+
</FieldContent>
|
|
166
|
+
</Field>
|
|
167
|
+
),
|
|
168
|
+
parameters: {
|
|
169
|
+
docs: {
|
|
170
|
+
description: {
|
|
171
|
+
story: "Horizontal field with content block for title and description.",
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const FieldGroupVertical: Story = {
|
|
178
|
+
render: () => (
|
|
179
|
+
<FieldGroup className="w-75">
|
|
180
|
+
<Field>
|
|
181
|
+
<FieldLabel htmlFor="first">First Name</FieldLabel>
|
|
182
|
+
<Input id="first" placeholder="John" />
|
|
183
|
+
</Field>
|
|
184
|
+
<Field>
|
|
185
|
+
<FieldLabel htmlFor="last">Last Name</FieldLabel>
|
|
186
|
+
<Input id="last" placeholder="Doe" />
|
|
187
|
+
</Field>
|
|
188
|
+
<Field>
|
|
189
|
+
<FieldLabel htmlFor="email2">Email</FieldLabel>
|
|
190
|
+
<Input id="email2" type="email" placeholder="john@example.com" />
|
|
191
|
+
</Field>
|
|
192
|
+
</FieldGroup>
|
|
193
|
+
),
|
|
194
|
+
parameters: {
|
|
195
|
+
docs: {
|
|
196
|
+
description: {
|
|
197
|
+
story: "Group of vertical fields for organizing related inputs.",
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export const FieldSetWithLegend: Story = {
|
|
204
|
+
render: () => (
|
|
205
|
+
<FieldSet className="w-75">
|
|
206
|
+
<FieldLegend>Contact Information</FieldLegend>
|
|
207
|
+
<Field>
|
|
208
|
+
<FieldLabel htmlFor="phone">Phone</FieldLabel>
|
|
209
|
+
<Input id="phone" type="tel" placeholder="+1 (555) 000-0000" />
|
|
210
|
+
</Field>
|
|
211
|
+
<Field>
|
|
212
|
+
<FieldLabel htmlFor="address">Address</FieldLabel>
|
|
213
|
+
<Input id="address" placeholder="123 Main St" />
|
|
214
|
+
</Field>
|
|
215
|
+
</FieldSet>
|
|
216
|
+
),
|
|
217
|
+
parameters: {
|
|
218
|
+
docs: {
|
|
219
|
+
description: {
|
|
220
|
+
story:
|
|
221
|
+
"FieldSet with legend for grouping related fields with a heading.",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export const FieldSetWithRadioGroup: Story = {
|
|
228
|
+
render: () => (
|
|
229
|
+
<FieldSet className="w-75">
|
|
230
|
+
<FieldLegend>Notification Preferences</FieldLegend>
|
|
231
|
+
<FieldDescription>
|
|
232
|
+
Choose how you want to receive notifications.
|
|
233
|
+
</FieldDescription>
|
|
234
|
+
<RadioGroup defaultValue="email">
|
|
235
|
+
<Field orientation="horizontal">
|
|
236
|
+
<RadioGroupItem value="email" id="r1" />
|
|
237
|
+
<FieldContent>
|
|
238
|
+
<FieldTitle>Email</FieldTitle>
|
|
239
|
+
<FieldDescription>Get notified via email.</FieldDescription>
|
|
240
|
+
</FieldContent>
|
|
241
|
+
</Field>
|
|
242
|
+
<Field orientation="horizontal">
|
|
243
|
+
<RadioGroupItem value="sms" id="r2" />
|
|
244
|
+
<FieldContent>
|
|
245
|
+
<FieldTitle>SMS</FieldTitle>
|
|
246
|
+
<FieldDescription>Get notified via text message.</FieldDescription>
|
|
247
|
+
</FieldContent>
|
|
248
|
+
</Field>
|
|
249
|
+
<Field orientation="horizontal">
|
|
250
|
+
<RadioGroupItem value="push" id="r3" />
|
|
251
|
+
<FieldContent>
|
|
252
|
+
<FieldTitle>Push</FieldTitle>
|
|
253
|
+
<FieldDescription>
|
|
254
|
+
Get notified via push notification.
|
|
255
|
+
</FieldDescription>
|
|
256
|
+
</FieldContent>
|
|
257
|
+
</Field>
|
|
258
|
+
</RadioGroup>
|
|
259
|
+
</FieldSet>
|
|
260
|
+
),
|
|
261
|
+
parameters: {
|
|
262
|
+
docs: {
|
|
263
|
+
description: {
|
|
264
|
+
story: "FieldSet with radio group for single-selection options.",
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export const WithSeparator: Story = {
|
|
271
|
+
render: () => (
|
|
272
|
+
<FieldGroup className="w-75">
|
|
273
|
+
<Field>
|
|
274
|
+
<FieldLabel htmlFor="user">Username</FieldLabel>
|
|
275
|
+
<Input id="user" placeholder="Enter username" />
|
|
276
|
+
</Field>
|
|
277
|
+
<FieldSeparator />
|
|
278
|
+
<Field>
|
|
279
|
+
<FieldLabel htmlFor="pwd">Password</FieldLabel>
|
|
280
|
+
<Input id="pwd" type="password" placeholder="Enter password" />
|
|
281
|
+
</Field>
|
|
282
|
+
</FieldGroup>
|
|
283
|
+
),
|
|
284
|
+
parameters: {
|
|
285
|
+
docs: {
|
|
286
|
+
description: {
|
|
287
|
+
story: "Fields separated by a visual divider.",
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export const SeparatorWithText: Story = {
|
|
294
|
+
render: () => (
|
|
295
|
+
<FieldGroup className="w-75">
|
|
296
|
+
<Field>
|
|
297
|
+
<FieldLabel htmlFor="email3">Email</FieldLabel>
|
|
298
|
+
<Input id="email3" type="email" placeholder="you@example.com" />
|
|
299
|
+
</Field>
|
|
300
|
+
<FieldSeparator>or</FieldSeparator>
|
|
301
|
+
<Field>
|
|
302
|
+
<FieldLabel htmlFor="phone2">Phone</FieldLabel>
|
|
303
|
+
<Input id="phone2" type="tel" placeholder="+1 (555) 000-0000" />
|
|
304
|
+
</Field>
|
|
305
|
+
</FieldGroup>
|
|
306
|
+
),
|
|
307
|
+
parameters: {
|
|
308
|
+
docs: {
|
|
309
|
+
description: {
|
|
310
|
+
story: "Separator with text content for alternative options.",
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
export const DisabledField: Story = {
|
|
317
|
+
render: () => (
|
|
318
|
+
<Field className="w-75" data-disabled="true">
|
|
319
|
+
<FieldLabel htmlFor="disabled">Disabled Field</FieldLabel>
|
|
320
|
+
<Input id="disabled" disabled placeholder="Cannot edit" />
|
|
321
|
+
<FieldDescription>This field is disabled.</FieldDescription>
|
|
322
|
+
</Field>
|
|
323
|
+
),
|
|
324
|
+
parameters: {
|
|
325
|
+
docs: {
|
|
326
|
+
description: {
|
|
327
|
+
story: "Disabled field with reduced opacity styling.",
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export const ResponsiveOrientation: Story = {
|
|
334
|
+
render: () => (
|
|
335
|
+
<FieldGroup className="w-125">
|
|
336
|
+
<Field orientation="responsive">
|
|
337
|
+
<Checkbox id="marketing" />
|
|
338
|
+
<FieldContent>
|
|
339
|
+
<FieldTitle>Marketing emails</FieldTitle>
|
|
340
|
+
<FieldDescription>
|
|
341
|
+
Receive emails about new products, features, and more.
|
|
342
|
+
</FieldDescription>
|
|
343
|
+
</FieldContent>
|
|
344
|
+
</Field>
|
|
345
|
+
<Field orientation="responsive">
|
|
346
|
+
<Checkbox id="security" defaultChecked />
|
|
347
|
+
<FieldContent>
|
|
348
|
+
<FieldTitle>Security emails</FieldTitle>
|
|
349
|
+
<FieldDescription>
|
|
350
|
+
Receive emails about your account security.
|
|
351
|
+
</FieldDescription>
|
|
352
|
+
</FieldContent>
|
|
353
|
+
</Field>
|
|
354
|
+
</FieldGroup>
|
|
355
|
+
),
|
|
356
|
+
parameters: {
|
|
357
|
+
docs: {
|
|
358
|
+
description: {
|
|
359
|
+
story:
|
|
360
|
+
"Responsive orientation that switches between vertical and horizontal based on container width.",
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { AlertCircleIcon } from "lucide-react";
|
|
5
|
+
import type * as React from "react";
|
|
6
|
+
import { useMemo } from "react";
|
|
7
|
+
import { cn } from "#src/web/lib/utils.ts";
|
|
8
|
+
import { Label } from "#src/web/ui/label.tsx";
|
|
9
|
+
import { Separator } from "#src/web/ui/separator.tsx";
|
|
10
|
+
|
|
11
|
+
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
|
12
|
+
return (
|
|
13
|
+
<fieldset
|
|
14
|
+
data-slot="field-set"
|
|
15
|
+
className={cn(
|
|
16
|
+
"gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col",
|
|
17
|
+
className,
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function FieldLegend({
|
|
25
|
+
className,
|
|
26
|
+
variant = "legend",
|
|
27
|
+
...props
|
|
28
|
+
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
|
|
29
|
+
return (
|
|
30
|
+
<legend
|
|
31
|
+
data-slot="field-legend"
|
|
32
|
+
data-variant={variant}
|
|
33
|
+
className={cn(
|
|
34
|
+
"mb-3 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
data-slot="field-group"
|
|
46
|
+
className={cn(
|
|
47
|
+
"gap-7 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4 group/field-group @container/field-group flex w-full flex-col",
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fieldVariants = cva(
|
|
56
|
+
"data-[invalid=true]:text-destructive gap-3 group/field flex w-full",
|
|
57
|
+
{
|
|
58
|
+
variants: {
|
|
59
|
+
orientation: {
|
|
60
|
+
vertical: "flex-col [&>*]:w-full [&>.sr-only]:w-auto",
|
|
61
|
+
horizontal:
|
|
62
|
+
"flex-row items-center [&>[data-slot=field-label]]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
63
|
+
responsive:
|
|
64
|
+
"flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
defaultVariants: {
|
|
68
|
+
orientation: "vertical",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
function Field({
|
|
74
|
+
className,
|
|
75
|
+
orientation = "vertical",
|
|
76
|
+
...props
|
|
77
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
role="group"
|
|
81
|
+
data-slot="field"
|
|
82
|
+
data-orientation={orientation}
|
|
83
|
+
className={cn(fieldVariants({ orientation }), className)}
|
|
84
|
+
{...props}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
data-slot="field-content"
|
|
93
|
+
className={cn(
|
|
94
|
+
"gap-1 group/field-content flex flex-1 flex-col leading-snug",
|
|
95
|
+
className,
|
|
96
|
+
)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function FieldLabel({
|
|
103
|
+
className,
|
|
104
|
+
...props
|
|
105
|
+
}: React.ComponentProps<typeof Label>) {
|
|
106
|
+
return (
|
|
107
|
+
<Label
|
|
108
|
+
data-slot="field-label"
|
|
109
|
+
className={cn(
|
|
110
|
+
"has-data-checked:bg-primary/5 has-data-checked:border-primary dark:has-data-checked:bg-primary/10 gap-2 group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border *:data-[slot=field]:p-3 group/field-label peer/field-label flex w-fit leading-snug",
|
|
111
|
+
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
|
|
112
|
+
className,
|
|
113
|
+
)}
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
120
|
+
return (
|
|
121
|
+
<div
|
|
122
|
+
data-slot="field-label"
|
|
123
|
+
className={cn(
|
|
124
|
+
"gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50 flex w-fit items-center leading-snug",
|
|
125
|
+
className,
|
|
126
|
+
)}
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
133
|
+
return (
|
|
134
|
+
<p
|
|
135
|
+
data-slot="field-description"
|
|
136
|
+
className={cn(
|
|
137
|
+
"text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-data-[orientation=horizontal]/field:text-balance",
|
|
138
|
+
"last:mt-0 nth-last-2:-mt-1",
|
|
139
|
+
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
|
|
140
|
+
className,
|
|
141
|
+
)}
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function FieldSeparator({
|
|
148
|
+
children,
|
|
149
|
+
className,
|
|
150
|
+
...props
|
|
151
|
+
}: React.ComponentProps<"div"> & {
|
|
152
|
+
children?: React.ReactNode;
|
|
153
|
+
}) {
|
|
154
|
+
return (
|
|
155
|
+
<div
|
|
156
|
+
data-slot="field-separator"
|
|
157
|
+
data-content={!!children}
|
|
158
|
+
className={cn(
|
|
159
|
+
"-my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2 relative",
|
|
160
|
+
className,
|
|
161
|
+
)}
|
|
162
|
+
{...props}
|
|
163
|
+
>
|
|
164
|
+
<Separator className="absolute inset-0 top-1/2" />
|
|
165
|
+
{children && (
|
|
166
|
+
<span
|
|
167
|
+
className="text-muted-foreground px-2 bg-background relative mx-auto block w-fit"
|
|
168
|
+
data-slot="field-separator-content"
|
|
169
|
+
>
|
|
170
|
+
{children}
|
|
171
|
+
</span>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function FieldError({
|
|
178
|
+
className,
|
|
179
|
+
children,
|
|
180
|
+
errors,
|
|
181
|
+
showIcon = true,
|
|
182
|
+
...props
|
|
183
|
+
}: React.ComponentProps<"div"> & {
|
|
184
|
+
errors?: Array<{ message?: string } | undefined>;
|
|
185
|
+
/** Whether to show an icon alongside the error message. */
|
|
186
|
+
showIcon?: boolean;
|
|
187
|
+
}) {
|
|
188
|
+
const content = useMemo(() => {
|
|
189
|
+
if (children) {
|
|
190
|
+
return children;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!errors?.length) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const uniqueErrors = [
|
|
198
|
+
...new Map(errors.map((error) => [error?.message, error])).values(),
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
if (uniqueErrors?.length === 1) {
|
|
202
|
+
return uniqueErrors[0]?.message;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<ul className="ml-4 flex list-disc flex-col gap-1">
|
|
207
|
+
{uniqueErrors.map(
|
|
208
|
+
(error) =>
|
|
209
|
+
error?.message && <li key={error.message}>{error.message}</li>,
|
|
210
|
+
)}
|
|
211
|
+
</ul>
|
|
212
|
+
);
|
|
213
|
+
}, [children, errors]);
|
|
214
|
+
|
|
215
|
+
if (!content) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<div
|
|
221
|
+
role="alert"
|
|
222
|
+
data-slot="field-error"
|
|
223
|
+
className={cn(
|
|
224
|
+
"text-destructive text-sm font-normal",
|
|
225
|
+
showIcon && "flex items-start gap-1.5",
|
|
226
|
+
className,
|
|
227
|
+
)}
|
|
228
|
+
{...props}
|
|
229
|
+
>
|
|
230
|
+
{showIcon && (
|
|
231
|
+
<AlertCircleIcon
|
|
232
|
+
className="size-4 shrink-0 mt-0.5"
|
|
233
|
+
aria-hidden="true"
|
|
234
|
+
/>
|
|
235
|
+
)}
|
|
236
|
+
{showIcon ? <span>{content}</span> : content}
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export {
|
|
242
|
+
Field,
|
|
243
|
+
FieldLabel,
|
|
244
|
+
FieldDescription,
|
|
245
|
+
FieldError,
|
|
246
|
+
FieldGroup,
|
|
247
|
+
FieldLegend,
|
|
248
|
+
FieldSeparator,
|
|
249
|
+
FieldSet,
|
|
250
|
+
FieldContent,
|
|
251
|
+
FieldTitle,
|
|
252
|
+
};
|
package/src/web/ui/globals.css
CHANGED