@emara/ui 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ui/.gitkeep +0 -0
- package/components/ui/accordion.stories.tsx +231 -0
- package/components/ui/accordion.tsx +250 -0
- package/components/ui/app-shell.stories.tsx +270 -0
- package/components/ui/app-shell.tsx +491 -0
- package/components/ui/avatar.stories.tsx +174 -0
- package/components/ui/avatar.tsx +257 -0
- package/components/ui/badge.stories.tsx +127 -0
- package/components/ui/badge.tsx +146 -0
- package/components/ui/breadcrumb.stories.tsx +92 -0
- package/components/ui/breadcrumb.tsx +302 -0
- package/components/ui/button.stories.tsx +186 -0
- package/components/ui/button.tsx +128 -0
- package/components/ui/card.stories.tsx +279 -0
- package/components/ui/card.tsx +250 -0
- package/components/ui/checkbox.stories.tsx +93 -0
- package/components/ui/checkbox.tsx +131 -0
- package/components/ui/combobox.stories.tsx +489 -0
- package/components/ui/combobox.tsx +874 -0
- package/components/ui/context-menu.stories.tsx +202 -0
- package/components/ui/context-menu.tsx +309 -0
- package/components/ui/data-table.stories.tsx +227 -0
- package/components/ui/data-table.tsx +539 -0
- package/components/ui/date-picker.stories.tsx +225 -0
- package/components/ui/date-picker.tsx +597 -0
- package/components/ui/dialog.stories.tsx +193 -0
- package/components/ui/dialog.tsx +262 -0
- package/components/ui/divider.stories.tsx +84 -0
- package/components/ui/divider.tsx +135 -0
- package/components/ui/drawer.stories.tsx +218 -0
- package/components/ui/drawer.tsx +329 -0
- package/components/ui/dropdown-menu.stories.tsx +270 -0
- package/components/ui/dropdown-menu.tsx +353 -0
- package/components/ui/empty-state.stories.tsx +121 -0
- package/components/ui/empty-state.tsx +289 -0
- package/components/ui/field-group.stories.tsx +201 -0
- package/components/ui/field-group.tsx +276 -0
- package/components/ui/form.stories.tsx +219 -0
- package/components/ui/form.tsx +542 -0
- package/components/ui/input.stories.tsx +154 -0
- package/components/ui/input.tsx +208 -0
- package/components/ui/label.stories.tsx +84 -0
- package/components/ui/label.tsx +98 -0
- package/components/ui/page-header.stories.tsx +136 -0
- package/components/ui/page-header.tsx +315 -0
- package/components/ui/pagination.stories.tsx +136 -0
- package/components/ui/pagination.tsx +427 -0
- package/components/ui/popover.stories.tsx +212 -0
- package/components/ui/popover.tsx +167 -0
- package/components/ui/radio-group.stories.tsx +96 -0
- package/components/ui/radio-group.tsx +250 -0
- package/components/ui/select.stories.tsx +203 -0
- package/components/ui/select.tsx +318 -0
- package/components/ui/sidebar.stories.tsx +186 -0
- package/components/ui/sidebar.tsx +623 -0
- package/components/ui/skeleton.stories.tsx +131 -0
- package/components/ui/skeleton.tsx +311 -0
- package/components/ui/switch.stories.tsx +74 -0
- package/components/ui/switch.tsx +186 -0
- package/components/ui/table.stories.tsx +107 -0
- package/components/ui/table.tsx +285 -0
- package/components/ui/tabs.stories.tsx +222 -0
- package/components/ui/tabs.tsx +287 -0
- package/components/ui/textarea.stories.tsx +96 -0
- package/components/ui/textarea.tsx +182 -0
- package/components/ui/toast.stories.tsx +169 -0
- package/components/ui/toast.tsx +250 -0
- package/components/ui/tooltip.stories.tsx +146 -0
- package/components/ui/tooltip.tsx +156 -0
- package/components/ui/top-bar.stories.tsx +182 -0
- package/components/ui/top-bar.tsx +155 -0
- package/dist/components/ui/accordion.d.ts +45 -0
- package/dist/components/ui/accordion.d.ts.map +1 -0
- package/dist/components/ui/accordion.js +99 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/app-shell.d.ts +70 -0
- package/dist/components/ui/app-shell.d.ts.map +1 -0
- package/dist/components/ui/app-shell.js +199 -0
- package/dist/components/ui/app-shell.js.map +1 -0
- package/dist/components/ui/avatar.d.ts +41 -0
- package/dist/components/ui/avatar.d.ts.map +1 -0
- package/dist/components/ui/avatar.js +104 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/badge.d.ts +27 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/badge.js +65 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +35 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.js +88 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/button.d.ts +26 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +73 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/card.d.ts +52 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/card.js +96 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +18 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -0
- package/dist/components/ui/checkbox.js +59 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/combobox.d.ts +194 -0
- package/dist/components/ui/combobox.d.ts.map +1 -0
- package/dist/components/ui/combobox.js +361 -0
- package/dist/components/ui/combobox.js.map +1 -0
- package/dist/components/ui/context-menu.d.ts +46 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/context-menu.js +95 -0
- package/dist/components/ui/context-menu.js.map +1 -0
- package/dist/components/ui/data-table.d.ts +53 -0
- package/dist/components/ui/data-table.d.ts.map +1 -0
- package/dist/components/ui/data-table.js +163 -0
- package/dist/components/ui/data-table.js.map +1 -0
- package/dist/components/ui/date-picker.d.ts +103 -0
- package/dist/components/ui/date-picker.d.ts.map +1 -0
- package/dist/components/ui/date-picker.js +306 -0
- package/dist/components/ui/date-picker.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +40 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/dialog.js +110 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/divider.d.ts +30 -0
- package/dist/components/ui/divider.d.ts.map +1 -0
- package/dist/components/ui/divider.js +62 -0
- package/dist/components/ui/divider.js.map +1 -0
- package/dist/components/ui/drawer.d.ts +56 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/drawer.js +147 -0
- package/dist/components/ui/drawer.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +63 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.js +116 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/empty-state.d.ts +43 -0
- package/dist/components/ui/empty-state.d.ts.map +1 -0
- package/dist/components/ui/empty-state.js +128 -0
- package/dist/components/ui/empty-state.js.map +1 -0
- package/dist/components/ui/field-group.d.ts +38 -0
- package/dist/components/ui/field-group.d.ts.map +1 -0
- package/dist/components/ui/field-group.js +107 -0
- package/dist/components/ui/field-group.js.map +1 -0
- package/dist/components/ui/form.d.ts +67 -0
- package/dist/components/ui/form.d.ts.map +1 -0
- package/dist/components/ui/form.js +286 -0
- package/dist/components/ui/form.js.map +1 -0
- package/dist/components/ui/input.d.ts +36 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/input.js +99 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/label.d.ts +37 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +34 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/page-header.d.ts +65 -0
- package/dist/components/ui/page-header.d.ts.map +1 -0
- package/dist/components/ui/page-header.js +140 -0
- package/dist/components/ui/page-header.js.map +1 -0
- package/dist/components/ui/pagination.d.ts +67 -0
- package/dist/components/ui/pagination.d.ts.map +1 -0
- package/dist/components/ui/pagination.js +109 -0
- package/dist/components/ui/pagination.js.map +1 -0
- package/dist/components/ui/popover.d.ts +28 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/popover.js +85 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/radio-group.d.ts +35 -0
- package/dist/components/ui/radio-group.d.ts.map +1 -0
- package/dist/components/ui/radio-group.js +103 -0
- package/dist/components/ui/radio-group.js.map +1 -0
- package/dist/components/ui/select.d.ts +42 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +86 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/sidebar.d.ts +59 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sidebar.js +189 -0
- package/dist/components/ui/sidebar.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +77 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -0
- package/dist/components/ui/skeleton.js +115 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/switch.d.ts +26 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/components/ui/switch.js +84 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/table.d.ts +52 -0
- package/dist/components/ui/table.d.ts.map +1 -0
- package/dist/components/ui/table.js +109 -0
- package/dist/components/ui/table.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +42 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/tabs.js +163 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +26 -0
- package/dist/components/ui/textarea.d.ts.map +1 -0
- package/dist/components/ui/textarea.js +96 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/toast.d.ts +77 -0
- package/dist/components/ui/toast.d.ts.map +1 -0
- package/dist/components/ui/toast.js +141 -0
- package/dist/components/ui/toast.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +31 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip.js +71 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/components/ui/top-bar.d.ts +30 -0
- package/dist/components/ui/top-bar.d.ts.map +1 -0
- package/dist/components/ui/top-bar.js +64 -0
- package/dist/components/ui/top-bar.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/lib/utils.ts +6 -0
- package/package.json +112 -0
- package/styles/globals.css +685 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Table,
|
|
5
|
+
TableBody,
|
|
6
|
+
TableCaption,
|
|
7
|
+
TableCell,
|
|
8
|
+
TableFooter,
|
|
9
|
+
TableHead,
|
|
10
|
+
TableHeader,
|
|
11
|
+
TableRow,
|
|
12
|
+
} from "./table";
|
|
13
|
+
|
|
14
|
+
const meta: Meta<typeof Table> = {
|
|
15
|
+
title: "Data/Table",
|
|
16
|
+
component: Table,
|
|
17
|
+
parameters: { layout: "padded" },
|
|
18
|
+
argTypes: {
|
|
19
|
+
density: { control: "select", options: ["warm", "dense"] },
|
|
20
|
+
striped: { control: "boolean" },
|
|
21
|
+
bordered: { control: "boolean" },
|
|
22
|
+
hoverable: { control: "boolean" },
|
|
23
|
+
stickyHeader: { control: "boolean" },
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default meta;
|
|
28
|
+
type Story = StoryObj<typeof Table>;
|
|
29
|
+
|
|
30
|
+
const ROWS = [
|
|
31
|
+
{ id: "INV001", status: "Paid", method: "Credit card", amount: 250.0 },
|
|
32
|
+
{ id: "INV002", status: "Pending", method: "Bank transfer", amount: 150.0 },
|
|
33
|
+
{ id: "INV003", status: "Unpaid", method: "PayPal", amount: 350.0 },
|
|
34
|
+
{ id: "INV004", status: "Paid", method: "Credit card", amount: 450.0 },
|
|
35
|
+
{ id: "INV005", status: "Paid", method: "Credit card", amount: 550.0 },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export const Default: Story = {
|
|
39
|
+
render: (args) => (
|
|
40
|
+
<Table {...args}>
|
|
41
|
+
<TableCaption>Recent invoices</TableCaption>
|
|
42
|
+
<TableHeader>
|
|
43
|
+
<TableRow>
|
|
44
|
+
<TableHead>Invoice</TableHead>
|
|
45
|
+
<TableHead>Status</TableHead>
|
|
46
|
+
<TableHead>Method</TableHead>
|
|
47
|
+
<TableHead className="text-end">Amount</TableHead>
|
|
48
|
+
</TableRow>
|
|
49
|
+
</TableHeader>
|
|
50
|
+
<TableBody>
|
|
51
|
+
{ROWS.map((r) => (
|
|
52
|
+
<TableRow key={r.id}>
|
|
53
|
+
<TableCell className="font-medium">{r.id}</TableCell>
|
|
54
|
+
<TableCell>{r.status}</TableCell>
|
|
55
|
+
<TableCell>{r.method}</TableCell>
|
|
56
|
+
<TableCell className="text-end tabular-nums">${r.amount.toFixed(2)}</TableCell>
|
|
57
|
+
</TableRow>
|
|
58
|
+
))}
|
|
59
|
+
</TableBody>
|
|
60
|
+
<TableFooter>
|
|
61
|
+
<TableRow>
|
|
62
|
+
<TableCell colSpan={3}>Total</TableCell>
|
|
63
|
+
<TableCell className="text-end tabular-nums">
|
|
64
|
+
${ROWS.reduce((s, r) => s + r.amount, 0).toFixed(2)}
|
|
65
|
+
</TableCell>
|
|
66
|
+
</TableRow>
|
|
67
|
+
</TableFooter>
|
|
68
|
+
</Table>
|
|
69
|
+
),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const Striped: Story = { ...Default, args: { striped: true } };
|
|
73
|
+
export const Bordered: Story = { ...Default, args: { bordered: true } };
|
|
74
|
+
export const Hoverable: Story = { ...Default, args: { hoverable: true } };
|
|
75
|
+
export const DenseDensity: Story = { ...Default, args: { density: "dense", striped: true } };
|
|
76
|
+
export const AllOptions: Story = {
|
|
77
|
+
...Default,
|
|
78
|
+
args: { striped: true, bordered: true, hoverable: true },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const StickyHeader: Story = {
|
|
82
|
+
args: { stickyHeader: true, striped: true },
|
|
83
|
+
render: (args) => (
|
|
84
|
+
<div className="h-72">
|
|
85
|
+
<Table {...args}>
|
|
86
|
+
<TableHeader>
|
|
87
|
+
<TableRow>
|
|
88
|
+
<TableHead>Invoice</TableHead>
|
|
89
|
+
<TableHead>Status</TableHead>
|
|
90
|
+
<TableHead className="text-end">Amount</TableHead>
|
|
91
|
+
</TableRow>
|
|
92
|
+
</TableHeader>
|
|
93
|
+
<TableBody>
|
|
94
|
+
{Array.from({ length: 25 }, (_, i) => (
|
|
95
|
+
<TableRow key={i}>
|
|
96
|
+
<TableCell>INV{String(i + 1).padStart(3, "0")}</TableCell>
|
|
97
|
+
<TableCell>{i % 2 ? "Paid" : "Pending"}</TableCell>
|
|
98
|
+
<TableCell className="text-end tabular-nums">
|
|
99
|
+
${((i + 1) * 50).toFixed(2)}
|
|
100
|
+
</TableCell>
|
|
101
|
+
</TableRow>
|
|
102
|
+
))}
|
|
103
|
+
</TableBody>
|
|
104
|
+
</Table>
|
|
105
|
+
</div>
|
|
106
|
+
),
|
|
107
|
+
};
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, forwardRef, useContext, useMemo } from "react";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
// Per docs/emara-ui-phase-4-components.md §1.1.
|
|
9
|
+
|
|
10
|
+
type TableDensity = "warm" | "dense";
|
|
11
|
+
|
|
12
|
+
interface TableContextValue {
|
|
13
|
+
density?: TableDensity;
|
|
14
|
+
striped: boolean;
|
|
15
|
+
bordered: boolean;
|
|
16
|
+
hoverable: boolean;
|
|
17
|
+
stickyHeader: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const TableContext = createContext<TableContextValue | null>(null);
|
|
21
|
+
|
|
22
|
+
function useTableContext(): TableContextValue {
|
|
23
|
+
// Subcomponents are safe outside <Table> for raw composition.
|
|
24
|
+
return (
|
|
25
|
+
useContext(TableContext) ?? {
|
|
26
|
+
striped: false,
|
|
27
|
+
bordered: false,
|
|
28
|
+
hoverable: false,
|
|
29
|
+
stickyHeader: false,
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- Table (root) -----------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
const tableVariants = cva("w-full caption-bottom border-separate border-spacing-0 text-sm", {
|
|
37
|
+
variants: {
|
|
38
|
+
bordered: {
|
|
39
|
+
true: "",
|
|
40
|
+
false: "",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
defaultVariants: { bordered: false },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
type TableRootVariants = VariantProps<typeof tableVariants>;
|
|
47
|
+
|
|
48
|
+
type TableProps = React.HTMLAttributes<HTMLTableElement> &
|
|
49
|
+
TableRootVariants & {
|
|
50
|
+
density?: TableDensity;
|
|
51
|
+
striped?: boolean;
|
|
52
|
+
bordered?: boolean;
|
|
53
|
+
hoverable?: boolean;
|
|
54
|
+
stickyHeader?: boolean;
|
|
55
|
+
/** Wraps the table in an overflow container. Default true. */
|
|
56
|
+
wrap?: boolean;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const Table = forwardRef<HTMLTableElement, TableProps>(function Table(
|
|
60
|
+
{
|
|
61
|
+
className,
|
|
62
|
+
density,
|
|
63
|
+
striped = false,
|
|
64
|
+
bordered = false,
|
|
65
|
+
hoverable = false,
|
|
66
|
+
stickyHeader = false,
|
|
67
|
+
wrap = true,
|
|
68
|
+
...props
|
|
69
|
+
},
|
|
70
|
+
ref,
|
|
71
|
+
) {
|
|
72
|
+
const ctx = useMemo<TableContextValue>(
|
|
73
|
+
() => ({
|
|
74
|
+
...(density !== undefined ? { density } : {}),
|
|
75
|
+
striped,
|
|
76
|
+
bordered,
|
|
77
|
+
hoverable,
|
|
78
|
+
stickyHeader,
|
|
79
|
+
}),
|
|
80
|
+
[density, striped, bordered, hoverable, stickyHeader],
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const tableElement = (
|
|
84
|
+
<table
|
|
85
|
+
ref={ref}
|
|
86
|
+
data-density={density}
|
|
87
|
+
data-striped={striped || undefined}
|
|
88
|
+
data-bordered={bordered || undefined}
|
|
89
|
+
data-hoverable={hoverable || undefined}
|
|
90
|
+
data-sticky-header={stickyHeader || undefined}
|
|
91
|
+
className={cn(tableVariants({ bordered }), className)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<TableContext.Provider value={ctx}>
|
|
98
|
+
{wrap ? (
|
|
99
|
+
<div className="border-border relative w-full overflow-auto rounded-md border">
|
|
100
|
+
{tableElement}
|
|
101
|
+
</div>
|
|
102
|
+
) : (
|
|
103
|
+
tableElement
|
|
104
|
+
)}
|
|
105
|
+
</TableContext.Provider>
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
Table.displayName = "Table";
|
|
109
|
+
|
|
110
|
+
// --- TableHeader ------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
const TableHeader = forwardRef<
|
|
113
|
+
HTMLTableSectionElement,
|
|
114
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
115
|
+
>(function TableHeader({ className, ...props }, ref) {
|
|
116
|
+
const { stickyHeader } = useTableContext();
|
|
117
|
+
return (
|
|
118
|
+
<thead
|
|
119
|
+
ref={ref}
|
|
120
|
+
className={cn(
|
|
121
|
+
"bg-muted/50 [&_tr]:border-border [&_tr]:border-b",
|
|
122
|
+
stickyHeader && "z-sticky bg-background sticky top-0",
|
|
123
|
+
className,
|
|
124
|
+
)}
|
|
125
|
+
{...props}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
TableHeader.displayName = "TableHeader";
|
|
130
|
+
|
|
131
|
+
// --- TableBody --------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
const TableBody = forwardRef<
|
|
134
|
+
HTMLTableSectionElement,
|
|
135
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
136
|
+
>(function TableBody({ className, ...props }, ref) {
|
|
137
|
+
return <tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />;
|
|
138
|
+
});
|
|
139
|
+
TableBody.displayName = "TableBody";
|
|
140
|
+
|
|
141
|
+
// --- TableFooter ------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
const TableFooter = forwardRef<
|
|
144
|
+
HTMLTableSectionElement,
|
|
145
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
146
|
+
>(function TableFooter({ className, ...props }, ref) {
|
|
147
|
+
return (
|
|
148
|
+
<tfoot
|
|
149
|
+
ref={ref}
|
|
150
|
+
className={cn(
|
|
151
|
+
"border-border bg-muted/30 border-t font-medium [&_tr]:last:border-b-0",
|
|
152
|
+
className,
|
|
153
|
+
)}
|
|
154
|
+
{...props}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
TableFooter.displayName = "TableFooter";
|
|
159
|
+
|
|
160
|
+
// --- TableRow ---------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
const TableRow = forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
|
|
163
|
+
function TableRow({ className, ...props }, ref) {
|
|
164
|
+
const { striped, hoverable } = useTableContext();
|
|
165
|
+
return (
|
|
166
|
+
<tr
|
|
167
|
+
ref={ref}
|
|
168
|
+
className={cn(
|
|
169
|
+
"border-border border-b transition-colors",
|
|
170
|
+
striped && "even:bg-muted/30",
|
|
171
|
+
hoverable && "hover:bg-muted/40",
|
|
172
|
+
"data-[state=selected]:bg-accent/40",
|
|
173
|
+
className,
|
|
174
|
+
)}
|
|
175
|
+
{...props}
|
|
176
|
+
/>
|
|
177
|
+
);
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
TableRow.displayName = "TableRow";
|
|
181
|
+
|
|
182
|
+
// --- TableHead --------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
const tableHeadVariants = cva(
|
|
185
|
+
[
|
|
186
|
+
"text-start align-middle font-medium text-muted-foreground",
|
|
187
|
+
"[&:has([role=checkbox])]:pe-0",
|
|
188
|
+
].join(" "),
|
|
189
|
+
{
|
|
190
|
+
variants: {
|
|
191
|
+
density: {
|
|
192
|
+
warm: "h-10 px-3",
|
|
193
|
+
dense: "h-8 px-2",
|
|
194
|
+
},
|
|
195
|
+
bordered: {
|
|
196
|
+
true: "border-e border-border last:border-e-0",
|
|
197
|
+
false: "",
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
defaultVariants: { density: "warm", bordered: false },
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
type TableHeadProps = React.ThHTMLAttributes<HTMLTableCellElement> & {
|
|
205
|
+
density?: TableDensity;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const TableHead = forwardRef<HTMLTableCellElement, TableHeadProps>(function TableHead(
|
|
209
|
+
{ className, density, ...props },
|
|
210
|
+
ref,
|
|
211
|
+
) {
|
|
212
|
+
const { density: ctxDensity, bordered } = useTableContext();
|
|
213
|
+
const resolved = density ?? ctxDensity ?? "warm";
|
|
214
|
+
return (
|
|
215
|
+
<th
|
|
216
|
+
ref={ref}
|
|
217
|
+
className={cn(tableHeadVariants({ density: resolved, bordered }), className)}
|
|
218
|
+
{...props}
|
|
219
|
+
/>
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
TableHead.displayName = "TableHead";
|
|
223
|
+
|
|
224
|
+
// --- TableCell --------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
const tableCellVariants = cva(["align-middle [&:has([role=checkbox])]:pe-0"].join(" "), {
|
|
227
|
+
variants: {
|
|
228
|
+
density: {
|
|
229
|
+
warm: "px-3 py-2.5",
|
|
230
|
+
dense: "px-2 py-1.5",
|
|
231
|
+
},
|
|
232
|
+
bordered: {
|
|
233
|
+
true: "border-e border-border last:border-e-0",
|
|
234
|
+
false: "",
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
defaultVariants: { density: "warm", bordered: false },
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
type TableCellProps = React.TdHTMLAttributes<HTMLTableCellElement> & {
|
|
241
|
+
density?: TableDensity;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(function TableCell(
|
|
245
|
+
{ className, density, ...props },
|
|
246
|
+
ref,
|
|
247
|
+
) {
|
|
248
|
+
const { density: ctxDensity, bordered } = useTableContext();
|
|
249
|
+
const resolved = density ?? ctxDensity ?? "warm";
|
|
250
|
+
return (
|
|
251
|
+
<td
|
|
252
|
+
ref={ref}
|
|
253
|
+
className={cn(tableCellVariants({ density: resolved, bordered }), className)}
|
|
254
|
+
{...props}
|
|
255
|
+
/>
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
TableCell.displayName = "TableCell";
|
|
259
|
+
|
|
260
|
+
// --- TableCaption -----------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
const TableCaption = forwardRef<
|
|
263
|
+
HTMLTableCaptionElement,
|
|
264
|
+
React.HTMLAttributes<HTMLTableCaptionElement>
|
|
265
|
+
>(function TableCaption({ className, ...props }, ref) {
|
|
266
|
+
return (
|
|
267
|
+
<caption ref={ref} className={cn("text-muted-foreground mt-3 text-sm", className)} {...props} />
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
TableCaption.displayName = "TableCaption";
|
|
271
|
+
|
|
272
|
+
export {
|
|
273
|
+
Table,
|
|
274
|
+
TableHeader,
|
|
275
|
+
TableBody,
|
|
276
|
+
TableFooter,
|
|
277
|
+
TableRow,
|
|
278
|
+
TableHead,
|
|
279
|
+
TableCell,
|
|
280
|
+
TableCaption,
|
|
281
|
+
tableVariants,
|
|
282
|
+
tableHeadVariants,
|
|
283
|
+
tableCellVariants,
|
|
284
|
+
};
|
|
285
|
+
export type { TableProps, TableHeadProps, TableCellProps, TableDensity };
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { RiHome4Line, RiSettings4Line, RiUserLine } from "@remixicon/react";
|
|
3
|
+
|
|
4
|
+
import { Badge } from "./badge";
|
|
5
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs";
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Tabs> = {
|
|
8
|
+
title: "Data/Tabs",
|
|
9
|
+
component: Tabs,
|
|
10
|
+
parameters: { layout: "padded" },
|
|
11
|
+
argTypes: {
|
|
12
|
+
variant: { control: "select", options: ["line", "pill", "card", "vertical"] },
|
|
13
|
+
size: { control: "select", options: ["sm", "md", "lg"] },
|
|
14
|
+
lazy: { control: "boolean" },
|
|
15
|
+
unmountInactive: { control: "boolean" },
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
type Story = StoryObj<typeof Tabs>;
|
|
21
|
+
|
|
22
|
+
function PanelText({ name }: { name: string }) {
|
|
23
|
+
return <p className="text-muted-foreground text-sm">Content for the {name} panel.</p>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const Line: Story = {
|
|
27
|
+
args: { variant: "line", defaultValue: "overview" },
|
|
28
|
+
render: (args) => (
|
|
29
|
+
<Tabs {...args} className="w-96">
|
|
30
|
+
<TabsList>
|
|
31
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
32
|
+
<TabsTrigger value="activity">Activity</TabsTrigger>
|
|
33
|
+
<TabsTrigger value="settings">Settings</TabsTrigger>
|
|
34
|
+
</TabsList>
|
|
35
|
+
<TabsContent value="overview">
|
|
36
|
+
<PanelText name="overview" />
|
|
37
|
+
</TabsContent>
|
|
38
|
+
<TabsContent value="activity">
|
|
39
|
+
<PanelText name="activity" />
|
|
40
|
+
</TabsContent>
|
|
41
|
+
<TabsContent value="settings">
|
|
42
|
+
<PanelText name="settings" />
|
|
43
|
+
</TabsContent>
|
|
44
|
+
</Tabs>
|
|
45
|
+
),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const Pill: Story = {
|
|
49
|
+
args: { variant: "pill", defaultValue: "overview" },
|
|
50
|
+
render: (args) => (
|
|
51
|
+
<Tabs {...args} className="w-96">
|
|
52
|
+
<TabsList>
|
|
53
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
54
|
+
<TabsTrigger value="activity">Activity</TabsTrigger>
|
|
55
|
+
<TabsTrigger value="settings">Settings</TabsTrigger>
|
|
56
|
+
</TabsList>
|
|
57
|
+
<TabsContent value="overview">
|
|
58
|
+
<PanelText name="overview" />
|
|
59
|
+
</TabsContent>
|
|
60
|
+
<TabsContent value="activity">
|
|
61
|
+
<PanelText name="activity" />
|
|
62
|
+
</TabsContent>
|
|
63
|
+
<TabsContent value="settings">
|
|
64
|
+
<PanelText name="settings" />
|
|
65
|
+
</TabsContent>
|
|
66
|
+
</Tabs>
|
|
67
|
+
),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Card: Story = {
|
|
71
|
+
args: { variant: "card", defaultValue: "overview" },
|
|
72
|
+
render: (args) => (
|
|
73
|
+
<Tabs {...args} className="w-96">
|
|
74
|
+
<TabsList>
|
|
75
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
76
|
+
<TabsTrigger value="activity">Activity</TabsTrigger>
|
|
77
|
+
<TabsTrigger value="settings">Settings</TabsTrigger>
|
|
78
|
+
</TabsList>
|
|
79
|
+
<TabsContent
|
|
80
|
+
value="overview"
|
|
81
|
+
className="border-border bg-card rounded-md rounded-ss-none border p-4"
|
|
82
|
+
>
|
|
83
|
+
<PanelText name="overview" />
|
|
84
|
+
</TabsContent>
|
|
85
|
+
<TabsContent
|
|
86
|
+
value="activity"
|
|
87
|
+
className="border-border bg-card rounded-md rounded-ss-none border p-4"
|
|
88
|
+
>
|
|
89
|
+
<PanelText name="activity" />
|
|
90
|
+
</TabsContent>
|
|
91
|
+
<TabsContent
|
|
92
|
+
value="settings"
|
|
93
|
+
className="border-border bg-card rounded-md rounded-ss-none border p-4"
|
|
94
|
+
>
|
|
95
|
+
<PanelText name="settings" />
|
|
96
|
+
</TabsContent>
|
|
97
|
+
</Tabs>
|
|
98
|
+
),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const Vertical: Story = {
|
|
102
|
+
args: { variant: "vertical", defaultValue: "overview" },
|
|
103
|
+
render: (args) => (
|
|
104
|
+
<Tabs {...args} className="w-[36rem]">
|
|
105
|
+
<TabsList>
|
|
106
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
107
|
+
<TabsTrigger value="activity">Activity</TabsTrigger>
|
|
108
|
+
<TabsTrigger value="settings">Settings</TabsTrigger>
|
|
109
|
+
</TabsList>
|
|
110
|
+
<TabsContent value="overview">
|
|
111
|
+
<PanelText name="overview" />
|
|
112
|
+
</TabsContent>
|
|
113
|
+
<TabsContent value="activity">
|
|
114
|
+
<PanelText name="activity" />
|
|
115
|
+
</TabsContent>
|
|
116
|
+
<TabsContent value="settings">
|
|
117
|
+
<PanelText name="settings" />
|
|
118
|
+
</TabsContent>
|
|
119
|
+
</Tabs>
|
|
120
|
+
),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const WithIconsAndBadges: Story = {
|
|
124
|
+
args: { variant: "line", defaultValue: "home" },
|
|
125
|
+
render: (args) => (
|
|
126
|
+
<Tabs {...args} className="w-[28rem]">
|
|
127
|
+
<TabsList>
|
|
128
|
+
<TabsTrigger value="home" icon={<RiHome4Line />}>
|
|
129
|
+
Home
|
|
130
|
+
</TabsTrigger>
|
|
131
|
+
<TabsTrigger
|
|
132
|
+
value="profile"
|
|
133
|
+
icon={<RiUserLine />}
|
|
134
|
+
badge={
|
|
135
|
+
<Badge size="xs" variant="secondary">
|
|
136
|
+
3
|
|
137
|
+
</Badge>
|
|
138
|
+
}
|
|
139
|
+
>
|
|
140
|
+
Profile
|
|
141
|
+
</TabsTrigger>
|
|
142
|
+
<TabsTrigger value="settings" icon={<RiSettings4Line />}>
|
|
143
|
+
Settings
|
|
144
|
+
</TabsTrigger>
|
|
145
|
+
</TabsList>
|
|
146
|
+
<TabsContent value="home">
|
|
147
|
+
<PanelText name="home" />
|
|
148
|
+
</TabsContent>
|
|
149
|
+
<TabsContent value="profile">
|
|
150
|
+
<PanelText name="profile" />
|
|
151
|
+
</TabsContent>
|
|
152
|
+
<TabsContent value="settings">
|
|
153
|
+
<PanelText name="settings" />
|
|
154
|
+
</TabsContent>
|
|
155
|
+
</Tabs>
|
|
156
|
+
),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const Sizes: Story = {
|
|
160
|
+
render: () => (
|
|
161
|
+
<div className="w-96 space-y-6">
|
|
162
|
+
{(["sm", "md", "lg"] as const).map((size) => (
|
|
163
|
+
<Tabs key={size} variant="pill" size={size} defaultValue="a">
|
|
164
|
+
<TabsList>
|
|
165
|
+
<TabsTrigger value="a">{size.toUpperCase()} A</TabsTrigger>
|
|
166
|
+
<TabsTrigger value="b">{size.toUpperCase()} B</TabsTrigger>
|
|
167
|
+
</TabsList>
|
|
168
|
+
<TabsContent value="a">
|
|
169
|
+
<PanelText name={`${size}-a`} />
|
|
170
|
+
</TabsContent>
|
|
171
|
+
<TabsContent value="b">
|
|
172
|
+
<PanelText name={`${size}-b`} />
|
|
173
|
+
</TabsContent>
|
|
174
|
+
</Tabs>
|
|
175
|
+
))}
|
|
176
|
+
</div>
|
|
177
|
+
),
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const Scrollable: Story = {
|
|
181
|
+
args: { variant: "line", defaultValue: "tab-1" },
|
|
182
|
+
render: (args) => (
|
|
183
|
+
<div className="w-72">
|
|
184
|
+
<Tabs {...args}>
|
|
185
|
+
<TabsList scrollable>
|
|
186
|
+
{Array.from({ length: 10 }, (_, i) => (
|
|
187
|
+
<TabsTrigger key={i} value={`tab-${i + 1}`}>
|
|
188
|
+
Section {i + 1}
|
|
189
|
+
</TabsTrigger>
|
|
190
|
+
))}
|
|
191
|
+
</TabsList>
|
|
192
|
+
<TabsContent value="tab-1">
|
|
193
|
+
<PanelText name="tab-1" />
|
|
194
|
+
</TabsContent>
|
|
195
|
+
</Tabs>
|
|
196
|
+
</div>
|
|
197
|
+
),
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const DisabledTrigger: Story = {
|
|
201
|
+
args: { variant: "line", defaultValue: "a" },
|
|
202
|
+
render: (args) => (
|
|
203
|
+
<Tabs {...args} className="w-96">
|
|
204
|
+
<TabsList>
|
|
205
|
+
<TabsTrigger value="a">Enabled</TabsTrigger>
|
|
206
|
+
<TabsTrigger value="b" disabled>
|
|
207
|
+
Disabled
|
|
208
|
+
</TabsTrigger>
|
|
209
|
+
<TabsTrigger value="c">Enabled</TabsTrigger>
|
|
210
|
+
</TabsList>
|
|
211
|
+
<TabsContent value="a">
|
|
212
|
+
<PanelText name="a" />
|
|
213
|
+
</TabsContent>
|
|
214
|
+
<TabsContent value="b">
|
|
215
|
+
<PanelText name="b" />
|
|
216
|
+
</TabsContent>
|
|
217
|
+
<TabsContent value="c">
|
|
218
|
+
<PanelText name="c" />
|
|
219
|
+
</TabsContent>
|
|
220
|
+
</Tabs>
|
|
221
|
+
),
|
|
222
|
+
};
|