@shadow-js/ui 0.0.0-stage → 1.0.1
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/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +598 -2
- package/dist/cli.mjs +2281 -0
- package/dist/dev/cli/schema.d.ts +71 -0
- package/dist/dev/index.d.ts +6 -0
- package/dist/dev/index.js +5 -0
- package/dist/dev/locales/ar.d.ts +139 -0
- package/dist/dev/locales/ar.js +127 -0
- package/dist/dev/locales/ar.js.map +1 -0
- package/dist/dev/locales/en.d.ts +139 -0
- package/dist/dev/locales/en.js +127 -0
- package/dist/dev/locales/en.js.map +1 -0
- package/dist/dev/registry/utils/utils.d.ts +27 -0
- package/dist/dev/registry/utils/utils.js +26 -0
- package/dist/dev/registry/utils/utils.js.map +1 -0
- package/dist/dev/theme/theme-logic.d.ts +185 -0
- package/dist/dev/theme/theme-logic.js +183 -0
- package/dist/dev/theme/theme-logic.js.map +1 -0
- package/dist/dev/theme/theme-provider.d.ts +88 -0
- package/dist/dev/theme/theme-provider.js +149 -0
- package/dist/dev/theme/theme-provider.js.map +1 -0
- package/dist/prod/index.js +1 -0
- package/dist/prod/locales/ar.js +2 -0
- package/dist/prod/locales/ar.js.map +1 -0
- package/dist/prod/locales/en.js +2 -0
- package/dist/prod/locales/en.js.map +1 -0
- package/dist/prod/registry/utils/utils.js +2 -0
- package/dist/prod/registry/utils/utils.js.map +1 -0
- package/dist/prod/theme/theme-logic.js +2 -0
- package/dist/prod/theme/theme-logic.js.map +1 -0
- package/dist/prod/theme/theme-provider.js +2 -0
- package/dist/prod/theme/theme-provider.js.map +1 -0
- package/package.json +102 -4
- package/registry/accordion/accordion.tsx +210 -0
- package/registry/accordion/item.json +4 -0
- package/registry/alert/alert.tsx +200 -0
- package/registry/alert/item.json +4 -0
- package/registry/alert-dialog/alert-dialog.tsx +530 -0
- package/registry/alert-dialog/item.json +4 -0
- package/registry/aspect-ratio/aspect-ratio.tsx +71 -0
- package/registry/aspect-ratio/item.json +4 -0
- package/registry/attachment/attachment.tsx +459 -0
- package/registry/attachment/item.json +4 -0
- package/registry/avatar/avatar.tsx +385 -0
- package/registry/avatar/item.json +4 -0
- package/registry/badge/badge.tsx +108 -0
- package/registry/badge/item.json +4 -0
- package/registry/breadcrumb/breadcrumb.tsx +354 -0
- package/registry/breadcrumb/item.json +4 -0
- package/registry/bubble/bubble.tsx +260 -0
- package/registry/bubble/item.json +4 -0
- package/registry/button/button.tsx +125 -0
- package/registry/button/item.json +4 -0
- package/registry/button-group/button-group.tsx +189 -0
- package/registry/button-group/item.json +4 -0
- package/registry/calendar/calendar.tsx +348 -0
- package/registry/calendar/item.json +4 -0
- package/registry/card/card.tsx +311 -0
- package/registry/card/item.json +4 -0
- package/registry/carousel/carousel.tsx +851 -0
- package/registry/carousel/item.json +4 -0
- package/registry/chart/chart.tsx +1956 -0
- package/registry/chart/item.json +4 -0
- package/registry/chart-scale/chart-scale.ts +670 -0
- package/registry/chart-scale/item.json +5 -0
- package/registry/checkbox/checkbox.tsx +79 -0
- package/registry/checkbox/item.json +4 -0
- package/registry/code-block/code-block.tsx +470 -0
- package/registry/code-block/item.json +4 -0
- package/registry/collapsible/collapsible.tsx +117 -0
- package/registry/collapsible/item.json +4 -0
- package/registry/combobox/combobox.tsx +977 -0
- package/registry/combobox/item.json +4 -0
- package/registry/command/command.tsx +757 -0
- package/registry/command/item.json +4 -0
- package/registry/context-menu/context-menu.tsx +697 -0
- package/registry/context-menu/item.json +4 -0
- package/registry/data-table/data-table.tsx +437 -0
- package/registry/data-table/item.json +4 -0
- package/registry/date-picker/date-picker.tsx +349 -0
- package/registry/date-picker/item.json +4 -0
- package/registry/dialog/dialog.tsx +412 -0
- package/registry/dialog/item.json +4 -0
- package/registry/direction/direction.tsx +88 -0
- package/registry/direction/item.json +4 -0
- package/registry/drawer/drawer.tsx +787 -0
- package/registry/drawer/item.json +4 -0
- package/registry/dropdown-menu/dropdown-menu.tsx +692 -0
- package/registry/dropdown-menu/item.json +4 -0
- package/registry/empty/empty.tsx +240 -0
- package/registry/empty/item.json +4 -0
- package/registry/field/field.tsx +624 -0
- package/registry/field/item.json +4 -0
- package/registry/form/form.tsx +1112 -0
- package/registry/form/item.json +4 -0
- package/registry/hover-card/hover-card.tsx +144 -0
- package/registry/hover-card/item.json +4 -0
- package/registry/icons/icons.tsx +1691 -0
- package/registry/icons/item.json +4 -0
- package/registry/input/input.tsx +43 -0
- package/registry/input/item.json +4 -0
- package/registry/input-group/input-group.tsx +315 -0
- package/registry/input-group/item.json +4 -0
- package/registry/input-otp/input-otp.tsx +329 -0
- package/registry/input-otp/item.json +4 -0
- package/registry/item/item.json +4 -0
- package/registry/item/item.tsx +439 -0
- package/registry/kbd/item.json +4 -0
- package/registry/kbd/kbd.tsx +65 -0
- package/registry/label/item.json +4 -0
- package/registry/label/label.tsx +60 -0
- package/registry/marker/item.json +4 -0
- package/registry/marker/marker.tsx +175 -0
- package/registry/menubar/item.json +4 -0
- package/registry/menubar/menubar.tsx +694 -0
- package/registry/message/item.json +4 -0
- package/registry/message/message.tsx +280 -0
- package/registry/message-scroller/item.json +4 -0
- package/registry/message-scroller/message-scroller.tsx +262 -0
- package/registry/native-select/item.json +4 -0
- package/registry/native-select/native-select.tsx +113 -0
- package/registry/navigation-menu/item.json +4 -0
- package/registry/navigation-menu/navigation-menu.tsx +362 -0
- package/registry/pagination/item.json +4 -0
- package/registry/pagination/pagination.tsx +460 -0
- package/registry/password-input/item.json +5 -0
- package/registry/password-input/password-input.tsx +494 -0
- package/registry/popover/item.json +4 -0
- package/registry/popover/popover.tsx +284 -0
- package/registry/progress/item.json +4 -0
- package/registry/progress/progress.tsx +190 -0
- package/registry/questionnaire/item.json +4 -0
- package/registry/questionnaire/questionnaire.tsx +569 -0
- package/registry/radio-group/item.json +4 -0
- package/registry/radio-group/radio-group.tsx +101 -0
- package/registry/resizable/item.json +4 -0
- package/registry/resizable/resizable.tsx +455 -0
- package/registry/scroll-area/item.json +4 -0
- package/registry/scroll-area/scroll-area.tsx +149 -0
- package/registry/select/item.json +4 -0
- package/registry/select/select.tsx +571 -0
- package/registry/separator/item.json +4 -0
- package/registry/separator/separator.tsx +52 -0
- package/registry/sheet/item.json +4 -0
- package/registry/sheet/sheet.tsx +476 -0
- package/registry/sidebar/item.json +4 -0
- package/registry/sidebar/sidebar.tsx +1870 -0
- package/registry/skeleton/item.json +4 -0
- package/registry/skeleton/skeleton.tsx +47 -0
- package/registry/slider/item.json +4 -0
- package/registry/slider/slider.tsx +149 -0
- package/registry/spinner/item.json +4 -0
- package/registry/spinner/spinner.tsx +51 -0
- package/registry/switch/item.json +4 -0
- package/registry/switch/switch.tsx +93 -0
- package/registry/table/item.json +4 -0
- package/registry/table/table.tsx +373 -0
- package/registry/tabs/item.json +4 -0
- package/registry/tabs/tabs.tsx +209 -0
- package/registry/textarea/item.json +4 -0
- package/registry/textarea/textarea.tsx +51 -0
- package/registry/toast/item.json +4 -0
- package/registry/toast/toast.tsx +949 -0
- package/registry/toggle/item.json +4 -0
- package/registry/toggle/toggle.tsx +110 -0
- package/registry/toggle-group/item.json +4 -0
- package/registry/toggle-group/toggle-group.tsx +189 -0
- package/registry/tooltip/item.json +4 -0
- package/registry/tooltip/tooltip.tsx +169 -0
- package/registry/trace/item.json +4 -0
- package/registry/trace/trace.tsx +356 -0
- package/registry/ui-strings/item.json +5 -0
- package/registry/ui-strings/ui-strings.tsx +454 -0
- package/registry/use-highlighted-code/item.json +5 -0
- package/registry/use-highlighted-code/use-highlighted-code.tsx +145 -0
- package/registry/use-mobile/item.json +5 -0
- package/registry/use-mobile/use-mobile.ts +46 -0
- package/registry/utils/item.json +5 -0
- package/registry/utils/utils.ts +38 -0
- package/registry.json +3274 -0
- package/styles/animations.css +321 -0
- package/styles/theme-core.css +55 -0
- package/styles/theme.css +12 -0
- package/styles/themes/gray.css +69 -0
- package/styles/themes/neutral.css +69 -0
- package/styles/themes/slate.css +69 -0
- package/styles/themes/stone.css +69 -0
- package/styles/themes/zinc.css +69 -0
- package/styles/typeset.css +251 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { splitProps } from "@shadow-js/core";
|
|
2
|
+
import type { ComponentProps, JSX } from "@shadow-js/web";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
/** The attributes that make a scroll container a named region. @internal */
|
|
6
|
+
interface RegionAttributes {
|
|
7
|
+
readonly role?: "region" | undefined;
|
|
8
|
+
readonly "aria-label"?: string | null | undefined;
|
|
9
|
+
readonly "aria-labelledby"?: string | null | undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A responsive table. The `table` sits in a container that scrolls horizontally when the table is
|
|
14
|
+
* wider than the space it has. `class` goes on the `table` element.
|
|
15
|
+
*
|
|
16
|
+
* Keyboard and ARIA: the container takes keyboard focus (`tabindex="0"`), so keyboard users can
|
|
17
|
+
* scroll a wide table. The component adds no keys of its own. When the table has an accessible name
|
|
18
|
+
* (`aria-label` or `aria-labelledby`), the container is a `role="region"` with the same name, so the
|
|
19
|
+
* scrolling area is announced by name. Name the table with a {@link TableCaption} or an
|
|
20
|
+
* `aria-label`.
|
|
21
|
+
*
|
|
22
|
+
* shadcn/ui differences: the scroll container is focusable and, for a named table, a labeled
|
|
23
|
+
* region. shadcn/ui sets neither. `class` replaces `className`.
|
|
24
|
+
*
|
|
25
|
+
* @param props - The props: the attributes of the `table`, and `class`.
|
|
26
|
+
* @returns The scroll container, with the table inside.
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* import {
|
|
30
|
+
* Table,
|
|
31
|
+
* TableBody,
|
|
32
|
+
* TableCaption,
|
|
33
|
+
* TableCell,
|
|
34
|
+
* TableHead,
|
|
35
|
+
* TableHeader,
|
|
36
|
+
* TableRow,
|
|
37
|
+
* } from "@/components/ui/table";
|
|
38
|
+
*
|
|
39
|
+
* export function Invoices() {
|
|
40
|
+
* return (
|
|
41
|
+
* <Table>
|
|
42
|
+
* <TableCaption>Recent invoices</TableCaption>
|
|
43
|
+
* <TableHeader>
|
|
44
|
+
* <TableRow>
|
|
45
|
+
* <TableHead scope="col">Invoice</TableHead>
|
|
46
|
+
* <TableHead scope="col">Amount</TableHead>
|
|
47
|
+
* </TableRow>
|
|
48
|
+
* </TableHeader>
|
|
49
|
+
* <TableBody>
|
|
50
|
+
* <TableRow>
|
|
51
|
+
* <TableCell>INV001</TableCell>
|
|
52
|
+
* <TableCell>$250.00</TableCell>
|
|
53
|
+
* </TableRow>
|
|
54
|
+
* </TableBody>
|
|
55
|
+
* </Table>
|
|
56
|
+
* );
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
* @since 1.0.0
|
|
60
|
+
* @see {@link TableCaption}
|
|
61
|
+
*/
|
|
62
|
+
function Table(props: ComponentProps<"table">): JSX.Element {
|
|
63
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
64
|
+
// A named table names its scroll region too; an unnamed one is not a region at all.
|
|
65
|
+
const region = (): RegionAttributes =>
|
|
66
|
+
props["aria-label"] === undefined && props["aria-labelledby"] === undefined
|
|
67
|
+
? {}
|
|
68
|
+
: {
|
|
69
|
+
role: "region",
|
|
70
|
+
"aria-label": props["aria-label"],
|
|
71
|
+
"aria-labelledby": props["aria-labelledby"],
|
|
72
|
+
};
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
data-slot="table-container"
|
|
76
|
+
class="relative w-full overflow-x-auto"
|
|
77
|
+
tabindex={0}
|
|
78
|
+
{...region()}
|
|
79
|
+
>
|
|
80
|
+
<table data-slot="table" class={cn("w-full caption-bottom text-sm", local.class)} {...rest}>
|
|
81
|
+
{local.children}
|
|
82
|
+
</table>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The `thead` of a table. Its rows get a bottom border.
|
|
89
|
+
*
|
|
90
|
+
* Keyboard and ARIA: no keys, and no role of its own (it is a row group). Its header cells are
|
|
91
|
+
* column headers (see {@link TableHead}).
|
|
92
|
+
*
|
|
93
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
94
|
+
*
|
|
95
|
+
* @param props - The props: the attributes of the `thead`, and `class`.
|
|
96
|
+
* @returns The `thead` element.
|
|
97
|
+
* @example
|
|
98
|
+
* ```tsx
|
|
99
|
+
* import { Table, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
100
|
+
*
|
|
101
|
+
* export function Columns() {
|
|
102
|
+
* return (
|
|
103
|
+
* <Table>
|
|
104
|
+
* <TableHeader>
|
|
105
|
+
* <TableRow>
|
|
106
|
+
* <TableHead scope="col">Invoice</TableHead>
|
|
107
|
+
* </TableRow>
|
|
108
|
+
* </TableHeader>
|
|
109
|
+
* </Table>
|
|
110
|
+
* );
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
* @since 1.0.0
|
|
114
|
+
*/
|
|
115
|
+
function TableHeader(props: ComponentProps<"thead">): JSX.Element {
|
|
116
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
117
|
+
return (
|
|
118
|
+
<thead data-slot="table-header" class={cn("[&_tr]:border-b", local.class)} {...rest}>
|
|
119
|
+
{local.children}
|
|
120
|
+
</thead>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The `tbody` of a table. Its last row has no bottom border.
|
|
126
|
+
*
|
|
127
|
+
* Keyboard and ARIA: no keys, and no role of its own (it is a row group).
|
|
128
|
+
*
|
|
129
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
130
|
+
*
|
|
131
|
+
* @param props - The props: the attributes of the `tbody`, and `class`.
|
|
132
|
+
* @returns The `tbody` element.
|
|
133
|
+
* @example
|
|
134
|
+
* ```tsx
|
|
135
|
+
* import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
|
|
136
|
+
*
|
|
137
|
+
* export function Rows() {
|
|
138
|
+
* return (
|
|
139
|
+
* <Table>
|
|
140
|
+
* <TableBody>
|
|
141
|
+
* <TableRow>
|
|
142
|
+
* <TableCell>INV001</TableCell>
|
|
143
|
+
* </TableRow>
|
|
144
|
+
* </TableBody>
|
|
145
|
+
* </Table>
|
|
146
|
+
* );
|
|
147
|
+
* }
|
|
148
|
+
* ```
|
|
149
|
+
* @since 1.0.0
|
|
150
|
+
*/
|
|
151
|
+
function TableBody(props: ComponentProps<"tbody">): JSX.Element {
|
|
152
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
153
|
+
return (
|
|
154
|
+
<tbody data-slot="table-body" class={cn("[&_tr:last-child]:border-0", local.class)} {...rest}>
|
|
155
|
+
{local.children}
|
|
156
|
+
</tbody>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The `tfoot` of a table, for totals. It has a top border and a muted background, and its last row
|
|
162
|
+
* has no bottom border.
|
|
163
|
+
*
|
|
164
|
+
* Keyboard and ARIA: no keys, and no role of its own (it is a row group).
|
|
165
|
+
*
|
|
166
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
167
|
+
*
|
|
168
|
+
* @param props - The props: the attributes of the `tfoot`, and `class`.
|
|
169
|
+
* @returns The `tfoot` element.
|
|
170
|
+
* @example
|
|
171
|
+
* ```tsx
|
|
172
|
+
* import { Table, TableCell, TableFooter, TableRow } from "@/components/ui/table";
|
|
173
|
+
*
|
|
174
|
+
* export function Total() {
|
|
175
|
+
* return (
|
|
176
|
+
* <Table>
|
|
177
|
+
* <TableFooter>
|
|
178
|
+
* <TableRow>
|
|
179
|
+
* <TableCell>Total</TableCell>
|
|
180
|
+
* <TableCell>$250.00</TableCell>
|
|
181
|
+
* </TableRow>
|
|
182
|
+
* </TableFooter>
|
|
183
|
+
* </Table>
|
|
184
|
+
* );
|
|
185
|
+
* }
|
|
186
|
+
* ```
|
|
187
|
+
* @since 1.0.0
|
|
188
|
+
*/
|
|
189
|
+
function TableFooter(props: ComponentProps<"tfoot">): JSX.Element {
|
|
190
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
191
|
+
return (
|
|
192
|
+
<tfoot
|
|
193
|
+
data-slot="table-footer"
|
|
194
|
+
class={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", local.class)}
|
|
195
|
+
{...rest}
|
|
196
|
+
>
|
|
197
|
+
{local.children}
|
|
198
|
+
</tfoot>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* A row of cells. It highlights on hover, and with `data-state="selected"` it takes the selected
|
|
204
|
+
* background.
|
|
205
|
+
*
|
|
206
|
+
* Keyboard and ARIA: no keys; the row has the `row` role. Set `data-state="selected"` for the
|
|
207
|
+
* highlight only; it is not announced, so mark a selected row with `aria-selected` if assistive
|
|
208
|
+
* technology must know.
|
|
209
|
+
*
|
|
210
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
211
|
+
*
|
|
212
|
+
* @param props - The props: the attributes of the `tr`, and `class`.
|
|
213
|
+
* @returns The `tr` element.
|
|
214
|
+
* @example
|
|
215
|
+
* ```tsx
|
|
216
|
+
* import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
|
|
217
|
+
*
|
|
218
|
+
* export function SelectedRow() {
|
|
219
|
+
* return (
|
|
220
|
+
* <Table>
|
|
221
|
+
* <TableBody>
|
|
222
|
+
* <TableRow data-state="selected" aria-selected="true">
|
|
223
|
+
* <TableCell>INV001</TableCell>
|
|
224
|
+
* </TableRow>
|
|
225
|
+
* </TableBody>
|
|
226
|
+
* </Table>
|
|
227
|
+
* );
|
|
228
|
+
* }
|
|
229
|
+
* ```
|
|
230
|
+
* @since 1.0.0
|
|
231
|
+
*/
|
|
232
|
+
function TableRow(props: ComponentProps<"tr">): JSX.Element {
|
|
233
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
234
|
+
return (
|
|
235
|
+
<tr
|
|
236
|
+
data-slot="table-row"
|
|
237
|
+
class={cn(
|
|
238
|
+
"border-b transition-colors hover:bg-muted/50 motion-reduce:transition-none data-[state=selected]:bg-muted forced-colors:data-[state=selected]:bg-[Highlight] forced-colors:data-[state=selected]:text-[HighlightText]",
|
|
239
|
+
local.class,
|
|
240
|
+
)}
|
|
241
|
+
{...rest}
|
|
242
|
+
>
|
|
243
|
+
{local.children}
|
|
244
|
+
</tr>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* A header cell (`th`). Give it `scope="col"` (a column header) or `scope="row"` (a row header), so
|
|
250
|
+
* assistive technology knows what the cell heads.
|
|
251
|
+
*
|
|
252
|
+
* Keyboard and ARIA: no keys. In a header row it is a `columnheader`; `scope="row"` makes it a
|
|
253
|
+
* row header.
|
|
254
|
+
*
|
|
255
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
256
|
+
*
|
|
257
|
+
* @param props - The props: the attributes of the `th`, and `class`.
|
|
258
|
+
* @returns The `th` element.
|
|
259
|
+
* @example
|
|
260
|
+
* ```tsx
|
|
261
|
+
* import { Table, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
262
|
+
*
|
|
263
|
+
* export function AmountHead() {
|
|
264
|
+
* return (
|
|
265
|
+
* <Table>
|
|
266
|
+
* <TableHeader>
|
|
267
|
+
* <TableRow>
|
|
268
|
+
* <TableHead scope="col">Amount</TableHead>
|
|
269
|
+
* </TableRow>
|
|
270
|
+
* </TableHeader>
|
|
271
|
+
* </Table>
|
|
272
|
+
* );
|
|
273
|
+
* }
|
|
274
|
+
* ```
|
|
275
|
+
* @since 1.0.0
|
|
276
|
+
*/
|
|
277
|
+
function TableHead(props: ComponentProps<"th">): JSX.Element {
|
|
278
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
279
|
+
return (
|
|
280
|
+
<th
|
|
281
|
+
data-slot="table-head"
|
|
282
|
+
class={cn(
|
|
283
|
+
"h-10 px-2 text-start align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pe-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
284
|
+
local.class,
|
|
285
|
+
)}
|
|
286
|
+
{...rest}
|
|
287
|
+
>
|
|
288
|
+
{local.children}
|
|
289
|
+
</th>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* A data cell (`td`).
|
|
295
|
+
*
|
|
296
|
+
* Keyboard and ARIA: no keys; the cell has the `cell` role.
|
|
297
|
+
*
|
|
298
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
299
|
+
*
|
|
300
|
+
* @param props - The props: the attributes of the `td`, and `class`.
|
|
301
|
+
* @returns The `td` element.
|
|
302
|
+
* @example
|
|
303
|
+
* ```tsx
|
|
304
|
+
* import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
|
|
305
|
+
*
|
|
306
|
+
* export function Amount() {
|
|
307
|
+
* return (
|
|
308
|
+
* <Table>
|
|
309
|
+
* <TableBody>
|
|
310
|
+
* <TableRow>
|
|
311
|
+
* <TableCell>$250.00</TableCell>
|
|
312
|
+
* </TableRow>
|
|
313
|
+
* </TableBody>
|
|
314
|
+
* </Table>
|
|
315
|
+
* );
|
|
316
|
+
* }
|
|
317
|
+
* ```
|
|
318
|
+
* @since 1.0.0
|
|
319
|
+
*/
|
|
320
|
+
function TableCell(props: ComponentProps<"td">): JSX.Element {
|
|
321
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
322
|
+
return (
|
|
323
|
+
<td
|
|
324
|
+
data-slot="table-cell"
|
|
325
|
+
class={cn(
|
|
326
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
327
|
+
local.class,
|
|
328
|
+
)}
|
|
329
|
+
{...rest}
|
|
330
|
+
>
|
|
331
|
+
{local.children}
|
|
332
|
+
</td>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* The caption of a table. It is shown below the table and is the table's accessible name.
|
|
338
|
+
*
|
|
339
|
+
* Keyboard and ARIA: no keys. The caption names the table, so a table needs a caption or an
|
|
340
|
+
* `aria-label` for its name.
|
|
341
|
+
*
|
|
342
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
343
|
+
*
|
|
344
|
+
* @param props - The props: the attributes of the `caption`, and `class`.
|
|
345
|
+
* @returns The `caption` element.
|
|
346
|
+
* @example
|
|
347
|
+
* ```tsx
|
|
348
|
+
* import { Table, TableCaption } from "@/components/ui/table";
|
|
349
|
+
*
|
|
350
|
+
* export function Captioned() {
|
|
351
|
+
* return (
|
|
352
|
+
* <Table>
|
|
353
|
+
* <TableCaption>A list of your recent invoices.</TableCaption>
|
|
354
|
+
* </Table>
|
|
355
|
+
* );
|
|
356
|
+
* }
|
|
357
|
+
* ```
|
|
358
|
+
* @since 1.0.0
|
|
359
|
+
*/
|
|
360
|
+
function TableCaption(props: ComponentProps<"caption">): JSX.Element {
|
|
361
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
362
|
+
return (
|
|
363
|
+
<caption
|
|
364
|
+
data-slot="table-caption"
|
|
365
|
+
class={cn("mt-4 text-sm text-muted-foreground", local.class)}
|
|
366
|
+
{...rest}
|
|
367
|
+
>
|
|
368
|
+
{local.children}
|
|
369
|
+
</caption>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { splitProps } from "@shadow-js/core";
|
|
2
|
+
import {
|
|
3
|
+
type TabsContentProps,
|
|
4
|
+
type TabsListProps,
|
|
5
|
+
Tabs as TabsPrimitive,
|
|
6
|
+
type TabsProps,
|
|
7
|
+
type TabsTriggerProps,
|
|
8
|
+
} from "@shadow-js/primitives";
|
|
9
|
+
import type { JSX } from "@shadow-js/web";
|
|
10
|
+
import { cn } from "@/lib/utils";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A set of sections, one visible at a time, switched with tabs. Control the selected tab with
|
|
14
|
+
* `value` and `onValueChange`, or start it with `defaultValue`. `orientation` (`"horizontal"` or
|
|
15
|
+
* `"vertical"`), `dir` and `activationMode` are passed to the primitive.
|
|
16
|
+
*
|
|
17
|
+
* Keyboard and ARIA: the selected tab is the one tab stop of the list. Arrow keys move between
|
|
18
|
+
* enabled tabs (Up and Down when vertical, Left and Right when horizontal, swapped in a
|
|
19
|
+
* right-to-left layout), Home and End go to the first and last tabs, and the arrows wrap around.
|
|
20
|
+
* With `activationMode="automatic"` (the default) moving focus selects the tab; with `"manual"` only
|
|
21
|
+
* Enter, Space or a click selects it. A click always selects. The list is `role="tablist"` and each
|
|
22
|
+
* tab is `role="tab"` with `aria-selected`. The panel is `role="tabpanel"`, named by its tab, and
|
|
23
|
+
* focusable so its content can be reached. Name the list with a `TabsList` `aria-label`.
|
|
24
|
+
*
|
|
25
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
26
|
+
*
|
|
27
|
+
* @param props - The props, see {@link TabsProps}.
|
|
28
|
+
* @returns The root `div` with the list and the panels inside.
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
32
|
+
*
|
|
33
|
+
* export function SettingsTabs() {
|
|
34
|
+
* return (
|
|
35
|
+
* <Tabs defaultValue="account">
|
|
36
|
+
* <TabsList aria-label="Settings">
|
|
37
|
+
* <TabsTrigger value="account">Account</TabsTrigger>
|
|
38
|
+
* <TabsTrigger value="password">Password</TabsTrigger>
|
|
39
|
+
* </TabsList>
|
|
40
|
+
* <TabsContent value="account">Account settings</TabsContent>
|
|
41
|
+
* <TabsContent value="password">Password settings</TabsContent>
|
|
42
|
+
* </Tabs>
|
|
43
|
+
* );
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
* @since 1.0.0
|
|
47
|
+
* @see {@link TabsList}
|
|
48
|
+
*/
|
|
49
|
+
function Tabs(props: TabsProps): JSX.Element {
|
|
50
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
51
|
+
return (
|
|
52
|
+
<TabsPrimitive
|
|
53
|
+
data-slot="tabs"
|
|
54
|
+
class={cn("group/tabs flex flex-col gap-2 data-[orientation=vertical]:flex-row", local.class)}
|
|
55
|
+
{...rest}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The own props of {@link TabsList}, on top of the primitive's list props.
|
|
62
|
+
* @since 1.0.0
|
|
63
|
+
* @see {@link TabsList}
|
|
64
|
+
*/
|
|
65
|
+
export interface TabsListOwnProps {
|
|
66
|
+
/**
|
|
67
|
+
* The look of the list. `"default"` is a muted pill; `"line"` is a transparent list whose selected tab
|
|
68
|
+
* has an underline.
|
|
69
|
+
* @default "default"
|
|
70
|
+
*/
|
|
71
|
+
readonly variant?: "default" | "line" | undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The row (or column, when vertical) of tabs. It is a `role="tablist"` and owns the arrow-key
|
|
76
|
+
* movement between its tabs. Name it with `aria-label` or `aria-labelledby`.
|
|
77
|
+
*
|
|
78
|
+
* Keyboard and ARIA: see {@link Tabs}. The list sets `aria-orientation="vertical"` only when the
|
|
79
|
+
* tabs are stacked. Pass `loop={false}` to stop the arrows at the ends instead of wrapping.
|
|
80
|
+
*
|
|
81
|
+
* `variant` is `"default"` (a muted pill behind the tabs) or `"line"` (a transparent list, with an
|
|
82
|
+
* underline under the selected tab). It is styled by the list and the trigger together, so the line
|
|
83
|
+
* variant works through the triggers' own classes.
|
|
84
|
+
*
|
|
85
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
86
|
+
*
|
|
87
|
+
* @param props - The props: `loop`, `variant` and the attributes of the list `div`.
|
|
88
|
+
* @returns The `div` with the tabs inside.
|
|
89
|
+
* @default variant "default"
|
|
90
|
+
* @example
|
|
91
|
+
* ```tsx
|
|
92
|
+
* import { TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
93
|
+
*
|
|
94
|
+
* export function Sections() {
|
|
95
|
+
* return (
|
|
96
|
+
* <TabsList aria-label="Sections">
|
|
97
|
+
* <TabsTrigger value="overview">Overview</TabsTrigger>
|
|
98
|
+
* <TabsTrigger value="activity">Activity</TabsTrigger>
|
|
99
|
+
* </TabsList>
|
|
100
|
+
* );
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
* @since 1.0.0
|
|
104
|
+
*/
|
|
105
|
+
function TabsList(props: TabsListProps & TabsListOwnProps): JSX.Element {
|
|
106
|
+
const [local, rest] = splitProps(props, ["class", "variant"]);
|
|
107
|
+
const variant = (): "default" | "line" => local.variant ?? "default";
|
|
108
|
+
return (
|
|
109
|
+
<TabsPrimitive.List
|
|
110
|
+
data-slot="tabs-list"
|
|
111
|
+
data-variant={variant()}
|
|
112
|
+
class={cn(
|
|
113
|
+
"group/tabs-list inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground data-[orientation=vertical]:h-fit data-[orientation=vertical]:flex-col",
|
|
114
|
+
variant() === "line" ? "gap-1 bg-transparent" : "bg-muted",
|
|
115
|
+
local.class,
|
|
116
|
+
)}
|
|
117
|
+
{...rest}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* One tab of a {@link Tabs}: the button that selects the panel with the same `value`. A disabled
|
|
124
|
+
* tab cannot be selected and the arrow keys skip it.
|
|
125
|
+
*
|
|
126
|
+
* Keyboard and ARIA: a `button` with `role="tab"`, `aria-selected` (`"true"` or `"false"`),
|
|
127
|
+
* `aria-controls` pointing at its panel, and `data-state` (`"active"` or `"inactive"`). It is in the
|
|
128
|
+
* tab order only when selected. Enter and Space select it, and so does a click. Arrow keys and
|
|
129
|
+
* Home and End are handled by the list (see {@link Tabs}).
|
|
130
|
+
*
|
|
131
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
132
|
+
*
|
|
133
|
+
* @param props - The props: `value` (required), `disabled`, and the attributes of the `button`.
|
|
134
|
+
* @returns The tab `button`.
|
|
135
|
+
* @example
|
|
136
|
+
* ```tsx
|
|
137
|
+
* import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
138
|
+
*
|
|
139
|
+
* export function Billing() {
|
|
140
|
+
* return (
|
|
141
|
+
* <Tabs defaultValue="plans">
|
|
142
|
+
* <TabsList aria-label="Billing">
|
|
143
|
+
* <TabsTrigger value="plans">Plans</TabsTrigger>
|
|
144
|
+
* <TabsTrigger value="invoices" disabled>
|
|
145
|
+
* Invoices
|
|
146
|
+
* </TabsTrigger>
|
|
147
|
+
* </TabsList>
|
|
148
|
+
* <TabsContent value="plans">Choose a plan.</TabsContent>
|
|
149
|
+
* </Tabs>
|
|
150
|
+
* );
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
* @since 1.0.0
|
|
154
|
+
*/
|
|
155
|
+
function TabsTrigger(props: TabsTriggerProps): JSX.Element {
|
|
156
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
157
|
+
return (
|
|
158
|
+
<TabsPrimitive.Trigger
|
|
159
|
+
data-slot="tabs-trigger"
|
|
160
|
+
class={cn(
|
|
161
|
+
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground transition-[color,box-shadow] group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent after:absolute after:bg-foreground 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:-end-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring motion-reduce:transition-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:shadow-sm dark:text-muted-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground forced-colors:disabled:opacity-100 forced-colors:data-[state=active]:bg-[Highlight] forced-colors:data-[state=active]:text-[HighlightText] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 outline-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-transparent",
|
|
162
|
+
local.class,
|
|
163
|
+
)}
|
|
164
|
+
{...rest}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The panel of the tab with the same `value`. Only the selected panel is rendered; the others are
|
|
171
|
+
* removed until they are selected again.
|
|
172
|
+
*
|
|
173
|
+
* Keyboard and ARIA: a `div` with `role="tabpanel"`, named by its tab (`aria-labelledby`), with
|
|
174
|
+
* `tabindex="0"` so keyboard users can reach its content, and `data-state` (`"active"` or
|
|
175
|
+
* `"inactive"`). The component adds no key handling.
|
|
176
|
+
*
|
|
177
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
178
|
+
*
|
|
179
|
+
* @param props - The props: `value` (required), `forceMount`, and the attributes of the `div`.
|
|
180
|
+
* @returns The panel `div`.
|
|
181
|
+
* @example
|
|
182
|
+
* ```tsx
|
|
183
|
+
* import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
184
|
+
*
|
|
185
|
+
* export function Profile() {
|
|
186
|
+
* return (
|
|
187
|
+
* <Tabs defaultValue="general">
|
|
188
|
+
* <TabsList aria-label="Profile">
|
|
189
|
+
* <TabsTrigger value="general">General</TabsTrigger>
|
|
190
|
+
* </TabsList>
|
|
191
|
+
* <TabsContent value="general">Name and email.</TabsContent>
|
|
192
|
+
* </Tabs>
|
|
193
|
+
* );
|
|
194
|
+
* }
|
|
195
|
+
* ```
|
|
196
|
+
* @since 1.0.0
|
|
197
|
+
*/
|
|
198
|
+
function TabsContent(props: TabsContentProps): JSX.Element {
|
|
199
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
200
|
+
return (
|
|
201
|
+
<TabsPrimitive.Content
|
|
202
|
+
data-slot="tabs-content"
|
|
203
|
+
class={cn("flex-1 outline-none", local.class)}
|
|
204
|
+
{...rest}
|
|
205
|
+
/>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export { Tabs, TabsContent, TabsList, TabsTrigger };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { splitProps } from "@shadow-js/core";
|
|
2
|
+
import type { ComponentProps, JSX } from "@shadow-js/web";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A multi-line text field that grows with its content (`field-sizing: content`). It is a native
|
|
7
|
+
* `textarea`, so every `textarea` attribute passes through (`value`, `rows`, `disabled`, `name`,
|
|
8
|
+
* `onInput`, and so on). `aria-invalid="true"` turns the border and the focus ring destructive.
|
|
9
|
+
*
|
|
10
|
+
* Keyboard and ARIA: the browser's own keys apply. Tab moves focus in and out, Enter inserts a line
|
|
11
|
+
* break, and the arrow keys move the caret. It has the `textbox` role. Name it with a
|
|
12
|
+
* {@link Label} (`id` on the textarea, `htmlFor` on the label) or with `aria-label`. Set
|
|
13
|
+
* `aria-invalid="true"` and `aria-describedby` on the textarea to show an error.
|
|
14
|
+
*
|
|
15
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
16
|
+
*
|
|
17
|
+
* @param props - The props: the attributes of the native `textarea`. `class` is merged with the base
|
|
18
|
+
* classes.
|
|
19
|
+
* @returns The `textarea` element.
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* import { Label } from "@/components/ui/label";
|
|
23
|
+
* import { Textarea } from "@/components/ui/textarea";
|
|
24
|
+
*
|
|
25
|
+
* export function Feedback() {
|
|
26
|
+
* return (
|
|
27
|
+
* <div class="grid gap-2">
|
|
28
|
+
* <Label htmlFor="feedback">Feedback</Label>
|
|
29
|
+
* <Textarea id="feedback" placeholder="Type your message here." rows={3} />
|
|
30
|
+
* </div>
|
|
31
|
+
* );
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
* @since 1.0.0
|
|
35
|
+
* @see {@link Label}
|
|
36
|
+
*/
|
|
37
|
+
function Textarea(props: ComponentProps<"textarea">): JSX.Element {
|
|
38
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
39
|
+
return (
|
|
40
|
+
<textarea
|
|
41
|
+
data-slot="textarea"
|
|
42
|
+
class={cn(
|
|
43
|
+
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 motion-reduce:transition-none md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-transparent",
|
|
44
|
+
local.class,
|
|
45
|
+
)}
|
|
46
|
+
{...rest}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { Textarea };
|