@madecki/ui 1.5.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +99 -12
- package/cursor-rule-template.md +1 -1
- package/dist/index.cjs +362 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -8
- package/dist/index.d.ts +79 -8
- package/dist/index.js +361 -69
- package/dist/index.js.map +1 -1
- package/llm-context.md +31 -13
- package/package.json +5 -2
package/llm-context.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
This file provides context for AI assistants building apps with the @madecki/ui component library.
|
|
4
4
|
|
|
5
|
+
## Changelog and versioning
|
|
6
|
+
|
|
7
|
+
When upgrading **`@madecki/ui`**, read **`CHANGELOG.md`** for breaking changes, new components, and API adjustments. Release notes are **generated on publish** from [Conventional Commits](https://www.conventionalcommits.org/) on **`main`** only (not hand-edited). The **latest** npm version is always the **topmost** `## [x.y.z]` section and should match **`package.json` `"version"`** in the installed tarball. The same file ships with the package and is addressable as:
|
|
8
|
+
|
|
9
|
+
- Path: `node_modules/@madecki/ui/CHANGELOG.md`
|
|
10
|
+
- Export: `@madecki/ui/CHANGELOG.md`
|
|
11
|
+
|
|
12
|
+
Always align code with the **semver** of the version you install (**major** bumps may include breaking changes).
|
|
13
|
+
|
|
5
14
|
## Setup (Required — do this before any UI work)
|
|
6
15
|
|
|
7
16
|
### globals.css
|
|
@@ -36,7 +45,7 @@ When building UI, ALWAYS check if @madecki/ui has a component before creating cu
|
|
|
36
45
|
|
|
37
46
|
### Typography
|
|
38
47
|
- `Heading` - Headings h1-h6. Props: `level?: 1-6`, `size?: "xs"|"sm"|"md"|"lg"|"xl"|"2xl"|"3xl"|"4xl"`, `weight?: "normal"|"medium"|"semibold"|"bold"`, `color?: "default"|"muted"|"primary"|"success"|"warning"|"danger"`
|
|
39
|
-
- `Text` - Body text. Props: `as?: "p"|"span"|"div"|"label"`, `size?: "xs"|"sm"|"md"|"lg"`, `weight?: "normal"|"medium"|"semibold"|"bold"`, `color?: "default"|"muted"|"primary"|"success"|"warning"|"danger"`
|
|
48
|
+
- `Text` - Body text. Props: `id?`, `as?: "p"|"span"|"div"|"label"`, `size?: "xs"|"sm"|"md"|"lg"`, `weight?: "normal"|"medium"|"semibold"|"bold"`, `color?: "default"|"muted"|"primary"|"success"|"warning"|"danger"`
|
|
40
49
|
|
|
41
50
|
### Buttons & tags
|
|
42
51
|
- `Button` - Primary button. Shares visual styling with `Tag`. Interactive: `cursor-pointer` when enabled, hover affordances in dark mode, `cursor-not-allowed` when `disabled`. Props: `variant: "primary"|"success"|"warning"|"danger"|"info"|"blue"`, `size?: "xs"|"sm"|"md"|"lg"`, `isActive?: boolean` (requires `id` prop; shows × icon; when active, `onClick` is called with no argument — use this to detect deselect), `label?: string`, `id?: string`, `disabled?: boolean`, `type?: "button"|"submit"|"reset"`
|
|
@@ -44,11 +53,16 @@ When building UI, ALWAYS check if @madecki/ui has a component before creating cu
|
|
|
44
53
|
- `Tag` - Non-interactive label/chip: same `variant` / `size` / look as `Button`, rendered as `<span>`. No `:hover` styles; default cursor (or `cursor-default` when `muted`). Props: `variant`, `size?`, `label?` / `children`, `className?`, `filled?` (filled background like an active button, without the × icon), `muted?` (reduced emphasis, same opacity treatment as a disabled button). Use `Button` when the control must be clickable.
|
|
45
54
|
- `ButtonTransparent` - Outlined button. Same props as Button.
|
|
46
55
|
- `GradientButton` - Gradient border button. Props: `size?: "sm"|"md"|"lg"`
|
|
47
|
-
- `RadioButtons` -
|
|
56
|
+
- `RadioButtons` - Segmented control styled as buttons; exposed as a **radiogroup** with `role="radio"` / `aria-checked` on each option. Props: **`label`** (group label, visible by default), **`labelVisibility?: "visible" | "sr-only"`** (default `"visible"`), `items: Omit<ButtonProps, "size"|"onClick"|"isActive"|"role"|"ariaChecked">[]` (each item needs `id` and `variant`, plus `label` or `children`), `onChange: (value: string) => void`, `size?`, `className?`
|
|
48
57
|
|
|
49
58
|
### Forms
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
Shared: **`labelVisibility?: "visible" | "sr-only"`** (default **`"visible"`**) on `Input`, `Textarea`, `Select`, and `RadioButtons` — use **`"sr-only"`** when the label must stay in the DOM for assistive tech but not be shown visually.
|
|
60
|
+
|
|
61
|
+
- `Input` - Text input. Props: `name`, `label`, `labelVisibility?`, `onChange?: (value: string) => void`, optional **`value`** (controlled) or **`defaultValue`** (uncontrolled initial only), `placeholder?`, **`type?`** (any standard HTML input type from React’s `HTMLInputTypeAttribute`, including `date`, `time`, `datetime-local`, etc.), **`maxLength?`**, `variant?`, `required?`, `pattern?`, `title?`, **`ariaLabel?`** (optional override; omit when the visible `<label>` is enough), `spellCheck?`, `disabled?`, `className?`, `icon?`, **`testId?`** (`data-testid` on the native `<input>` for Playwright). See below for controlled vs uncontrolled and testing.
|
|
62
|
+
- `Textarea` - Multi-line field, same variants and label pattern as `Input`. Props: `name`, `label`, `labelVisibility?`, `onChange?`, `value?`, `defaultValue?`, `placeholder?`, `variant?`, `rows?` (default 4), `maxLength?`, `required?`, `ariaLabel?`, `spellCheck?`, `disabled?`, `className?`, `testId?`.
|
|
63
|
+
- `Select` - Combobox styled like `Input`, with trailing chevron. Props: `name`, `label`, `labelVisibility?`, `options: { value, label }[]`, `placeholder?`, `variant?` (same as Input), `disabled?`, `className?`, `testId?`. Typing filters the list. **Single (default):** `value?`, `defaultValue?`, `onChange?: (value: string) => void`. **Multi:** `multi={true}`, `value?`, `defaultValue?`, `onChange?: (value: string[]) => void`. The dropdown is absolutely positioned under the field so it does not shift surrounding layout. The listbox is named via **`aria-labelledby`** pointing at the field label. See below for controlled vs uncontrolled and testing.
|
|
64
|
+
|
|
65
|
+
Export **`LabelVisibility`** from `@madecki/ui` for typing `labelVisibility` props.
|
|
52
66
|
|
|
53
67
|
### Navigation
|
|
54
68
|
- `Tabs` - Tab navigation. Props: `tabs: {label, value}[]`, `onTabClick: (value) => void`
|
|
@@ -56,7 +70,9 @@ When building UI, ALWAYS check if @madecki/ui has a component before creating cu
|
|
|
56
70
|
### Feedback
|
|
57
71
|
- `Spinner` - Loading spinner. Props: `size?: "sm"|"md"|"lg"`
|
|
58
72
|
- `SpinnerOverlay` - Full-screen loader. Props: `isVisible: boolean`
|
|
73
|
+
- `Toast` - Fixed corner notification. Props: `children: ReactNode`, `variant?: "info"|"success"|"danger"` (default `"info"`), `placement?: "top-left"|"top-right"|"bottom-left"|"bottom-right"` (default `"bottom-right"`), `autoCloseMs?: number` (default `5000`; pass `0` to disable auto-close), `onClose?: () => void`, `className?: string`. Dismissible with a close control; calls `onClose` when auto-closed or closed manually. Uses `role="alert"` / `aria-live="assertive"` for `danger`, otherwise `role="status"` / `aria-live="polite"`.
|
|
59
74
|
- `ContentBox` - Info/warning boxes. Props: `variant?: "info"|"warning"|"success"|"danger"`, `icon?: ReactNode`
|
|
75
|
+
- `DetailsPanel` - Native `<details>` / `<summary>` disclosure styled like `ContentBox`. Props: **`summary: ReactNode`** (summary row), **`children: ReactNode`** (body when expanded), `variant?: "info"|"warning"|"success"|"danger"` (default `"info"`), `icon?: ReactNode` (inline before summary text), `defaultOpen?: boolean`, `className?: string`, `id?: string`
|
|
60
76
|
|
|
61
77
|
### Content
|
|
62
78
|
- `BlockQuote` - Styled quote. Props: `children: ReactNode`
|
|
@@ -109,10 +125,11 @@ import { Grid, GridItem } from "@madecki/ui";
|
|
|
109
125
|
</Grid>
|
|
110
126
|
|
|
111
127
|
// Form
|
|
112
|
-
import { Input, Select, Button, Stack } from "@madecki/ui";
|
|
128
|
+
import { Input, Textarea, Select, Button, Stack } from "@madecki/ui";
|
|
113
129
|
|
|
114
130
|
<Stack gap="4">
|
|
115
131
|
<Input name="email" label="Email" type="email" onChange={setEmail} />
|
|
132
|
+
<Textarea name="bio" label="Bio" rows={5} onChange={setBio} />
|
|
116
133
|
<Select
|
|
117
134
|
name="role"
|
|
118
135
|
label="Role"
|
|
@@ -138,7 +155,7 @@ If **`value !== undefined`**, the input is controlled (including **`value=""`**)
|
|
|
138
155
|
|
|
139
156
|
### Input — testing
|
|
140
157
|
|
|
141
|
-
- Prefer **`
|
|
158
|
+
- Prefer querying by **accessible name** from the label, e.g. **`getByRole("textbox", { name: "Email" })`** or **`getByLabelText("Email")`**. Optional **`testId`** still sets `data-testid` on the native `<input>` for Playwright when you want a stable hook.
|
|
142
159
|
- With **controlled** `value` + `onChange`, `fill()` / typing stay in sync as long as the parent updates state from `onChange` (no `onInputCapture` workaround needed).
|
|
143
160
|
|
|
144
161
|
### Select — controlled vs uncontrolled
|
|
@@ -155,24 +172,25 @@ If **`value !== undefined`**, the input is controlled (including **`value=""`**)
|
|
|
155
172
|
|
|
156
173
|
### Select — testing
|
|
157
174
|
|
|
175
|
+
**Prefer label / role:** `getByRole("combobox", { name: "Role" })` matches the combobox’s accessible name from the visible (or sr-only) label.
|
|
176
|
+
|
|
158
177
|
**Stable hooks:** Set `testId` for predictable selectors (default is `select-{name}`).
|
|
159
178
|
|
|
160
|
-
- Combobox input: `data-testid={testId}
|
|
161
|
-
- Open list: `data-testid="{testId}-listbox"` (`role="listbox"`). Open the field first (click or focus) so the list is in the DOM.
|
|
179
|
+
- Combobox input: `data-testid={testId}`, `role="combobox"`.
|
|
180
|
+
- Open list: `data-testid="{testId}-listbox"` (`role="listbox"`, named via `aria-labelledby` → field label). Open the field first (click or focus) so the list is in the DOM.
|
|
162
181
|
- Each option: `data-testid="{testId}-option-{slug}"` and `data-option-value` with the real value. Slug = option `value` with non-alphanumeric characters replaced by `_`.
|
|
163
182
|
|
|
164
183
|
**Playwright examples:**
|
|
165
184
|
|
|
166
185
|
```ts
|
|
167
|
-
|
|
168
|
-
await page.getByTestId(
|
|
169
|
-
await
|
|
170
|
-
await page.getByTestId(`${base}-option-admin`).click();
|
|
186
|
+
await page.getByRole("combobox", { name: "Role" }).click();
|
|
187
|
+
await expect(page.getByTestId("signup-role-listbox")).toBeVisible();
|
|
188
|
+
await page.getByTestId("signup-role-option-admin").click();
|
|
171
189
|
// Or by accessible name (option text is the label):
|
|
172
190
|
await page.getByRole("option", { name: "Admin" }).click();
|
|
173
191
|
```
|
|
174
192
|
|
|
175
|
-
**Unit / component tests (e.g. Vitest + Testing Library):**
|
|
193
|
+
**Unit / component tests (e.g. Vitest + Testing Library):** Open the combobox with `getByRole("combobox", { name: "…" })`, then assert the listbox and options with `getByTestId` or `getByRole("option", { name: "…" })`.
|
|
176
194
|
|
|
177
195
|
## Rules
|
|
178
196
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@madecki/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Reusable React UI components with Tailwind CSS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -29,11 +29,13 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"./llm-context": "./llm-context.md",
|
|
32
|
-
"./cursor-rule-template": "./cursor-rule-template.md"
|
|
32
|
+
"./cursor-rule-template": "./cursor-rule-template.md",
|
|
33
|
+
"./CHANGELOG.md": "./CHANGELOG.md"
|
|
33
34
|
},
|
|
34
35
|
"files": [
|
|
35
36
|
"dist",
|
|
36
37
|
"README.md",
|
|
38
|
+
"CHANGELOG.md",
|
|
37
39
|
"llm-context.md",
|
|
38
40
|
"cursor-rule-template.md"
|
|
39
41
|
],
|
|
@@ -47,6 +49,7 @@
|
|
|
47
49
|
"test": "vitest run",
|
|
48
50
|
"test:watch": "vitest",
|
|
49
51
|
"typecheck": "tsc --noEmit",
|
|
52
|
+
"precheck": "npm run typecheck && npm run lint && npm run build && npm test && npm run build-storybook",
|
|
50
53
|
"prepublishOnly": "npm run build",
|
|
51
54
|
"storybook": "storybook dev -p 6006",
|
|
52
55
|
"build-storybook": "storybook build",
|