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,261 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { IconBrandGithub, IconCalendar, IconMapPin } from "@tabler/icons-react";
|
|
3
|
+
import {
|
|
4
|
+
Avatar,
|
|
5
|
+
AvatarFallback,
|
|
6
|
+
AvatarImage,
|
|
7
|
+
Button,
|
|
8
|
+
HoverCard,
|
|
9
|
+
HoverCardContent,
|
|
10
|
+
HoverCardTrigger,
|
|
11
|
+
} from "appos/web";
|
|
12
|
+
|
|
13
|
+
const meta = {
|
|
14
|
+
title: "Base/HoverCard",
|
|
15
|
+
component: HoverCard,
|
|
16
|
+
tags: ["autodocs"],
|
|
17
|
+
parameters: {
|
|
18
|
+
layout: "centered",
|
|
19
|
+
docs: {
|
|
20
|
+
description: {
|
|
21
|
+
component:
|
|
22
|
+
"A preview card that appears on hover. Use for showing additional information without leaving the current context.",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
} satisfies Meta<typeof HoverCard>;
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
29
|
+
type Story = StoryObj<typeof meta>;
|
|
30
|
+
|
|
31
|
+
export const Default: Story = {
|
|
32
|
+
render: () => (
|
|
33
|
+
<HoverCard>
|
|
34
|
+
<HoverCardTrigger render={<Button variant="link" />}>
|
|
35
|
+
@nextjs
|
|
36
|
+
</HoverCardTrigger>
|
|
37
|
+
<HoverCardContent>
|
|
38
|
+
<div className="flex justify-between space-x-4">
|
|
39
|
+
<Avatar>
|
|
40
|
+
<AvatarImage src="https://github.com/vercel.png" />
|
|
41
|
+
<AvatarFallback>VC</AvatarFallback>
|
|
42
|
+
</Avatar>
|
|
43
|
+
<div className="space-y-1">
|
|
44
|
+
<h4 className="text-sm font-semibold">@nextjs</h4>
|
|
45
|
+
<p className="text-sm">
|
|
46
|
+
The React Framework – created and maintained by @vercel.
|
|
47
|
+
</p>
|
|
48
|
+
<div className="flex items-center pt-2">
|
|
49
|
+
<IconCalendar className="mr-2 size-4 opacity-70" />
|
|
50
|
+
<span className="text-muted-foreground text-xs">
|
|
51
|
+
Joined December 2016
|
|
52
|
+
</span>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</HoverCardContent>
|
|
57
|
+
</HoverCard>
|
|
58
|
+
),
|
|
59
|
+
parameters: {
|
|
60
|
+
docs: {
|
|
61
|
+
description: {
|
|
62
|
+
story:
|
|
63
|
+
"Default hover card showing user profile preview with avatar and details.",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const WithGitHubProfile: Story = {
|
|
70
|
+
render: () => (
|
|
71
|
+
<HoverCard>
|
|
72
|
+
<HoverCardTrigger
|
|
73
|
+
render={
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
className="text-sm font-medium underline underline-offset-4"
|
|
77
|
+
/>
|
|
78
|
+
}
|
|
79
|
+
>
|
|
80
|
+
shadcn
|
|
81
|
+
</HoverCardTrigger>
|
|
82
|
+
<HoverCardContent className="w-80">
|
|
83
|
+
<div className="flex gap-4">
|
|
84
|
+
<Avatar className="size-12">
|
|
85
|
+
<AvatarImage src="https://github.com/shadcn.png" />
|
|
86
|
+
<AvatarFallback>SC</AvatarFallback>
|
|
87
|
+
</Avatar>
|
|
88
|
+
<div className="space-y-1">
|
|
89
|
+
<h4 className="text-sm font-semibold">shadcn</h4>
|
|
90
|
+
<p className="text-muted-foreground text-sm">Building @shadcn/ui</p>
|
|
91
|
+
<div className="flex items-center gap-4 pt-2 text-xs text-muted-foreground">
|
|
92
|
+
<div className="flex items-center gap-1">
|
|
93
|
+
<IconBrandGithub className="size-3" />
|
|
94
|
+
<span>12.5k followers</span>
|
|
95
|
+
</div>
|
|
96
|
+
<div className="flex items-center gap-1">
|
|
97
|
+
<IconMapPin className="size-3" />
|
|
98
|
+
<span>New York</span>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</HoverCardContent>
|
|
104
|
+
</HoverCard>
|
|
105
|
+
),
|
|
106
|
+
parameters: {
|
|
107
|
+
docs: {
|
|
108
|
+
description: {
|
|
109
|
+
story:
|
|
110
|
+
"GitHub-style profile hover card with follower count and location.",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const SideTop: Story = {
|
|
117
|
+
render: () => (
|
|
118
|
+
<HoverCard>
|
|
119
|
+
<HoverCardTrigger render={<Button variant="outline" />}>
|
|
120
|
+
Hover me (top)
|
|
121
|
+
</HoverCardTrigger>
|
|
122
|
+
<HoverCardContent side="top">
|
|
123
|
+
<p className="text-sm">This card appears above the trigger.</p>
|
|
124
|
+
</HoverCardContent>
|
|
125
|
+
</HoverCard>
|
|
126
|
+
),
|
|
127
|
+
parameters: {
|
|
128
|
+
docs: {
|
|
129
|
+
description: {
|
|
130
|
+
story: "Hover card positioned above the trigger element.",
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const SideLeft: Story = {
|
|
137
|
+
render: () => (
|
|
138
|
+
<HoverCard>
|
|
139
|
+
<HoverCardTrigger render={<Button variant="outline" />}>
|
|
140
|
+
Hover me (left)
|
|
141
|
+
</HoverCardTrigger>
|
|
142
|
+
<HoverCardContent side="left">
|
|
143
|
+
<p className="text-sm">This card appears to the left of the trigger.</p>
|
|
144
|
+
</HoverCardContent>
|
|
145
|
+
</HoverCard>
|
|
146
|
+
),
|
|
147
|
+
parameters: {
|
|
148
|
+
docs: {
|
|
149
|
+
description: {
|
|
150
|
+
story: "Hover card positioned to the left of the trigger element.",
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const SideRight: Story = {
|
|
157
|
+
render: () => (
|
|
158
|
+
<HoverCard>
|
|
159
|
+
<HoverCardTrigger render={<Button variant="outline" />}>
|
|
160
|
+
Hover me (right)
|
|
161
|
+
</HoverCardTrigger>
|
|
162
|
+
<HoverCardContent side="right">
|
|
163
|
+
<p className="text-sm">
|
|
164
|
+
This card appears to the right of the trigger.
|
|
165
|
+
</p>
|
|
166
|
+
</HoverCardContent>
|
|
167
|
+
</HoverCard>
|
|
168
|
+
),
|
|
169
|
+
parameters: {
|
|
170
|
+
docs: {
|
|
171
|
+
description: {
|
|
172
|
+
story: "Hover card positioned to the right of the trigger element.",
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const AlignStart: Story = {
|
|
179
|
+
render: () => (
|
|
180
|
+
<HoverCard>
|
|
181
|
+
<HoverCardTrigger render={<Button variant="outline" />}>
|
|
182
|
+
Align start
|
|
183
|
+
</HoverCardTrigger>
|
|
184
|
+
<HoverCardContent align="start">
|
|
185
|
+
<p className="text-sm">
|
|
186
|
+
This card is aligned to the start of the trigger.
|
|
187
|
+
</p>
|
|
188
|
+
</HoverCardContent>
|
|
189
|
+
</HoverCard>
|
|
190
|
+
),
|
|
191
|
+
parameters: {
|
|
192
|
+
docs: {
|
|
193
|
+
description: {
|
|
194
|
+
story: "Hover card aligned to the start of the trigger.",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const AlignEnd: Story = {
|
|
201
|
+
render: () => (
|
|
202
|
+
<HoverCard>
|
|
203
|
+
<HoverCardTrigger render={<Button variant="outline" />}>
|
|
204
|
+
Align end
|
|
205
|
+
</HoverCardTrigger>
|
|
206
|
+
<HoverCardContent align="end">
|
|
207
|
+
<p className="text-sm">
|
|
208
|
+
This card is aligned to the end of the trigger.
|
|
209
|
+
</p>
|
|
210
|
+
</HoverCardContent>
|
|
211
|
+
</HoverCard>
|
|
212
|
+
),
|
|
213
|
+
parameters: {
|
|
214
|
+
docs: {
|
|
215
|
+
description: {
|
|
216
|
+
story: "Hover card aligned to the end of the trigger.",
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export const ProductPreview: Story = {
|
|
223
|
+
render: () => (
|
|
224
|
+
<HoverCard>
|
|
225
|
+
<HoverCardTrigger
|
|
226
|
+
render={
|
|
227
|
+
<button
|
|
228
|
+
type="button"
|
|
229
|
+
className="text-sm font-medium underline underline-offset-4"
|
|
230
|
+
/>
|
|
231
|
+
}
|
|
232
|
+
>
|
|
233
|
+
MacBook Pro 14"
|
|
234
|
+
</HoverCardTrigger>
|
|
235
|
+
<HoverCardContent className="w-72">
|
|
236
|
+
<div className="space-y-2">
|
|
237
|
+
<div className="aspect-video bg-muted rounded-md flex items-center justify-center">
|
|
238
|
+
<span className="text-muted-foreground text-xs">Product Image</span>
|
|
239
|
+
</div>
|
|
240
|
+
<div>
|
|
241
|
+
<h4 className="font-semibold">MacBook Pro 14"</h4>
|
|
242
|
+
<p className="text-muted-foreground text-sm">
|
|
243
|
+
Apple M3 Pro chip, 18GB RAM, 512GB SSD
|
|
244
|
+
</p>
|
|
245
|
+
</div>
|
|
246
|
+
<div className="flex items-center justify-between text-sm">
|
|
247
|
+
<span className="font-semibold">$1,999</span>
|
|
248
|
+
<span className="text-green-600">In Stock</span>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</HoverCardContent>
|
|
252
|
+
</HoverCard>
|
|
253
|
+
),
|
|
254
|
+
parameters: {
|
|
255
|
+
docs: {
|
|
256
|
+
description: {
|
|
257
|
+
story: "Product preview card for e-commerce hover interactions.",
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react/preview-card";
|
|
4
|
+
import { cn } from "#src/web/lib/utils.ts";
|
|
5
|
+
|
|
6
|
+
function HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {
|
|
7
|
+
return <PreviewCardPrimitive.Root data-slot="hover-card" {...props} />;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {
|
|
11
|
+
return (
|
|
12
|
+
<PreviewCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function HoverCardContent({
|
|
17
|
+
className,
|
|
18
|
+
side = "bottom",
|
|
19
|
+
sideOffset = 4,
|
|
20
|
+
align = "center",
|
|
21
|
+
alignOffset = 4,
|
|
22
|
+
...props
|
|
23
|
+
}: PreviewCardPrimitive.Popup.Props &
|
|
24
|
+
Pick<
|
|
25
|
+
PreviewCardPrimitive.Positioner.Props,
|
|
26
|
+
"align" | "alignOffset" | "side" | "sideOffset"
|
|
27
|
+
>) {
|
|
28
|
+
return (
|
|
29
|
+
<PreviewCardPrimitive.Portal data-slot="hover-card-portal">
|
|
30
|
+
<PreviewCardPrimitive.Positioner
|
|
31
|
+
align={align}
|
|
32
|
+
alignOffset={alignOffset}
|
|
33
|
+
side={side}
|
|
34
|
+
sideOffset={sideOffset}
|
|
35
|
+
className="isolate z-50"
|
|
36
|
+
>
|
|
37
|
+
<PreviewCardPrimitive.Popup
|
|
38
|
+
data-slot="hover-card-content"
|
|
39
|
+
className={cn(
|
|
40
|
+
"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 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 ring-foreground/10 bg-popover text-popover-foreground w-64 rounded-lg p-4 text-sm shadow-md ring-1 duration-100 z-50 origin-(--transform-origin) outline-hidden",
|
|
41
|
+
className,
|
|
42
|
+
)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
</PreviewCardPrimitive.Positioner>
|
|
46
|
+
</PreviewCardPrimitive.Portal>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { HoverCard, HoverCardTrigger, HoverCardContent };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export * from "./accordion.tsx";
|
|
2
|
+
export * from "./alert.tsx";
|
|
3
|
+
export * from "./alert-dialog.tsx";
|
|
4
|
+
export * from "./animated-check.tsx";
|
|
5
|
+
export * from "./aspect-ratio.tsx";
|
|
6
|
+
export * from "./avatar.tsx";
|
|
7
|
+
export * from "./badge.tsx";
|
|
8
|
+
export * from "./brand.tsx";
|
|
9
|
+
export * from "./breadcrumb.tsx";
|
|
10
|
+
export * from "./button.tsx";
|
|
11
|
+
export * from "./button-group.tsx";
|
|
12
|
+
export * from "./calendar.tsx";
|
|
13
|
+
export * from "./card.tsx";
|
|
14
|
+
export * from "./carousel.tsx";
|
|
15
|
+
export * from "./chart.tsx";
|
|
16
|
+
export * from "./checkbox.tsx";
|
|
17
|
+
export * from "./collapsible.tsx";
|
|
18
|
+
export * from "./command.tsx";
|
|
19
|
+
export * from "./context-menu.tsx";
|
|
20
|
+
export * from "./dialog.tsx";
|
|
21
|
+
export * from "./drawer.tsx";
|
|
22
|
+
export * from "./dropdown-menu.tsx";
|
|
23
|
+
export * from "./empty.tsx";
|
|
24
|
+
export * from "./error-page.tsx";
|
|
25
|
+
export * from "./field.tsx";
|
|
26
|
+
export * from "./hover-card.tsx";
|
|
27
|
+
export * from "./input.tsx";
|
|
28
|
+
export * from "./input-group.tsx";
|
|
29
|
+
export * from "./input-otp.tsx";
|
|
30
|
+
export * from "./item.tsx";
|
|
31
|
+
export * from "./kbd.tsx";
|
|
32
|
+
export * from "./label.tsx";
|
|
33
|
+
export * from "./menubar.tsx";
|
|
34
|
+
export * from "./navigation-menu.tsx";
|
|
35
|
+
export * from "./pagination.tsx";
|
|
36
|
+
export * from "./popover.tsx";
|
|
37
|
+
export * from "./progress.tsx";
|
|
38
|
+
export * from "./radio-group.tsx";
|
|
39
|
+
export * from "./resizable.tsx";
|
|
40
|
+
export * from "./scroll-area.tsx";
|
|
41
|
+
export * from "./select.tsx";
|
|
42
|
+
export * from "./separator.tsx";
|
|
43
|
+
export * from "./sheet.tsx";
|
|
44
|
+
export * from "./sidebar.tsx";
|
|
45
|
+
export * from "./skeleton.tsx";
|
|
46
|
+
export * from "./slider.tsx";
|
|
47
|
+
export * from "./sonner.tsx";
|
|
48
|
+
export * from "./spinner.tsx";
|
|
49
|
+
export * from "./switch.tsx";
|
|
50
|
+
export * from "./table.tsx";
|
|
51
|
+
export * from "./tabs.tsx";
|
|
52
|
+
export * from "./textarea.tsx";
|
|
53
|
+
export * from "./toggle.tsx";
|
|
54
|
+
export * from "./toggle-group.tsx";
|
|
55
|
+
export * from "./tooltip.tsx";
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import {
|
|
3
|
+
IconCopy,
|
|
4
|
+
IconEye,
|
|
5
|
+
IconEyeOff,
|
|
6
|
+
IconMail,
|
|
7
|
+
IconSearch,
|
|
8
|
+
IconSend,
|
|
9
|
+
} from "@tabler/icons-react";
|
|
10
|
+
import {
|
|
11
|
+
InputGroup,
|
|
12
|
+
InputGroupAddon,
|
|
13
|
+
InputGroupButton,
|
|
14
|
+
InputGroupInput,
|
|
15
|
+
InputGroupText,
|
|
16
|
+
InputGroupTextarea,
|
|
17
|
+
Kbd,
|
|
18
|
+
} from "appos/web";
|
|
19
|
+
import { useState } from "react";
|
|
20
|
+
import { expect, userEvent, within } from "storybook/test";
|
|
21
|
+
|
|
22
|
+
const meta = {
|
|
23
|
+
title: "Base/InputGroup",
|
|
24
|
+
component: InputGroup,
|
|
25
|
+
tags: ["autodocs"],
|
|
26
|
+
parameters: {
|
|
27
|
+
layout: "centered",
|
|
28
|
+
docs: {
|
|
29
|
+
description: {
|
|
30
|
+
component:
|
|
31
|
+
"A compound input component that combines inputs with addons, buttons, and icons. Supports inline and block positioning.",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
} satisfies Meta<typeof InputGroup>;
|
|
36
|
+
|
|
37
|
+
export default meta;
|
|
38
|
+
type Story = StoryObj<typeof meta>;
|
|
39
|
+
|
|
40
|
+
export const Default: Story = {
|
|
41
|
+
render: () => (
|
|
42
|
+
<InputGroup className="w-75">
|
|
43
|
+
<InputGroupAddon>
|
|
44
|
+
<IconMail className="size-4" />
|
|
45
|
+
</InputGroupAddon>
|
|
46
|
+
<InputGroupInput placeholder="Enter email" />
|
|
47
|
+
</InputGroup>
|
|
48
|
+
),
|
|
49
|
+
parameters: {
|
|
50
|
+
docs: {
|
|
51
|
+
description: {
|
|
52
|
+
story: "Default input group with icon addon at inline-start position.",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
play: async ({ canvasElement }) => {
|
|
57
|
+
const canvas = within(canvasElement);
|
|
58
|
+
const input = canvas.getByPlaceholderText("Enter email");
|
|
59
|
+
await userEvent.type(input, "test@example.com");
|
|
60
|
+
await expect(input).toHaveValue("test@example.com");
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const WithButton: Story = {
|
|
65
|
+
render: () => (
|
|
66
|
+
<InputGroup className="w-75">
|
|
67
|
+
<InputGroupInput placeholder="Search..." />
|
|
68
|
+
<InputGroupAddon align="inline-end">
|
|
69
|
+
<InputGroupButton>
|
|
70
|
+
<IconSearch className="size-4" />
|
|
71
|
+
</InputGroupButton>
|
|
72
|
+
</InputGroupAddon>
|
|
73
|
+
</InputGroup>
|
|
74
|
+
),
|
|
75
|
+
parameters: {
|
|
76
|
+
docs: {
|
|
77
|
+
description: {
|
|
78
|
+
story: "Input group with action button at inline-end.",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const WithTextAddon: Story = {
|
|
85
|
+
render: () => (
|
|
86
|
+
<InputGroup className="w-75">
|
|
87
|
+
<InputGroupAddon>
|
|
88
|
+
<InputGroupText>https://</InputGroupText>
|
|
89
|
+
</InputGroupAddon>
|
|
90
|
+
<InputGroupInput placeholder="example.com" />
|
|
91
|
+
</InputGroup>
|
|
92
|
+
),
|
|
93
|
+
parameters: {
|
|
94
|
+
docs: {
|
|
95
|
+
description: {
|
|
96
|
+
story: "Input group with text addon for URL prefix.",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const WithKbd: Story = {
|
|
103
|
+
render: () => (
|
|
104
|
+
<InputGroup className="w-75">
|
|
105
|
+
<InputGroupInput placeholder="Search..." />
|
|
106
|
+
<InputGroupAddon align="inline-end">
|
|
107
|
+
<Kbd>⌘K</Kbd>
|
|
108
|
+
</InputGroupAddon>
|
|
109
|
+
</InputGroup>
|
|
110
|
+
),
|
|
111
|
+
parameters: {
|
|
112
|
+
docs: {
|
|
113
|
+
description: {
|
|
114
|
+
story: "Input group with keyboard shortcut indicator.",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const PasswordToggle: Story = {
|
|
121
|
+
render: () => {
|
|
122
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
123
|
+
return (
|
|
124
|
+
<InputGroup className="w-75">
|
|
125
|
+
<InputGroupInput
|
|
126
|
+
type={showPassword ? "text" : "password"}
|
|
127
|
+
placeholder="Enter password"
|
|
128
|
+
/>
|
|
129
|
+
<InputGroupAddon align="inline-end">
|
|
130
|
+
<InputGroupButton
|
|
131
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
132
|
+
aria-label={showPassword ? "Hide password" : "Show password"}
|
|
133
|
+
>
|
|
134
|
+
{showPassword ? (
|
|
135
|
+
<IconEyeOff className="size-4" />
|
|
136
|
+
) : (
|
|
137
|
+
<IconEye className="size-4" />
|
|
138
|
+
)}
|
|
139
|
+
</InputGroupButton>
|
|
140
|
+
</InputGroupAddon>
|
|
141
|
+
</InputGroup>
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
parameters: {
|
|
145
|
+
docs: {
|
|
146
|
+
description: {
|
|
147
|
+
story: "Password input with show/hide toggle button.",
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const CopyToClipboard: Story = {
|
|
154
|
+
render: () => (
|
|
155
|
+
<InputGroup className="w-75">
|
|
156
|
+
<InputGroupInput defaultValue="abc123xyz" readOnly />
|
|
157
|
+
<InputGroupAddon align="inline-end">
|
|
158
|
+
<InputGroupButton aria-label="Copy to clipboard">
|
|
159
|
+
<IconCopy className="size-4" />
|
|
160
|
+
</InputGroupButton>
|
|
161
|
+
</InputGroupAddon>
|
|
162
|
+
</InputGroup>
|
|
163
|
+
),
|
|
164
|
+
parameters: {
|
|
165
|
+
docs: {
|
|
166
|
+
description: {
|
|
167
|
+
story: "Read-only input with copy button for sharing codes or tokens.",
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const BothSides: Story = {
|
|
174
|
+
render: () => (
|
|
175
|
+
<InputGroup className="w-75">
|
|
176
|
+
<InputGroupAddon align="inline-start">
|
|
177
|
+
<InputGroupText>$</InputGroupText>
|
|
178
|
+
</InputGroupAddon>
|
|
179
|
+
<InputGroupInput placeholder="0.00" type="number" />
|
|
180
|
+
<InputGroupAddon align="inline-end">
|
|
181
|
+
<InputGroupText>USD</InputGroupText>
|
|
182
|
+
</InputGroupAddon>
|
|
183
|
+
</InputGroup>
|
|
184
|
+
),
|
|
185
|
+
parameters: {
|
|
186
|
+
docs: {
|
|
187
|
+
description: {
|
|
188
|
+
story: "Input group with addons on both sides for currency input.",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export const WithTextarea: Story = {
|
|
195
|
+
render: () => (
|
|
196
|
+
<InputGroup className="w-100">
|
|
197
|
+
<InputGroupTextarea placeholder="Type your message..." rows={3} />
|
|
198
|
+
<InputGroupAddon align="inline-end">
|
|
199
|
+
<InputGroupButton size="icon-sm" aria-label="Send message">
|
|
200
|
+
<IconSend className="size-4" />
|
|
201
|
+
</InputGroupButton>
|
|
202
|
+
</InputGroupAddon>
|
|
203
|
+
</InputGroup>
|
|
204
|
+
),
|
|
205
|
+
parameters: {
|
|
206
|
+
docs: {
|
|
207
|
+
description: {
|
|
208
|
+
story: "Input group with textarea for message composition.",
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export const BlockStartAddon: Story = {
|
|
215
|
+
render: () => (
|
|
216
|
+
<InputGroup className="w-75">
|
|
217
|
+
<InputGroupAddon align="block-start">
|
|
218
|
+
<InputGroupText>Label Text</InputGroupText>
|
|
219
|
+
</InputGroupAddon>
|
|
220
|
+
<InputGroupInput placeholder="Enter value" />
|
|
221
|
+
</InputGroup>
|
|
222
|
+
),
|
|
223
|
+
parameters: {
|
|
224
|
+
docs: {
|
|
225
|
+
description: {
|
|
226
|
+
story:
|
|
227
|
+
"Input group with addon positioned at block-start (above input).",
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const BlockEndAddon: Story = {
|
|
234
|
+
render: () => (
|
|
235
|
+
<InputGroup className="w-75">
|
|
236
|
+
<InputGroupInput placeholder="Enter value" />
|
|
237
|
+
<InputGroupAddon align="block-end">
|
|
238
|
+
<InputGroupText className="text-muted-foreground text-xs">
|
|
239
|
+
Helper text below
|
|
240
|
+
</InputGroupText>
|
|
241
|
+
</InputGroupAddon>
|
|
242
|
+
</InputGroup>
|
|
243
|
+
),
|
|
244
|
+
parameters: {
|
|
245
|
+
docs: {
|
|
246
|
+
description: {
|
|
247
|
+
story: "Input group with addon positioned at block-end (below input).",
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export const Invalid: Story = {
|
|
254
|
+
render: () => (
|
|
255
|
+
<InputGroup className="w-75">
|
|
256
|
+
<InputGroupAddon>
|
|
257
|
+
<IconMail className="size-4" />
|
|
258
|
+
</InputGroupAddon>
|
|
259
|
+
<InputGroupInput placeholder="Enter email" aria-invalid="true" />
|
|
260
|
+
</InputGroup>
|
|
261
|
+
),
|
|
262
|
+
parameters: {
|
|
263
|
+
docs: {
|
|
264
|
+
description: {
|
|
265
|
+
story: "Input group in invalid state with error styling.",
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export const MultipleButtons: Story = {
|
|
272
|
+
render: () => (
|
|
273
|
+
<InputGroup className="w-87.5">
|
|
274
|
+
<InputGroupInput placeholder="Search files..." />
|
|
275
|
+
<InputGroupAddon align="inline-end">
|
|
276
|
+
<InputGroupButton aria-label="Search">
|
|
277
|
+
<IconSearch className="size-4" />
|
|
278
|
+
</InputGroupButton>
|
|
279
|
+
<InputGroupButton aria-label="Clear">×</InputGroupButton>
|
|
280
|
+
</InputGroupAddon>
|
|
281
|
+
</InputGroup>
|
|
282
|
+
),
|
|
283
|
+
parameters: {
|
|
284
|
+
docs: {
|
|
285
|
+
description: {
|
|
286
|
+
story: "Input group with multiple action buttons.",
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
};
|