@edux-design/buttons 0.1.4 → 0.3.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.
@@ -0,0 +1,146 @@
1
+ # Toolbar controls
2
+
3
+ `ToolbarButton`, `ToolbarToggleButton`, `Toolbar`, `ToolbarGroup` and
4
+ `ToolbarSeparator` extend the Buttons package without changing existing Button,
5
+ IconButton or ButtonGroup APIs. Use them for compact editor, preview and page
6
+ actions. Commands, editor selection, persistence and menu state belong to the host.
7
+
8
+ ```tsx
9
+ import {
10
+ Toolbar,
11
+ ToolbarGroup,
12
+ ToolbarSeparator,
13
+ ToolbarButton,
14
+ ToolbarToggleButton,
15
+ } from "@edux-design/buttons";
16
+
17
+ <Toolbar aria-label="Document formatting">
18
+ <ToolbarGroup aria-label="Text style">
19
+ <ToolbarToggleButton pressed={bold} onPressedChange={setBold}>
20
+ Bold
21
+ </ToolbarToggleButton>
22
+ <ToolbarToggleButton defaultPressed>Italic</ToolbarToggleButton>
23
+ </ToolbarGroup>
24
+ <ToolbarSeparator />
25
+ <ToolbarButton onClick={save}>Save draft</ToolbarButton>
26
+ </Toolbar>;
27
+ ```
28
+
29
+ ## Buttons and state
30
+
31
+ All buttons render a single native `button`, forward an `HTMLButtonElement` ref,
32
+ and accept native attributes, events, ARIA, data attributes, `className`, `style`
33
+ and arbitrary children. Their default `type` is `button`; `type="submit"` is
34
+ supported. Handlers receive the native React event with the actual button as
35
+ `event.currentTarget`, suitable for menu anchors and measurement. No wrapper is
36
+ inserted around the button. An optional `icon` slot is decorative (`aria-hidden`).
37
+
38
+ | Prop | Default | Contract |
39
+ | --------------- | --------- | ----------------------------------------------------------- |
40
+ | `size` | `small` | `small`, `medium`, `large`: 36, 44, 56px semantic controls |
41
+ | `variant` | `neutral` | `neutral`, `primary`, `danger`; existing semantic tokens |
42
+ | `iconOnly` | `false` | Square geometry; caller supplies an accessible name |
43
+ | `icon` | — | Decorative icon before children |
44
+ | `active` | `false` | ToolbarButton compatibility visual state, no internal state |
45
+ | `disabled` | `false` | Native disabled behaviour; blocks activation |
46
+ | `aria-disabled` | — | Also blocks activation; roving focus skips this action |
47
+
48
+ An ordinary action has no `aria-pressed` attribute by default. Native
49
+ `aria-pressed` true/false/mixed is forwarded and controls selected styling.
50
+ `active` is a compatibility flag: true selects the visual and defaults
51
+ `aria-pressed` to true. Explicit native `aria-pressed`, including false or
52
+ undefined, takes precedence for semantics. A question selector can use
53
+ `active={current}` and `aria-pressed={undefined}` with the host's appropriate
54
+ `aria-current` semantics. It need not become a toggle merely to show selection.
55
+
56
+ `ToolbarToggleButton` is an independent toggle with boolean `pressed`,
57
+ `defaultPressed` (false by default), and
58
+ `onPressedChange(nextBoolean, clickEvent)`. It always exposes true or false
59
+ `aria-pressed`. Controlled state changes only when its host updates `pressed`.
60
+ The native `onClick` runs first; `event.preventDefault()` cancels state changes
61
+ and the change callback. Disabled actions cannot change state. Enter and Space
62
+ use native button activation. Keep a toggle's accessible label stable between
63
+ states. `active` is reserved for ToolbarButton, not the dedicated toggle.
64
+
65
+ The selected border reserves its space in every state, keeping dimensions stable.
66
+ Transitions honour reduced motion. Disabled controls keep their disabled colours
67
+ on hover. There are no new palette or semantic tokens.
68
+
69
+ ## Grouping and keyboard policy
70
+
71
+ `Toolbar` renders a `div` with `role="toolbar"`; supply `aria-label` or
72
+ `aria-labelledby`. `ToolbarGroup` is a native `div` with `role="group"`, not a
73
+ radiogroup. Give meaningful groups accessible names. Root and group support
74
+ `orientation="horizontal"` (default) or `vertical`, refs and native div props.
75
+ `ToolbarSeparator` renders a non-focusable separator; its `orientation` refers
76
+ to the line, and defaults to `vertical` for a horizontal toolbar.
77
+
78
+ **Native Tab order is the default.** Grouping does not change button tab stops,
79
+ activate toggles, intercept arrows or manage menu state. Buttons and groups also
80
+ work independently without a Toolbar. This preserves existing footer/editor
81
+ keyboard behaviour when replacing presentation.
82
+
83
+ Opt into `keyboardNavigation="roving"` deliberately to use a single tab stop
84
+ across ToolbarButton and ToolbarToggleButton descendants. After hydration:
85
+
86
+ - Horizontal Left/Right arrows move focus in the layout direction, including RTL.
87
+ - Vertical Up/Down arrows move focus; the perpendicular axis remains available.
88
+ - Home/End focus the first/last enabled visible action.
89
+ - `loop` defaults to true; false stops at either end.
90
+ - Tab/Shift+Tab leave normally; returning remembers the last enabled action.
91
+ - Arrow navigation moves focus without activating an action or toggle.
92
+ - Native disabled, aria-disabled, hidden/inert and CSS-hidden actions are skipped.
93
+ - Nested toolbars keep independent focus. Inputs and other widgets retain their
94
+ own keys and tab order; place complex widgets in separate labelled groups when
95
+ a strict one-stop toolbar is required.
96
+ - Modified keys and caller-prevented key events are left alone, including table
97
+ Alt+Arrow shortcuts. The host's root `onKeyDown` runs before navigation.
98
+
99
+ Roving mode owns action tab stops while enabled, updates them when actions change,
100
+ and restores the original native values when disabled/unmounted. It does not
101
+ capture focus, auto-focus on mount, intercept Escape or handle editor commands.
102
+
103
+ ## Payload compact and selection adapters
104
+
105
+ Existing Payload toolbars use 32px controls. The package's standard small control
106
+ is 36px. Preserve 32px explicitly during local acceptance rather than silently
107
+ resizing the client. This is component/host geometry, not a new global token:
108
+
109
+ ```tsx
110
+ const compact = { height: 32, minWidth: 32 };
111
+
112
+ <ToolbarButton
113
+ style={compact}
114
+ onMouseDown={(event) => {
115
+ event.preventDefault(); // Host preserves editor selection.
116
+ onMouseDown?.(event); // Existing handler observes defaultPrevented.
117
+ }}
118
+ onClick={runCommand}
119
+ >
120
+ Insert
121
+ </ToolbarButton>;
122
+ ```
123
+
124
+ Mouse prevention is optional host behaviour. The package does not prevent pointer
125
+ focus or focus the editor. Keyboard navigation and activation remain available.
126
+ Table handles can retain their class/style/data hooks and specialised geometry,
127
+ pointer handlers, currentTarget anchors and Alt+Arrow reordering in local adapters.
128
+
129
+ ## Tooltip, menu and theme composition
130
+
131
+ Use TooltipTrigger's `asChild` API around the same button, or pass that button
132
+ as the existing Menu's `button` prop. Provide TooltipProvider as usual.
133
+ Tooltips describe an action; icon-only actions
134
+ still need `aria-label`/`aria-labelledby`. Injected refs/handlers/ARIA are forwarded.
135
+ Menus own their `aria-expanded`, popup keyboard behaviour and focus restoration;
136
+ use ToolbarButton, not an independent toggle, for a menu trigger.
137
+
138
+ Import `@edux-design/tokens/theme` once, and include installed packages in Tailwind
139
+ source detection. Use existing `theme--light`/`theme--dark` and `brand--blue`/
140
+ `brand--teal` classes on the same scope. The controls use existing semantic
141
+ foreground, background, border and focus tokens. They do not own a theme provider.
142
+
143
+ Storybook **Components/Toolbar** includes native actions, explicit roving focus,
144
+ all sizes/states, light/dark × blue/teal, narrow wrapping, vertical/RTL, tooltip/menu
145
+ composition, a measured Payload32px recipe and the editor selection adapter. The package includes interaction,
146
+ structural accessibility, public consumer type and CJS/SSR fixtures.
package/package.json CHANGED
@@ -1,44 +1,32 @@
1
1
  {
2
2
  "name": "@edux-design/buttons",
3
- "version": "0.1.4",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css"
7
7
  ],
8
- "files": [
9
- "dist",
10
- "README.md",
11
- "CHANGELOG.md"
12
- ],
13
8
  "types": "./dist/index.d.ts",
14
9
  "exports": {
15
10
  ".": {
11
+ "types": "./dist/index.d.ts",
16
12
  "import": "./dist/index.mjs",
17
- "require": "./dist/index.js",
18
- "types": "./dist/index.d.ts"
13
+ "require": "./dist/index.js"
19
14
  }
20
15
  },
21
- "scripts": {
22
- "lint": "eslint . --max-warnings 0",
23
- "build": "tsup src/index.js --format esm,cjs --dts",
24
- "dev": "tsup src/index.js --watch --format esm,cjs --dts",
25
- "prepack": "npm run build",
26
- "generate:component": "turbo gen react-component",
27
- "check-types": "tsc --noEmit"
28
- },
29
- "devDependencies": {
30
- "typescript": "^5.9.2",
31
- "tailwindcss": "^4.1.12",
32
- "postcss": "^8.5.6",
33
- "tsup": "^8.5.0",
34
- "@tailwindcss/cli": "^4.1.12",
35
- "@tailwindcss/postcss": "^4.1.12"
36
- },
37
16
  "dependencies": {
38
17
  "@edux-design/utils": "*"
39
18
  },
40
19
  "peerDependencies": {
20
+ "@edux-design/tokens": ">=0.2.0 <0.3.0",
41
21
  "react": "^19.1.0",
42
22
  "react-dom": "^19.1.0"
43
- }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "docs",
27
+ "README.md",
28
+ "CHANGELOG.md",
29
+ "LICENSE",
30
+ "LICENSE.md"
31
+ ]
44
32
  }