@edux-design/buttons 0.1.4 → 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.
package/CHANGELOG.md CHANGED
@@ -1,16 +1,11 @@
1
1
  # @edux-design/buttons
2
2
 
3
- ## 0.1.4
3
+ ## 0.2.0
4
4
 
5
- ### Patch Changes
6
-
7
- - Fix package publishing so built dist files are included in the npm tarballs.
8
-
9
- ## 0.1.3
10
-
11
- ### Patch Changes
5
+ ### Minor Changes
12
6
 
13
- - Add shared control height tokens and update Button to use token-backed size classes.
7
+ - Migrated Button, IconButton, MenuButton, ButtonCounter, and ButtonGroup to the shared semantic control-size tokens without changing their dimensions.
8
+ - Added a visual compatibility matrix covering themes, brands, sizes, and variants.
14
9
 
15
10
  ## 0.1.2
16
11
 
package/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # @edux-design/buttons
2
2
 
3
- Headless-ish button primitives wired to the Edux design tokens. The package ships four components:
3
+ Headless-ish button primitives wired to the Edux design tokens. The package ships five components:
4
4
 
5
- - `Button` – primary action button with size + variant helpers.
6
- - `IconButton` – circular/square icon surface that reuses the same states.
7
- - `MenuButton` – trigger wrapper that pairs nicely with popovers/menus.
8
- - `ButtonGroup` – segmented control with a sliding active state meant for contextual filters.
5
+ - `Button` - primary action button with size + variant helpers.
6
+ - `IconButton` - circular icon-only button surface.
7
+ - `MenuButton` - semantic menu trigger button.
8
+ - `ButtonGroup` - segmented control with a sliding active state meant for contextual filters.
9
+ - `ButtonCounter` - increment/decrement control with optional editable value.
9
10
 
10
11
  Everything is built with Tailwind utilities via the shared `cx` helper so styling stays consistent with the rest of the system.
11
12
 
@@ -19,17 +20,24 @@ pnpm add @edux-design/buttons @edux-design/utils
19
20
  npm install @edux-design/buttons @edux-design/utils
20
21
  ```
21
22
 
22
- Peer deps: `react@^19.1.0`, `react-dom@^19.1.0`.
23
+ Peer deps: `@edux-design/tokens@>=0.2.0 <0.3.0`, `react@^19.1.0`, `react-dom@^19.1.0`.
23
24
 
24
25
  ---
25
26
 
26
27
  ## Usage
27
28
 
28
29
  ```jsx
29
- import { Button, IconButton, MenuButton } from "@edux-design/buttons";
30
- import { Close, Chevron } from "@edux-design/icons";
31
-
32
- export function Actions() {
30
+ import {
31
+ Button,
32
+ ButtonCounter,
33
+ ButtonGroup,
34
+ ButtonGroupItem,
35
+ IconButton,
36
+ MenuButton,
37
+ } from "@edux-design/buttons";
38
+ import { Close, Warning } from "@edux-design/icons";
39
+
40
+ export function Actions({ menuOpen, setMenuOpen }) {
33
41
  return (
34
42
  <div className="flex gap-4">
35
43
  <Button size="small">Save</Button>
@@ -39,55 +47,67 @@ export function Actions() {
39
47
  <Button isDanger disabled>
40
48
  Delete
41
49
  </Button>
42
- <IconButton icon={<Close />} aria-label="Close dialog" />
43
- <MenuButton caret={<Chevron />} onClick={() => null}>
50
+ <IconButton icon={<Close aria-hidden="true" />} aria-label="Close dialog" />
51
+ <MenuButton
52
+ aria-expanded={menuOpen}
53
+ aria-controls="actions-menu"
54
+ onClick={() => setMenuOpen((prev) => !prev)}
55
+ >
44
56
  Options
45
57
  </MenuButton>
58
+ <ButtonCounter defaultValue={2} min={1} aria-label="Quantity" />
46
59
  </div>
47
60
  );
48
61
  }
49
- ```
50
-
51
- ### ButtonGroup
52
-
53
- ```jsx
54
- import React from "react";
55
- import { ButtonGroup } from "@edux-design/buttons";
56
- import { Warning } from "@edux-design/icons";
57
62
 
58
63
  export function SegmentedFilter() {
59
64
  const [value, setValue] = React.useState("inbox");
60
65
 
61
66
  return (
62
- <ButtonGroup
63
- value={value}
64
- onChange={(nextValue) => setValue(nextValue)}
65
- options={[
66
- { value: "inbox", label: "Label (11)" },
67
- { value: "drafts", label: "Label (11)" },
68
- {
69
- value: "alerts",
70
- label: "Label",
71
- endAdornment: <Warning className="h-4 w-4 text-warning-600" />,
72
- },
73
- ]}
74
- />
67
+ <ButtonGroup value={value} onChange={(nextValue) => setValue(nextValue)}>
68
+ <ButtonGroupItem value="inbox">Inbox (11)</ButtonGroupItem>
69
+ <ButtonGroupItem value="drafts">Drafts (11)</ButtonGroupItem>
70
+ <ButtonGroupItem
71
+ value="alerts"
72
+ endAdornment={<Warning className="h-4 w-4 text-warning-600" />}
73
+ >
74
+ Alerts
75
+ </ButtonGroupItem>
76
+ </ButtonGroup>
75
77
  );
76
78
  }
77
79
  ```
78
80
 
79
- Provide `options` as strings or objects (`{ value, label, startAdornment, endAdornment, disabled }`). The active pill animates over the newly selected option and the group behaves like an accessible radiogroup (arrow keys + home/end). Use `isStretched` when the control should fill the parent width.
81
+ ### Accessibility notes
82
+
83
+ - `IconButton` requires an accessible name via `aria-label` or `aria-labelledby`.
84
+ - `MenuButton` renders a native `<button>` and defaults `aria-haspopup` to `"menu"`.
85
+ - `ButtonGroup` uses radiogroup semantics with roving `tabIndex` and supports arrow keys plus home/end.
86
+ - `ButtonCounter` exposes its value through a labelled output in display mode and a native `spinbutton` in editable mode.
80
87
 
81
88
  ### Key props
82
89
 
83
- | Component | Important props |
84
- | ------------- | --------------- |
85
- | `Button` | `size="small"|"medium"|"large"`, `isSecondary`, `isDanger`, `isBare`, `isStretched`, `disabled` |
86
- | `IconButton` | `icon`, `size`, `isBare`, `isDanger`, `disabled` |
87
- | `MenuButton` | Accepts all `Button` props plus caret slot and ARIA attributes for menu triggers |
88
- | `ButtonGroup` | `options`, `value` / `defaultValue`, `onChange`, `size`, `isStretched`, per-option `startAdornment` / `endAdornment`, `disabled` |
90
+ | Component | Important props |
91
+ | --------- | --------------- |
92
+ | `Button` | `size="small"|"medium"|"large"`, `isSecondary`, `isDanger`, `isBare`, `isStretched`, `disabled` |
93
+ | `IconButton` | `icon`, `size`, `isBare`, `isDanger`, `disabled`, `aria-label` / `aria-labelledby` |
94
+ | `MenuButton` | Native button props, `size`, `caret`, `aria-expanded`, `aria-controls` |
95
+ | `ButtonGroup` | `value` / `defaultValue`, `onChange`, `size`, `isStretched`, per-option `startAdornment` / `endAdornment`, `disabled` |
96
+ | `ButtonCounter` | `value` / `defaultValue`, `min`, `max`, `step`, `isEditable`, `inputProps` |
97
+
98
+ Buttons warn in development if you pass conflicting variant booleans so you know which style wins. `IconButton` also warns when it is missing an accessible name.
89
99
 
90
- Buttons warn in development if you pass conflicting variant booleans so you know which style wins.
100
+ ### Shared control sizes
101
+
102
+ Button components map their public size names to the semantic control-size contract from `@edux-design/tokens`:
103
+
104
+ | Public size | Token utility | Computed size |
105
+ | --- | --- | --- |
106
+ | `small` | `control-sm` | `36px` |
107
+ | `medium` | `control-md` | `44px` |
108
+ | `large` | `control-lg` | `56px` |
109
+
110
+ `Button` uses the shared height, `IconButton` and ButtonCounter controls use the shared square size, and `MenuButton` uses the shared minimum height. Component-specific content widths remain owned by their component.
91
111
 
92
112
  ---
93
113
 
@@ -99,12 +119,4 @@ pnpm --filter @edux-design/buttons check-types
99
119
  pnpm --filter @edux-design/buttons build
100
120
  ```
101
121
 
102
- Stories live under `src/demo` (e.g. `Button.stories.jsx`, `ButtonGroup.stories.jsx`) for quick visual verification.
103
-
104
- ---
105
-
106
- ## Notes
107
-
108
- - Variants map directly to the design tokens defined in `@edux-design/tokens`.
109
- - Hover/focus styles are already applied; avoid overriding unless you have a matching token.
110
- - If you need new variants (ghost, link, destructive-secondary, etc.), add them in `Button.jsx` and update the stories + README accordingly.
122
+ Stories live under `src/demo` (for example `Button.stories.jsx` and `ButtonGroup.stories.jsx`) for quick visual verification.
@@ -0,0 +1,4 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none"
2
+ xmlns="http://www.w3.org/2000/svg">
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M13.5858 12L9.29289 7.70711C8.90237 7.31658 8.90237 6.68342 9.29289 6.29289C9.68342 5.90237 10.3166 5.90237 10.7071 6.29289L15.7071 11.2929C16.0976 11.6834 16.0976 12.3166 15.7071 12.7071L10.7071 17.7071C10.3166 18.0976 9.68342 18.0976 9.29289 17.7071C8.90237 17.3166 8.90237 16.6834 9.29289 16.2929L13.5858 12Z" fill="currentColor"/>
4
+ </svg>
@@ -0,0 +1,10 @@
1
+ // src/elements/Button.jsx
2
+ import React from "react";
3
+ function Button() {
4
+ return /* @__PURE__ */ React.createElement("div", { className: "border-2 border-primary-700" }, "Button");
5
+ }
6
+ var Button_default = Button;
7
+
8
+ export {
9
+ Button_default
10
+ };
@@ -0,0 +1,39 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/elements/Button.jsx
30
+ var Button_exports = {};
31
+ __export(Button_exports, {
32
+ default: () => Button_default
33
+ });
34
+ module.exports = __toCommonJS(Button_exports);
35
+ var import_react = __toESM(require("react"));
36
+ function Button() {
37
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "border-2 border-primary-700" }, "Button");
38
+ }
39
+ var Button_default = Button;
@@ -0,0 +1,6 @@
1
+ import {
2
+ Button_default
3
+ } from "../chunk-M47FNL4P.mjs";
4
+ export {
5
+ Button_default as default
6
+ };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,3 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
1
  import * as react from 'react';
3
2
 
4
3
  /**
@@ -8,6 +7,8 @@ import * as react from 'react';
8
7
  * Designed for circular icon-only buttons that follow the same
9
8
  * design-system patterns as the regular Button.
10
9
  *
10
+ * Provide an accessible name with `aria-label` or `aria-labelledby`.
11
+ *
11
12
  * @typedef {React.ButtonHTMLAttributes<HTMLButtonElement> & {
12
13
  * icon: React.ReactNode,
13
14
  * size?: "small"|"medium"|"large",
@@ -30,8 +31,8 @@ import * as react from 'react';
30
31
  * @example
31
32
  * ```jsx
32
33
  * <IconButton icon={<XIcon />} aria-label="Close" />
33
- * <IconButton icon={<EditIcon />} isSecondary />
34
- * <IconButton icon={<TrashIcon />} isDanger disabled />
34
+ * <IconButton icon={<EditIcon />} isSecondary aria-label="Edit" />
35
+ * <IconButton icon={<TrashIcon />} isDanger disabled aria-label="Delete" />
35
36
  * ```
36
37
  */
37
38
  declare const IconButton: React.ForwardRefExoticComponent<IconButtonProps & React.RefAttributes<HTMLButtonElement>>;
@@ -41,6 +42,8 @@ declare const IconButton: React.ForwardRefExoticComponent<IconButtonProps & Reac
41
42
  * Extends all native HTML `<button>` attributes and accepts an icon prop.
42
43
  * Designed for circular icon-only buttons that follow the same
43
44
  * design-system patterns as the regular Button.
45
+ *
46
+ * Provide an accessible name with `aria-label` or `aria-labelledby`.
44
47
  */
45
48
  type IconButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
46
49
  icon: React.ReactNode;
@@ -110,29 +113,40 @@ type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
110
113
  /**
111
114
  * Props for the MenuButton component.
112
115
  *
113
- * @typedef {Object} MenuButtonProps
114
- * @property {React.ReactNode} [children] - Content to display inside the button (usually label or icon).
115
- * @property {"small"|"medium"} [size="medium"] - Size variant controlling button height.
116
+ * @typedef {React.ButtonHTMLAttributes<HTMLButtonElement> & {
117
+ * children?: React.ReactNode,
118
+ * caret?: React.ReactNode,
119
+ * size?: "small"|"medium",
120
+ * className?: string,
121
+ * }} MenuButtonProps
116
122
  */
117
123
  /**
118
124
  * MenuButton component.
119
125
  *
120
- * Provides a styled menu trigger button with optional sizing.
121
- * It uses a `MenuButtonContext` provider to share internal state
122
- * with child menu items.
126
+ * Provides a styled button intended for menu triggers.
127
+ * Consumers should pass `aria-expanded`, `aria-controls`, and any
128
+ * custom labeling they need. `aria-haspopup="menu"` is applied by default.
123
129
  *
124
130
  * @component
125
- * @type {React.FC<MenuButtonProps>}
131
+ * @type {React.ForwardRefExoticComponent<MenuButtonProps & React.RefAttributes<HTMLButtonElement>>}
126
132
  *
127
133
  * @example
128
134
  * ```jsx
129
- * <MenuButton size="small">Options</MenuButton>
135
+ * <MenuButton aria-expanded={open} onClick={() => setOpen((prev) => !prev)}>
136
+ * Options
137
+ * </MenuButton>
130
138
  * ```
131
139
  */
132
- declare function MenuButton({ children, size }: {
133
- children: any;
134
- size: any;
135
- }): react_jsx_runtime.JSX.Element;
140
+ declare const MenuButton: React.ForwardRefExoticComponent<MenuButtonProps & React.RefAttributes<HTMLButtonElement>>;
141
+ /**
142
+ * Props for the MenuButton component.
143
+ */
144
+ type MenuButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
145
+ children?: React.ReactNode;
146
+ caret?: React.ReactNode;
147
+ size?: "small" | "medium";
148
+ className?: string;
149
+ };
136
150
 
137
151
  /**
138
152
  * @typedef {Object} ButtonGroupOption
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
1
  import * as react from 'react';
3
2
 
4
3
  /**
@@ -8,6 +7,8 @@ import * as react from 'react';
8
7
  * Designed for circular icon-only buttons that follow the same
9
8
  * design-system patterns as the regular Button.
10
9
  *
10
+ * Provide an accessible name with `aria-label` or `aria-labelledby`.
11
+ *
11
12
  * @typedef {React.ButtonHTMLAttributes<HTMLButtonElement> & {
12
13
  * icon: React.ReactNode,
13
14
  * size?: "small"|"medium"|"large",
@@ -30,8 +31,8 @@ import * as react from 'react';
30
31
  * @example
31
32
  * ```jsx
32
33
  * <IconButton icon={<XIcon />} aria-label="Close" />
33
- * <IconButton icon={<EditIcon />} isSecondary />
34
- * <IconButton icon={<TrashIcon />} isDanger disabled />
34
+ * <IconButton icon={<EditIcon />} isSecondary aria-label="Edit" />
35
+ * <IconButton icon={<TrashIcon />} isDanger disabled aria-label="Delete" />
35
36
  * ```
36
37
  */
37
38
  declare const IconButton: React.ForwardRefExoticComponent<IconButtonProps & React.RefAttributes<HTMLButtonElement>>;
@@ -41,6 +42,8 @@ declare const IconButton: React.ForwardRefExoticComponent<IconButtonProps & Reac
41
42
  * Extends all native HTML `<button>` attributes and accepts an icon prop.
42
43
  * Designed for circular icon-only buttons that follow the same
43
44
  * design-system patterns as the regular Button.
45
+ *
46
+ * Provide an accessible name with `aria-label` or `aria-labelledby`.
44
47
  */
45
48
  type IconButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
46
49
  icon: React.ReactNode;
@@ -110,29 +113,40 @@ type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
110
113
  /**
111
114
  * Props for the MenuButton component.
112
115
  *
113
- * @typedef {Object} MenuButtonProps
114
- * @property {React.ReactNode} [children] - Content to display inside the button (usually label or icon).
115
- * @property {"small"|"medium"} [size="medium"] - Size variant controlling button height.
116
+ * @typedef {React.ButtonHTMLAttributes<HTMLButtonElement> & {
117
+ * children?: React.ReactNode,
118
+ * caret?: React.ReactNode,
119
+ * size?: "small"|"medium",
120
+ * className?: string,
121
+ * }} MenuButtonProps
116
122
  */
117
123
  /**
118
124
  * MenuButton component.
119
125
  *
120
- * Provides a styled menu trigger button with optional sizing.
121
- * It uses a `MenuButtonContext` provider to share internal state
122
- * with child menu items.
126
+ * Provides a styled button intended for menu triggers.
127
+ * Consumers should pass `aria-expanded`, `aria-controls`, and any
128
+ * custom labeling they need. `aria-haspopup="menu"` is applied by default.
123
129
  *
124
130
  * @component
125
- * @type {React.FC<MenuButtonProps>}
131
+ * @type {React.ForwardRefExoticComponent<MenuButtonProps & React.RefAttributes<HTMLButtonElement>>}
126
132
  *
127
133
  * @example
128
134
  * ```jsx
129
- * <MenuButton size="small">Options</MenuButton>
135
+ * <MenuButton aria-expanded={open} onClick={() => setOpen((prev) => !prev)}>
136
+ * Options
137
+ * </MenuButton>
130
138
  * ```
131
139
  */
132
- declare function MenuButton({ children, size }: {
133
- children: any;
134
- size: any;
135
- }): react_jsx_runtime.JSX.Element;
140
+ declare const MenuButton: React.ForwardRefExoticComponent<MenuButtonProps & React.RefAttributes<HTMLButtonElement>>;
141
+ /**
142
+ * Props for the MenuButton component.
143
+ */
144
+ type MenuButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
145
+ children?: React.ReactNode;
146
+ caret?: React.ReactNode;
147
+ size?: "small" | "medium";
148
+ className?: string;
149
+ };
136
150
 
137
151
  /**
138
152
  * @typedef {Object} ButtonGroupOption