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,115 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type * as React from "react";
|
|
4
|
+
import { cn } from "#src/web/lib/utils.ts";
|
|
5
|
+
|
|
6
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
data-slot="table-container"
|
|
10
|
+
className="relative w-full overflow-x-auto"
|
|
11
|
+
>
|
|
12
|
+
<table
|
|
13
|
+
data-slot="table"
|
|
14
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
22
|
+
return (
|
|
23
|
+
<thead
|
|
24
|
+
data-slot="table-header"
|
|
25
|
+
className={cn("[&_tr]:border-b", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
32
|
+
return (
|
|
33
|
+
<tbody
|
|
34
|
+
data-slot="table-body"
|
|
35
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
42
|
+
return (
|
|
43
|
+
<tfoot
|
|
44
|
+
data-slot="table-footer"
|
|
45
|
+
className={cn(
|
|
46
|
+
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
|
|
47
|
+
className,
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
55
|
+
return (
|
|
56
|
+
<tr
|
|
57
|
+
data-slot="table-row"
|
|
58
|
+
className={cn(
|
|
59
|
+
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
|
60
|
+
className,
|
|
61
|
+
)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
68
|
+
return (
|
|
69
|
+
<th
|
|
70
|
+
data-slot="table-head"
|
|
71
|
+
className={cn(
|
|
72
|
+
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0",
|
|
73
|
+
className,
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
81
|
+
return (
|
|
82
|
+
<td
|
|
83
|
+
data-slot="table-cell"
|
|
84
|
+
className={cn(
|
|
85
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
|
|
86
|
+
className,
|
|
87
|
+
)}
|
|
88
|
+
{...props}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function TableCaption({
|
|
94
|
+
className,
|
|
95
|
+
...props
|
|
96
|
+
}: React.ComponentProps<"caption">) {
|
|
97
|
+
return (
|
|
98
|
+
<caption
|
|
99
|
+
data-slot="table-caption"
|
|
100
|
+
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export {
|
|
107
|
+
Table,
|
|
108
|
+
TableHeader,
|
|
109
|
+
TableBody,
|
|
110
|
+
TableFooter,
|
|
111
|
+
TableHead,
|
|
112
|
+
TableRow,
|
|
113
|
+
TableCell,
|
|
114
|
+
TableCaption,
|
|
115
|
+
};
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardHeader,
|
|
8
|
+
CardTitle,
|
|
9
|
+
Input,
|
|
10
|
+
Label,
|
|
11
|
+
Tabs,
|
|
12
|
+
TabsContent,
|
|
13
|
+
TabsList,
|
|
14
|
+
TabsTrigger,
|
|
15
|
+
} from "appos/web";
|
|
16
|
+
import { BellIcon, CreditCardIcon, SettingsIcon, UserIcon } from "lucide-react";
|
|
17
|
+
import { expect, userEvent, within } from "storybook/test";
|
|
18
|
+
|
|
19
|
+
const meta = {
|
|
20
|
+
title: "Base/Tabs",
|
|
21
|
+
component: Tabs,
|
|
22
|
+
tags: ["autodocs"],
|
|
23
|
+
parameters: {
|
|
24
|
+
layout: "centered",
|
|
25
|
+
docs: {
|
|
26
|
+
description: {
|
|
27
|
+
component:
|
|
28
|
+
"A tabs component for organizing content into switchable panels. Supports horizontal and vertical orientations with default and line variants.",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
} satisfies Meta<typeof Tabs>;
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
type Story = StoryObj<typeof meta>;
|
|
36
|
+
|
|
37
|
+
export const Default: Story = {
|
|
38
|
+
render: () => (
|
|
39
|
+
<Tabs defaultValue="account" className="w-100">
|
|
40
|
+
<TabsList>
|
|
41
|
+
<TabsTrigger value="account">Account</TabsTrigger>
|
|
42
|
+
<TabsTrigger value="password">Password</TabsTrigger>
|
|
43
|
+
</TabsList>
|
|
44
|
+
<TabsContent value="account">
|
|
45
|
+
<Card>
|
|
46
|
+
<CardHeader>
|
|
47
|
+
<CardTitle>Account</CardTitle>
|
|
48
|
+
<CardDescription>
|
|
49
|
+
Make changes to your account here.
|
|
50
|
+
</CardDescription>
|
|
51
|
+
</CardHeader>
|
|
52
|
+
<CardContent className="space-y-2">
|
|
53
|
+
<div className="space-y-1">
|
|
54
|
+
<Label htmlFor="name">Name</Label>
|
|
55
|
+
<Input id="name" defaultValue="John Doe" />
|
|
56
|
+
</div>
|
|
57
|
+
<div className="space-y-1">
|
|
58
|
+
<Label htmlFor="username">Username</Label>
|
|
59
|
+
<Input id="username" defaultValue="@johndoe" />
|
|
60
|
+
</div>
|
|
61
|
+
</CardContent>
|
|
62
|
+
</Card>
|
|
63
|
+
</TabsContent>
|
|
64
|
+
<TabsContent value="password">
|
|
65
|
+
<Card>
|
|
66
|
+
<CardHeader>
|
|
67
|
+
<CardTitle>Password</CardTitle>
|
|
68
|
+
<CardDescription>Change your password here.</CardDescription>
|
|
69
|
+
</CardHeader>
|
|
70
|
+
<CardContent className="space-y-2">
|
|
71
|
+
<div className="space-y-1">
|
|
72
|
+
<Label htmlFor="current">Current password</Label>
|
|
73
|
+
<Input id="current" type="password" />
|
|
74
|
+
</div>
|
|
75
|
+
<div className="space-y-1">
|
|
76
|
+
<Label htmlFor="new">New password</Label>
|
|
77
|
+
<Input id="new" type="password" />
|
|
78
|
+
</div>
|
|
79
|
+
</CardContent>
|
|
80
|
+
</Card>
|
|
81
|
+
</TabsContent>
|
|
82
|
+
</Tabs>
|
|
83
|
+
),
|
|
84
|
+
parameters: {
|
|
85
|
+
docs: {
|
|
86
|
+
description: {
|
|
87
|
+
story: "Default tabs with card content.",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
play: async ({ canvasElement }) => {
|
|
92
|
+
const canvas = within(canvasElement);
|
|
93
|
+
const passwordTab = canvas.getByRole("tab", { name: /password/i });
|
|
94
|
+
await userEvent.click(passwordTab);
|
|
95
|
+
await expect(canvas.getByText("Current password")).toBeInTheDocument();
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const LineVariant: Story = {
|
|
100
|
+
render: () => (
|
|
101
|
+
<Tabs defaultValue="overview" className="w-100">
|
|
102
|
+
<TabsList variant="line">
|
|
103
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
104
|
+
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
|
105
|
+
<TabsTrigger value="reports">Reports</TabsTrigger>
|
|
106
|
+
</TabsList>
|
|
107
|
+
<TabsContent value="overview" className="pt-4">
|
|
108
|
+
<p className="text-sm text-muted-foreground">
|
|
109
|
+
Overview content goes here.
|
|
110
|
+
</p>
|
|
111
|
+
</TabsContent>
|
|
112
|
+
<TabsContent value="analytics" className="pt-4">
|
|
113
|
+
<p className="text-sm text-muted-foreground">
|
|
114
|
+
Analytics content goes here.
|
|
115
|
+
</p>
|
|
116
|
+
</TabsContent>
|
|
117
|
+
<TabsContent value="reports" className="pt-4">
|
|
118
|
+
<p className="text-sm text-muted-foreground">
|
|
119
|
+
Reports content goes here.
|
|
120
|
+
</p>
|
|
121
|
+
</TabsContent>
|
|
122
|
+
</Tabs>
|
|
123
|
+
),
|
|
124
|
+
parameters: {
|
|
125
|
+
docs: {
|
|
126
|
+
description: {
|
|
127
|
+
story: "Tabs with line variant styling.",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const WithIcons: Story = {
|
|
134
|
+
render: () => (
|
|
135
|
+
<Tabs defaultValue="profile" className="w-100">
|
|
136
|
+
<TabsList>
|
|
137
|
+
<TabsTrigger value="profile">
|
|
138
|
+
<UserIcon />
|
|
139
|
+
Profile
|
|
140
|
+
</TabsTrigger>
|
|
141
|
+
<TabsTrigger value="notifications">
|
|
142
|
+
<BellIcon />
|
|
143
|
+
Notifications
|
|
144
|
+
</TabsTrigger>
|
|
145
|
+
<TabsTrigger value="settings">
|
|
146
|
+
<SettingsIcon />
|
|
147
|
+
Settings
|
|
148
|
+
</TabsTrigger>
|
|
149
|
+
</TabsList>
|
|
150
|
+
<TabsContent value="profile" className="pt-4">
|
|
151
|
+
<p className="text-sm text-muted-foreground">
|
|
152
|
+
Manage your profile settings.
|
|
153
|
+
</p>
|
|
154
|
+
</TabsContent>
|
|
155
|
+
<TabsContent value="notifications" className="pt-4">
|
|
156
|
+
<p className="text-sm text-muted-foreground">
|
|
157
|
+
Configure notification preferences.
|
|
158
|
+
</p>
|
|
159
|
+
</TabsContent>
|
|
160
|
+
<TabsContent value="settings" className="pt-4">
|
|
161
|
+
<p className="text-sm text-muted-foreground">
|
|
162
|
+
Adjust general settings.
|
|
163
|
+
</p>
|
|
164
|
+
</TabsContent>
|
|
165
|
+
</Tabs>
|
|
166
|
+
),
|
|
167
|
+
parameters: {
|
|
168
|
+
docs: {
|
|
169
|
+
description: {
|
|
170
|
+
story: "Tabs with icons in triggers.",
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const Vertical: Story = {
|
|
177
|
+
render: () => (
|
|
178
|
+
<Tabs defaultValue="general" orientation="vertical" className="w-125">
|
|
179
|
+
<TabsList className="h-fit">
|
|
180
|
+
<TabsTrigger value="general">General</TabsTrigger>
|
|
181
|
+
<TabsTrigger value="security">Security</TabsTrigger>
|
|
182
|
+
<TabsTrigger value="billing">Billing</TabsTrigger>
|
|
183
|
+
<TabsTrigger value="team">Team</TabsTrigger>
|
|
184
|
+
</TabsList>
|
|
185
|
+
<TabsContent value="general" className="flex-1">
|
|
186
|
+
<Card>
|
|
187
|
+
<CardHeader>
|
|
188
|
+
<CardTitle>General Settings</CardTitle>
|
|
189
|
+
<CardDescription>Manage your general settings.</CardDescription>
|
|
190
|
+
</CardHeader>
|
|
191
|
+
<CardContent>
|
|
192
|
+
<div className="space-y-2">
|
|
193
|
+
<Label htmlFor="org-name">Organization Name</Label>
|
|
194
|
+
<Input id="org-name" defaultValue="Acme Inc" />
|
|
195
|
+
</div>
|
|
196
|
+
</CardContent>
|
|
197
|
+
</Card>
|
|
198
|
+
</TabsContent>
|
|
199
|
+
<TabsContent value="security" className="flex-1">
|
|
200
|
+
<Card>
|
|
201
|
+
<CardHeader>
|
|
202
|
+
<CardTitle>Security</CardTitle>
|
|
203
|
+
<CardDescription>Configure security options.</CardDescription>
|
|
204
|
+
</CardHeader>
|
|
205
|
+
</Card>
|
|
206
|
+
</TabsContent>
|
|
207
|
+
<TabsContent value="billing" className="flex-1">
|
|
208
|
+
<Card>
|
|
209
|
+
<CardHeader>
|
|
210
|
+
<CardTitle>Billing</CardTitle>
|
|
211
|
+
<CardDescription>Manage billing and payments.</CardDescription>
|
|
212
|
+
</CardHeader>
|
|
213
|
+
</Card>
|
|
214
|
+
</TabsContent>
|
|
215
|
+
<TabsContent value="team" className="flex-1">
|
|
216
|
+
<Card>
|
|
217
|
+
<CardHeader>
|
|
218
|
+
<CardTitle>Team</CardTitle>
|
|
219
|
+
<CardDescription>Manage team members.</CardDescription>
|
|
220
|
+
</CardHeader>
|
|
221
|
+
</Card>
|
|
222
|
+
</TabsContent>
|
|
223
|
+
</Tabs>
|
|
224
|
+
),
|
|
225
|
+
parameters: {
|
|
226
|
+
docs: {
|
|
227
|
+
description: {
|
|
228
|
+
story: "Vertical tabs for settings pages.",
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export const VerticalLine: Story = {
|
|
235
|
+
render: () => (
|
|
236
|
+
<Tabs defaultValue="account" orientation="vertical" className="w-125">
|
|
237
|
+
<TabsList variant="line" className="h-fit">
|
|
238
|
+
<TabsTrigger value="account">
|
|
239
|
+
<UserIcon />
|
|
240
|
+
Account
|
|
241
|
+
</TabsTrigger>
|
|
242
|
+
<TabsTrigger value="billing">
|
|
243
|
+
<CreditCardIcon />
|
|
244
|
+
Billing
|
|
245
|
+
</TabsTrigger>
|
|
246
|
+
<TabsTrigger value="notifications">
|
|
247
|
+
<BellIcon />
|
|
248
|
+
Notifications
|
|
249
|
+
</TabsTrigger>
|
|
250
|
+
<TabsTrigger value="settings">
|
|
251
|
+
<SettingsIcon />
|
|
252
|
+
Settings
|
|
253
|
+
</TabsTrigger>
|
|
254
|
+
</TabsList>
|
|
255
|
+
<TabsContent value="account" className="flex-1 pl-4">
|
|
256
|
+
<h3 className="text-lg font-medium">Account</h3>
|
|
257
|
+
<p className="text-sm text-muted-foreground mt-2">
|
|
258
|
+
Update your account information.
|
|
259
|
+
</p>
|
|
260
|
+
</TabsContent>
|
|
261
|
+
<TabsContent value="billing" className="flex-1 pl-4">
|
|
262
|
+
<h3 className="text-lg font-medium">Billing</h3>
|
|
263
|
+
<p className="text-sm text-muted-foreground mt-2">
|
|
264
|
+
Manage your billing and subscription.
|
|
265
|
+
</p>
|
|
266
|
+
</TabsContent>
|
|
267
|
+
<TabsContent value="notifications" className="flex-1 pl-4">
|
|
268
|
+
<h3 className="text-lg font-medium">Notifications</h3>
|
|
269
|
+
<p className="text-sm text-muted-foreground mt-2">
|
|
270
|
+
Configure your notification preferences.
|
|
271
|
+
</p>
|
|
272
|
+
</TabsContent>
|
|
273
|
+
<TabsContent value="settings" className="flex-1 pl-4">
|
|
274
|
+
<h3 className="text-lg font-medium">Settings</h3>
|
|
275
|
+
<p className="text-sm text-muted-foreground mt-2">
|
|
276
|
+
Adjust your general settings.
|
|
277
|
+
</p>
|
|
278
|
+
</TabsContent>
|
|
279
|
+
</Tabs>
|
|
280
|
+
),
|
|
281
|
+
parameters: {
|
|
282
|
+
docs: {
|
|
283
|
+
description: {
|
|
284
|
+
story: "Vertical tabs with line variant and icons.",
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
export const Disabled: Story = {
|
|
291
|
+
render: () => (
|
|
292
|
+
<Tabs defaultValue="active" className="w-100">
|
|
293
|
+
<TabsList>
|
|
294
|
+
<TabsTrigger value="active">Active</TabsTrigger>
|
|
295
|
+
<TabsTrigger value="disabled" disabled>
|
|
296
|
+
Disabled
|
|
297
|
+
</TabsTrigger>
|
|
298
|
+
<TabsTrigger value="other">Other</TabsTrigger>
|
|
299
|
+
</TabsList>
|
|
300
|
+
<TabsContent value="active" className="pt-4">
|
|
301
|
+
<p className="text-sm text-muted-foreground">Active tab content.</p>
|
|
302
|
+
</TabsContent>
|
|
303
|
+
<TabsContent value="other" className="pt-4">
|
|
304
|
+
<p className="text-sm text-muted-foreground">Other tab content.</p>
|
|
305
|
+
</TabsContent>
|
|
306
|
+
</Tabs>
|
|
307
|
+
),
|
|
308
|
+
parameters: {
|
|
309
|
+
docs: {
|
|
310
|
+
description: {
|
|
311
|
+
story: "Tabs with a disabled trigger.",
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
export const FullWidth: Story = {
|
|
318
|
+
render: () => (
|
|
319
|
+
<Tabs defaultValue="tab1" className="w-100">
|
|
320
|
+
<TabsList className="w-full">
|
|
321
|
+
<TabsTrigger value="tab1" className="flex-1">
|
|
322
|
+
Tab 1
|
|
323
|
+
</TabsTrigger>
|
|
324
|
+
<TabsTrigger value="tab2" className="flex-1">
|
|
325
|
+
Tab 2
|
|
326
|
+
</TabsTrigger>
|
|
327
|
+
<TabsTrigger value="tab3" className="flex-1">
|
|
328
|
+
Tab 3
|
|
329
|
+
</TabsTrigger>
|
|
330
|
+
</TabsList>
|
|
331
|
+
<TabsContent value="tab1" className="pt-4">
|
|
332
|
+
Content for Tab 1
|
|
333
|
+
</TabsContent>
|
|
334
|
+
<TabsContent value="tab2" className="pt-4">
|
|
335
|
+
Content for Tab 2
|
|
336
|
+
</TabsContent>
|
|
337
|
+
<TabsContent value="tab3" className="pt-4">
|
|
338
|
+
Content for Tab 3
|
|
339
|
+
</TabsContent>
|
|
340
|
+
</Tabs>
|
|
341
|
+
),
|
|
342
|
+
parameters: {
|
|
343
|
+
docs: {
|
|
344
|
+
description: {
|
|
345
|
+
story: "Full-width tabs with equal tab widths.",
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
export const ProductTabs: Story = {
|
|
352
|
+
render: () => (
|
|
353
|
+
<Tabs defaultValue="description" className="w-125">
|
|
354
|
+
<TabsList variant="line">
|
|
355
|
+
<TabsTrigger value="description">Description</TabsTrigger>
|
|
356
|
+
<TabsTrigger value="specifications">Specifications</TabsTrigger>
|
|
357
|
+
<TabsTrigger value="reviews">Reviews (24)</TabsTrigger>
|
|
358
|
+
</TabsList>
|
|
359
|
+
<TabsContent value="description" className="pt-4">
|
|
360
|
+
<p className="text-sm text-muted-foreground">
|
|
361
|
+
This is a premium product with exceptional quality and craftsmanship.
|
|
362
|
+
Made from sustainable materials and designed to last a lifetime.
|
|
363
|
+
</p>
|
|
364
|
+
</TabsContent>
|
|
365
|
+
<TabsContent value="specifications" className="pt-4">
|
|
366
|
+
<ul className="text-sm space-y-2">
|
|
367
|
+
<li className="flex justify-between">
|
|
368
|
+
<span className="text-muted-foreground">Dimensions</span>
|
|
369
|
+
<span>10 x 5 x 2 inches</span>
|
|
370
|
+
</li>
|
|
371
|
+
<li className="flex justify-between">
|
|
372
|
+
<span className="text-muted-foreground">Weight</span>
|
|
373
|
+
<span>1.5 lbs</span>
|
|
374
|
+
</li>
|
|
375
|
+
<li className="flex justify-between">
|
|
376
|
+
<span className="text-muted-foreground">Material</span>
|
|
377
|
+
<span>Recycled aluminum</span>
|
|
378
|
+
</li>
|
|
379
|
+
</ul>
|
|
380
|
+
</TabsContent>
|
|
381
|
+
<TabsContent value="reviews" className="pt-4">
|
|
382
|
+
<div className="space-y-4">
|
|
383
|
+
<div className="flex items-center gap-2">
|
|
384
|
+
<span className="text-2xl font-bold">4.8</span>
|
|
385
|
+
<span className="text-sm text-muted-foreground">out of 5</span>
|
|
386
|
+
</div>
|
|
387
|
+
<Button variant="outline" size="sm">
|
|
388
|
+
Write a Review
|
|
389
|
+
</Button>
|
|
390
|
+
</div>
|
|
391
|
+
</TabsContent>
|
|
392
|
+
</Tabs>
|
|
393
|
+
),
|
|
394
|
+
parameters: {
|
|
395
|
+
docs: {
|
|
396
|
+
description: {
|
|
397
|
+
story: "Product detail tabs with line variant.",
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
import { cn } from "#src/web/lib/utils.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A set of layered content panels displayed one at a time.
|
|
9
|
+
*
|
|
10
|
+
* Organizes content into distinct views that users can switch between.
|
|
11
|
+
* Supports horizontal and vertical orientations.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <Tabs defaultValue="account">
|
|
16
|
+
* <TabsList>
|
|
17
|
+
* <TabsTrigger value="account">Account</TabsTrigger>
|
|
18
|
+
* <TabsTrigger value="settings">Settings</TabsTrigger>
|
|
19
|
+
* </TabsList>
|
|
20
|
+
* <TabsContent value="account">Account content</TabsContent>
|
|
21
|
+
* <TabsContent value="settings">Settings content</TabsContent>
|
|
22
|
+
* </Tabs>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function Tabs({
|
|
26
|
+
className,
|
|
27
|
+
orientation = "horizontal",
|
|
28
|
+
...props
|
|
29
|
+
}: TabsPrimitive.Root.Props) {
|
|
30
|
+
return (
|
|
31
|
+
<TabsPrimitive.Root
|
|
32
|
+
data-slot="tabs"
|
|
33
|
+
data-orientation={orientation}
|
|
34
|
+
className={cn(
|
|
35
|
+
"gap-2 group/tabs flex data-[orientation=horizontal]:flex-col",
|
|
36
|
+
className,
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** TabsList variant styles for different visual appearances. */
|
|
44
|
+
const tabsListVariants = cva(
|
|
45
|
+
"rounded-lg p-[3px] group-data-horizontal/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
|
|
46
|
+
{
|
|
47
|
+
variants: {
|
|
48
|
+
variant: {
|
|
49
|
+
default: "bg-muted",
|
|
50
|
+
line: "gap-1 bg-transparent",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
defaultVariants: {
|
|
54
|
+
variant: "default",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The container for tab triggers.
|
|
61
|
+
*
|
|
62
|
+
* Supports default (pill-style) and line variants. Arranges triggers
|
|
63
|
+
* horizontally or vertically based on parent orientation.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* <TabsList variant="line">
|
|
68
|
+
* <TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
|
69
|
+
* </TabsList>
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
function TabsList({
|
|
73
|
+
className,
|
|
74
|
+
variant = "default",
|
|
75
|
+
...props
|
|
76
|
+
}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
|
|
77
|
+
return (
|
|
78
|
+
<TabsPrimitive.List
|
|
79
|
+
data-slot="tabs-list"
|
|
80
|
+
data-variant={variant}
|
|
81
|
+
className={cn(tabsListVariants({ variant }), className)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A button that activates its associated tab content panel.
|
|
89
|
+
*
|
|
90
|
+
* Displays active state styling and supports keyboard navigation.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* <TabsTrigger value="settings">Settings</TabsTrigger>
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
|
|
98
|
+
return (
|
|
99
|
+
<TabsPrimitive.Tab
|
|
100
|
+
data-slot="tabs-trigger"
|
|
101
|
+
className={cn(
|
|
102
|
+
"gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
103
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
|
104
|
+
"data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground",
|
|
105
|
+
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
|
106
|
+
className,
|
|
107
|
+
)}
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The content panel associated with a tab trigger.
|
|
115
|
+
*
|
|
116
|
+
* Only visible when its corresponding trigger is active. Hidden panels
|
|
117
|
+
* are removed from the accessibility tree.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```tsx
|
|
121
|
+
* <TabsContent value="settings">
|
|
122
|
+
* <p>Settings panel content.</p>
|
|
123
|
+
* </TabsContent>
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
|
|
127
|
+
return (
|
|
128
|
+
<TabsPrimitive.Panel
|
|
129
|
+
data-slot="tabs-content"
|
|
130
|
+
className={cn("text-sm flex-1 outline-none", className)}
|
|
131
|
+
{...props}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|