@alisonquinter17/alieta-ui 0.1.0 → 0.2.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/LICENSE +21 -0
- package/README.md +277 -33
- package/dist/components/Buttons/Button.d.ts +10 -10
- package/dist/components/Icons/Icon.d.ts +70 -46
- package/dist/components/Icons/blumio-icons/BlumioSVG.d.ts +3 -1
- package/dist/components/Loader/Loader.d.ts +29 -7
- package/dist/index.d.ts +6 -1
- package/dist/index.js +210 -0
- package/dist/styles.css +1 -0
- package/dist/utils/cx.d.ts +7 -0
- package/package.json +71 -42
- package/dist/components/Buttons/Button.stories.d.ts +0 -54
- package/dist/components/Buttons/index.d.ts +0 -2
- package/dist/components/Icons/Icon.stories.d.ts +0 -51
- package/dist/components/Icons/index.d.ts +0 -2
- package/dist/components/Loader/Loader.stories.d.ts +0 -8
- package/dist/components/Loader/index.d.ts +0 -1
- package/dist/components/index.d.ts +0 -3
- package/dist/index.cjs.js +0 -22
- package/dist/index.es.js +0 -500
- package/src/components/Buttons/Button.stories.tsx +0 -126
- package/src/components/Buttons/Button.tsx +0 -79
- package/src/components/Buttons/index.ts +0 -2
- package/src/components/Icons/Icon.stories.tsx +0 -166
- package/src/components/Icons/Icon.tsx +0 -79
- package/src/components/Icons/blumio-icons/BlumioSVG.tsx +0 -47
- package/src/components/Icons/index.ts +0 -3
- package/src/components/Loader/Loader.stories.tsx +0 -55
- package/src/components/Loader/Loader.tsx +0 -43
- package/src/components/Loader/index.ts +0 -1
- package/src/components/index.tsx +0 -3
- package/src/index.css +0 -3
- package/src/index.tsx +0 -1
- package/src/types/ionicons.d.ts +0 -11
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Alison Quintero
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,54 +1,298 @@
|
|
|
1
|
-
|
|
1
|
+
# Alieta UI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A small React component library: `Button`, `Icon`, `Loader` and `Spinner`.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Alieta ships a **compiled stylesheet**, so you do not need Tailwind, a
|
|
6
|
+
`safelist`, or any build configuration to use it. Everything visual is driven by
|
|
7
|
+
CSS variables you can override.
|
|
8
|
+
|
|
9
|
+
> **Pre-1.0 — the API can still change.** While the version is `0.x`, a minor
|
|
10
|
+
> bump (`0.1.x` → `0.2.0`) may contain breaking changes. Pin an exact version if
|
|
11
|
+
> that matters to you.
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- React **18.2** or **19** (and the matching `react-dom`)
|
|
16
|
+
- Any React setup: Next.js, Vite, Remix, CRA — Alieta is framework-agnostic and
|
|
17
|
+
has **zero runtime dependencies**
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
6
20
|
|
|
7
21
|
```bash
|
|
8
|
-
npm
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
npm install @alisonquinter17/alieta-ui
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then import the stylesheet **once**, at the root of your app:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import "@alisonquinter17/alieta-ui/styles.css";
|
|
15
29
|
```
|
|
16
30
|
|
|
17
|
-
|
|
31
|
+
That is the entire setup. The stylesheet contains no reset and no Tailwind
|
|
32
|
+
Preflight, so it will not restyle your headings, lists or form controls. Every
|
|
33
|
+
class it defines is namespaced `alieta:*` and every variable `--alieta-*`, so it
|
|
34
|
+
cannot collide with your own CSS.
|
|
35
|
+
|
|
36
|
+
## Components
|
|
18
37
|
|
|
19
|
-
|
|
38
|
+
### Button
|
|
20
39
|
|
|
21
|
-
|
|
40
|
+
```tsx
|
|
41
|
+
import { Button } from "@alisonquinter17/alieta-ui";
|
|
42
|
+
|
|
43
|
+
<Button onClick={save}>Save</Button>
|
|
44
|
+
<Button variant="secondary" size="large">Cancel</Button>
|
|
45
|
+
<Button variant="destructive" onClick={remove}>Delete</Button>
|
|
46
|
+
<Button isLoading>Saving...</Button>
|
|
47
|
+
```
|
|
22
48
|
|
|
23
|
-
|
|
49
|
+
A real `<button>`: it forwards its ref, accepts every native button prop, and
|
|
50
|
+
defaults to `type="button"` so it never submits a form by accident.
|
|
24
51
|
|
|
25
|
-
|
|
52
|
+
| Prop | Type | Default | Notes |
|
|
53
|
+
| ----------- | ---------------------------------------------------------- | ----------- | ------------------------------------------------------------ |
|
|
54
|
+
| `variant` | `"primary" \| "secondary" \| "ghost" \| "destructive"` | `"primary"` | |
|
|
55
|
+
| `size` | `"small" \| "medium" \| "large"` | `"medium"` | |
|
|
56
|
+
| `icon` | `ReactNode` | — | Rendered before the label, exactly as given |
|
|
57
|
+
| `isLoading` | `boolean` | `false` | Disables the button and sets `aria-busy="true"` |
|
|
58
|
+
| `disabled` | `boolean` | `false` | `isLoading` also disables; `disabled={false}` cannot undo it |
|
|
26
59
|
|
|
27
|
-
|
|
28
|
-
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
60
|
+
Plus every prop of `React.ButtonHTMLAttributes<HTMLButtonElement>`.
|
|
29
61
|
|
|
30
|
-
|
|
62
|
+
`icon` takes any node — Alieta's `<Icon>`, your own SVG, or a component from an
|
|
63
|
+
icon package:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
<Button icon={<Icon name="blumio" iconSize="sm" />}>Bundled icon</Button>
|
|
67
|
+
<Button icon={<MyCheckIcon />}>Your own SVG</Button>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
An icon-only button needs its own accessible name:
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
<Button aria-label="Delete" icon={<TrashIcon />} />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Icon
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { Icon } from "@alisonquinter17/alieta-ui";
|
|
80
|
+
|
|
81
|
+
<Icon name="blumio" iconSize="xl" /> {/* decorative */}
|
|
82
|
+
<Icon name="blumio" iconSize="xl" label="Blumio" /> {/* meaningful */}
|
|
83
|
+
```
|
|
31
84
|
|
|
32
|
-
|
|
85
|
+
Icons are bundled React components — no CDN script, no `<ion-icon>` custom
|
|
86
|
+
element to register, no icon font. `name` is typed, so only icons that actually
|
|
87
|
+
ship can be requested.
|
|
33
88
|
|
|
34
|
-
|
|
89
|
+
| Prop | Type | Default | Notes |
|
|
90
|
+
| ---------- | --------------------------------------------------- | ------- | -------------------------------------------- |
|
|
91
|
+
| `name` | `"blumio"` | — | Typed; unknown names are rejected at compile time |
|
|
92
|
+
| `iconSize` | `"xs"` … `"6xl"` | `"md"` | 12px, 16px, 24px, 36px, 48px, 64px, 96px, 128px, 160px, 192px |
|
|
93
|
+
| `label` | `string` | — | Omit for decorative icons (they get `aria-hidden`); set it when the icon carries the meaning |
|
|
35
94
|
|
|
36
|
-
|
|
95
|
+
The library intentionally ships a **small** registry — right now just the Blumio
|
|
96
|
+
mark. For anything else, pass your own SVG straight to the component that needs
|
|
97
|
+
it. The mark uses `currentColor`, so it takes the colour of its container.
|
|
37
98
|
|
|
38
|
-
|
|
99
|
+
`Icon` is **presentational, not a control.** It renders a `<span>`, which is not
|
|
100
|
+
focusable and not reachable by keyboard, so it should never be used as a button.
|
|
101
|
+
The type enforces this: every event handler (`onClick`, `onKeyDown`,
|
|
102
|
+
`onPointerDown`, …), `role`, `tabIndex`, `accessKey`, `autoFocus`,
|
|
103
|
+
`contentEditable` and `draggable` are rejected at compile time. `children` and
|
|
104
|
+
`dangerouslySetInnerHTML` are rejected too, and so are `aria-label` and
|
|
105
|
+
`aria-hidden`, which `Icon` derives from `label`. `className`, `style`, `id`,
|
|
106
|
+
`title`, `data-*` and descriptive `aria-*` attributes are still accepted.
|
|
39
107
|
|
|
40
|
-
|
|
108
|
+
Any interaction belongs on a real control. For a clickable icon, put it inside a
|
|
109
|
+
`Button`:
|
|
41
110
|
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
111
|
+
```tsx
|
|
112
|
+
<Button aria-label="Delete" icon={<Icon name="blumio" iconSize="sm" />} />
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The same goes for interactive tooltips: the trigger must be a focusable element
|
|
116
|
+
such as a `Button`, not the icon itself, or keyboard users cannot open it.
|
|
117
|
+
|
|
118
|
+
A dedicated `IconButton` component is planned for a later release.
|
|
119
|
+
|
|
120
|
+
### Loader and Spinner
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { Loader, Spinner } from "@alisonquinter17/alieta-ui";
|
|
124
|
+
|
|
125
|
+
<Loader /> {/* full-viewport overlay */}
|
|
126
|
+
<Loader message="Loading data..." />
|
|
127
|
+
<Loader showOverlay={false} /> {/* fills its container */}
|
|
128
|
+
<Spinner label="Saving" /> {/* inline indicator */}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`Loader` is a single `role="status"` live region containing a decorative
|
|
132
|
+
spinner, so the busy state is announced exactly **once**.
|
|
133
|
+
|
|
134
|
+
| Prop (`Loader`) | Type | Default | Notes |
|
|
135
|
+
| --------------- | ----------- | ----------- | -------------------------------------------------------- |
|
|
136
|
+
| `message` | `ReactNode` | — | A plain string also becomes the accessible name |
|
|
137
|
+
| `showOverlay` | `boolean` | `true` | Renders as a `position: fixed` full-viewport layer |
|
|
138
|
+
| `label` | `string` | `"Loading"` | Accessible name when `message` is absent or not plain text |
|
|
139
|
+
|
|
140
|
+
**About the overlay:** it is a plain positioned element. It adds no global
|
|
141
|
+
styles and does **not** lock page scrolling — your app stays in control of that.
|
|
142
|
+
|
|
143
|
+
| Prop (`Spinner`) | Type | Default | Notes |
|
|
144
|
+
| ---------------- | ------------------------------------ | ----------- | ------------------------------------------------ |
|
|
145
|
+
| `size` | `"small" \| "medium" \| "large"` | `"medium"` | 16px, 20px, 32px |
|
|
146
|
+
| `label` | `string \| null` | `"Loading"` | `null` hides it from screen readers — use it next to something that already announces the busy state |
|
|
147
|
+
|
|
148
|
+
Both accept `className` and their native element props.
|
|
149
|
+
|
|
150
|
+
## Theming
|
|
151
|
+
|
|
152
|
+
Every colour and shape comes from a CSS variable with a sensible default.
|
|
153
|
+
Override the ones you care about in your own `:root` — unprefixed declarations
|
|
154
|
+
beat Tailwind's `@layer`, so you never need `!important`:
|
|
155
|
+
|
|
156
|
+
```css
|
|
157
|
+
:root {
|
|
158
|
+
--alieta-color-primary: #2f6f62;
|
|
159
|
+
--alieta-color-primary-hover: #255a4f;
|
|
160
|
+
--alieta-radius: 0.75rem;
|
|
51
161
|
}
|
|
52
162
|
```
|
|
53
163
|
|
|
54
|
-
|
|
164
|
+
You can scope an override to any subtree instead of the whole page:
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
<section style={{ "--alieta-color-primary": "#2f6f62" } as React.CSSProperties}>
|
|
168
|
+
<Button>Themed just in here</Button>
|
|
169
|
+
</section>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Tokens
|
|
173
|
+
|
|
174
|
+
| Token | Default | Used by |
|
|
175
|
+
| --------------------------------- | --------- | ---------------------------------------- |
|
|
176
|
+
| `--alieta-color-primary` | `#6d28d9` | Primary button background |
|
|
177
|
+
| `--alieta-color-primary-hover` | `#5b21b6` | Primary button hover |
|
|
178
|
+
| `--alieta-color-primary-contrast` | `#ffffff` | Text on the primary background |
|
|
179
|
+
| `--alieta-color-secondary` | `#6d28d9` | Secondary / ghost border and text |
|
|
180
|
+
| `--alieta-color-secondary-hover` | `#5b21b6` | Secondary / ghost hover |
|
|
181
|
+
| `--alieta-color-danger` | `#be123c` | Destructive button |
|
|
182
|
+
| `--alieta-color-danger-hover` | `#9f1239` | Destructive button hover |
|
|
183
|
+
| `--alieta-color-surface` | `#ffffff` | Loader overlay background |
|
|
184
|
+
| `--alieta-color-border` | `#d4d4d8` | Border of disabled outlined buttons |
|
|
185
|
+
| `--alieta-color-muted` | `#52525b` | Loader message text |
|
|
186
|
+
| `--alieta-color-disabled-surface` | `#e4e4e7` | Disabled button background |
|
|
187
|
+
| `--alieta-color-disabled-content` | `#71717a` | Disabled button text |
|
|
188
|
+
| `--alieta-radius` | `0.5rem` | Button corner radius |
|
|
189
|
+
| `--alieta-focus-ring` | `2px solid var(--alieta-color-primary)` | `:focus-visible` outline |
|
|
190
|
+
| `--alieta-focus-ring-offset` | `2px` | Gap between the ring and the element |
|
|
191
|
+
| `--alieta-text-body-s/m/l` | `0.875` / `0.9375` / `1rem` | Small / medium / large button text |
|
|
192
|
+
|
|
193
|
+
The defaults meet WCAG AA: white on `--alieta-color-primary` is 7.1:1, and
|
|
194
|
+
`--alieta-color-danger` on white is 6.3:1. If you change these colours, check
|
|
195
|
+
the contrast of your own pair.
|
|
196
|
+
|
|
197
|
+
## Usage in Next.js
|
|
198
|
+
|
|
199
|
+
Alieta is a plain React library — it does **not** depend on Next.js and will not
|
|
200
|
+
pull a copy of it into your project. Import the stylesheet in your root layout:
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
// app/layout.tsx
|
|
204
|
+
import "@alisonquinter17/alieta-ui/styles.css";
|
|
205
|
+
import "./globals.css"; // your own CSS last, so your token overrides win
|
|
206
|
+
|
|
207
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
208
|
+
return (
|
|
209
|
+
<html lang="en">
|
|
210
|
+
<body>{children}</body>
|
|
211
|
+
</html>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The components are plain client-side React. They have no `"use client"`
|
|
217
|
+
directive, so import them from a Client Component (or add the directive in your
|
|
218
|
+
own wrapper) when you need interactivity.
|
|
219
|
+
|
|
220
|
+
## Usage in Vite
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
// src/main.tsx
|
|
224
|
+
import "@alisonquinter17/alieta-ui/styles.css";
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Nothing else to configure.
|
|
228
|
+
|
|
229
|
+
## Local development
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
npm ci # install
|
|
233
|
+
npm run dev # Storybook on http://localhost:6006
|
|
234
|
+
npm run lint # ESLint
|
|
235
|
+
npm run typecheck # tsc --noEmit
|
|
236
|
+
npm test # Vitest, once
|
|
237
|
+
npm run test:watch # Vitest, watch mode
|
|
238
|
+
npm run build # build dist/ (JS, styles.css, .d.ts)
|
|
239
|
+
npm run build:storybook # static Storybook
|
|
240
|
+
npm run check # lint + typecheck + test + build
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Storybook
|
|
244
|
+
|
|
245
|
+
`npm run dev` (or `npm run storybook`) opens Storybook on port 6006. It loads
|
|
246
|
+
the same `styles.css` that ships in the package, so what you see there is what
|
|
247
|
+
consumers get. The a11y addon reports accessibility violations in its panel.
|
|
248
|
+
|
|
249
|
+
### Tests
|
|
250
|
+
|
|
251
|
+
Vitest with jsdom and Testing Library. No browser download is needed, so
|
|
252
|
+
`npm test` works the same locally and in CI.
|
|
253
|
+
|
|
254
|
+
### Build
|
|
255
|
+
|
|
256
|
+
Vite in library mode produces:
|
|
257
|
+
|
|
258
|
+
- `dist/index.js` — ESM. React and the JSX runtime stay external.
|
|
259
|
+
- `dist/styles.css` — the compiled stylesheet (Tailwind v4 with `prefix(alieta)`, no Preflight).
|
|
260
|
+
- `dist/**/*.d.ts` — type declarations, excluding stories and tests.
|
|
261
|
+
|
|
262
|
+
Tailwind is a **build-time** tool here. It never reaches the consumer.
|
|
263
|
+
|
|
264
|
+
## Publishing
|
|
265
|
+
|
|
266
|
+
`npm publish` is not run by hand. Publishing happens through GitHub Actions
|
|
267
|
+
using npm **Trusted Publishing** (OIDC) — there is no `NPM_TOKEN` and no secret
|
|
268
|
+
anywhere in the repository.
|
|
269
|
+
|
|
270
|
+
1. Update the version with `npm version <patch|minor|major>` and push the commit
|
|
271
|
+
and tag.
|
|
272
|
+
2. Create a **GitHub Release** for that tag.
|
|
273
|
+
3. Publishing the release runs `.github/workflows/publish.yml`, which installs,
|
|
274
|
+
lints, typechecks, tests, builds, and only then publishes. If any step fails,
|
|
275
|
+
nothing is published.
|
|
276
|
+
|
|
277
|
+
### One-time npm setup
|
|
278
|
+
|
|
279
|
+
On npmjs.com, under the package's **Settings → Trusted Publisher**, register
|
|
280
|
+
exactly:
|
|
281
|
+
|
|
282
|
+
| Field | Value |
|
|
283
|
+
| ----------------- | ----------------- |
|
|
284
|
+
| GitHub owner | `AlisonQuinter17` |
|
|
285
|
+
| Repository | `Alieta-UI` |
|
|
286
|
+
| Workflow filename | `publish.yml` |
|
|
287
|
+
| Allowed action | `npm publish` |
|
|
288
|
+
|
|
289
|
+
Leave the environment field empty (the workflow does not use one).
|
|
290
|
+
|
|
291
|
+
## Versioning
|
|
292
|
+
|
|
293
|
+
[Semantic Versioning](https://semver.org). While the package is `0.x`, minor
|
|
294
|
+
releases may break the API — see the pre-1.0 note at the top.
|
|
295
|
+
|
|
296
|
+
## License
|
|
297
|
+
|
|
298
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { IconSize } from '../Icons/Icon';
|
|
1
|
+
import { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
3
2
|
export type ButtonVariant = "primary" | "secondary" | "ghost" | "destructive";
|
|
4
3
|
export type ButtonSize = "small" | "medium" | "large";
|
|
5
|
-
export interface ButtonProps extends
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
iconSize?: IconSize;
|
|
10
|
-
padding?: string;
|
|
4
|
+
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
5
|
+
/** Visual style of the button. */
|
|
6
|
+
variant?: ButtonVariant;
|
|
7
|
+
/** Controls height, padding and font size. */
|
|
11
8
|
size?: ButtonSize;
|
|
9
|
+
/** Element rendered before the label. Decorative — give the button a text
|
|
10
|
+
* label, or an `aria-label` when it is icon-only. */
|
|
11
|
+
icon?: ReactNode;
|
|
12
|
+
/** Swaps the icon for a spinner and disables the button. */
|
|
12
13
|
isLoading?: boolean;
|
|
13
|
-
type?: "button" | "submit" | "reset";
|
|
14
14
|
}
|
|
15
|
-
export declare const Button:
|
|
15
|
+
export declare const Button: import('react').ForwardRefExoticComponent<ButtonProps & import('react').RefAttributes<HTMLButtonElement>>;
|
|
@@ -1,51 +1,75 @@
|
|
|
1
|
+
import { DOMAttributes, HTMLAttributes } from 'react';
|
|
2
|
+
import { BlumioSVG } from './blumio-icons/BlumioSVG';
|
|
3
|
+
/**
|
|
4
|
+
* Every icon Alieta ships. Icons are bundled as React components — there is no
|
|
5
|
+
* CDN script, no custom element to register and no icon font to load.
|
|
6
|
+
*
|
|
7
|
+
* To render an icon that is not in here, pass your own SVG straight to the
|
|
8
|
+
* component that needs it (for example `<Button icon={<MySvg />} />`).
|
|
9
|
+
*/
|
|
10
|
+
declare const icons: {
|
|
11
|
+
blumio: typeof BlumioSVG;
|
|
12
|
+
};
|
|
13
|
+
export type IconName = keyof typeof icons;
|
|
14
|
+
/** Sizes are the CSS lengths applied to the icon box. */
|
|
1
15
|
declare const iconSizes: {
|
|
2
|
-
readonly xs:
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
readonly
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
readonly
|
|
11
|
-
|
|
12
|
-
readonly height: "1.5rem";
|
|
13
|
-
};
|
|
14
|
-
readonly lg: {
|
|
15
|
-
readonly width: "2.25rem";
|
|
16
|
-
readonly height: "2.25rem";
|
|
17
|
-
};
|
|
18
|
-
readonly xl: {
|
|
19
|
-
readonly width: "3rem";
|
|
20
|
-
readonly height: "3rem";
|
|
21
|
-
};
|
|
22
|
-
readonly "2xl": {
|
|
23
|
-
readonly width: "4rem";
|
|
24
|
-
readonly height: "4rem";
|
|
25
|
-
};
|
|
26
|
-
readonly "3xl": {
|
|
27
|
-
readonly width: "6rem";
|
|
28
|
-
readonly height: "6rem";
|
|
29
|
-
};
|
|
30
|
-
readonly "4xl": {
|
|
31
|
-
readonly width: "8rem";
|
|
32
|
-
readonly height: "8rem";
|
|
33
|
-
};
|
|
34
|
-
readonly "5xl": {
|
|
35
|
-
readonly width: "10rem";
|
|
36
|
-
readonly height: "10rem";
|
|
37
|
-
};
|
|
38
|
-
readonly "6xl": {
|
|
39
|
-
readonly width: "12rem";
|
|
40
|
-
readonly height: "12rem";
|
|
41
|
-
};
|
|
16
|
+
readonly xs: "alieta:size-3";
|
|
17
|
+
readonly sm: "alieta:size-4";
|
|
18
|
+
readonly md: "alieta:size-6";
|
|
19
|
+
readonly lg: "alieta:size-9";
|
|
20
|
+
readonly xl: "alieta:size-12";
|
|
21
|
+
readonly "2xl": "alieta:size-16";
|
|
22
|
+
readonly "3xl": "alieta:size-24";
|
|
23
|
+
readonly "4xl": "alieta:size-32";
|
|
24
|
+
readonly "5xl": "alieta:size-40";
|
|
25
|
+
readonly "6xl": "alieta:size-48";
|
|
42
26
|
};
|
|
43
27
|
export type IconSize = keyof typeof iconSizes;
|
|
44
|
-
|
|
45
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The props `Icon` deliberately refuses.
|
|
30
|
+
*
|
|
31
|
+
* A `<span>` is not focusable and not reachable by keyboard, so wiring it up as
|
|
32
|
+
* a control produces something a mouse user can operate and nobody else can.
|
|
33
|
+
* Rather than document that as a caveat, the type makes it impossible:
|
|
34
|
+
*
|
|
35
|
+
* - Every event handler (`onClick`, `onKeyDown`, `onPointerDown`, …) —
|
|
36
|
+
* interaction belongs on a real control. Use `Button` today, or the planned
|
|
37
|
+
* `IconButton`.
|
|
38
|
+
* - `role`, `tabIndex`, `accessKey`, `autoFocus`, `contentEditable`,
|
|
39
|
+
* `draggable` — the escape hatches people reach for to fake a control out
|
|
40
|
+
* of a `<span>`.
|
|
41
|
+
* - `aria-label` / `aria-hidden` — `Icon` derives both from the `label` prop,
|
|
42
|
+
* so they cannot drift out of sync with its `role`.
|
|
43
|
+
* - `children` / `dangerouslySetInnerHTML` — the content is always the
|
|
44
|
+
* bundled SVG named by `name`.
|
|
45
|
+
*/
|
|
46
|
+
type EventHandlerProps = Exclude<keyof DOMAttributes<HTMLSpanElement>, "children" | "dangerouslySetInnerHTML">;
|
|
47
|
+
type RefusedProps = EventHandlerProps | "children" | "dangerouslySetInnerHTML" | "role" | "tabIndex" | "accessKey" | "autoFocus" | "contentEditable" | "draggable" | "aria-label" | "aria-hidden";
|
|
48
|
+
/**
|
|
49
|
+
* Renders a bundled icon inside a `<span>`. Strictly presentational.
|
|
50
|
+
*
|
|
51
|
+
* Any interaction must use a real control — `Button` today, or the planned
|
|
52
|
+
* `IconButton`:
|
|
53
|
+
*
|
|
54
|
+
* <Button aria-label="Delete" icon={<Icon name="blumio" iconSize="sm" />} />
|
|
55
|
+
*
|
|
56
|
+
* The same applies to interactive tooltips: the trigger must be a focusable
|
|
57
|
+
* element (a `Button`), not the icon itself, or keyboard users cannot reach it.
|
|
58
|
+
*
|
|
59
|
+
* `className`, `style`, `id`, `title`, `data-*` and descriptive `aria-*`
|
|
60
|
+
* attributes (`aria-describedby`, …) are still accepted.
|
|
61
|
+
*/
|
|
62
|
+
export interface IconProps extends Omit<HTMLAttributes<HTMLSpanElement>, RefusedProps> {
|
|
63
|
+
/** Which bundled icon to render. */
|
|
64
|
+
name: IconName;
|
|
65
|
+
/** @default "md" */
|
|
46
66
|
iconSize?: IconSize;
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Accessible name. Leave it out for decorative icons — the icon is then
|
|
69
|
+
* hidden from assistive technology, which is the right default next to a
|
|
70
|
+
* text label. Set it when the icon is the only thing conveying meaning.
|
|
71
|
+
*/
|
|
72
|
+
label?: string;
|
|
49
73
|
}
|
|
50
|
-
declare
|
|
51
|
-
export
|
|
74
|
+
export declare function Icon({ name, iconSize, label, className, ...rest }: IconProps): import("react").JSX.Element | null;
|
|
75
|
+
export {};
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
import { SVGProps } from 'react';
|
|
2
|
+
/** Blumio brand mark. Paths inherit `currentColor`, so it can be themed. */
|
|
3
|
+
export declare function BlumioSVG(props: SVGProps<SVGSVGElement>): import("react").JSX.Element;
|
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
|
|
1
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export type SpinnerSize = "small" | "medium" | "large";
|
|
3
|
+
export interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {
|
|
4
|
+
/** @default "medium" */
|
|
5
|
+
size?: SpinnerSize;
|
|
6
|
+
/**
|
|
7
|
+
* Accessible name announced by screen readers.
|
|
8
|
+
* Pass `null` when the spinner sits next to something that already announces
|
|
9
|
+
* the busy state (a button with `aria-busy`, a `<Loader>`), so the state is not
|
|
10
|
+
* read out twice.
|
|
11
|
+
* @default "Loading"
|
|
12
|
+
*/
|
|
13
|
+
label?: string | null;
|
|
14
|
+
}
|
|
15
|
+
export declare function Spinner({ size, label, className, ...rest }: SpinnerProps): import("react").JSX.Element;
|
|
16
|
+
export interface LoaderProps extends HTMLAttributes<HTMLDivElement> {
|
|
17
|
+
/** Optional text rendered under the spinner. */
|
|
3
18
|
message?: ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* Renders the loader as a fixed, full-viewport overlay above the page.
|
|
21
|
+
* The overlay is a plain positioned element: it adds no global styles and
|
|
22
|
+
* does not lock scrolling — the host application stays in control.
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
4
25
|
showOverlay?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Accessible name, used when `message` is absent or is not plain text.
|
|
28
|
+
* @default "Loading"
|
|
29
|
+
*/
|
|
30
|
+
label?: string;
|
|
5
31
|
}
|
|
6
|
-
export declare
|
|
7
|
-
export declare const Spinner: ({ className }: {
|
|
8
|
-
className?: string;
|
|
9
|
-
}) => import("react/jsx-runtime").JSX.Element;
|
|
10
|
-
export {};
|
|
32
|
+
export declare function Loader({ message, showOverlay, label, className, ...rest }: LoaderProps): import("react").JSX.Element;
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { Button } from './components/Buttons/Button';
|
|
2
|
+
export type { ButtonProps, ButtonSize, ButtonVariant } from './components/Buttons/Button';
|
|
3
|
+
export { Icon } from './components/Icons/Icon';
|
|
4
|
+
export type { IconName, IconProps, IconSize } from './components/Icons/Icon';
|
|
5
|
+
export { Loader, Spinner } from './components/Loader/Loader';
|
|
6
|
+
export type { LoaderProps, SpinnerProps, SpinnerSize } from './components/Loader/Loader';
|