@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.
Files changed (190) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +598 -2
  4. package/dist/cli.mjs +2293 -0
  5. package/dist/dev/cli/schema.d.ts +73 -0
  6. package/dist/dev/index.d.ts +6 -0
  7. package/dist/dev/index.js +5 -0
  8. package/dist/dev/locales/ar.d.ts +141 -0
  9. package/dist/dev/locales/ar.js +128 -0
  10. package/dist/dev/locales/ar.js.map +1 -0
  11. package/dist/dev/locales/en.d.ts +141 -0
  12. package/dist/dev/locales/en.js +128 -0
  13. package/dist/dev/locales/en.js.map +1 -0
  14. package/dist/dev/registry/utils/utils.d.ts +29 -0
  15. package/dist/dev/registry/utils/utils.js +27 -0
  16. package/dist/dev/registry/utils/utils.js.map +1 -0
  17. package/dist/dev/theme/theme-logic.d.ts +187 -0
  18. package/dist/dev/theme/theme-logic.js +184 -0
  19. package/dist/dev/theme/theme-logic.js.map +1 -0
  20. package/dist/dev/theme/theme-provider.d.ts +90 -0
  21. package/dist/dev/theme/theme-provider.js +150 -0
  22. package/dist/dev/theme/theme-provider.js.map +1 -0
  23. package/dist/prod/index.js +1 -0
  24. package/dist/prod/locales/ar.js +2 -0
  25. package/dist/prod/locales/ar.js.map +1 -0
  26. package/dist/prod/locales/en.js +2 -0
  27. package/dist/prod/locales/en.js.map +1 -0
  28. package/dist/prod/registry/utils/utils.js +2 -0
  29. package/dist/prod/registry/utils/utils.js.map +1 -0
  30. package/dist/prod/theme/theme-logic.js +2 -0
  31. package/dist/prod/theme/theme-logic.js.map +1 -0
  32. package/dist/prod/theme/theme-provider.js +2 -0
  33. package/dist/prod/theme/theme-provider.js.map +1 -0
  34. package/package.json +102 -4
  35. package/registry/accordion/accordion.tsx +210 -0
  36. package/registry/accordion/item.json +4 -0
  37. package/registry/alert/alert.tsx +200 -0
  38. package/registry/alert/item.json +4 -0
  39. package/registry/alert-dialog/alert-dialog.tsx +530 -0
  40. package/registry/alert-dialog/item.json +4 -0
  41. package/registry/aspect-ratio/aspect-ratio.tsx +71 -0
  42. package/registry/aspect-ratio/item.json +4 -0
  43. package/registry/attachment/attachment.tsx +459 -0
  44. package/registry/attachment/item.json +4 -0
  45. package/registry/avatar/avatar.tsx +385 -0
  46. package/registry/avatar/item.json +4 -0
  47. package/registry/badge/badge.tsx +108 -0
  48. package/registry/badge/item.json +4 -0
  49. package/registry/breadcrumb/breadcrumb.tsx +354 -0
  50. package/registry/breadcrumb/item.json +4 -0
  51. package/registry/bubble/bubble.tsx +260 -0
  52. package/registry/bubble/item.json +4 -0
  53. package/registry/button/button.tsx +125 -0
  54. package/registry/button/item.json +4 -0
  55. package/registry/button-group/button-group.tsx +189 -0
  56. package/registry/button-group/item.json +4 -0
  57. package/registry/calendar/calendar.tsx +348 -0
  58. package/registry/calendar/item.json +4 -0
  59. package/registry/card/card.tsx +311 -0
  60. package/registry/card/item.json +4 -0
  61. package/registry/carousel/carousel.tsx +851 -0
  62. package/registry/carousel/item.json +4 -0
  63. package/registry/chart/chart.tsx +1956 -0
  64. package/registry/chart/item.json +4 -0
  65. package/registry/chart-scale/chart-scale.ts +670 -0
  66. package/registry/chart-scale/item.json +5 -0
  67. package/registry/checkbox/checkbox.tsx +79 -0
  68. package/registry/checkbox/item.json +4 -0
  69. package/registry/code-block/code-block.tsx +470 -0
  70. package/registry/code-block/item.json +4 -0
  71. package/registry/collapsible/collapsible.tsx +117 -0
  72. package/registry/collapsible/item.json +4 -0
  73. package/registry/combobox/combobox.tsx +977 -0
  74. package/registry/combobox/item.json +4 -0
  75. package/registry/command/command.tsx +757 -0
  76. package/registry/command/item.json +4 -0
  77. package/registry/context-menu/context-menu.tsx +697 -0
  78. package/registry/context-menu/item.json +4 -0
  79. package/registry/data-table/data-table.tsx +437 -0
  80. package/registry/data-table/item.json +4 -0
  81. package/registry/date-picker/date-picker.tsx +349 -0
  82. package/registry/date-picker/item.json +4 -0
  83. package/registry/dialog/dialog.tsx +412 -0
  84. package/registry/dialog/item.json +4 -0
  85. package/registry/direction/direction.tsx +88 -0
  86. package/registry/direction/item.json +4 -0
  87. package/registry/drawer/drawer.tsx +787 -0
  88. package/registry/drawer/item.json +4 -0
  89. package/registry/dropdown-menu/dropdown-menu.tsx +692 -0
  90. package/registry/dropdown-menu/item.json +4 -0
  91. package/registry/empty/empty.tsx +240 -0
  92. package/registry/empty/item.json +4 -0
  93. package/registry/field/field.tsx +624 -0
  94. package/registry/field/item.json +4 -0
  95. package/registry/form/form.tsx +1112 -0
  96. package/registry/form/item.json +4 -0
  97. package/registry/hover-card/hover-card.tsx +144 -0
  98. package/registry/hover-card/item.json +4 -0
  99. package/registry/icons/icons.tsx +1691 -0
  100. package/registry/icons/item.json +4 -0
  101. package/registry/input/input.tsx +43 -0
  102. package/registry/input/item.json +4 -0
  103. package/registry/input-group/input-group.tsx +315 -0
  104. package/registry/input-group/item.json +4 -0
  105. package/registry/input-otp/input-otp.tsx +329 -0
  106. package/registry/input-otp/item.json +4 -0
  107. package/registry/item/item.json +4 -0
  108. package/registry/item/item.tsx +439 -0
  109. package/registry/kbd/item.json +4 -0
  110. package/registry/kbd/kbd.tsx +65 -0
  111. package/registry/label/item.json +4 -0
  112. package/registry/label/label.tsx +60 -0
  113. package/registry/marker/item.json +4 -0
  114. package/registry/marker/marker.tsx +175 -0
  115. package/registry/menubar/item.json +4 -0
  116. package/registry/menubar/menubar.tsx +694 -0
  117. package/registry/message/item.json +4 -0
  118. package/registry/message/message.tsx +280 -0
  119. package/registry/message-scroller/item.json +4 -0
  120. package/registry/message-scroller/message-scroller.tsx +262 -0
  121. package/registry/native-select/item.json +4 -0
  122. package/registry/native-select/native-select.tsx +113 -0
  123. package/registry/navigation-menu/item.json +4 -0
  124. package/registry/navigation-menu/navigation-menu.tsx +362 -0
  125. package/registry/pagination/item.json +4 -0
  126. package/registry/pagination/pagination.tsx +460 -0
  127. package/registry/password-input/item.json +5 -0
  128. package/registry/password-input/password-input.tsx +494 -0
  129. package/registry/popover/item.json +4 -0
  130. package/registry/popover/popover.tsx +284 -0
  131. package/registry/progress/item.json +4 -0
  132. package/registry/progress/progress.tsx +190 -0
  133. package/registry/questionnaire/item.json +4 -0
  134. package/registry/questionnaire/questionnaire.tsx +569 -0
  135. package/registry/radio-group/item.json +4 -0
  136. package/registry/radio-group/radio-group.tsx +101 -0
  137. package/registry/resizable/item.json +4 -0
  138. package/registry/resizable/resizable.tsx +455 -0
  139. package/registry/scroll-area/item.json +4 -0
  140. package/registry/scroll-area/scroll-area.tsx +149 -0
  141. package/registry/select/item.json +4 -0
  142. package/registry/select/select.tsx +571 -0
  143. package/registry/separator/item.json +4 -0
  144. package/registry/separator/separator.tsx +52 -0
  145. package/registry/sheet/item.json +4 -0
  146. package/registry/sheet/sheet.tsx +476 -0
  147. package/registry/sidebar/item.json +4 -0
  148. package/registry/sidebar/sidebar.tsx +1870 -0
  149. package/registry/skeleton/item.json +4 -0
  150. package/registry/skeleton/skeleton.tsx +47 -0
  151. package/registry/slider/item.json +4 -0
  152. package/registry/slider/slider.tsx +149 -0
  153. package/registry/spinner/item.json +4 -0
  154. package/registry/spinner/spinner.tsx +51 -0
  155. package/registry/switch/item.json +4 -0
  156. package/registry/switch/switch.tsx +93 -0
  157. package/registry/table/item.json +4 -0
  158. package/registry/table/table.tsx +373 -0
  159. package/registry/tabs/item.json +4 -0
  160. package/registry/tabs/tabs.tsx +209 -0
  161. package/registry/textarea/item.json +4 -0
  162. package/registry/textarea/textarea.tsx +51 -0
  163. package/registry/toast/item.json +4 -0
  164. package/registry/toast/toast.tsx +949 -0
  165. package/registry/toggle/item.json +4 -0
  166. package/registry/toggle/toggle.tsx +110 -0
  167. package/registry/toggle-group/item.json +4 -0
  168. package/registry/toggle-group/toggle-group.tsx +189 -0
  169. package/registry/tooltip/item.json +4 -0
  170. package/registry/tooltip/tooltip.tsx +169 -0
  171. package/registry/trace/item.json +4 -0
  172. package/registry/trace/trace.tsx +356 -0
  173. package/registry/ui-strings/item.json +5 -0
  174. package/registry/ui-strings/ui-strings.tsx +454 -0
  175. package/registry/use-highlighted-code/item.json +5 -0
  176. package/registry/use-highlighted-code/use-highlighted-code.tsx +145 -0
  177. package/registry/use-mobile/item.json +5 -0
  178. package/registry/use-mobile/use-mobile.ts +46 -0
  179. package/registry/utils/item.json +5 -0
  180. package/registry/utils/utils.ts +38 -0
  181. package/registry.json +3274 -0
  182. package/styles/animations.css +321 -0
  183. package/styles/theme-core.css +55 -0
  184. package/styles/theme.css +12 -0
  185. package/styles/themes/gray.css +69 -0
  186. package/styles/themes/neutral.css +69 -0
  187. package/styles/themes/slate.css +69 -0
  188. package/styles/themes/stone.css +69 -0
  189. package/styles/themes/zinc.css +69 -0
  190. package/styles/typeset.css +251 -0
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "Icons",
3
+ "description": "The inline SVG icons the components use (check, chevrons, close, search...). Replace with your icon library if you prefer."
4
+ }
@@ -0,0 +1,43 @@
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 text field: the browser's own `input`, in the kit's look. Use it for single-line text, email,
7
+ * number, date and file entry. All `input` attributes pass through.
8
+ *
9
+ * Keyboard and ARIA: the keys are the browser's own (typing, the arrow keys, Tab). The component
10
+ * adds no role and no `aria-*` attribute; the browser gives the input its role from its `type`.
11
+ * Name the field with a {@link Label} (`htmlFor` pointing at the input's `id`), or with
12
+ * `aria-label` or `aria-labelledby`. `aria-invalid="true"`, which you pass, turns the border and
13
+ * the border destructive and tints the ring.
14
+ *
15
+ * shadcn/ui differences: `class` replaces `className`.
16
+ *
17
+ * @param props The `input` attributes. `class` adds to the field's classes; `type` is the input type.
18
+ * @returns The `input` element.
19
+ * @example
20
+ * import { Input } from "@/components/ui/input";
21
+ *
22
+ * <Input type="email" placeholder="Email" />
23
+ * @since 1.0.0
24
+ * @see {@link Label}
25
+ */
26
+ function Input(props: ComponentProps<"input">): JSX.Element {
27
+ const [local, rest] = splitProps(props, ["class", "type"]);
28
+ return (
29
+ <input
30
+ type={local.type}
31
+ data-slot="input"
32
+ class={cn(
33
+ "flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
34
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 outline-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-transparent",
35
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
36
+ local.class,
37
+ )}
38
+ {...rest}
39
+ />
40
+ );
41
+ }
42
+
43
+ export { Input };
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "Input",
3
+ "description": "A form input field."
4
+ }
@@ -0,0 +1,315 @@
1
+ import { splitProps } from "@shadow-js/core";
2
+ import type { ComponentProps, JSX } from "@shadow-js/web";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const addonVariants = cva(
7
+ "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)]",
8
+ {
9
+ variants: {
10
+ align: {
11
+ "inline-start": "order-first ps-3 has-[>button]:ms-[-0.45rem] has-[>kbd]:ms-[-0.35rem]",
12
+ "inline-end": "order-last pe-3 has-[>button]:me-[-0.4rem] has-[>kbd]:me-[-0.35rem]",
13
+ "block-start":
14
+ "order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5",
15
+ "block-end":
16
+ "order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5",
17
+ },
18
+ },
19
+ defaultVariants: { align: "inline-start" },
20
+ },
21
+ );
22
+
23
+ /**
24
+ * A text control with addons (text, icons, buttons) on its sides. The group draws the border, the
25
+ * radius and the focus ring, so the control inside it is borderless. Use it in place of a bare
26
+ * {@link InputGroupInput}, which has no frame of its own.
27
+ *
28
+ * Keyboard and ARIA: the group is a `role="group"` with no keys of its own; the control and the
29
+ * buttons keep theirs. Name the group with `aria-label` or `aria-labelledby` when it needs a name
30
+ * of its own; the control inside still needs one. An `aria-invalid="true"` on the control turns the
31
+ * group's border destructive.
32
+ *
33
+ * @param props The `div` attributes. `class` adds to the group's classes.
34
+ * @returns The group `div` with the addons and the control inside.
35
+ * @example
36
+ * import {
37
+ * InputGroup,
38
+ * InputGroupAddon,
39
+ * InputGroupInput,
40
+ * InputGroupText,
41
+ * } from "@/components/ui/input-group";
42
+ *
43
+ * <InputGroup>
44
+ * <InputGroupAddon>
45
+ * <InputGroupText>https://</InputGroupText>
46
+ * </InputGroupAddon>
47
+ * <InputGroupInput placeholder="example.com" aria-label="Website" />
48
+ * </InputGroup>
49
+ * @since 1.0.0
50
+ * @see {@link InputGroupAddon}
51
+ */
52
+ function InputGroup(props: ComponentProps<"div">): JSX.Element {
53
+ const [local, rest] = splitProps(props, ["class"]);
54
+ return (
55
+ <div
56
+ role="group"
57
+ data-slot="input-group"
58
+ class={cn(
59
+ "group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30",
60
+ "focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50",
61
+ "has-[[aria-invalid=true]]:border-destructive has-[[aria-invalid=true]]:ring-destructive/20 dark:has-[[aria-invalid=true]]:ring-destructive/40",
62
+ "has-[textarea]:h-auto has-[[data-slot=input-group-control]:disabled]:opacity-50",
63
+ // A block addon sits above or below the control, so the parts stack in a column.
64
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col",
65
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col",
66
+ local.class,
67
+ )}
68
+ {...rest}
69
+ />
70
+ );
71
+ }
72
+
73
+ /**
74
+ * Props of {@link InputGroupAddon}: the `div` attributes plus `align`.
75
+ * @since 1.0.0
76
+ * @see {@link InputGroupAddon}
77
+ */
78
+ export type InputGroupAddonProps = ComponentProps<"div"> &
79
+ VariantProps<typeof addonVariants> & {
80
+ /**
81
+ * The side the addon sits on. `inline-start` and `inline-end` are beside the control;
82
+ * `block-start` and `block-end` are above and below it, and the group stacks into a column.
83
+ * @default "inline-start"
84
+ */
85
+ readonly align?: "inline-start" | "inline-end" | "block-start" | "block-end" | undefined;
86
+ };
87
+
88
+ /**
89
+ * An addon on a side of the group: text, an icon or a button. A click on the addon outside its
90
+ * buttons moves focus to the group's control, so the whole strip reads as part of the field. Your
91
+ * own `onClick` runs first; the focus move is the default.
92
+ *
93
+ * Keyboard and ARIA: the addon has no keys and a `role="presentation"`. The buttons and text in it
94
+ * keep their own behavior.
95
+ *
96
+ * @param props The addon's `div` attributes. `align` picks the side (see {@link InputGroupAddonProps});
97
+ * `onClick` is called before the focus move.
98
+ * @returns The addon `div`.
99
+ * @example
100
+ * import {
101
+ * InputGroup,
102
+ * InputGroupAddon,
103
+ * InputGroupButton,
104
+ * InputGroupInput,
105
+ * } from "@/components/ui/input-group";
106
+ *
107
+ * <InputGroup>
108
+ * <InputGroupInput aria-label="Share link" value="https://example.com" readOnly />
109
+ * <InputGroupAddon align="inline-end">
110
+ * <InputGroupButton aria-label="Copy link">Copy</InputGroupButton>
111
+ * </InputGroupAddon>
112
+ * </InputGroup>
113
+ * @since 1.0.0
114
+ * @see {@link InputGroup}
115
+ */
116
+ function InputGroupAddon(props: InputGroupAddonProps): JSX.Element {
117
+ const [local, rest] = splitProps(props, ["class", "align", "onClick"]);
118
+ // The application's own click handler runs first; the focus move is the default.
119
+ const onClick = (event: MouseEvent): void => {
120
+ (local.onClick as ((event: MouseEvent) => void) | undefined)?.(event);
121
+ const target = event.target;
122
+ if (target instanceof Element && target.closest("button") !== null) return;
123
+ const group = (event.currentTarget as HTMLElement).closest('[data-slot="input-group"]');
124
+ group?.querySelector<HTMLElement>("input,textarea")?.focus();
125
+ };
126
+ return (
127
+ <div
128
+ role="presentation"
129
+ data-slot="input-group-addon"
130
+ data-align={local.align ?? "inline-start"}
131
+ class={cn(addonVariants({ align: local.align }), local.class)}
132
+ onClick={onClick}
133
+ {...rest}
134
+ />
135
+ );
136
+ }
137
+
138
+ /**
139
+ * The bare text control inside a group: no border, no ring, and it fills the free width. Use it
140
+ * only inside an {@link InputGroup}.
141
+ *
142
+ * Keyboard and ARIA: a native `input` with the browser's keys. It has no name of its own: pass
143
+ * `aria-label`, or a {@link Label} whose `htmlFor` points at its `id`. Its `data-slot` is
144
+ * `input-group-control`, which the group's styles check.
145
+ *
146
+ * @param props The `input` attributes. `class` adds to the control's classes.
147
+ * @returns The `input` element.
148
+ * @example
149
+ * import { InputGroup, InputGroupInput } from "@/components/ui/input-group";
150
+ *
151
+ * <InputGroup>
152
+ * <InputGroupInput aria-label="Search" placeholder="Search" />
153
+ * </InputGroup>
154
+ * @since 1.0.0
155
+ * @see {@link InputGroup}
156
+ */
157
+ function InputGroupInput(props: ComponentProps<"input">): JSX.Element {
158
+ const [local, rest] = splitProps(props, ["class"]);
159
+ return (
160
+ <input
161
+ data-slot="input-group-control"
162
+ class={cn(
163
+ "flex h-9 w-full min-w-0 flex-1 rounded-none border-0 bg-transparent px-3 py-1 text-base shadow-none outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed md:text-sm",
164
+ local.class,
165
+ )}
166
+ {...rest}
167
+ />
168
+ );
169
+ }
170
+
171
+ /**
172
+ * The bare multi-line control inside a group: no border, no ring, and it fills the free width.
173
+ * Use it only inside an {@link InputGroup}.
174
+ *
175
+ * Keyboard and ARIA: a native `textarea` with the browser's keys. Name it with `aria-label`, or with
176
+ * a {@link Label} whose `htmlFor` points at its `id`.
177
+ *
178
+ * @param props The `textarea` attributes. `class` adds to the control's classes.
179
+ * @returns The `textarea` element.
180
+ * @example
181
+ * import { InputGroup, InputGroupTextarea } from "@/components/ui/input-group";
182
+ *
183
+ * <InputGroup>
184
+ * <InputGroupTextarea aria-label="Message" placeholder="Write a message" />
185
+ * </InputGroup>
186
+ * @since 1.0.0
187
+ * @see {@link InputGroup}
188
+ */
189
+ function InputGroupTextarea(props: ComponentProps<"textarea">): JSX.Element {
190
+ const [local, rest] = splitProps(props, ["class"]);
191
+ return (
192
+ <textarea
193
+ data-slot="input-group-control"
194
+ class={cn(
195
+ "flex min-h-16 w-full flex-1 resize-none rounded-none border-0 bg-transparent px-3 py-3 text-base shadow-none outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed md:text-sm",
196
+ local.class,
197
+ )}
198
+ {...rest}
199
+ />
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Text inside an addon: a unit, a protocol or a prefix.
205
+ *
206
+ * Keyboard and ARIA: a `span` with no role and no keys.
207
+ *
208
+ * @param props The `span` attributes. `class` adds to the classes.
209
+ * @returns The `span` element.
210
+ * @example
211
+ * import { InputGroupText } from "@/components/ui/input-group";
212
+ *
213
+ * <InputGroupText>USD</InputGroupText>
214
+ * @since 1.0.0
215
+ * @see {@link InputGroupAddon}
216
+ */
217
+ function InputGroupText(props: ComponentProps<"span">): JSX.Element {
218
+ const [local, rest] = splitProps(props, ["class"]);
219
+ return (
220
+ <span
221
+ class={cn("flex items-center gap-2 text-sm text-muted-foreground", local.class)}
222
+ {...rest}
223
+ />
224
+ );
225
+ }
226
+
227
+ const inputGroupButtonVariants = cva(
228
+ "inline-flex shrink-0 items-center justify-center gap-2 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-transparent",
229
+ {
230
+ variants: {
231
+ variant: {
232
+ default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
233
+ destructive:
234
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
235
+ outline:
236
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
237
+ secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
238
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
239
+ link: "text-primary underline-offset-4 hover:underline",
240
+ },
241
+ size: {
242
+ xs: "h-6 gap-1 rounded-sm px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3.5",
243
+ sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2",
244
+ "icon-xs": "size-6 rounded-sm p-0 [&_svg:not([class*='size-'])]:size-3.5",
245
+ "icon-sm": "size-8 rounded-md p-0",
246
+ },
247
+ },
248
+ defaultVariants: {
249
+ variant: "ghost",
250
+ size: "xs",
251
+ },
252
+ },
253
+ );
254
+
255
+ /**
256
+ * Props of {@link InputGroupButton}: the `button` attributes, plus `variant` and `size`.
257
+ * @see {@link InputGroupButton}
258
+ * @since 1.0.0
259
+ */
260
+ export type InputGroupButtonProps = ComponentProps<"button"> &
261
+ VariantProps<typeof inputGroupButtonVariants>;
262
+
263
+ /**
264
+ * A small button inside an addon, styled to sit in the group. It is a `type="button"` button, so it
265
+ * never submits a surrounding form unless you pass `type="submit"`.
266
+ *
267
+ * Keyboard and ARIA: a native button (Enter and Space press it). Its name is its text; a button with
268
+ * only an icon needs an `aria-label`.
269
+ *
270
+ * shadcn/ui differences: the classes come from this file, not from the Button item, so the group does not
271
+ * depend on the Button item's sizes. `variant` and `size` match shadcn's button sizes for groups: `xs`
272
+ * (the default, 24 px tall), `sm` (32 px), `icon-xs` and `icon-sm` (square). `variant` takes the Button
273
+ * item's variants, and defaults to `ghost`.
274
+ *
275
+ * @param props The `button` attributes, `variant` and `size`: see {@link InputGroupButtonProps}. `class`
276
+ * adds to the classes.
277
+ * @returns The `button` element.
278
+ * @example
279
+ * import { SearchIcon } from "@/components/ui/icons";
280
+ * import { InputGroup, InputGroupButton, InputGroupInput } from "@/components/ui/input-group";
281
+ *
282
+ * <InputGroup>
283
+ * <InputGroupInput aria-label="Search" />
284
+ * <InputGroupButton aria-label="Clear">Clear</InputGroupButton>
285
+ * <InputGroupButton size="icon-xs" variant="secondary" aria-label="Search">
286
+ * <SearchIcon />
287
+ * </InputGroupButton>
288
+ * </InputGroup>
289
+ * @default size "xs", variant "ghost"
290
+ * @since 1.0.0
291
+ * @see {@link InputGroupAddon}
292
+ */
293
+ function InputGroupButton(props: InputGroupButtonProps): JSX.Element {
294
+ const [local, rest] = splitProps(props, ["class", "variant", "size"]);
295
+ return (
296
+ <button
297
+ type="button"
298
+ data-slot="input-group-button"
299
+ class={cn(
300
+ inputGroupButtonVariants({ variant: local.variant, size: local.size }),
301
+ local.class,
302
+ )}
303
+ {...rest}
304
+ />
305
+ );
306
+ }
307
+
308
+ export {
309
+ InputGroup,
310
+ InputGroupAddon,
311
+ InputGroupButton,
312
+ InputGroupInput,
313
+ InputGroupText,
314
+ InputGroupTextarea,
315
+ };
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "Input Group",
3
+ "description": "A text control with addons (text, icons, buttons) on its sides, sharing one border and focus ring."
4
+ }