@shadow-js/ui 0.0.0-stage → 1.0.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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +598 -2
- package/dist/cli.mjs +2293 -0
- package/dist/dev/cli/schema.d.ts +73 -0
- package/dist/dev/index.d.ts +6 -0
- package/dist/dev/index.js +5 -0
- package/dist/dev/locales/ar.d.ts +141 -0
- package/dist/dev/locales/ar.js +128 -0
- package/dist/dev/locales/ar.js.map +1 -0
- package/dist/dev/locales/en.d.ts +141 -0
- package/dist/dev/locales/en.js +128 -0
- package/dist/dev/locales/en.js.map +1 -0
- package/dist/dev/registry/utils/utils.d.ts +29 -0
- package/dist/dev/registry/utils/utils.js +27 -0
- package/dist/dev/registry/utils/utils.js.map +1 -0
- package/dist/dev/theme/theme-logic.d.ts +187 -0
- package/dist/dev/theme/theme-logic.js +184 -0
- package/dist/dev/theme/theme-logic.js.map +1 -0
- package/dist/dev/theme/theme-provider.d.ts +90 -0
- package/dist/dev/theme/theme-provider.js +150 -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,571 @@
|
|
|
1
|
+
import { splitProps } from "@shadow-js/core";
|
|
2
|
+
import {
|
|
3
|
+
type PolymorphicTarget,
|
|
4
|
+
type SelectContentProps as SelectContentPrimitiveProps,
|
|
5
|
+
type SelectGroupProps,
|
|
6
|
+
SelectItemAlignedContent,
|
|
7
|
+
type SelectItemProps as SelectItemPrimitiveProps,
|
|
8
|
+
type SelectLabelProps,
|
|
9
|
+
Select as SelectPrimitive,
|
|
10
|
+
type SelectProps,
|
|
11
|
+
type SelectScrollButtonProps,
|
|
12
|
+
type SelectSeparatorProps,
|
|
13
|
+
type SelectTriggerProps as SelectTriggerPrimitiveProps,
|
|
14
|
+
type SelectValueProps,
|
|
15
|
+
} from "@shadow-js/primitives";
|
|
16
|
+
import type { JSX } from "@shadow-js/web";
|
|
17
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "@/components/ui/icons";
|
|
18
|
+
import { type ClassValue, cn } from "@/lib/utils";
|
|
19
|
+
|
|
20
|
+
/** The props as the implementation sees them. */
|
|
21
|
+
type Loose = {
|
|
22
|
+
readonly class?: ClassValue;
|
|
23
|
+
readonly children?: JSX.Element;
|
|
24
|
+
readonly [prop: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A control that picks one value from a list shown in a popup, like a native `select` with a styled
|
|
29
|
+
* listbox. Put a {@link SelectTrigger} (with a {@link SelectValue}) and a {@link SelectContent} of
|
|
30
|
+
* {@link SelectItem}s inside it. Control it with `value` and `onValueChange`, and `open` and
|
|
31
|
+
* `onOpenChange`, or start it with `defaultValue` and `defaultOpen`. With a `name` it takes part in
|
|
32
|
+
* native forms. The value `""` means nothing is selected and shows the placeholder, so no item may
|
|
33
|
+
* use `""`.
|
|
34
|
+
*
|
|
35
|
+
* Keyboard and ARIA: the trigger is a combobox that opens the list with Enter, Space, ArrowDown or
|
|
36
|
+
* ArrowUp. Inside the list, the arrow keys, Home and End move between items, Enter and Space select
|
|
37
|
+
* the focused item, Escape closes the list without changing the value, and focus returns to the
|
|
38
|
+
* trigger. Name the trigger with a `Label` (`id` on the trigger, `htmlFor` on the label) or with
|
|
39
|
+
* `aria-label`. See {@link SelectTrigger} and {@link SelectContent}.
|
|
40
|
+
*
|
|
41
|
+
* shadcn/ui differences: `class` replaces `className`. `position` is `"popper"` (the default) or
|
|
42
|
+
* `"item-aligned"`, which puts the selected item over the trigger like a native select.
|
|
43
|
+
*
|
|
44
|
+
* @param props - The props, see {@link SelectProps}.
|
|
45
|
+
* @returns The select context around its parts. It renders no element of its own, except the hidden
|
|
46
|
+
* native `select` when `name` is set.
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* import {
|
|
50
|
+
* Select,
|
|
51
|
+
* SelectContent,
|
|
52
|
+
* SelectGroup,
|
|
53
|
+
* SelectItem,
|
|
54
|
+
* SelectLabel,
|
|
55
|
+
* SelectTrigger,
|
|
56
|
+
* SelectValue,
|
|
57
|
+
* } from "@/components/ui/select";
|
|
58
|
+
*
|
|
59
|
+
* export function FruitPicker() {
|
|
60
|
+
* return (
|
|
61
|
+
* <Select defaultValue="apple">
|
|
62
|
+
* <SelectTrigger class="w-[180px]" aria-label="Fruit">
|
|
63
|
+
* <SelectValue placeholder="Select a fruit" />
|
|
64
|
+
* </SelectTrigger>
|
|
65
|
+
* <SelectContent>
|
|
66
|
+
* <SelectGroup>
|
|
67
|
+
* <SelectLabel>Fruits</SelectLabel>
|
|
68
|
+
* <SelectItem value="apple">Apple</SelectItem>
|
|
69
|
+
* <SelectItem value="banana">Banana</SelectItem>
|
|
70
|
+
* </SelectGroup>
|
|
71
|
+
* </SelectContent>
|
|
72
|
+
* </Select>
|
|
73
|
+
* );
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
* @since 1.0.0
|
|
77
|
+
* @see {@link SelectTrigger}
|
|
78
|
+
*/
|
|
79
|
+
function Select(props: SelectProps): JSX.Element {
|
|
80
|
+
return <SelectPrimitive {...props} />;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A group of related items under a {@link SelectLabel}. It is a `role="group"` named by its label.
|
|
85
|
+
*
|
|
86
|
+
* Keyboard and ARIA: no keys of its own. The group is named by its {@link SelectLabel}.
|
|
87
|
+
*
|
|
88
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
89
|
+
*
|
|
90
|
+
* @param props - The props: the attributes of the group element.
|
|
91
|
+
* @returns The group element.
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel } from "@/components/ui/select";
|
|
95
|
+
*
|
|
96
|
+
* export function FruitList() {
|
|
97
|
+
* return (
|
|
98
|
+
* <Select>
|
|
99
|
+
* <SelectContent>
|
|
100
|
+
* <SelectGroup>
|
|
101
|
+
* <SelectLabel>Fruits</SelectLabel>
|
|
102
|
+
* <SelectItem value="apple">Apple</SelectItem>
|
|
103
|
+
* </SelectGroup>
|
|
104
|
+
* </SelectContent>
|
|
105
|
+
* </Select>
|
|
106
|
+
* );
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
* @since 1.0.0
|
|
110
|
+
* @see {@link SelectLabel}
|
|
111
|
+
*/
|
|
112
|
+
function SelectGroup<T extends PolymorphicTarget = "div">(props: SelectGroupProps<T>): JSX.Element {
|
|
113
|
+
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The text of the selected item, shown inside the trigger, or `placeholder` while nothing is
|
|
118
|
+
* selected. The text comes from the item itself, so it is known before the list is ever opened.
|
|
119
|
+
*
|
|
120
|
+
* Keyboard and ARIA: no keys. It is the trigger's visible value, so name the trigger, not this
|
|
121
|
+
* part. Server markup contains the selected text only when you pass it as `children`, which is then
|
|
122
|
+
* shown while a value is selected: `<SelectValue placeholder="Pick">{label}</SelectValue>`.
|
|
123
|
+
*
|
|
124
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
125
|
+
*
|
|
126
|
+
* @param props - The props: `placeholder`, `children`, and the attributes of the value element.
|
|
127
|
+
* @returns The selected text, or the placeholder.
|
|
128
|
+
* @example
|
|
129
|
+
* ```tsx
|
|
130
|
+
* import { Select, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
131
|
+
*
|
|
132
|
+
* export function FruitTrigger() {
|
|
133
|
+
* return (
|
|
134
|
+
* <Select>
|
|
135
|
+
* <SelectTrigger aria-label="Fruit">
|
|
136
|
+
* <SelectValue placeholder="Select a fruit" />
|
|
137
|
+
* </SelectTrigger>
|
|
138
|
+
* </Select>
|
|
139
|
+
* );
|
|
140
|
+
* }
|
|
141
|
+
* ```
|
|
142
|
+
* @since 1.0.0
|
|
143
|
+
* @see {@link SelectTrigger}
|
|
144
|
+
*/
|
|
145
|
+
function SelectValue<T extends PolymorphicTarget = "span">(
|
|
146
|
+
props: SelectValueProps<T>,
|
|
147
|
+
): JSX.Element {
|
|
148
|
+
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Own props of {@link SelectTrigger}.
|
|
153
|
+
* @since 1.0.0
|
|
154
|
+
* @see {@link SelectTrigger}
|
|
155
|
+
*/
|
|
156
|
+
export interface SelectTriggerExtraProps {
|
|
157
|
+
/**
|
|
158
|
+
* `default` (36px high) or `sm` (32px).
|
|
159
|
+
* @default "default"
|
|
160
|
+
*/
|
|
161
|
+
readonly size?: "sm" | "default" | undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Props of {@link SelectTrigger}: the attributes of the rendered `button` and `size`.
|
|
166
|
+
* @since 1.0.0
|
|
167
|
+
* @see {@link SelectTrigger}
|
|
168
|
+
*/
|
|
169
|
+
export type SelectTriggerProps<T extends PolymorphicTarget = "button"> =
|
|
170
|
+
SelectTriggerPrimitiveProps<T> & SelectTriggerExtraProps;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The button that opens the list. It shows the selected text and a chevron. `size` sets its height
|
|
174
|
+
* (`"default"` is 36px, `"sm"` is 32px), and `aria-invalid="true"` turns its border destructive.
|
|
175
|
+
*
|
|
176
|
+
* Keyboard and ARIA: a `button` with `role="combobox"`, `aria-haspopup="listbox"` and
|
|
177
|
+
* `aria-expanded`, and `aria-controls` while the list is open. Enter, Space, ArrowDown and ArrowUp
|
|
178
|
+
* open the list. While the list is closed, typing a letter changes the value to the next matching
|
|
179
|
+
* item without opening it. A mouse press opens the list, so you can press, drag onto an item and
|
|
180
|
+
* release; touch opens it on click. Name the trigger with a `Label` (`id` on the trigger, `htmlFor`
|
|
181
|
+
* on the label) or with `aria-label`.
|
|
182
|
+
*
|
|
183
|
+
* shadcn/ui differences: `class` replaces `className`. The `size` values are the same.
|
|
184
|
+
*
|
|
185
|
+
* @param props - The props: `size`, `class`, and the attributes of the trigger button.
|
|
186
|
+
* @returns The trigger button.
|
|
187
|
+
* @example
|
|
188
|
+
* ```tsx
|
|
189
|
+
* import { Select, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
190
|
+
*
|
|
191
|
+
* export function SmallFruit() {
|
|
192
|
+
* return (
|
|
193
|
+
* <Select>
|
|
194
|
+
* <SelectTrigger size="sm" id="fruit" aria-label="Fruit">
|
|
195
|
+
* <SelectValue placeholder="Fruit" />
|
|
196
|
+
* </SelectTrigger>
|
|
197
|
+
* </Select>
|
|
198
|
+
* );
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
* @since 1.0.0
|
|
202
|
+
* @see {@link SelectContent}
|
|
203
|
+
*/
|
|
204
|
+
function SelectTrigger<T extends PolymorphicTarget = "button">(
|
|
205
|
+
props: SelectTriggerProps<T>,
|
|
206
|
+
): JSX.Element {
|
|
207
|
+
const [local, rest] = splitProps(props as unknown as Loose & SelectTriggerExtraProps, [
|
|
208
|
+
"class",
|
|
209
|
+
"size",
|
|
210
|
+
"children",
|
|
211
|
+
]);
|
|
212
|
+
return (
|
|
213
|
+
<SelectPrimitive.Trigger
|
|
214
|
+
data-slot="select-trigger"
|
|
215
|
+
data-size={local.size ?? "default"}
|
|
216
|
+
class={cn(
|
|
217
|
+
"flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none 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 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-transparent",
|
|
218
|
+
local.class,
|
|
219
|
+
)}
|
|
220
|
+
{...rest}
|
|
221
|
+
>
|
|
222
|
+
{local.children}
|
|
223
|
+
<SelectPrimitive.Icon class="flex">
|
|
224
|
+
<ChevronDownIcon class="size-4 opacity-50" />
|
|
225
|
+
</SelectPrimitive.Icon>
|
|
226
|
+
</SelectPrimitive.Trigger>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Own props of {@link SelectContent}.
|
|
232
|
+
* @since 1.0.0
|
|
233
|
+
* @see {@link SelectContent}
|
|
234
|
+
*/
|
|
235
|
+
export interface SelectContentExtraProps {
|
|
236
|
+
/**
|
|
237
|
+
* Where the list sits. `"popper"` is below the trigger, flipping to stay in view. `"item-aligned"`
|
|
238
|
+
* puts the selected item (the first enabled one when nothing is selected) over the trigger's text, as
|
|
239
|
+
* Radix's item-aligned position and a native select do; `side`, `align` and `sideOffset` are then ignored.
|
|
240
|
+
* @default "popper"
|
|
241
|
+
*/
|
|
242
|
+
readonly position?: "popper" | "item-aligned" | undefined;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Props of {@link SelectContent}: the primitive's placement props (`side`, `align`, `sideOffset`,
|
|
247
|
+
* `collisionPadding`...), `position` and the attributes of the rendered `div`.
|
|
248
|
+
* @since 1.0.0
|
|
249
|
+
* @see {@link SelectContent}
|
|
250
|
+
*/
|
|
251
|
+
export type SelectContentProps<T extends PolymorphicTarget = "div"> =
|
|
252
|
+
SelectContentPrimitiveProps<T> & SelectContentExtraProps;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* The popup list of items. It is rendered into `document.body`, placed below the trigger by default
|
|
256
|
+
* (flipping to stay in view), at least as wide as the trigger, and it scrolls when the items do not
|
|
257
|
+
* fit, with scroll buttons at both ends. While open it is modal, like a native select.
|
|
258
|
+
*
|
|
259
|
+
* Keyboard and ARIA: the list is a `role="listbox"` with `aria-orientation="vertical"`, and it takes
|
|
260
|
+
* focus on open (the selected item, else the first enabled one). ArrowDown, ArrowUp, Home, End,
|
|
261
|
+
* PageDown and PageUp move focus, without wrapping. Enter and Space select the focused item, and
|
|
262
|
+
* typing jumps to the next item that starts with the typed text. Escape closes the list, and Tab
|
|
263
|
+
* closes it without selecting. Focus returns to the trigger on close.
|
|
264
|
+
*
|
|
265
|
+
* shadcn/ui differences: `class` replaces `className`. `position` is `"popper"` (the default here;
|
|
266
|
+
* Radix's default is item-aligned) or `"item-aligned"`.
|
|
267
|
+
*
|
|
268
|
+
* @param props - The props: `side`, `align`, `sideOffset`, `position`, `class`, and the attributes of
|
|
269
|
+
* the list element.
|
|
270
|
+
* @returns The list in a portal.
|
|
271
|
+
* @example
|
|
272
|
+
* ```tsx
|
|
273
|
+
* import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
274
|
+
*
|
|
275
|
+
* export function TopList() {
|
|
276
|
+
* return (
|
|
277
|
+
* <Select>
|
|
278
|
+
* <SelectTrigger aria-label="Fruit">
|
|
279
|
+
* <SelectValue placeholder="Pick" />
|
|
280
|
+
* </SelectTrigger>
|
|
281
|
+
* <SelectContent side="top">
|
|
282
|
+
* <SelectItem value="apple">Apple</SelectItem>
|
|
283
|
+
* </SelectContent>
|
|
284
|
+
* </Select>
|
|
285
|
+
* );
|
|
286
|
+
* }
|
|
287
|
+
* ```
|
|
288
|
+
* @since 1.0.0
|
|
289
|
+
* @see {@link SelectItem}
|
|
290
|
+
*/
|
|
291
|
+
function SelectContent<T extends PolymorphicTarget = "div">(
|
|
292
|
+
props: SelectContentProps<T>,
|
|
293
|
+
): JSX.Element {
|
|
294
|
+
const [local, rest] = splitProps(props as unknown as Loose & SelectContentExtraProps, [
|
|
295
|
+
"class",
|
|
296
|
+
"children",
|
|
297
|
+
"position",
|
|
298
|
+
]);
|
|
299
|
+
// The aligned content is its own export, so a select that never aligns does not carry its code.
|
|
300
|
+
const Part = (
|
|
301
|
+
local.position === "item-aligned" ? SelectItemAlignedContent : SelectPrimitive.Content
|
|
302
|
+
) as typeof SelectPrimitive.Content;
|
|
303
|
+
return (
|
|
304
|
+
<SelectPrimitive.Portal>
|
|
305
|
+
<Part
|
|
306
|
+
data-slot="select-content"
|
|
307
|
+
style={{ "min-width": "max(8rem, var(--primitives-anchor-width))" }}
|
|
308
|
+
class={cn(
|
|
309
|
+
"relative z-50 max-h-(--primitives-available-height) min-w-[8rem] origin-(--primitives-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
310
|
+
(local.position ?? "popper") === "popper" &&
|
|
311
|
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
312
|
+
local.class,
|
|
313
|
+
)}
|
|
314
|
+
{...rest}
|
|
315
|
+
>
|
|
316
|
+
<SelectScrollUpButton />
|
|
317
|
+
<SelectPrimitive.Viewport class="w-full min-w-(--primitives-anchor-width) scroll-my-1 p-1">
|
|
318
|
+
{local.children}
|
|
319
|
+
</SelectPrimitive.Viewport>
|
|
320
|
+
<SelectScrollDownButton />
|
|
321
|
+
</Part>
|
|
322
|
+
</SelectPrimitive.Portal>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* The visible heading of a {@link SelectGroup}. It is not an option: it cannot be focused or
|
|
328
|
+
* selected.
|
|
329
|
+
*
|
|
330
|
+
* Keyboard and ARIA: no keys. The label names its group.
|
|
331
|
+
*
|
|
332
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
333
|
+
*
|
|
334
|
+
* @param props - The props: the attributes of the label element, and `class`.
|
|
335
|
+
* @returns The label element.
|
|
336
|
+
* @example
|
|
337
|
+
* ```tsx
|
|
338
|
+
* import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel } from "@/components/ui/select";
|
|
339
|
+
*
|
|
340
|
+
* export function Labeled() {
|
|
341
|
+
* return (
|
|
342
|
+
* <Select>
|
|
343
|
+
* <SelectContent>
|
|
344
|
+
* <SelectGroup>
|
|
345
|
+
* <SelectLabel>Fruits</SelectLabel>
|
|
346
|
+
* <SelectItem value="apple">Apple</SelectItem>
|
|
347
|
+
* </SelectGroup>
|
|
348
|
+
* </SelectContent>
|
|
349
|
+
* </Select>
|
|
350
|
+
* );
|
|
351
|
+
* }
|
|
352
|
+
* ```
|
|
353
|
+
* @since 1.0.0
|
|
354
|
+
* @see {@link SelectGroup}
|
|
355
|
+
*/
|
|
356
|
+
function SelectLabel<T extends PolymorphicTarget = "div">(props: SelectLabelProps<T>): JSX.Element {
|
|
357
|
+
const [local, rest] = splitProps(props as unknown as Loose, ["class"]);
|
|
358
|
+
return (
|
|
359
|
+
<SelectPrimitive.Label
|
|
360
|
+
data-slot="select-label"
|
|
361
|
+
class={cn("px-2 py-1.5 text-xs text-muted-foreground", local.class)}
|
|
362
|
+
{...rest}
|
|
363
|
+
/>
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Props of {@link SelectItem}: the primitive's props (`value`, `disabled`, `textValue`) and the
|
|
369
|
+
* attributes of the rendered `div`.
|
|
370
|
+
* @since 1.0.0
|
|
371
|
+
* @see {@link SelectItem}
|
|
372
|
+
*/
|
|
373
|
+
export type SelectItemProps<T extends PolymorphicTarget = "div"> = SelectItemPrimitiveProps<T>;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* One option of a {@link SelectContent}. Its text names the option, is what the trigger shows once
|
|
377
|
+
* it is chosen, and is what typing matches. A check mark marks the selected option.
|
|
378
|
+
*
|
|
379
|
+
* Keyboard and ARIA: `role="option"` with `aria-selected`, named by its text, and `aria-disabled`
|
|
380
|
+
* while disabled. Enter and Space select it. A mouse selects on release, and touch selects on tap.
|
|
381
|
+
* Moving the pointer over an item focuses it. A `disabled` item cannot be picked and is skipped by
|
|
382
|
+
* the arrow keys.
|
|
383
|
+
*
|
|
384
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
385
|
+
*
|
|
386
|
+
* @param props - The props: `value` (required), `disabled`, `textValue`, and the attributes of the
|
|
387
|
+
* option element.
|
|
388
|
+
* @returns The option element.
|
|
389
|
+
* @example
|
|
390
|
+
* ```tsx
|
|
391
|
+
* import { Select, SelectContent, SelectItem } from "@/components/ui/select";
|
|
392
|
+
*
|
|
393
|
+
* export function Apple() {
|
|
394
|
+
* return (
|
|
395
|
+
* <Select>
|
|
396
|
+
* <SelectContent>
|
|
397
|
+
* <SelectItem value="apple">Apple</SelectItem>
|
|
398
|
+
* </SelectContent>
|
|
399
|
+
* </Select>
|
|
400
|
+
* );
|
|
401
|
+
* }
|
|
402
|
+
* ```
|
|
403
|
+
* @since 1.0.0
|
|
404
|
+
* @see {@link SelectContent}
|
|
405
|
+
*/
|
|
406
|
+
function SelectItem<T extends PolymorphicTarget = "div">(props: SelectItemProps<T>): JSX.Element {
|
|
407
|
+
const [local, rest] = splitProps(props as unknown as Loose & { readonly value: string }, [
|
|
408
|
+
"class",
|
|
409
|
+
"children",
|
|
410
|
+
"value",
|
|
411
|
+
]);
|
|
412
|
+
return (
|
|
413
|
+
<SelectPrimitive.Item
|
|
414
|
+
data-slot="select-item"
|
|
415
|
+
value={local.value}
|
|
416
|
+
class={cn(
|
|
417
|
+
"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 ps-2 pe-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 forced-colors:focus:bg-[Highlight] forced-colors:focus:text-[HighlightText] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
418
|
+
local.class,
|
|
419
|
+
)}
|
|
420
|
+
{...rest}
|
|
421
|
+
>
|
|
422
|
+
<span class="absolute end-2 flex size-3.5 items-center justify-center">
|
|
423
|
+
<SelectPrimitive.ItemIndicator>
|
|
424
|
+
<CheckIcon class="size-4" />
|
|
425
|
+
</SelectPrimitive.ItemIndicator>
|
|
426
|
+
</span>
|
|
427
|
+
<SelectPrimitive.ItemText>{local.children}</SelectPrimitive.ItemText>
|
|
428
|
+
</SelectPrimitive.Item>
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* A divider between items or groups.
|
|
434
|
+
*
|
|
435
|
+
* Keyboard and ARIA: no keys. The divider is hidden from assistive technology (`aria-hidden`).
|
|
436
|
+
*
|
|
437
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
438
|
+
*
|
|
439
|
+
* @param props - The props: the attributes of the divider element, and `class`.
|
|
440
|
+
* @returns The divider element.
|
|
441
|
+
* @example
|
|
442
|
+
* ```tsx
|
|
443
|
+
* import { Select, SelectContent, SelectItem, SelectSeparator } from "@/components/ui/select";
|
|
444
|
+
*
|
|
445
|
+
* export function Dividers() {
|
|
446
|
+
* return (
|
|
447
|
+
* <Select>
|
|
448
|
+
* <SelectContent>
|
|
449
|
+
* <SelectItem value="apple">Apple</SelectItem>
|
|
450
|
+
* <SelectSeparator />
|
|
451
|
+
* <SelectItem value="banana">Banana</SelectItem>
|
|
452
|
+
* </SelectContent>
|
|
453
|
+
* </Select>
|
|
454
|
+
* );
|
|
455
|
+
* }
|
|
456
|
+
* ```
|
|
457
|
+
* @since 1.0.0
|
|
458
|
+
*/
|
|
459
|
+
function SelectSeparator<T extends PolymorphicTarget = "div">(
|
|
460
|
+
props: SelectSeparatorProps<T>,
|
|
461
|
+
): JSX.Element {
|
|
462
|
+
const [local, rest] = splitProps(props as unknown as Loose, ["class"]);
|
|
463
|
+
return (
|
|
464
|
+
<SelectPrimitive.Separator
|
|
465
|
+
data-slot="select-separator"
|
|
466
|
+
class={cn("pointer-events-none -mx-1 my-1 h-px bg-border", local.class)}
|
|
467
|
+
{...rest}
|
|
468
|
+
/>
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* The button at the top of the list, shown while the list can scroll up. Hovering or pressing it
|
|
474
|
+
* scrolls the list.
|
|
475
|
+
*
|
|
476
|
+
* Keyboard and ARIA: no keys. The button is hidden from assistive technology (`aria-hidden`);
|
|
477
|
+
* keyboard users scroll with the arrow keys.
|
|
478
|
+
*
|
|
479
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
480
|
+
*
|
|
481
|
+
* @param props - The props: the attributes of the button element, and `class`.
|
|
482
|
+
* @returns The button, or nothing when the list cannot scroll up.
|
|
483
|
+
* @example
|
|
484
|
+
* ```tsx
|
|
485
|
+
* import { Select, SelectContent, SelectItem, SelectScrollUpButton } from "@/components/ui/select";
|
|
486
|
+
*
|
|
487
|
+
* export function LongList() {
|
|
488
|
+
* return (
|
|
489
|
+
* <Select>
|
|
490
|
+
* <SelectContent>
|
|
491
|
+
* <SelectScrollUpButton />
|
|
492
|
+
* <SelectItem value="apple">Apple</SelectItem>
|
|
493
|
+
* </SelectContent>
|
|
494
|
+
* </Select>
|
|
495
|
+
* );
|
|
496
|
+
* }
|
|
497
|
+
* ```
|
|
498
|
+
* @since 1.0.0
|
|
499
|
+
* @see {@link SelectScrollDownButton}
|
|
500
|
+
*/
|
|
501
|
+
function SelectScrollUpButton<T extends PolymorphicTarget = "div">(
|
|
502
|
+
props: SelectScrollButtonProps<T>,
|
|
503
|
+
): JSX.Element {
|
|
504
|
+
const [local, rest] = splitProps(props as unknown as Loose, ["class"]);
|
|
505
|
+
return (
|
|
506
|
+
<SelectPrimitive.ScrollUpButton
|
|
507
|
+
data-slot="select-scroll-up-button"
|
|
508
|
+
class={cn("flex cursor-default items-center justify-center py-1", local.class)}
|
|
509
|
+
{...rest}
|
|
510
|
+
>
|
|
511
|
+
<ChevronUpIcon class="size-4" />
|
|
512
|
+
</SelectPrimitive.ScrollUpButton>
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* The button at the bottom of the list, shown while the list can scroll down. Hovering or pressing
|
|
518
|
+
* it scrolls the list.
|
|
519
|
+
*
|
|
520
|
+
* Keyboard and ARIA: no keys. The button is hidden from assistive technology (`aria-hidden`);
|
|
521
|
+
* keyboard users scroll with the arrow keys.
|
|
522
|
+
*
|
|
523
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
524
|
+
*
|
|
525
|
+
* @param props - The props: the attributes of the button element, and `class`.
|
|
526
|
+
* @returns The button, or nothing when the list cannot scroll down.
|
|
527
|
+
* @example
|
|
528
|
+
* ```tsx
|
|
529
|
+
* import { Select, SelectContent, SelectItem, SelectScrollDownButton } from "@/components/ui/select";
|
|
530
|
+
*
|
|
531
|
+
* export function LongListBottom() {
|
|
532
|
+
* return (
|
|
533
|
+
* <Select>
|
|
534
|
+
* <SelectContent>
|
|
535
|
+
* <SelectItem value="apple">Apple</SelectItem>
|
|
536
|
+
* <SelectScrollDownButton />
|
|
537
|
+
* </SelectContent>
|
|
538
|
+
* </Select>
|
|
539
|
+
* );
|
|
540
|
+
* }
|
|
541
|
+
* ```
|
|
542
|
+
* @since 1.0.0
|
|
543
|
+
* @see {@link SelectScrollUpButton}
|
|
544
|
+
*/
|
|
545
|
+
function SelectScrollDownButton<T extends PolymorphicTarget = "div">(
|
|
546
|
+
props: SelectScrollButtonProps<T>,
|
|
547
|
+
): JSX.Element {
|
|
548
|
+
const [local, rest] = splitProps(props as unknown as Loose, ["class"]);
|
|
549
|
+
return (
|
|
550
|
+
<SelectPrimitive.ScrollDownButton
|
|
551
|
+
data-slot="select-scroll-down-button"
|
|
552
|
+
class={cn("flex cursor-default items-center justify-center py-1", local.class)}
|
|
553
|
+
{...rest}
|
|
554
|
+
>
|
|
555
|
+
<ChevronDownIcon class="size-4" />
|
|
556
|
+
</SelectPrimitive.ScrollDownButton>
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export {
|
|
561
|
+
Select,
|
|
562
|
+
SelectContent,
|
|
563
|
+
SelectGroup,
|
|
564
|
+
SelectItem,
|
|
565
|
+
SelectLabel,
|
|
566
|
+
SelectScrollDownButton,
|
|
567
|
+
SelectScrollUpButton,
|
|
568
|
+
SelectSeparator,
|
|
569
|
+
SelectTrigger,
|
|
570
|
+
SelectValue,
|
|
571
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { splitProps } from "@shadow-js/core";
|
|
2
|
+
import { Separator as SeparatorPrimitive, type SeparatorProps } from "@shadow-js/primitives";
|
|
3
|
+
import type { JSX } from "@shadow-js/web";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A thin line that separates content, horizontal (the default) or vertical. It is decorative unless
|
|
8
|
+
* you say otherwise: a decorative separator is removed from the accessibility tree (`role="none"`).
|
|
9
|
+
* Pass `decorative={false}` to expose it as `role="separator"`, and `aria-orientation="vertical"` is
|
|
10
|
+
* then set when it is vertical.
|
|
11
|
+
*
|
|
12
|
+
* Keyboard and ARIA: the separator is not focusable and has no keys. Decorative: `role="none"`, and
|
|
13
|
+
* no `aria-orientation`. Semantic (`decorative={false}`): `role="separator"`, plus
|
|
14
|
+
* `aria-orientation="vertical"` only when vertical, because horizontal is the default of the role.
|
|
15
|
+
* Both carry `data-orientation` (`"horizontal"` or `"vertical"`).
|
|
16
|
+
*
|
|
17
|
+
* shadcn/ui differences: `class` replaces `className`.
|
|
18
|
+
*
|
|
19
|
+
* @param props - The props: `orientation`, `decorative`, and the attributes of the `div`.
|
|
20
|
+
* @returns The separator element.
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* import { Separator } from "@/components/ui/separator";
|
|
24
|
+
*
|
|
25
|
+
* export function Toolbar() {
|
|
26
|
+
* return (
|
|
27
|
+
* <div class="flex h-5 items-center gap-4">
|
|
28
|
+
* <span>Home</span>
|
|
29
|
+
* <Separator orientation="vertical" />
|
|
30
|
+
* <span>Docs</span>
|
|
31
|
+
* </div>
|
|
32
|
+
* );
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
* @since 1.0.0
|
|
36
|
+
*/
|
|
37
|
+
function Separator(props: SeparatorProps): JSX.Element {
|
|
38
|
+
const [local, rest] = splitProps(props, ["class", "decorative"]);
|
|
39
|
+
return (
|
|
40
|
+
<SeparatorPrimitive
|
|
41
|
+
data-slot="separator"
|
|
42
|
+
decorative={local.decorative ?? true}
|
|
43
|
+
class={cn(
|
|
44
|
+
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px forced-colors:bg-[CanvasText]",
|
|
45
|
+
local.class,
|
|
46
|
+
)}
|
|
47
|
+
{...rest}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { Separator };
|