@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,569 @@
|
|
|
1
|
+
import { splitProps } from "@shadow-js/core";
|
|
2
|
+
import {
|
|
3
|
+
type PolymorphicTarget,
|
|
4
|
+
type QuestionnaireChoiceInputProps,
|
|
5
|
+
type QuestionnaireChoiceProps,
|
|
6
|
+
type QuestionnaireChoicesProps,
|
|
7
|
+
type QuestionnaireDescriptionProps,
|
|
8
|
+
type QuestionnaireErrorProps,
|
|
9
|
+
type QuestionnaireInputProps,
|
|
10
|
+
type QuestionnaireItemProps,
|
|
11
|
+
type QuestionnaireNextProps,
|
|
12
|
+
type QuestionnaireOwnProps,
|
|
13
|
+
type QuestionnairePreviousProps,
|
|
14
|
+
Questionnaire as QuestionnairePrimitive,
|
|
15
|
+
type QuestionnaireProgressProps,
|
|
16
|
+
type QuestionnaireSkipProps,
|
|
17
|
+
type QuestionnaireSubmitProps,
|
|
18
|
+
type QuestionnaireTitleProps,
|
|
19
|
+
} from "@shadow-js/primitives";
|
|
20
|
+
import type { ComponentProps, JSX } from "@shadow-js/web";
|
|
21
|
+
import { Button } from "@/components/ui/button";
|
|
22
|
+
import { useUiStrings } from "@/lib/ui-strings";
|
|
23
|
+
import { cn } from "@/lib/utils";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A multi-step form: one question shows at a time, with progress, checks and the keys to answer quickly. It handles the showing question, the answers' status, validation, progress
|
|
27
|
+
* and the move from one question to the next. The card, dialog or page around it handles closing, saving and sending. Answers are native form values: read them with `FormData` in `onSubmit`.
|
|
28
|
+
* Its words ("Question 2 of 5", the errors, the buttons) come from the kit's strings.
|
|
29
|
+
*
|
|
30
|
+
* Keyboard and ARIA: Tab moves between answers and buttons; the arrow keys move between answers and, outside a field, between questions; Enter continues; Cmd or Ctrl with Enter submits from the last
|
|
31
|
+
* question; with `shortcuts` a letter or number picks an answer. Each question is a `fieldset` with a `legend`, and the progress is a named `progressbar`. See the primitive for the full list.
|
|
32
|
+
*
|
|
33
|
+
* Compared with shadcn/ui: `as` replaces `render`, `class` replaces `className`, and every part has a `data-slot`.
|
|
34
|
+
*
|
|
35
|
+
* @param props - The questionnaire props: `item`, `defaultItem`, `onItemChange`, `shortcuts`, `onSubmit` and the `form` attributes.
|
|
36
|
+
* @returns The form.
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* import {
|
|
40
|
+
* Questionnaire,
|
|
41
|
+
* QuestionnaireActions,
|
|
42
|
+
* QuestionnaireChoice,
|
|
43
|
+
* QuestionnaireChoices,
|
|
44
|
+
* QuestionnaireError,
|
|
45
|
+
* QuestionnaireItem,
|
|
46
|
+
* QuestionnaireNext,
|
|
47
|
+
* QuestionnaireProgress,
|
|
48
|
+
* QuestionnaireSubmit,
|
|
49
|
+
* QuestionnaireTitle,
|
|
50
|
+
* } from "@/components/ui/questionnaire";
|
|
51
|
+
*
|
|
52
|
+
* export function Survey() {
|
|
53
|
+
* return (
|
|
54
|
+
* <Questionnaire onSubmit={(event) => console.log(new FormData(event.currentTarget as HTMLFormElement))}>
|
|
55
|
+
* <QuestionnaireProgress />
|
|
56
|
+
* <QuestionnaireItem name="color" required>
|
|
57
|
+
* <QuestionnaireTitle>Pick a color</QuestionnaireTitle>
|
|
58
|
+
* <QuestionnaireChoices>
|
|
59
|
+
* <QuestionnaireChoice value="red">Red</QuestionnaireChoice>
|
|
60
|
+
* <QuestionnaireChoice value="blue">Blue</QuestionnaireChoice>
|
|
61
|
+
* </QuestionnaireChoices>
|
|
62
|
+
* <QuestionnaireError />
|
|
63
|
+
* </QuestionnaireItem>
|
|
64
|
+
* <QuestionnaireActions>
|
|
65
|
+
* <QuestionnaireNext />
|
|
66
|
+
* <QuestionnaireSubmit />
|
|
67
|
+
* </QuestionnaireActions>
|
|
68
|
+
* </Questionnaire>
|
|
69
|
+
* );
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
* @since 1.0.0
|
|
73
|
+
* @see {@link QuestionnaireItem}
|
|
74
|
+
* @see {@link QuestionnaireActions}
|
|
75
|
+
*/
|
|
76
|
+
function Questionnaire(
|
|
77
|
+
props: QuestionnaireOwnProps & Omit<ComponentProps<"form">, keyof QuestionnaireOwnProps>,
|
|
78
|
+
): JSX.Element {
|
|
79
|
+
const [local, rest] = splitProps(
|
|
80
|
+
props as unknown as {
|
|
81
|
+
readonly class?: string;
|
|
82
|
+
readonly messages?: QuestionnaireOwnProps["messages"];
|
|
83
|
+
},
|
|
84
|
+
["class", "messages"],
|
|
85
|
+
);
|
|
86
|
+
const t = useUiStrings();
|
|
87
|
+
return (
|
|
88
|
+
<QuestionnairePrimitive
|
|
89
|
+
data-slot="questionnaire"
|
|
90
|
+
messages={{
|
|
91
|
+
progress: (current, total) =>
|
|
92
|
+
t("questionnaire.progress", "Question {current} of {total}", { current, total }),
|
|
93
|
+
progressLabel: t("questionnaire.progressLabel", "Questionnaire progress"),
|
|
94
|
+
required: t("questionnaire.required", "Answer this question to continue."),
|
|
95
|
+
optional: t("questionnaire.optional", "Answer this question, or skip it, to continue."),
|
|
96
|
+
...local.messages,
|
|
97
|
+
}}
|
|
98
|
+
class={cn("flex w-full min-w-0 flex-col gap-5", local.class)}
|
|
99
|
+
{...(rest as object)}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Shows "Question 2 of 5" and a bar that fills as the reader goes. It is a named progress bar, so a screen reader hears the same text. With children you draw your own indicator;
|
|
106
|
+
* the numbers are on the element as `data-current` and `data-total`.
|
|
107
|
+
*
|
|
108
|
+
* Keyboard and ARIA: not focusable; `role="progressbar"` with `aria-valuenow`, `aria-valuemax` and `aria-valuetext`.
|
|
109
|
+
*
|
|
110
|
+
* @param props - The progress props.
|
|
111
|
+
* @returns The progress element.
|
|
112
|
+
* @example
|
|
113
|
+
* ```tsx
|
|
114
|
+
* import { QuestionnaireProgress } from "@/components/ui/questionnaire";
|
|
115
|
+
*
|
|
116
|
+
* export const Progress = <QuestionnaireProgress />;
|
|
117
|
+
* ```
|
|
118
|
+
* @since 1.0.0
|
|
119
|
+
* @see {@link Questionnaire}
|
|
120
|
+
*/
|
|
121
|
+
function QuestionnaireProgress<T extends PolymorphicTarget = "div">(
|
|
122
|
+
props: QuestionnaireProgressProps<T>,
|
|
123
|
+
): JSX.Element {
|
|
124
|
+
const [local, rest] = splitProps(props as unknown as { readonly class?: string }, ["class"]);
|
|
125
|
+
return (
|
|
126
|
+
<QuestionnairePrimitive.Progress
|
|
127
|
+
data-slot="questionnaire-progress"
|
|
128
|
+
class={cn("text-sm font-medium text-muted-foreground tabular-nums", local.class)}
|
|
129
|
+
{...(rest as object)}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* One question. Only the showing question is visible; the others are hidden and inert. `required` removes Skip for it, `multiple` makes the choices checkboxes, `disabled` takes it out of
|
|
136
|
+
* the questionnaire, and `invalid` shows its error from your own validator. `onStatusChange` reports `"unanswered"`, `"answered"` and `"skipped"`.
|
|
137
|
+
*
|
|
138
|
+
* Keyboard and ARIA: a native `fieldset` named by its title and described by its description and, while invalid, its error; `aria-invalid` while invalid.
|
|
139
|
+
*
|
|
140
|
+
* @param props - The item props: `name`, `required`, `multiple`, `disabled`, `invalid`, `onStatusChange` and the `fieldset` attributes.
|
|
141
|
+
* @returns The fieldset.
|
|
142
|
+
* @example
|
|
143
|
+
* ```tsx
|
|
144
|
+
* import { QuestionnaireItem, QuestionnaireTitle } from "@/components/ui/questionnaire";
|
|
145
|
+
*
|
|
146
|
+
* export const Question = (
|
|
147
|
+
* <QuestionnaireItem name="size" required>
|
|
148
|
+
* <QuestionnaireTitle>Pick a size</QuestionnaireTitle>
|
|
149
|
+
* </QuestionnaireItem>
|
|
150
|
+
* );
|
|
151
|
+
* ```
|
|
152
|
+
* @since 1.0.0
|
|
153
|
+
* @see {@link Questionnaire}
|
|
154
|
+
*/
|
|
155
|
+
function QuestionnaireItem<T extends PolymorphicTarget = "fieldset">(
|
|
156
|
+
props: QuestionnaireItemProps<T>,
|
|
157
|
+
): JSX.Element {
|
|
158
|
+
const [local, rest] = splitProps(props as unknown as { readonly class?: string }, ["class"]);
|
|
159
|
+
return (
|
|
160
|
+
<QuestionnairePrimitive.Item
|
|
161
|
+
data-slot="questionnaire-item"
|
|
162
|
+
class={cn("m-0 flex min-w-0 flex-col gap-3 border-0 p-0 outline-none", local.class)}
|
|
163
|
+
{...(rest as QuestionnaireItemProps<"fieldset">)}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The question, as the legend of the item's fieldset. Write it as a complete sentence: it is the name of the group of answers for assistive technology.
|
|
170
|
+
*
|
|
171
|
+
* Keyboard and ARIA: no keys; it names the item.
|
|
172
|
+
*
|
|
173
|
+
* @param props - The legend attributes and `class`.
|
|
174
|
+
* @returns The title.
|
|
175
|
+
* @example
|
|
176
|
+
* ```tsx
|
|
177
|
+
* import { QuestionnaireTitle } from "@/components/ui/questionnaire";
|
|
178
|
+
*
|
|
179
|
+
* export const Title = <QuestionnaireTitle>What should we build next?</QuestionnaireTitle>;
|
|
180
|
+
* ```
|
|
181
|
+
* @since 1.0.0
|
|
182
|
+
* @see {@link QuestionnaireItem}
|
|
183
|
+
*/
|
|
184
|
+
function QuestionnaireTitle<T extends PolymorphicTarget = "legend">(
|
|
185
|
+
props: QuestionnaireTitleProps<T>,
|
|
186
|
+
): JSX.Element {
|
|
187
|
+
const [local, rest] = splitProps(props as unknown as { readonly class?: string }, ["class"]);
|
|
188
|
+
return (
|
|
189
|
+
<QuestionnairePrimitive.Title
|
|
190
|
+
data-slot="questionnaire-title"
|
|
191
|
+
class={cn("text-base font-semibold text-balance", local.class)}
|
|
192
|
+
{...(rest as object)}
|
|
193
|
+
/>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Helper text under the question, read with it by a screen reader.
|
|
199
|
+
*
|
|
200
|
+
* Keyboard and ARIA: no keys; linked to the item with `aria-describedby`.
|
|
201
|
+
*
|
|
202
|
+
* @param props - The paragraph attributes and `class`.
|
|
203
|
+
* @returns The description.
|
|
204
|
+
* @example
|
|
205
|
+
* ```tsx
|
|
206
|
+
* import { QuestionnaireDescription } from "@/components/ui/questionnaire";
|
|
207
|
+
*
|
|
208
|
+
* export const Help = <QuestionnaireDescription>Choose one.</QuestionnaireDescription>;
|
|
209
|
+
* ```
|
|
210
|
+
* @since 1.0.0
|
|
211
|
+
* @see {@link QuestionnaireItem}
|
|
212
|
+
*/
|
|
213
|
+
function QuestionnaireDescription<T extends PolymorphicTarget = "p">(
|
|
214
|
+
props: QuestionnaireDescriptionProps<T>,
|
|
215
|
+
): JSX.Element {
|
|
216
|
+
const [local, rest] = splitProps(props as unknown as { readonly class?: string }, ["class"]);
|
|
217
|
+
return (
|
|
218
|
+
<QuestionnairePrimitive.Description
|
|
219
|
+
data-slot="questionnaire-description"
|
|
220
|
+
class={cn("-mt-1 text-sm text-muted-foreground", local.class)}
|
|
221
|
+
{...(rest as object)}
|
|
222
|
+
/>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The answers of a question: the fixed choices and an optional free-text input, stacked.
|
|
228
|
+
*
|
|
229
|
+
* Keyboard and ARIA: no role and no keys.
|
|
230
|
+
*
|
|
231
|
+
* @param props - The `div` attributes and `class`.
|
|
232
|
+
* @returns The container.
|
|
233
|
+
* @example
|
|
234
|
+
* ```tsx
|
|
235
|
+
* import { QuestionnaireChoices } from "@/components/ui/questionnaire";
|
|
236
|
+
*
|
|
237
|
+
* export const Answers = <QuestionnaireChoices />;
|
|
238
|
+
* ```
|
|
239
|
+
* @since 1.0.0
|
|
240
|
+
* @see {@link QuestionnaireChoice}
|
|
241
|
+
*/
|
|
242
|
+
function QuestionnaireChoices<T extends PolymorphicTarget = "div">(
|
|
243
|
+
props: QuestionnaireChoicesProps<T>,
|
|
244
|
+
): JSX.Element {
|
|
245
|
+
const [local, rest] = splitProps(props as unknown as { readonly class?: string }, ["class"]);
|
|
246
|
+
return (
|
|
247
|
+
<QuestionnairePrimitive.Choices
|
|
248
|
+
data-slot="questionnaire-choices"
|
|
249
|
+
class={cn("flex flex-col gap-2", local.class)}
|
|
250
|
+
{...(rest as object)}
|
|
251
|
+
/>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* One fixed answer: a bordered row with a native radio (or checkbox, in a `multiple` question), its text, and the key that picks it when the questionnaire has shortcuts.
|
|
257
|
+
* Plain children are the text; put several pieces in it for a title and a description.
|
|
258
|
+
*
|
|
259
|
+
* Keyboard and ARIA: native radio and checkbox behavior; Space picks it, and the whole row is the click target. The input has `aria-keyshortcuts` and `aria-invalid`.
|
|
260
|
+
*
|
|
261
|
+
* @param props - The choice props: `value`, `checked`, `defaultChecked`, `disabled`, `onChange` and the `label` attributes.
|
|
262
|
+
* @returns The choice.
|
|
263
|
+
* @example
|
|
264
|
+
* ```tsx
|
|
265
|
+
* import { QuestionnaireChoice } from "@/components/ui/questionnaire";
|
|
266
|
+
*
|
|
267
|
+
* export const Yes = <QuestionnaireChoice value="yes">Yes</QuestionnaireChoice>;
|
|
268
|
+
* ```
|
|
269
|
+
* @since 1.0.0
|
|
270
|
+
* @see {@link QuestionnaireChoices}
|
|
271
|
+
*/
|
|
272
|
+
function QuestionnaireChoice<T extends PolymorphicTarget = "label">(
|
|
273
|
+
props: QuestionnaireChoiceProps<T>,
|
|
274
|
+
): JSX.Element {
|
|
275
|
+
const [local, rest] = splitProps(
|
|
276
|
+
props as unknown as { readonly class?: string; readonly children?: JSX.Element },
|
|
277
|
+
["class", "children"],
|
|
278
|
+
);
|
|
279
|
+
return (
|
|
280
|
+
<QuestionnairePrimitive.Choice
|
|
281
|
+
data-slot="questionnaire-choice"
|
|
282
|
+
class={cn(
|
|
283
|
+
"flex cursor-pointer items-start gap-3 rounded-lg border p-3 text-sm transition-colors hover:bg-accent/50 has-[:checked]:border-primary has-[:checked]:bg-accent has-[:focus-visible]:ring-[3px] has-[:focus-visible]:ring-ring/50 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[invalid]:border-destructive motion-reduce:transition-none has-[:focus-visible]:outline-solid has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-transparent",
|
|
284
|
+
local.class,
|
|
285
|
+
)}
|
|
286
|
+
{...(rest as QuestionnaireChoiceProps<"label">)}
|
|
287
|
+
>
|
|
288
|
+
<QuestionnairePrimitive.ChoiceInput
|
|
289
|
+
data-slot="questionnaire-choice-input"
|
|
290
|
+
class="mt-0.5 size-4 shrink-0 accent-primary outline-none"
|
|
291
|
+
/>
|
|
292
|
+
<QuestionnairePrimitive.ChoiceLabel
|
|
293
|
+
data-slot="questionnaire-choice-label"
|
|
294
|
+
class="flex min-w-0 flex-1 flex-col gap-0.5"
|
|
295
|
+
>
|
|
296
|
+
{local.children}
|
|
297
|
+
</QuestionnairePrimitive.ChoiceLabel>
|
|
298
|
+
<QuestionnairePrimitive.ChoiceShortcut
|
|
299
|
+
data-slot="questionnaire-choice-shortcut"
|
|
300
|
+
class="ms-auto inline-flex size-5 shrink-0 items-center justify-center rounded border bg-muted font-mono text-xs text-muted-foreground"
|
|
301
|
+
/>
|
|
302
|
+
</QuestionnairePrimitive.Choice>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* A free-text answer beside the fixed choices, in the kit's input style. It is submitted under the item's name. Give it an accessible name (`aria-label`, or a visible label):
|
|
308
|
+
* a placeholder is not a name.
|
|
309
|
+
*
|
|
310
|
+
* Keyboard and ARIA: a native text field; shortcuts and arrow navigation pause while it has focus.
|
|
311
|
+
*
|
|
312
|
+
* @param props - The input props: `type` and the `input` attributes.
|
|
313
|
+
* @returns The input.
|
|
314
|
+
* @example
|
|
315
|
+
* ```tsx
|
|
316
|
+
* import { QuestionnaireInput } from "@/components/ui/questionnaire";
|
|
317
|
+
*
|
|
318
|
+
* export const Other = <QuestionnaireInput aria-label="Another answer" placeholder="Type another answer" />;
|
|
319
|
+
* ```
|
|
320
|
+
* @since 1.0.0
|
|
321
|
+
* @see {@link QuestionnaireChoices}
|
|
322
|
+
*/
|
|
323
|
+
function QuestionnaireInput<T extends PolymorphicTarget = "input">(
|
|
324
|
+
props: QuestionnaireInputProps<T>,
|
|
325
|
+
): JSX.Element {
|
|
326
|
+
const [local, rest] = splitProps(props as unknown as { readonly class?: string }, ["class"]);
|
|
327
|
+
return (
|
|
328
|
+
<QuestionnairePrimitive.Input
|
|
329
|
+
data-slot="questionnaire-input"
|
|
330
|
+
class={cn(
|
|
331
|
+
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs outline-none transition-[color,box-shadow] placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 data-[invalid]:border-destructive data-[invalid]:ring-destructive/20 md:text-sm dark:bg-input/30 motion-reduce:transition-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-transparent",
|
|
332
|
+
local.class,
|
|
333
|
+
)}
|
|
334
|
+
{...(rest as object)}
|
|
335
|
+
/>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* The message shown when the question is not answered. It is hidden until then, announced when it appears, and linked to the question.
|
|
341
|
+
*
|
|
342
|
+
* Keyboard and ARIA: `role="alert"`; no keys.
|
|
343
|
+
*
|
|
344
|
+
* @param props - The paragraph attributes and `class`.
|
|
345
|
+
* @returns The message, or nothing while valid.
|
|
346
|
+
* @example
|
|
347
|
+
* ```tsx
|
|
348
|
+
* import { QuestionnaireError } from "@/components/ui/questionnaire";
|
|
349
|
+
*
|
|
350
|
+
* export const Problem = <QuestionnaireError />;
|
|
351
|
+
* ```
|
|
352
|
+
* @since 1.0.0
|
|
353
|
+
* @see {@link QuestionnaireItem}
|
|
354
|
+
*/
|
|
355
|
+
function QuestionnaireError<T extends PolymorphicTarget = "p">(
|
|
356
|
+
props: QuestionnaireErrorProps<T>,
|
|
357
|
+
): JSX.Element {
|
|
358
|
+
const [local, rest] = splitProps(props as unknown as { readonly class?: string }, ["class"]);
|
|
359
|
+
return (
|
|
360
|
+
<QuestionnairePrimitive.Error
|
|
361
|
+
data-slot="questionnaire-error"
|
|
362
|
+
class={cn("text-sm text-destructive", local.class)}
|
|
363
|
+
{...(rest as object)}
|
|
364
|
+
/>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* A row for the buttons: Back and Skip at the start, Next or Submit at the end.
|
|
370
|
+
*
|
|
371
|
+
* Keyboard and ARIA: a plain `div`; the buttons inside are the focus stops.
|
|
372
|
+
*
|
|
373
|
+
* @param props - The `div` attributes and `class`.
|
|
374
|
+
* @returns The row.
|
|
375
|
+
* @example
|
|
376
|
+
* ```tsx
|
|
377
|
+
* import { QuestionnaireActions, QuestionnaireNext } from "@/components/ui/questionnaire";
|
|
378
|
+
*
|
|
379
|
+
* export const Row = (
|
|
380
|
+
* <QuestionnaireActions>
|
|
381
|
+
* <QuestionnaireNext />
|
|
382
|
+
* </QuestionnaireActions>
|
|
383
|
+
* );
|
|
384
|
+
* ```
|
|
385
|
+
* @since 1.0.0
|
|
386
|
+
* @see {@link QuestionnairePrevious}
|
|
387
|
+
*/
|
|
388
|
+
function QuestionnaireActions(props: ComponentProps<"div">): JSX.Element {
|
|
389
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
390
|
+
return (
|
|
391
|
+
<div
|
|
392
|
+
data-slot="questionnaire-actions"
|
|
393
|
+
class={cn("flex items-center gap-2", local.class)}
|
|
394
|
+
{...rest}
|
|
395
|
+
/>
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Goes back one question, without checking the answer. Shown from the second question on. With no children it reads "Back".
|
|
401
|
+
*
|
|
402
|
+
* Keyboard and ARIA: a native button; the left arrow (right in right-to-left text) does the same from outside a field.
|
|
403
|
+
*
|
|
404
|
+
* @param props - The button props (`variant` and `size` of {@link Button} too).
|
|
405
|
+
* @returns The button.
|
|
406
|
+
* @example
|
|
407
|
+
* ```tsx
|
|
408
|
+
* import { QuestionnairePrevious } from "@/components/ui/questionnaire";
|
|
409
|
+
*
|
|
410
|
+
* export const Back = <QuestionnairePrevious />;
|
|
411
|
+
* ```
|
|
412
|
+
* @since 1.0.0
|
|
413
|
+
* @see {@link QuestionnaireActions}
|
|
414
|
+
*/
|
|
415
|
+
function QuestionnairePrevious<T extends PolymorphicTarget = "button">(
|
|
416
|
+
props: QuestionnairePreviousProps<T> & { readonly variant?: string },
|
|
417
|
+
): JSX.Element {
|
|
418
|
+
const t = useUiStrings();
|
|
419
|
+
const [local, rest] = splitProps(
|
|
420
|
+
props as unknown as {
|
|
421
|
+
readonly class?: string;
|
|
422
|
+
readonly children?: JSX.Element;
|
|
423
|
+
readonly variant?: "outline";
|
|
424
|
+
},
|
|
425
|
+
["class", "children", "variant"],
|
|
426
|
+
);
|
|
427
|
+
return (
|
|
428
|
+
<QuestionnairePrimitive.Previous
|
|
429
|
+
as={Button}
|
|
430
|
+
data-slot="questionnaire-previous"
|
|
431
|
+
variant={local.variant ?? "outline"}
|
|
432
|
+
class={local.class}
|
|
433
|
+
{...(rest as object)}
|
|
434
|
+
>
|
|
435
|
+
{local.children ?? t("questionnaire.previous", "Back")}
|
|
436
|
+
</QuestionnairePrimitive.Previous>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Skips an optional question on purpose. Shown only while the question is optional. With no children it reads "Skip".
|
|
442
|
+
*
|
|
443
|
+
* Keyboard and ARIA: a native button.
|
|
444
|
+
*
|
|
445
|
+
* @param props - The button props.
|
|
446
|
+
* @returns The button.
|
|
447
|
+
* @example
|
|
448
|
+
* ```tsx
|
|
449
|
+
* import { QuestionnaireSkip } from "@/components/ui/questionnaire";
|
|
450
|
+
*
|
|
451
|
+
* export const Pass = <QuestionnaireSkip />;
|
|
452
|
+
* ```
|
|
453
|
+
* @since 1.0.0
|
|
454
|
+
* @see {@link QuestionnaireActions}
|
|
455
|
+
*/
|
|
456
|
+
function QuestionnaireSkip<T extends PolymorphicTarget = "button">(
|
|
457
|
+
props: QuestionnaireSkipProps<T> & { readonly variant?: string },
|
|
458
|
+
): JSX.Element {
|
|
459
|
+
const t = useUiStrings();
|
|
460
|
+
const [local, rest] = splitProps(
|
|
461
|
+
props as unknown as {
|
|
462
|
+
readonly class?: string;
|
|
463
|
+
readonly children?: JSX.Element;
|
|
464
|
+
readonly variant?: "ghost";
|
|
465
|
+
},
|
|
466
|
+
["class", "children", "variant"],
|
|
467
|
+
);
|
|
468
|
+
return (
|
|
469
|
+
<QuestionnairePrimitive.Skip
|
|
470
|
+
as={Button}
|
|
471
|
+
data-slot="questionnaire-skip"
|
|
472
|
+
variant={local.variant ?? "ghost"}
|
|
473
|
+
class={cn("me-auto", local.class)}
|
|
474
|
+
{...(rest as object)}
|
|
475
|
+
>
|
|
476
|
+
{local.children ?? t("questionnaire.skip", "Skip")}
|
|
477
|
+
</QuestionnairePrimitive.Skip>
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Checks the question and goes on; on a question that is not valid it shows the error instead. Shown on every question but the last. With no children it reads "Next".
|
|
483
|
+
*
|
|
484
|
+
* Keyboard and ARIA: a native button; Enter on a picked choice does the same.
|
|
485
|
+
*
|
|
486
|
+
* @param props - The button props.
|
|
487
|
+
* @returns The button.
|
|
488
|
+
* @example
|
|
489
|
+
* ```tsx
|
|
490
|
+
* import { QuestionnaireNext } from "@/components/ui/questionnaire";
|
|
491
|
+
*
|
|
492
|
+
* export const Forward = <QuestionnaireNext />;
|
|
493
|
+
* ```
|
|
494
|
+
* @since 1.0.0
|
|
495
|
+
* @see {@link QuestionnaireActions}
|
|
496
|
+
*/
|
|
497
|
+
function QuestionnaireNext<T extends PolymorphicTarget = "button">(
|
|
498
|
+
props: QuestionnaireNextProps<T>,
|
|
499
|
+
): JSX.Element {
|
|
500
|
+
const t = useUiStrings();
|
|
501
|
+
const [local, rest] = splitProps(
|
|
502
|
+
props as unknown as { readonly class?: string; readonly children?: JSX.Element },
|
|
503
|
+
["class", "children"],
|
|
504
|
+
);
|
|
505
|
+
return (
|
|
506
|
+
<QuestionnairePrimitive.Next
|
|
507
|
+
as={Button}
|
|
508
|
+
data-slot="questionnaire-next"
|
|
509
|
+
class={cn("ms-auto", local.class)}
|
|
510
|
+
{...(rest as object)}
|
|
511
|
+
>
|
|
512
|
+
{local.children ?? t("questionnaire.next", "Next")}
|
|
513
|
+
</QuestionnairePrimitive.Next>
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Submits from the last question, after every question has been checked. With no children it reads "Submit".
|
|
519
|
+
*
|
|
520
|
+
* Keyboard and ARIA: a native submit button; Cmd or Ctrl with Enter submits from the last question.
|
|
521
|
+
*
|
|
522
|
+
* @param props - The button props.
|
|
523
|
+
* @returns The button.
|
|
524
|
+
* @example
|
|
525
|
+
* ```tsx
|
|
526
|
+
* import { QuestionnaireSubmit } from "@/components/ui/questionnaire";
|
|
527
|
+
*
|
|
528
|
+
* export const Send = <QuestionnaireSubmit />;
|
|
529
|
+
* ```
|
|
530
|
+
* @since 1.0.0
|
|
531
|
+
* @see {@link QuestionnaireActions}
|
|
532
|
+
*/
|
|
533
|
+
function QuestionnaireSubmit<T extends PolymorphicTarget = "button">(
|
|
534
|
+
props: QuestionnaireSubmitProps<T>,
|
|
535
|
+
): JSX.Element {
|
|
536
|
+
const t = useUiStrings();
|
|
537
|
+
const [local, rest] = splitProps(
|
|
538
|
+
props as unknown as { readonly class?: string; readonly children?: JSX.Element },
|
|
539
|
+
["class", "children"],
|
|
540
|
+
);
|
|
541
|
+
return (
|
|
542
|
+
<QuestionnairePrimitive.Submit
|
|
543
|
+
as={Button}
|
|
544
|
+
data-slot="questionnaire-submit"
|
|
545
|
+
class={cn("ms-auto", local.class)}
|
|
546
|
+
{...(rest as object)}
|
|
547
|
+
>
|
|
548
|
+
{local.children ?? t("questionnaire.submit", "Submit")}
|
|
549
|
+
</QuestionnairePrimitive.Submit>
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export type { QuestionnaireChoiceInputProps };
|
|
554
|
+
export {
|
|
555
|
+
Questionnaire,
|
|
556
|
+
QuestionnaireActions,
|
|
557
|
+
QuestionnaireChoice,
|
|
558
|
+
QuestionnaireChoices,
|
|
559
|
+
QuestionnaireDescription,
|
|
560
|
+
QuestionnaireError,
|
|
561
|
+
QuestionnaireInput,
|
|
562
|
+
QuestionnaireItem,
|
|
563
|
+
QuestionnaireNext,
|
|
564
|
+
QuestionnairePrevious,
|
|
565
|
+
QuestionnaireProgress,
|
|
566
|
+
QuestionnaireSkip,
|
|
567
|
+
QuestionnaireSubmit,
|
|
568
|
+
QuestionnaireTitle,
|
|
569
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { splitProps } from "@shadow-js/core";
|
|
2
|
+
import {
|
|
3
|
+
type PolymorphicTarget,
|
|
4
|
+
type RadioGroupItemProps,
|
|
5
|
+
RadioGroup as RadioGroupPrimitive,
|
|
6
|
+
type RadioGroupProps,
|
|
7
|
+
} from "@shadow-js/primitives";
|
|
8
|
+
import type { JSX } from "@shadow-js/web";
|
|
9
|
+
import { CircleIcon } from "@/components/ui/icons";
|
|
10
|
+
import { type ClassValue, cn } from "@/lib/utils";
|
|
11
|
+
|
|
12
|
+
/** The props as the implementation sees them. */
|
|
13
|
+
type Loose = { readonly class?: ClassValue; readonly [prop: string]: unknown };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A set of radio buttons of which exactly one can be checked. Controlled with `value` and
|
|
17
|
+
* `onValueChange`, or uncontrolled with `defaultValue`. Use {@link RadioGroupItem} for each choice.
|
|
18
|
+
*
|
|
19
|
+
* Keyboard and ARIA: a `role="radiogroup"` of `role="radio"` items. The group is one tab stop: the
|
|
20
|
+
* checked item, or the first enabled one. The arrow keys move focus and check the focused item;
|
|
21
|
+
* Home and End move focus to the first and last enabled item without checking it; Space checks the
|
|
22
|
+
* focused item; Enter does nothing; Tab leaves the group. In right-to-left text, Left and Right
|
|
23
|
+
* swap. Name the group with `aria-label` or `aria-labelledby`. `required` sets `aria-required`.
|
|
24
|
+
*
|
|
25
|
+
* With a `name`, the checked value is submitted by a surrounding form, and `required` and form
|
|
26
|
+
* reset work as they do for native radios.
|
|
27
|
+
*
|
|
28
|
+
* @param props The `div` attributes plus the group's options: `value`, `defaultValue`,
|
|
29
|
+
* `onValueChange`, `name`, `required`, `disabled`, `orientation`, `dir`, `loop` and `form`. `class`
|
|
30
|
+
* adds to the group's classes.
|
|
31
|
+
* @returns The group element with its items inside.
|
|
32
|
+
* @example
|
|
33
|
+
* import { Label } from "@/components/ui/label";
|
|
34
|
+
* import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
35
|
+
*
|
|
36
|
+
* <RadioGroup defaultValue="pro" aria-label="Plan">
|
|
37
|
+
* <RadioGroupItem value="free" id="plan-free" />
|
|
38
|
+
* <Label htmlFor="plan-free">Free</Label>
|
|
39
|
+
* <RadioGroupItem value="pro" id="plan-pro" />
|
|
40
|
+
* <Label htmlFor="plan-pro">Pro</Label>
|
|
41
|
+
* </RadioGroup>
|
|
42
|
+
* @since 1.0.0
|
|
43
|
+
* @see {@link RadioGroupItem}
|
|
44
|
+
*/
|
|
45
|
+
function RadioGroup<T extends PolymorphicTarget = "div">(props: RadioGroupProps<T>): JSX.Element {
|
|
46
|
+
const [local, rest] = splitProps(props as unknown as Loose, ["class"]);
|
|
47
|
+
return (
|
|
48
|
+
<RadioGroupPrimitive data-slot="radio-group" class={cn("grid gap-3", local.class)} {...rest} />
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* One radio of a {@link RadioGroup}. It shows its dot only while it is checked. Name it with a
|
|
54
|
+
* {@link Label} (`id` and `htmlFor`) or with `aria-label`.
|
|
55
|
+
*
|
|
56
|
+
* Keyboard and ARIA: a `role="radio"` button with `aria-checked`. Space or a click checks it; Enter
|
|
57
|
+
* does nothing; the arrow keys are handled by the group. A checked radio stays checked, since
|
|
58
|
+
* pressing it again does not clear it. `disabled` disables the item and dims it.
|
|
59
|
+
*
|
|
60
|
+
* shadcn/ui differences: `as` replaces `asChild`.
|
|
61
|
+
*
|
|
62
|
+
* @param props The `button` attributes plus `value` (required; unique in the group), `disabled`,
|
|
63
|
+
* `required`, `as` and `class`.
|
|
64
|
+
* @returns The radio element.
|
|
65
|
+
* @example
|
|
66
|
+
* import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
67
|
+
*
|
|
68
|
+
* <RadioGroup aria-label="Plan">
|
|
69
|
+
* <RadioGroupItem value="pro" aria-label="Pro plan" />
|
|
70
|
+
* </RadioGroup>
|
|
71
|
+
* @since 1.0.0
|
|
72
|
+
* @see {@link RadioGroup}
|
|
73
|
+
*/
|
|
74
|
+
function RadioGroupItem<T extends PolymorphicTarget = "button">(
|
|
75
|
+
props: RadioGroupItemProps<T>,
|
|
76
|
+
): JSX.Element {
|
|
77
|
+
const [local, rest] = splitProps(props as unknown as Loose & { readonly value: string }, [
|
|
78
|
+
"class",
|
|
79
|
+
"value",
|
|
80
|
+
]);
|
|
81
|
+
return (
|
|
82
|
+
<RadioGroupPrimitive.Item
|
|
83
|
+
data-slot="radio-group-item"
|
|
84
|
+
value={local.value}
|
|
85
|
+
class={cn(
|
|
86
|
+
"inline-flex aspect-square size-4 shrink-0 items-center justify-center rounded-full border border-input text-primary 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 dark:bg-input/30 dark:aria-invalid:ring-destructive/40 forced-colors:border-[ButtonText] forced-colors:data-[state=checked]:border-[Highlight] focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-transparent",
|
|
87
|
+
local.class,
|
|
88
|
+
)}
|
|
89
|
+
{...rest}
|
|
90
|
+
>
|
|
91
|
+
<RadioGroupPrimitive.Indicator
|
|
92
|
+
data-slot="radio-group-indicator"
|
|
93
|
+
class="relative flex items-center justify-center"
|
|
94
|
+
>
|
|
95
|
+
<CircleIcon class="size-2 fill-primary forced-colors:fill-[Highlight]" />
|
|
96
|
+
</RadioGroupPrimitive.Indicator>
|
|
97
|
+
</RadioGroupPrimitive.Item>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { RadioGroup, RadioGroupItem };
|