@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/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` - Radio group. Props: `name: string`, `options: {label, value}[]`, `onChange: (value) => void`
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
- - `Input` - Text input. Props: `name`, `label`, `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?`, `spellCheck?`, `disabled?`, `className?`, `icon?`, **`testId?`** (`data-testid` on the native `<input>` for Playwright). See below for controlled vs uncontrolled and testing.
51
- - `Select` - Combobox styled like `Input`, with trailing chevron. Props: `name`, `label`, `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. See below for controlled vs uncontrolled and testing.
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 **`testId`** so Playwright can target `getByTestId("")` on the real `<input>` without a generic `locator("input")`.
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}` (also `role="combobox"`, `aria-label` = `label`).
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
- const base = "signup-role";
168
- await page.getByTestId(base).click();
169
- await expect(page.getByTestId(`${base}-listbox`)).toBeVisible();
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):** Render with a fixed `testId`, `userEvent.click` the combobox, assert the listbox and options with `getByTestId`, or use `getByRole("combobox")` / `getByRole("listbox")` / `getByRole("option", { name: "…" })`.
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.5.0",
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",