@api7/ui-components 0.4.0 → 0.6.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/README.md +221 -30
- package/dist/animations.css +12 -0
- package/dist/components/alert/alert.d.ts +0 -2
- package/dist/components/alert/alert.js +8 -9
- package/dist/components/avatar/avatar.d.ts +4 -6
- package/dist/components/avatar/avatar.js +7 -8
- package/dist/components/badge/badge.d.ts +0 -2
- package/dist/components/badge/badge.js +15 -16
- package/dist/components/button/button.d.ts +1 -4
- package/dist/components/button/button.js +15 -16
- package/dist/components/calendar/calendar.d.ts +1 -3
- package/dist/components/calendar/calendar.js +29 -30
- package/dist/components/checkbox/checkbox.d.ts +0 -2
- package/dist/components/checkbox/checkbox.js +4 -5
- package/dist/components/combobox/combobox.d.ts +0 -2
- package/dist/components/combobox/combobox.js +25 -25
- package/dist/components/data-table/data-table.d.ts +0 -2
- package/dist/components/data-table/data-table.js +8 -9
- package/dist/components/data-table/filter-header.d.ts +1 -3
- package/dist/components/data-table/filter-header.js +5 -6
- package/dist/components/data-table/sort-header.js +3 -4
- package/dist/components/date-picker/date-picker.d.ts +0 -2
- package/dist/components/date-picker/date-picker.js +15 -16
- package/dist/components/description-list/description-list.d.ts +24 -0
- package/dist/components/description-list/description-list.js +88 -0
- package/dist/components/dialog/dialog.d.ts +0 -2
- package/dist/components/dialog/dialog.js +12 -13
- package/dist/components/drawer/drawer.d.ts +0 -2
- package/dist/components/drawer/drawer.js +13 -14
- package/dist/components/empty/empty.d.ts +0 -2
- package/dist/components/empty/empty.js +9 -10
- package/dist/components/field/field.d.ts +0 -2
- package/dist/components/field/field.js +7 -8
- package/dist/components/field/labeled-field.d.ts +33 -0
- package/dist/components/field/labeled-field.js +67 -0
- package/dist/components/form/choice-fields.d.ts +1 -3
- package/dist/components/form/choice-fields.js +1 -2
- package/dist/components/form/date-fields.d.ts +0 -2
- package/dist/components/form/date-fields.js +2 -3
- package/dist/components/form/errors.d.ts +1 -3
- package/dist/components/form/errors.js +1 -2
- package/dist/components/form/form-context.d.ts +1 -3
- package/dist/components/form/form-context.js +1 -2
- package/dist/components/form/form-item.d.ts +6 -17
- package/dist/components/form/form-item.js +9 -58
- package/dist/components/form/form.d.ts +1 -3
- package/dist/components/form/form.js +1 -2
- package/dist/components/form/lazy-field-components.js +2 -3
- package/dist/components/form/submit-button.d.ts +1 -3
- package/dist/components/form/submit-button.js +1 -2
- package/dist/components/form/text-field.d.ts +1 -3
- package/dist/components/form/text-field.js +1 -2
- package/dist/components/form/toggle-fields.d.ts +1 -3
- package/dist/components/form/toggle-fields.js +30 -46
- package/dist/components/input/input.d.ts +0 -2
- package/dist/components/input/input.js +2 -3
- package/dist/components/input-group/input-group.d.ts +0 -2
- package/dist/components/input-group/input-group.js +12 -13
- package/dist/components/menu/menu.d.ts +14 -4
- package/dist/components/menu/menu.js +43 -31
- package/dist/components/number-field/number-field.d.ts +0 -2
- package/dist/components/number-field/number-field.js +8 -9
- package/dist/components/pagination/pagination.d.ts +1 -3
- package/dist/components/pagination/pagination.js +13 -14
- package/dist/components/popover/popover.d.ts +3 -3
- package/dist/components/popover/popover.js +14 -7
- package/dist/components/progress/progress.d.ts +17 -0
- package/dist/components/progress/progress.js +35 -0
- package/dist/components/radio-group/radio-group.d.ts +0 -2
- package/dist/components/radio-group/radio-group.js +4 -5
- package/dist/components/select/select.d.ts +0 -2
- package/dist/components/select/select.js +18 -18
- package/dist/components/separator/separator.d.ts +0 -2
- package/dist/components/separator/separator.js +2 -3
- package/dist/components/sidebar/sidebar.d.ts +0 -2
- package/dist/components/sidebar/sidebar.js +11 -12
- package/dist/components/skeleton/skeleton.d.ts +0 -2
- package/dist/components/skeleton/skeleton.js +2 -3
- package/dist/components/spinner/spinner.d.ts +12 -0
- package/dist/components/spinner/spinner.js +27 -0
- package/dist/components/switch/switch.d.ts +0 -2
- package/dist/components/switch/switch.js +3 -4
- package/dist/components/table/table.d.ts +0 -2
- package/dist/components/table/table.js +8 -9
- package/dist/components/tabs/tabs.d.ts +0 -2
- package/dist/components/tabs/tabs.js +5 -6
- package/dist/components/textarea/textarea.d.ts +0 -2
- package/dist/components/textarea/textarea.js +2 -3
- package/dist/components/toast/toast.d.ts +0 -2
- package/dist/components/toast/toast.js +19 -20
- package/dist/components/tooltip/tooltip.d.ts +0 -2
- package/dist/components/tooltip/tooltip.js +5 -4
- package/dist/config/config-provider.d.ts +1 -3
- package/dist/config/config-provider.js +1 -2
- package/dist/config/messages.d.ts +5 -3
- package/dist/config/messages.js +3 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +7 -3
- package/dist/lib/cn.js +5 -6
- package/dist/lib/control.js +4 -5
- package/dist/lib/popup.js +6 -6
- package/dist/theme.css +220 -0
- package/dist/tokens/contrast.d.ts +1 -3
- package/dist/tokens/contrast.js +1 -2
- package/dist/tokens/palette.d.ts +0 -2
- package/dist/tokens/palette.js +1 -2
- package/dist/tokens/scale.js +1 -2
- package/dist/tokens/semantic.d.ts +0 -2
- package/dist/tokens/semantic.js +0 -1
- package/dist/tokens/types.d.ts +1 -3
- package/dist/tokens/types.js +1 -2
- package/package.json +11 -9
- package/dist/styles.css +0 -2
package/README.md
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
# @api7/ui-components
|
|
2
2
|
|
|
3
|
-
API7 React component library built on [Base UI](https://base-ui.com) and Tailwind CSS v4.
|
|
3
|
+
API7 React component library built on [Base UI](https://base-ui.com) and Tailwind CSS v4. The components are styled with Tailwind utility classes, and the application's own Tailwind CSS compiles them, so the library and the application share one set of utilities and one theme.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
+
The library needs React 19 and an application that already builds with Tailwind CSS 4.1 or newer. If the application does not use Tailwind yet, follow [the Tailwind CSS installation guide](https://tailwindcss.com/docs/installation) for your build tool first, then come back here.
|
|
8
|
+
|
|
7
9
|
```sh
|
|
8
|
-
npm install @api7/ui-components react-day-picker lucide-react
|
|
10
|
+
npm install @api7/ui-components tailwindcss react-day-picker lucide-react
|
|
9
11
|
# or
|
|
10
|
-
pnpm add @api7/ui-components react-day-picker lucide-react
|
|
12
|
+
pnpm add @api7/ui-components tailwindcss react-day-picker lucide-react
|
|
11
13
|
# or
|
|
12
|
-
yarn add @api7/ui-components react-day-picker lucide-react
|
|
14
|
+
yarn add @api7/ui-components tailwindcss react-day-picker lucide-react
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
The package expects these peer dependencies in the application:
|
|
17
|
+
`tailwindcss` is already there if the application uses Tailwind. Install the integration of Tailwind for your build tool as well (`@tailwindcss/vite`, `@tailwindcss/postcss` or `@tailwindcss/cli`). The import of the theme works with the Vite plugin, the PostCSS plugin and the CLI, which are the ones tested. Other build tools, such as webpack, are not tested. The package expects these peer dependencies in the application:
|
|
16
18
|
|
|
17
19
|
| Package | Version | Needed for |
|
|
18
20
|
| ----------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
21
|
| `react`, `react-dom` | `^19` | Everything |
|
|
22
|
+
| `tailwindcss` | `^4.1.0` | Compiles the classes of the components and registers the theme. See [Quick start](#quick-start) |
|
|
20
23
|
| `lucide-react` | `^1` | Icons used inside components |
|
|
21
24
|
| `react-day-picker` | `^10.0.1` | Required. `Calendar`, `DatePicker` and `DateRangePicker` import it, and the package declares it as a required peer, so install it even if you do not use them |
|
|
22
25
|
| `@tanstack/react-form` | `^1.33.0` | `@api7/ui-components/form` (optional) |
|
|
@@ -26,27 +29,50 @@ Add the optional ones only if you import those entry points.
|
|
|
26
29
|
|
|
27
30
|
## Quick start
|
|
28
31
|
|
|
29
|
-
1.
|
|
32
|
+
1. In the main CSS file of the application (for example `src/index.css`), import Tailwind CSS and then the library theme, and make sure the entry file of the application imports that CSS file:
|
|
30
33
|
|
|
31
|
-
```
|
|
32
|
-
import '
|
|
34
|
+
```css
|
|
35
|
+
@import 'tailwindcss';
|
|
36
|
+
@import '@api7/ui-components';
|
|
33
37
|
```
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
The order matters. The theme extends the Tailwind theme, so it has to come after `tailwindcss`.
|
|
40
|
+
|
|
41
|
+
In CSS the package name resolves to the theme through the `style` export condition, and in JavaScript it resolves to the components. Tailwind CSS 4 and the tools that resolve CSS imports the way it does (the Tailwind CLI, `@tailwindcss/postcss` and `@tailwindcss/vite`) apply the condition. A tool that ignores it finds the JavaScript entry instead and fails on `@import`.
|
|
42
|
+
|
|
43
|
+
That is all the configuration there is. The theme file points Tailwind at the classes the components use, so you do not add an `@source` line for the library, and it registers the semantic colors (`bg-primary`, `text-muted-foreground`, `border-border` and the rest), the radii and the font as Tailwind theme values. The application keeps its own Tailwind palette, and the same classes work in your own markup. See [Using it with your Tailwind CSS](#using-it-with-your-tailwind-css) for what the theme changes in the application.
|
|
44
|
+
|
|
45
|
+
2. Load a font for `--font-sans`. It is Inter by default. For example, install `@fontsource-variable/inter` (`npm install @fontsource-variable/inter`) and import it in the same CSS file, or in the entry file of the application:
|
|
36
46
|
|
|
37
47
|
```ts
|
|
38
48
|
import '@fontsource-variable/inter';
|
|
39
49
|
```
|
|
40
50
|
|
|
41
|
-
To use another font,
|
|
51
|
+
To use another font, set `--font-sans` in an `@theme` block after the imports:
|
|
52
|
+
|
|
53
|
+
```css
|
|
54
|
+
@theme {
|
|
55
|
+
--font-sans: 'Your Font', sans-serif;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The library does not style the page. Components inherit the text color, the font and the font size from the page, so set them on `body` in the base layer of the application. The components are laid out and tested with this rule, and a different line height changes the height of components that do not set their own:
|
|
42
60
|
|
|
43
61
|
```css
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
@layer base {
|
|
63
|
+
body {
|
|
64
|
+
background-color: var(--color-background);
|
|
65
|
+
color: var(--color-foreground);
|
|
66
|
+
font-family: var(--font-sans);
|
|
67
|
+
font-size: var(--text-sm);
|
|
68
|
+
line-height: var(--text-sm--line-height);
|
|
69
|
+
}
|
|
46
70
|
}
|
|
47
71
|
```
|
|
48
72
|
|
|
49
|
-
3.
|
|
73
|
+
3. Put `isolate` on the root element of the application (`<div id="root" class="isolate">`). Popups, dialogs and drawers are rendered into `<body>`, outside your root, and they have `z-index: 50` (the toast has `1010`). An element of the application with a higher `z-index`, or a root with a `z-index` of its own, is painted over a popup that overlaps it. `isolate` makes the `z-index` values of the application local to the root, so every popup stays above it whatever values the application uses.
|
|
74
|
+
|
|
75
|
+
4. Use the components. No provider is required. Render `Toaster` once if you use `toast`, and wrap the application in `ConfigProvider` if you need a locale other than `en-US`:
|
|
50
76
|
|
|
51
77
|
```tsx
|
|
52
78
|
import { Button, ConfigProvider, Toaster, toast } from '@api7/ui-components';
|
|
@@ -61,17 +87,16 @@ Add the optional ones only if you import those entry points.
|
|
|
61
87
|
}
|
|
62
88
|
```
|
|
63
89
|
|
|
64
|
-
`ConfigProvider` accepts `locale` (`en-US` or `zh-CN`) and `messages`, an object that overrides the built-in texts, such as the loading label of a button, the close buttons, the empty state of the data table and the pagination labels. Define `messages` at module scope or memoize it.
|
|
90
|
+
`ConfigProvider` accepts `locale` (`en-US` or `zh-CN`) and `messages`, an object that overrides the built-in texts, such as the loading label of a button or a spinner, the close buttons, the empty state of the data table and the pagination labels. Define `messages` at module scope or memoize it.
|
|
65
91
|
|
|
66
92
|
## Entry points
|
|
67
93
|
|
|
68
|
-
| Import | Contents
|
|
69
|
-
| -------------------------------- |
|
|
70
|
-
| `@api7/ui-components` | Components, `toast` and `Toaster`, `ConfigProvider`, `useLocale` and `useMessages
|
|
71
|
-
| `@api7/ui-components/form` | Form
|
|
72
|
-
| `@api7/ui-components/data-table` | The data table built on TanStack Table
|
|
73
|
-
| `@api7/ui-components/tokens` | Design token data (`palette`, `themes`, `FAMILIES`, `STEPS` and the contrast helpers) for documentation pages
|
|
74
|
-
| `@api7/ui-components/styles.css` | The precompiled stylesheet |
|
|
94
|
+
| Import | Contents |
|
|
95
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
96
|
+
| `@api7/ui-components` | Components, `toast` and `Toaster`, `ConfigProvider`, `useLocale` and `useMessages`, and in CSS the theme for the Tailwind CSS of the application |
|
|
97
|
+
| `@api7/ui-components/form` | Form fields built on TanStack Form. Their form row, `LabeledField`, is in the main entry and works with any form library |
|
|
98
|
+
| `@api7/ui-components/data-table` | The data table built on TanStack Table |
|
|
99
|
+
| `@api7/ui-components/tokens` | Design token data (`palette`, `themes`, `FAMILIES`, `STEPS` and the contrast helpers) for documentation pages |
|
|
75
100
|
|
|
76
101
|
The package is ESM only. Component modules keep their `"use client"` directive, so the components work in a React Server Components setup such as the Next.js app router.
|
|
77
102
|
|
|
@@ -97,20 +122,64 @@ export function Actions() {
|
|
|
97
122
|
}
|
|
98
123
|
```
|
|
99
124
|
|
|
100
|
-
A link that looks like a button is an ordinary anchor or router link styled with `buttonVariants`.
|
|
125
|
+
A link that looks like a button is an ordinary anchor or router link styled with `buttonVariants`. It carries the same classes as a `Button`:
|
|
101
126
|
|
|
102
127
|
```tsx
|
|
103
128
|
import { buttonVariants } from '@api7/ui-components';
|
|
104
129
|
|
|
105
130
|
export function DocsLink() {
|
|
106
131
|
return (
|
|
107
|
-
<a href="/docs"
|
|
132
|
+
<a href="/docs" className={buttonVariants({ variant: 'outline' })}>
|
|
108
133
|
Docs
|
|
109
134
|
</a>
|
|
110
135
|
);
|
|
111
136
|
}
|
|
112
137
|
```
|
|
113
138
|
|
|
139
|
+
## Spinners
|
|
140
|
+
|
|
141
|
+
`Spinner` is an SVG that spins, and it is a status, so a screen reader announces it. It takes a `size`: `xs`, `sm`, `md` (the default) or `lg`. Its colour is the text colour, so set it with a `text-*` class, and any other `className` is merged onto the SVG.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { Spinner } from '@api7/ui-components';
|
|
145
|
+
|
|
146
|
+
<div className="flex justify-center py-12">
|
|
147
|
+
<Spinner />
|
|
148
|
+
</div>;
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Things worth knowing before you build on it:
|
|
152
|
+
|
|
153
|
+
- The announced name is the `spinner.label` message of the `ConfigProvider`, and an `aria-label` on the spinner replaces it, for example to say what is loading.
|
|
154
|
+
- The spinner mounts together with the loading state, and whether a screen reader announces a status that is inserted into the page is up to the screen reader. To make the loading state reliably announced, set `aria-busy` or `aria-live="polite"` on a container that is already on the page and put the spinner inside it.
|
|
155
|
+
- It does not wrap other elements. To spin your own icon, add `animate-spin` to it.
|
|
156
|
+
- It stops spinning when the user prefers reduced motion, and then shows the static icon.
|
|
157
|
+
- It does not position itself. A loading overlay over a table or a card is built where you use it, with the layout that place needs. `Button` has its own `loading` prop, use that for a button.
|
|
158
|
+
|
|
159
|
+
## Progress bars
|
|
160
|
+
|
|
161
|
+
`Progress` shows how far a task has come. It takes a `value` between `min` (0) and `max` (100), and a `null` value makes the bar indeterminate, for a task whose length is unknown. It takes a `size`: `xs`, `sm` (the default) or `md`, which are 4, 6 and 8px high. It takes a `variant`: `default`, `success`, `warning` or `destructive`, the colour of the fill. The bar fills the width of its container, so set the width on the container or with `className`.
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
import { Progress } from '@api7/ui-components';
|
|
165
|
+
|
|
166
|
+
<div className="flex flex-col gap-1">
|
|
167
|
+
<div className="flex justify-between text-xs">
|
|
168
|
+
<span id="cores-label">Cores</span>
|
|
169
|
+
<span>45 / 100</span>
|
|
170
|
+
</div>
|
|
171
|
+
<Progress value={45} aria-labelledby="cores-label" />
|
|
172
|
+
</div>;
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Things worth knowing before you build on it:
|
|
176
|
+
|
|
177
|
+
- There is no default accessible name, because only you know what is progressing. Give the bar an `aria-label`, or an `aria-labelledby` that points at the text next to it, as above.
|
|
178
|
+
- The label and the numbers around a bar are laid out where you use it. There is no label or value part.
|
|
179
|
+
- The variant is a fixed colour. A bar that changes colour as it fills, such as a quota that turns red near its limit, is a `variant` that you compute from the value where you use it.
|
|
180
|
+
- An indeterminate bar slides a segment along the track. When the user prefers reduced motion it shows the whole track at half opacity instead of a static segment, so it is not read as a partial value. The width of a determinate bar is animated too, and that stops as well.
|
|
181
|
+
- The fill uses the text colour of each tone (`primary-text`, `success-foreground`, `warning-foreground` and `destructive-text`), because those stay 3:1 against the track in both themes, and the plain tone colours do not.
|
|
182
|
+
|
|
114
183
|
## Forms
|
|
115
184
|
|
|
116
185
|
`@api7/ui-components/form` wraps TanStack Form. `useAppForm` returns a form whose `AppField` hands you ready made fields: `TextField`, `TextareaField`, `NumberField`, `SelectField`, `ComboboxField`, `DatePickerField`, `DateRangePickerField`, `CheckboxField`, `SwitchField` and `RadioGroupField`:
|
|
@@ -144,6 +213,35 @@ export function NameForm({ onSave }: { onSave: (name: string) => void }) {
|
|
|
144
213
|
|
|
145
214
|
Validators are the ones of TanStack Form, so a function or a Standard Schema such as a zod schema both work. A string returned by a validator is shown under the field.
|
|
146
215
|
|
|
216
|
+
### With another form library
|
|
217
|
+
|
|
218
|
+
The form row that these fields draw is `LabeledField` from the main entry, and it does not depend on TanStack Form. It takes the label, `labelTooltip`, `optional`, `description` and `layout` like the fields above, and the state of the field as props: `name`, `invalid`, `touched`, `dirty` and `errors`, a list of messages shown while `invalid` is true. The control inside it gets its id, `aria-invalid` and `aria-describedby` from the field, so it is wired the same way as in a TanStack form. `group` renders a fieldset with the label as its legend, for a group of controls such as radio buttons.
|
|
219
|
+
|
|
220
|
+
With react-hook-form, read the state with `useController` and pass it in:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
import { Input, LabeledField } from '@api7/ui-components';
|
|
224
|
+
import { useController } from 'react-hook-form';
|
|
225
|
+
|
|
226
|
+
export function NameField() {
|
|
227
|
+
const { field, fieldState } = useController({ name: 'name', rules: { required: 'Name is required' } });
|
|
228
|
+
return (
|
|
229
|
+
<LabeledField
|
|
230
|
+
label="Name"
|
|
231
|
+
name={field.name}
|
|
232
|
+
invalid={fieldState.invalid}
|
|
233
|
+
touched={fieldState.isTouched}
|
|
234
|
+
dirty={fieldState.isDirty}
|
|
235
|
+
errors={fieldState.error?.message ? [fieldState.error.message] : []}
|
|
236
|
+
>
|
|
237
|
+
<Input ref={field.ref} value={field.value} onValueChange={field.onChange} onBlur={field.onBlur} />
|
|
238
|
+
</LabeledField>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`invalid` decides when the error shows. The fields of `/form` show it once the field is touched, and react-hook-form sets `fieldState.invalid` after its own validation mode runs, on submit by default.
|
|
244
|
+
|
|
147
245
|
## Joining a field and a button
|
|
148
246
|
|
|
149
247
|
`InputGroup` joins an `Input` with a `Button` or an `InputGroupAddon` into one control. The group draws no border, ring or state of its own. Each part keeps its own border, hover, focus, invalid and disabled styling, and they touch with one divider between them, so the field and the button read as one. Only the outer corners of the group are round. An `Input` and a `Button` that stand alone are not changed.
|
|
@@ -175,9 +273,63 @@ export function HostField() {
|
|
|
175
273
|
|
|
176
274
|
The parts are the direct children of the group, in any order. An `InputGroupAddon` is a block with the muted background of the legacy console for text, an icon or a button, and a `Button` inside it loses its own surface. The state belongs to the part that has it: a focused field is framed alone, a button keeps the focus it has when it stands alone (a ring for the keyboard, none for a click), and an invalid field (`aria-invalid`, or a `Field` that is invalid) has the invalid border on the field only, as a disabled field has the disabled background on the field only. An outline button has the border colour of an input. A button with a fill (`primary`, `secondary` or `destructive`) keeps its own edge: at rest the border of the field toward it is transparent, so no line crosses the fill. The group is 32px high like an `Input`, and the parts stretch to that height, so they take no height of their own. Put the group inside a `Field` and a `FieldLabel` labels the field in it as it does any `Input`.
|
|
177
275
|
|
|
276
|
+
## Description lists
|
|
277
|
+
|
|
278
|
+
`DescriptionList` shows read-only label and value pairs, such as the fields of a detail page. It is composed from `DescriptionListItem`, `DescriptionListLabel` and `DescriptionListValue`, and there is no data array prop. To render a list of records, map over them where you use it, and filter out the ones you do not want to show there too.
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
import {
|
|
282
|
+
DescriptionList,
|
|
283
|
+
DescriptionListItem,
|
|
284
|
+
DescriptionListLabel,
|
|
285
|
+
DescriptionListValue,
|
|
286
|
+
Tooltip,
|
|
287
|
+
TooltipContent,
|
|
288
|
+
TooltipTrigger,
|
|
289
|
+
} from '@api7/ui-components';
|
|
290
|
+
import { Info } from 'lucide-react';
|
|
291
|
+
|
|
292
|
+
export function GatewayDetails({ gateway }: { gateway: { id: string; name: string; verifyTls: boolean } }) {
|
|
293
|
+
return (
|
|
294
|
+
<DescriptionList variant="outline" columns={2}>
|
|
295
|
+
<DescriptionListItem colSpan={2}>
|
|
296
|
+
<DescriptionListLabel>ID</DescriptionListLabel>
|
|
297
|
+
<DescriptionListValue>{gateway.id}</DescriptionListValue>
|
|
298
|
+
</DescriptionListItem>
|
|
299
|
+
<DescriptionListItem>
|
|
300
|
+
<DescriptionListLabel>Name</DescriptionListLabel>
|
|
301
|
+
<DescriptionListValue>{gateway.name}</DescriptionListValue>
|
|
302
|
+
</DescriptionListItem>
|
|
303
|
+
<DescriptionListItem>
|
|
304
|
+
<DescriptionListLabel>
|
|
305
|
+
Verify TLS
|
|
306
|
+
<Tooltip>
|
|
307
|
+
<TooltipTrigger aria-label="About Verify TLS">
|
|
308
|
+
<Info />
|
|
309
|
+
</TooltipTrigger>
|
|
310
|
+
<TooltipContent>Verify the certificate of the upstream server.</TooltipContent>
|
|
311
|
+
</Tooltip>
|
|
312
|
+
</DescriptionListLabel>
|
|
313
|
+
<DescriptionListValue>{gateway.verifyTls ? 'Enabled' : 'Disabled'}</DescriptionListValue>
|
|
314
|
+
</DescriptionListItem>
|
|
315
|
+
</DescriptionList>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`variant` is `default`, a list without borders, or `outline`, which reproduces the bordered grid of the legacy console detail pages with a tinted label column. `columns` is how many label and value pairs sit on one row and defaults to 1. An item takes a `colSpan` to span several of them. A fractional `columns` or `colSpan` is rounded down, one that is below 1 or not a number counts as 1, and a `colSpan` larger than `columns` is reduced to `columns`.
|
|
321
|
+
|
|
322
|
+
Things worth knowing before you build on it:
|
|
323
|
+
|
|
324
|
+
- The markup is a `dl`, and every item is a `div` that holds one `dt` and one `dd`. The label and the value are adjacent siblings, so a test can take the value of a label with its next sibling. There is no row element, because the browser lays the items out with CSS grid, so select the items, not rows.
|
|
325
|
+
- A row that is not filled, for example the third item with `columns={2}`, leaves the rest of the row empty. The last item does not stretch.
|
|
326
|
+
- The label column is 160px wide, as in the legacy console. Set `--description-list-label-width` in `style` or `className` on the list to change it.
|
|
327
|
+
- A value can hold any content. It grows with its content and long text wraps, so do not give a value a fixed height.
|
|
328
|
+
- A tooltip in a label is a `Tooltip` you place there, and its trigger needs an accessible name, as it does anywhere else.
|
|
329
|
+
|
|
178
330
|
## Theming
|
|
179
331
|
|
|
180
|
-
|
|
332
|
+
The library theme is the default theme of the application. The colors, the radii and the font are Tailwind theme variables, such as `--color-primary` and `--font-sans`, and the application overrides them in its own CSS after the import, as [Using it with your Tailwind CSS](#using-it-with-your-tailwind-css) explains. The light values are the values of the page, `[data-theme=light]` repeats them for a region, and `[data-theme=dark]` holds the dark values. The values themselves are in `src/tokens/semantic.ts`.
|
|
181
333
|
|
|
182
334
|
### Dark mode
|
|
183
335
|
|
|
@@ -187,20 +339,59 @@ The library only reads the attribute. It never follows `prefers-color-scheme`, n
|
|
|
187
339
|
|
|
188
340
|
`color-scheme` follows the theme as well, so scrollbars and native form controls match.
|
|
189
341
|
|
|
342
|
+
The library defines the Tailwind `dark` variant as "inside a `data-theme=dark` element", so `dark:bg-muted` in your own markup follows the same attribute. If the application declares its own `@custom-variant dark` after the import, for a `.dark` class or the media query, that declaration replaces the library one, and the `dark:` classes of the components follow it as well. The color variables still switch only with `data-theme`, so such an application gets the light tokens together with the `dark:` adjustments of the components, a mixed look. Set `data-theme` as well, or do not redefine the variant.
|
|
343
|
+
|
|
190
344
|
#### A region with another theme
|
|
191
345
|
|
|
192
|
-
`data-theme="light"` or `data-theme="dark"` on any element sets the color variables and the `color-scheme` of everything inside it, and the nearest ancestor wins, so a dark page can hold a light region and the other way round. This works for the color variables and for the `dark:` styles
|
|
346
|
+
`data-theme="light"` or `data-theme="dark"` on any element sets the color variables and the `color-scheme` of everything inside it, and the nearest ancestor wins, so a dark page can hold a light region and the other way round. This works for the color variables and for the `dark:` styles alike. The one case plain CSS cannot express is a dark element inside a light element inside a dark element: the innermost dark element is read as light in the `dark:` styles, though its variables are right.
|
|
193
347
|
|
|
194
348
|
The attribute does not paint a background or a text color, so a region that should look like a page of its own needs `bg-background text-foreground` (or your own rules) on it. A popup is rendered into `<body>`, so it follows `<html>`, not the region that opened it. To theme a popup, put the attribute on the popup element itself (`<PopoverContent data-theme="light">`, which works for the content of Popover, Menu, Select, Combobox, Tooltip, Dialog and Drawer, because each passes its props to the popup element), or on an element inside the popup's content, which then applies to what it wraps.
|
|
195
349
|
|
|
196
350
|
#### Writing dark styles in a component
|
|
197
351
|
|
|
198
|
-
A component styles itself with semantic tokens, and each token has a light and a dark value, so most components need nothing for dark mode. When a dark theme needs a different treatment than a token swap, a component may add
|
|
352
|
+
A component styles itself with semantic tokens, and each token has a light and a dark value, so most components need nothing for dark mode. When a dark theme needs a different treatment than a token swap, a component may add a `dark:` class together with a semantic token (`dark:bg-input/30`). Raw palette steps (`bg-blue-100`) are not used in a component at all, because the application's palette is the application's own. Badge is the one component with a color per palette family, and it reads the private variables `--badge-<color>-bg` and `--badge-<color>-fg`, which the theme sets for each theme from `BADGE_STEPS` in `src/tokens/contrast.ts`. A brand color has two tokens because a fill and a text color need different lightness in dark: `primary` and `destructive` are fills (with `primary-foreground` on top), while `primary-text` and `destructive-text` are for text and icons on a surface, such as the Button link variant, a selected Tab, error text or the destructive menu item. They equal the fill in light, so a page that colors text with `text-primary` passes in light and fails contrast in dark. Prefer a token to `dark:` wherever the token can carry the difference. `src/theme-classes.test.ts` enforces these rules. The values of the dark tokens are in `src/tokens/semantic.ts`, and every text and surface pair of both themes is tested for WCAG AA in `src/tokens/contrast.test.ts`.
|
|
353
|
+
|
|
354
|
+
### Using it with your Tailwind CSS
|
|
355
|
+
|
|
356
|
+
The components are ordinary Tailwind utilities, so there is one stylesheet and one cascade. The classes of the components are generated into the `utilities` layer of the application, next to its own utilities, and a `className` you pass to a component replaces the default classes that set the same property. Passing `p-6` to a component that has `px-3` leaves only `p-6`.
|
|
357
|
+
|
|
358
|
+
What the theme changes in the application:
|
|
359
|
+
|
|
360
|
+
- It adds the semantic colors to the Tailwind theme: `background`, `foreground`, `card`, `popover`, `primary`, `primary-text`, `muted`, `accent`, `destructive`, `success`, `warning`, `info`, `border`, `input`, `ring` and the others listed in `src/tokens/semantic.ts`. The names are common, so an application that already defines `--color-primary` or `--color-muted` gets the value of whichever declaration comes last.
|
|
361
|
+
- It sets `rounded-sm` to 2px and `rounded-md` to 4px, for the application's own markup as well. Other radii and the whole color palette stay as Tailwind defines them.
|
|
362
|
+
- It sets `--font-sans`. Tailwind's theme points `--default-font-family` at it, so Inter becomes the default font of the whole application, not only of the `font-sans` utility.
|
|
363
|
+
- It defines the `dark` variant, as described in [Dark mode](#dark-mode).
|
|
364
|
+
|
|
365
|
+
To change a color, redeclare the variable after the import, on the same selectors the library uses, so that both themes and the nested regions keep working:
|
|
199
366
|
|
|
200
|
-
|
|
367
|
+
```css
|
|
368
|
+
@import 'tailwindcss';
|
|
369
|
+
@import '@api7/ui-components';
|
|
201
370
|
|
|
202
|
-
|
|
371
|
+
:root,
|
|
372
|
+
[data-theme='light'] {
|
|
373
|
+
--color-primary: #0b5cff;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
[data-theme='dark'] {
|
|
377
|
+
--color-primary: #6ea0ff;
|
|
378
|
+
}
|
|
379
|
+
```
|
|
203
380
|
|
|
204
|
-
|
|
381
|
+
An `@theme` block with the same variable changes only the value of a page root that has no `data-theme` attribute. A `data-theme` region, including `<html data-theme="…">`, keeps the library value, because the library rules are not in a layer and Tailwind emits `@theme` values in the theme layer. Use `@theme` only for values that do not change with the theme, such as `--font-sans`.
|
|
205
382
|
|
|
206
|
-
|
|
383
|
+
## Popover arrows
|
|
384
|
+
|
|
385
|
+
`PopoverContent` has no arrow by default. Add `PopoverArrow` inside it for a pointer that follows the trigger when viewport collision handling shifts or flips the popup. The arrow uses the popover background token and accepts the Base UI arrow props, including `className` and `style` for appearance overrides.
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
import { Popover, PopoverArrow, PopoverContent, PopoverTrigger } from '@api7/ui-components';
|
|
389
|
+
|
|
390
|
+
<Popover>
|
|
391
|
+
<PopoverTrigger>View ID</PopoverTrigger>
|
|
392
|
+
<PopoverContent align="center" sideOffset={12}>
|
|
393
|
+
<PopoverArrow />
|
|
394
|
+
ID: service-production
|
|
395
|
+
</PopoverContent>
|
|
396
|
+
</Popover>;
|
|
397
|
+
```
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ComponentProps, ReactNode } from "react";
|
|
2
2
|
import { VariantProps } from "class-variance-authority";
|
|
3
|
-
//#region src/components/alert/alert.d.ts
|
|
4
3
|
declare const alertVariants: (props?: ({
|
|
5
4
|
variant?: "destructive" | "success" | "warning" | "info" | null | undefined;
|
|
6
5
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -12,5 +11,4 @@ export type AlertDescriptionProps = ComponentProps<'div'>;
|
|
|
12
11
|
declare function Alert({ variant, icon, className, children, ...props }: AlertProps): import("react").JSX.Element;
|
|
13
12
|
declare function AlertTitle({ className, ...props }: AlertTitleProps): import("react").JSX.Element;
|
|
14
13
|
declare function AlertDescription({ className, ...props }: AlertDescriptionProps): import("react").JSX.Element;
|
|
15
|
-
//#endregion
|
|
16
14
|
export { Alert, AlertDescription, AlertTitle, alertVariants };
|
|
@@ -2,13 +2,13 @@ import { cn } from "../../lib/cn.js";
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
4
|
import { CircleAlert, CircleCheck, Info, TriangleAlert } from "lucide-react";
|
|
5
|
-
|
|
6
|
-
const alertVariants = cva("
|
|
5
|
+
// src/components/alert/alert.tsx
|
|
6
|
+
const alertVariants = cva("relative flex w-full gap-2 rounded-sm px-3 py-2 text-sm text-foreground [&>svg]:mt-0.5 [&>svg]:size-4 [&>svg]:shrink-0", {
|
|
7
7
|
variants: { variant: {
|
|
8
|
-
info: "
|
|
9
|
-
success: "
|
|
10
|
-
warning: "
|
|
11
|
-
destructive: "
|
|
8
|
+
info: "bg-info-subtle [&>svg]:text-info",
|
|
9
|
+
success: "bg-success-subtle [&>svg]:text-success",
|
|
10
|
+
warning: "bg-warning-subtle [&>svg]:text-warning",
|
|
11
|
+
destructive: "bg-destructive-subtle [&>svg]:text-destructive-text"
|
|
12
12
|
} },
|
|
13
13
|
defaultVariants: { variant: "info" }
|
|
14
14
|
});
|
|
@@ -27,14 +27,14 @@ function Alert({ variant, icon, className, children, ...props }) {
|
|
|
27
27
|
...props,
|
|
28
28
|
"data-slot": "alert",
|
|
29
29
|
children: [icon === void 0 ? /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true" }) : icon || null, /* @__PURE__ */ jsx("div", {
|
|
30
|
-
className: "
|
|
30
|
+
className: "flex min-w-0 flex-col gap-0.5",
|
|
31
31
|
children
|
|
32
32
|
})]
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
function AlertTitle({ className, ...props }) {
|
|
36
36
|
return /* @__PURE__ */ jsx("div", {
|
|
37
|
-
className: cn("
|
|
37
|
+
className: cn("font-medium", className),
|
|
38
38
|
...props,
|
|
39
39
|
"data-slot": "alert-title"
|
|
40
40
|
});
|
|
@@ -46,5 +46,4 @@ function AlertDescription({ className, ...props }) {
|
|
|
46
46
|
"data-slot": "alert-description"
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
|
-
//#endregion
|
|
50
49
|
export { Alert, AlertDescription, AlertTitle, alertVariants };
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import { ComponentProps } from "react";
|
|
2
|
-
//#region src/components/avatar/avatar.d.ts
|
|
3
2
|
declare const SIZES: {
|
|
4
3
|
readonly sm: {
|
|
5
4
|
readonly px: 24;
|
|
6
|
-
readonly className: "
|
|
5
|
+
readonly className: "size-6 text-[10px]";
|
|
7
6
|
};
|
|
8
7
|
readonly md: {
|
|
9
8
|
readonly px: 28;
|
|
10
|
-
readonly className: "
|
|
9
|
+
readonly className: "size-7 text-xs";
|
|
11
10
|
};
|
|
12
11
|
readonly lg: {
|
|
13
12
|
readonly px: 40;
|
|
14
|
-
readonly className: "
|
|
13
|
+
readonly className: "size-10 text-base";
|
|
15
14
|
};
|
|
16
15
|
readonly xl: {
|
|
17
16
|
readonly px: 96;
|
|
18
|
-
readonly className: "
|
|
17
|
+
readonly className: "size-24 text-4xl";
|
|
19
18
|
};
|
|
20
19
|
};
|
|
21
20
|
export type AvatarSize = keyof typeof SIZES;
|
|
@@ -29,5 +28,4 @@ export type AvatarProps = Omit<ComponentProps<'span'>, 'children'> & {
|
|
|
29
28
|
alt?: string;
|
|
30
29
|
};
|
|
31
30
|
declare function Avatar({ src, name, size, alt, className, style, ...props }: AvatarProps): import("react").JSX.Element;
|
|
32
|
-
//#endregion
|
|
33
31
|
export { Avatar };
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { cn } from "../../lib/cn.js";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
|
-
|
|
4
|
+
// src/components/avatar/avatar.tsx
|
|
5
5
|
const SIZES = {
|
|
6
6
|
sm: {
|
|
7
7
|
px: 24,
|
|
8
|
-
className: "
|
|
8
|
+
className: "size-6 text-[10px]"
|
|
9
9
|
},
|
|
10
10
|
md: {
|
|
11
11
|
px: 28,
|
|
12
|
-
className: "
|
|
12
|
+
className: "size-7 text-xs"
|
|
13
13
|
},
|
|
14
14
|
lg: {
|
|
15
15
|
px: 40,
|
|
16
|
-
className: "
|
|
16
|
+
className: "size-10 text-base"
|
|
17
17
|
},
|
|
18
18
|
xl: {
|
|
19
19
|
px: 96,
|
|
20
|
-
className: "
|
|
20
|
+
className: "size-24 text-4xl"
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
function initialOf(name) {
|
|
@@ -36,7 +36,7 @@ function Avatar({ src, name, size = "md", alt = "", className, style, ...props }
|
|
|
36
36
|
return /* @__PURE__ */ jsx("span", {
|
|
37
37
|
"data-slot": "avatar",
|
|
38
38
|
"data-size": size,
|
|
39
|
-
className: cn("
|
|
39
|
+
className: cn("relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full font-medium text-white select-none", sizeClassName, className),
|
|
40
40
|
style: showImage ? style : {
|
|
41
41
|
backgroundColor: colourOf(name),
|
|
42
42
|
...style
|
|
@@ -51,7 +51,7 @@ function Avatar({ src, name, size = "md", alt = "", className, style, ...props }
|
|
|
51
51
|
loading: "lazy",
|
|
52
52
|
decoding: "async",
|
|
53
53
|
onError: () => setFailedSrc(src ?? null),
|
|
54
|
-
className: "
|
|
54
|
+
className: "size-full object-cover"
|
|
55
55
|
}) : /* @__PURE__ */ jsx("span", {
|
|
56
56
|
"data-slot": "avatar-initial",
|
|
57
57
|
"aria-hidden": "true",
|
|
@@ -59,5 +59,4 @@ function Avatar({ src, name, size = "md", alt = "", className, style, ...props }
|
|
|
59
59
|
})
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
-
//#endregion
|
|
63
62
|
export { Avatar };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ComponentProps } from "react";
|
|
2
2
|
import { VariantProps } from "class-variance-authority";
|
|
3
|
-
//#region src/components/badge/badge.d.ts
|
|
4
3
|
declare const badgeVariants: (props?: ({
|
|
5
4
|
color?: "gray" | "slate" | "blue" | "green" | "red" | "yellow" | "orange" | "cyan" | "indigo" | "purple" | "pink" | null | undefined;
|
|
6
5
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -10,5 +9,4 @@ export type BadgeProps = Omit<ComponentProps<'span'>, 'color'> & {
|
|
|
10
9
|
dot?: boolean;
|
|
11
10
|
};
|
|
12
11
|
declare function Badge({ color, dot, className, children, ...props }: BadgeProps): import("react").JSX.Element;
|
|
13
|
-
//#endregion
|
|
14
12
|
export { Badge, badgeVariants };
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { cn } from "../../lib/cn.js";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
|
-
|
|
5
|
-
const badgeVariants = cva("
|
|
4
|
+
// src/components/badge/badge.tsx
|
|
5
|
+
const badgeVariants = cva("inline-flex min-h-5 max-w-[500px] items-center gap-1.5 rounded-full px-2 text-xs font-medium", {
|
|
6
6
|
variants: { color: {
|
|
7
|
-
gray: "
|
|
8
|
-
slate: "
|
|
9
|
-
blue: "
|
|
10
|
-
green: "
|
|
11
|
-
red: "
|
|
12
|
-
yellow: "
|
|
13
|
-
orange: "
|
|
14
|
-
cyan: "
|
|
15
|
-
indigo: "
|
|
16
|
-
purple: "
|
|
17
|
-
pink: "
|
|
7
|
+
gray: "bg-(--badge-gray-bg) text-(color:--badge-gray-fg)",
|
|
8
|
+
slate: "bg-(--badge-slate-bg) text-(color:--badge-slate-fg)",
|
|
9
|
+
blue: "bg-(--badge-blue-bg) text-(color:--badge-blue-fg)",
|
|
10
|
+
green: "bg-(--badge-green-bg) text-(color:--badge-green-fg)",
|
|
11
|
+
red: "bg-(--badge-red-bg) text-(color:--badge-red-fg)",
|
|
12
|
+
yellow: "bg-(--badge-yellow-bg) text-(color:--badge-yellow-fg)",
|
|
13
|
+
orange: "bg-(--badge-orange-bg) text-(color:--badge-orange-fg)",
|
|
14
|
+
cyan: "bg-(--badge-cyan-bg) text-(color:--badge-cyan-fg)",
|
|
15
|
+
indigo: "bg-(--badge-indigo-bg) text-(color:--badge-indigo-fg)",
|
|
16
|
+
purple: "bg-(--badge-purple-bg) text-(color:--badge-purple-fg)",
|
|
17
|
+
pink: "bg-(--badge-pink-bg) text-(color:--badge-pink-fg)"
|
|
18
18
|
} },
|
|
19
19
|
defaultVariants: { color: "gray" }
|
|
20
20
|
});
|
|
@@ -26,13 +26,12 @@ function Badge({ color = "gray", dot = false, className, children, ...props }) {
|
|
|
26
26
|
"data-slot": "badge",
|
|
27
27
|
children: [dot && /* @__PURE__ */ jsx("span", {
|
|
28
28
|
"aria-hidden": "true",
|
|
29
|
-
className: "
|
|
29
|
+
className: "size-1.5 shrink-0 rounded-full bg-current",
|
|
30
30
|
"data-slot": "badge-dot"
|
|
31
31
|
}), /* @__PURE__ */ jsx("span", {
|
|
32
|
-
className: "
|
|
32
|
+
className: "truncate [&_img]:inline-block [&_svg]:inline-block [&_img]:align-text-bottom [&_svg]:align-text-bottom [&>img:first-child]:me-1.5 [&>svg:first-child]:me-1.5",
|
|
33
33
|
children
|
|
34
34
|
})]
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
-
//#endregion
|
|
38
37
|
export { Badge, badgeVariants };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { VariantProps } from "class-variance-authority";
|
|
2
2
|
import { Button } from "@base-ui/react/button";
|
|
3
|
-
//#region src/components/button/button.d.ts
|
|
4
3
|
declare const buttonVariants: (props?: ({
|
|
5
4
|
variant?: "primary" | "secondary" | "destructive" | "link" | "outline" | "ghost" | null | undefined;
|
|
6
5
|
size?: "sm" | "md" | "lg" | "icon" | null | undefined;
|
|
@@ -9,8 +8,7 @@ declare const buttonVariants: (props?: ({
|
|
|
9
8
|
* Base UI's Button enforces button semantics (`role="button"`, keyboard
|
|
10
9
|
* interaction, disabled state) on whatever `render` points at, so links must
|
|
11
10
|
* not be rendered through it. Style the anchor or router link element
|
|
12
|
-
* directly with `buttonVariants()` instead
|
|
13
|
-
* library's scoped CSS reset applies to it.
|
|
11
|
+
* directly with `buttonVariants()` instead.
|
|
14
12
|
*/
|
|
15
13
|
export interface ButtonProps extends Omit<Button.Props, 'className'>, VariantProps<typeof buttonVariants> {
|
|
16
14
|
className?: string;
|
|
@@ -18,5 +16,4 @@ export interface ButtonProps extends Omit<Button.Props, 'className'>, VariantPro
|
|
|
18
16
|
loadingLabel?: string;
|
|
19
17
|
}
|
|
20
18
|
declare function Button$1({ className, variant, size, loading, loadingLabel, disabled, focusableWhenDisabled, children, ...props }: ButtonProps): import("react").JSX.Element;
|
|
21
|
-
//#endregion
|
|
22
19
|
export { Button$1 as Button, buttonVariants };
|