@madecki/ui 1.5.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/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`
@@ -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"
@@ -138,7 +153,7 @@ If **`value !== undefined`**, the input is controlled (including **`value=""`**)
138
153
 
139
154
  ### Input — testing
140
155
 
141
- - Prefer **`testId`** so Playwright can target `getByTestId("")` on the real `<input>` without a generic `locator("input")`.
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.
142
157
  - With **controlled** `value` + `onChange`, `fill()` / typing stay in sync as long as the parent updates state from `onChange` (no `onInputCapture` workaround needed).
143
158
 
144
159
  ### Select — controlled vs uncontrolled
@@ -155,24 +170,25 @@ If **`value !== undefined`**, the input is controlled (including **`value=""`**)
155
170
 
156
171
  ### Select — testing
157
172
 
173
+ **Prefer label / role:** `getByRole("combobox", { name: "Role" })` matches the combobox’s accessible name from the visible (or sr-only) label.
174
+
158
175
  **Stable hooks:** Set `testId` for predictable selectors (default is `select-{name}`).
159
176
 
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.
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.
162
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 `_`.
163
180
 
164
181
  **Playwright examples:**
165
182
 
166
183
  ```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();
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();
171
187
  // Or by accessible name (option text is the label):
172
188
  await page.getByRole("option", { name: "Admin" }).click();
173
189
  ```
174
190
 
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: "…" })`.
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: "…" })`.
176
192
 
177
193
  ## Rules
178
194
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madecki/ui",
3
- "version": "1.5.0",
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
  ],