@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.
Files changed (34) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +277 -33
  3. package/dist/components/Buttons/Button.d.ts +10 -10
  4. package/dist/components/Icons/Icon.d.ts +70 -46
  5. package/dist/components/Icons/blumio-icons/BlumioSVG.d.ts +3 -1
  6. package/dist/components/Loader/Loader.d.ts +29 -7
  7. package/dist/index.d.ts +6 -1
  8. package/dist/index.js +210 -0
  9. package/dist/styles.css +1 -0
  10. package/dist/utils/cx.d.ts +7 -0
  11. package/package.json +71 -42
  12. package/dist/components/Buttons/Button.stories.d.ts +0 -54
  13. package/dist/components/Buttons/index.d.ts +0 -2
  14. package/dist/components/Icons/Icon.stories.d.ts +0 -51
  15. package/dist/components/Icons/index.d.ts +0 -2
  16. package/dist/components/Loader/Loader.stories.d.ts +0 -8
  17. package/dist/components/Loader/index.d.ts +0 -1
  18. package/dist/components/index.d.ts +0 -3
  19. package/dist/index.cjs.js +0 -22
  20. package/dist/index.es.js +0 -500
  21. package/src/components/Buttons/Button.stories.tsx +0 -126
  22. package/src/components/Buttons/Button.tsx +0 -79
  23. package/src/components/Buttons/index.ts +0 -2
  24. package/src/components/Icons/Icon.stories.tsx +0 -166
  25. package/src/components/Icons/Icon.tsx +0 -79
  26. package/src/components/Icons/blumio-icons/BlumioSVG.tsx +0 -47
  27. package/src/components/Icons/index.ts +0 -3
  28. package/src/components/Loader/Loader.stories.tsx +0 -55
  29. package/src/components/Loader/Loader.tsx +0 -43
  30. package/src/components/Loader/index.ts +0 -1
  31. package/src/components/index.tsx +0 -3
  32. package/src/index.css +0 -3
  33. package/src/index.tsx +0 -1
  34. 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
- This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
1
+ # Alieta UI
2
2
 
3
- ## Getting Started
3
+ A small React component library: `Button`, `Icon`, `Loader` and `Spinner`.
4
4
 
5
- First, run the development server:
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 run dev
9
- # or
10
- yarn dev
11
- # or
12
- pnpm dev
13
- # or
14
- bun dev
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
- Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
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
- You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
38
+ ### Button
20
39
 
21
- This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
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
- ## Learn More
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
- To learn more about Next.js, take a look at the following resources:
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
- - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
- - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
60
+ Plus every prop of `React.ButtonHTMLAttributes<HTMLButtonElement>`.
29
61
 
30
- You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
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
- ## Deploy on Vercel
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
- The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
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
- Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
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
- ## 🎨 TailwindCSS Configuration
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
- To ensure styles are properly applied, add the following to your `tailwind.config.js`:
108
+ Any interaction belongs on a real control. For a clickable icon, put it inside a
109
+ `Button`:
41
110
 
42
- ```javascript
43
- module.exports = {
44
- content: [
45
- // Your existing content paths...
46
- "./src/**/*.{js,ts,jsx,tsx}",
47
- // Add this line to include Alieta UI source files
48
- "./node_modules/@alisonquinter17/alieta-ui/src/**/*.{js,ts,jsx,tsx}",
49
- ],
50
- // ... rest of your config
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
- **Important**: Make sure your project has TailwindCSS installed and configured. Alieta UI components use TailwindCSS utility classes and require the consumer project to process these classes.
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 { HTMLProps, PropsWithChildren, ReactNode } from 'react';
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 Omit<HTMLProps<HTMLButtonElement>, "size"> {
6
- variant: ButtonVariant;
7
- icon?: ReactNode;
8
- iconStyle?: React.CSSProperties;
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: React.FC<PropsWithChildren<ButtonProps>>;
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
- readonly width: "0.75rem";
4
- readonly height: "0.75rem";
5
- };
6
- readonly sm: {
7
- readonly width: "1rem";
8
- readonly height: "1rem";
9
- };
10
- readonly md: {
11
- readonly width: "1.5rem";
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
- export interface IconProps {
45
- name: string;
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
- iconStyle?: React.CSSProperties;
48
- className?: string;
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 const Icon: React.FC<React.HTMLProps<HTMLSpanElement> & IconProps>;
51
- export default Icon;
74
+ export declare function Icon({ name, iconSize, label, className, ...rest }: IconProps): import("react").JSX.Element | null;
75
+ export {};
@@ -1 +1,3 @@
1
- export declare const BlumioSVG: React.ComponentType<React.SVGProps<SVGSVGElement>>;
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
- interface LoaderProps {
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 const Loader: React.FC<LoaderProps>;
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 * from './components';
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';