@madecki/ui 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +26 -12
- package/dist/index.cjs +234 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -8
- package/dist/index.d.ts +52 -8
- package/dist/index.js +235 -71
- package/dist/index.js.map +1 -1
- package/llm-context.md +42 -12
- package/package.json +4 -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`
|
|
@@ -109,10 +123,11 @@ import { Grid, GridItem } from "@madecki/ui";
|
|
|
109
123
|
</Grid>
|
|
110
124
|
|
|
111
125
|
// Form
|
|
112
|
-
import { Input, Select, Button, Stack } from "@madecki/ui";
|
|
126
|
+
import { Input, Textarea, Select, Button, Stack } from "@madecki/ui";
|
|
113
127
|
|
|
114
128
|
<Stack gap="4">
|
|
115
129
|
<Input name="email" label="Email" type="email" onChange={setEmail} />
|
|
130
|
+
<Textarea name="bio" label="Bio" rows={5} onChange={setBio} />
|
|
116
131
|
<Select
|
|
117
132
|
name="role"
|
|
118
133
|
label="Role"
|
|
@@ -127,6 +142,20 @@ import { Input, Select, Button, Stack } from "@madecki/ui";
|
|
|
127
142
|
</Stack>
|
|
128
143
|
```
|
|
129
144
|
|
|
145
|
+
### Input — controlled vs uncontrolled
|
|
146
|
+
|
|
147
|
+
| Mode | What you pass | Who owns the text |
|
|
148
|
+
|------|----------------|-------------------|
|
|
149
|
+
| **Controlled** | `value` (+ `onChange`) | The parent. The field always shows `value`. Update parent state in `onChange` (including for Playwright `fill`) or the UI will not match. |
|
|
150
|
+
| **Uncontrolled** | No `value`; optionally `defaultValue` | The `Input` component. `defaultValue` applies on first mount only; it does not resync if the prop changes later (remount with a `key` if you need that). |
|
|
151
|
+
|
|
152
|
+
If **`value !== undefined`**, the input is controlled (including **`value=""`**).
|
|
153
|
+
|
|
154
|
+
### Input — testing
|
|
155
|
+
|
|
156
|
+
- 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.
|
|
157
|
+
- With **controlled** `value` + `onChange`, `fill()` / typing stay in sync as long as the parent updates state from `onChange` (no `onInputCapture` workaround needed).
|
|
158
|
+
|
|
130
159
|
### Select — controlled vs uncontrolled
|
|
131
160
|
|
|
132
161
|
| Mode | What you pass | Who stores the selection |
|
|
@@ -141,24 +170,25 @@ import { Input, Select, Button, Stack } from "@madecki/ui";
|
|
|
141
170
|
|
|
142
171
|
### Select — testing
|
|
143
172
|
|
|
173
|
+
**Prefer label / role:** `getByRole("combobox", { name: "Role" })` matches the combobox’s accessible name from the visible (or sr-only) label.
|
|
174
|
+
|
|
144
175
|
**Stable hooks:** Set `testId` for predictable selectors (default is `select-{name}`).
|
|
145
176
|
|
|
146
|
-
- Combobox input: `data-testid={testId}
|
|
147
|
-
- Open list: `data-testid="{testId}-listbox"` (`role="listbox"`). Open the field first (click or focus) so the list is in the DOM.
|
|
177
|
+
- Combobox input: `data-testid={testId}`, `role="combobox"`.
|
|
178
|
+
- 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.
|
|
148
179
|
- Each option: `data-testid="{testId}-option-{slug}"` and `data-option-value` with the real value. Slug = option `value` with non-alphanumeric characters replaced by `_`.
|
|
149
180
|
|
|
150
181
|
**Playwright examples:**
|
|
151
182
|
|
|
152
183
|
```ts
|
|
153
|
-
|
|
154
|
-
await page.getByTestId(
|
|
155
|
-
await
|
|
156
|
-
await page.getByTestId(`${base}-option-admin`).click();
|
|
184
|
+
await page.getByRole("combobox", { name: "Role" }).click();
|
|
185
|
+
await expect(page.getByTestId("signup-role-listbox")).toBeVisible();
|
|
186
|
+
await page.getByTestId("signup-role-option-admin").click();
|
|
157
187
|
// Or by accessible name (option text is the label):
|
|
158
188
|
await page.getByRole("option", { name: "Admin" }).click();
|
|
159
189
|
```
|
|
160
190
|
|
|
161
|
-
**Unit / component tests (e.g. Vitest + Testing Library):**
|
|
191
|
+
**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: "…" })`.
|
|
162
192
|
|
|
163
193
|
## Rules
|
|
164
194
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@madecki/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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
|
],
|