@edux-design/chips 0.2.0 → 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.
- package/CHANGELOG.md +6 -0
- package/README.md +41 -9
- package/dist/index.d.mts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +64 -1
- package/dist/index.mjs +63 -1
- package/docs/action-chip-inventory.md +21 -0
- package/docs/action-chip.md +85 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @edux-design/chips
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add ActionChip as a distinct native button with native ref and activation, count and adornment content, and visual selected state. Preserve the existing div-based Chip and removal contract; do not introduce nested interactive controls.
|
|
8
|
+
|
|
3
9
|
## 0.2.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @edux-design/chips
|
|
2
2
|
|
|
3
|
-
Token-driven
|
|
3
|
+
Token-driven compact chips. `Chip` renders an informational or removable div-root pill; `ActionChip` renders a native button for filter actions and overlay triggers. Both support label/count composition with existing primitives.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -37,7 +37,9 @@ import { TagNumber } from "@edux-design/tags";
|
|
|
37
37
|
export function Filters({ onRemove }) {
|
|
38
38
|
return (
|
|
39
39
|
<Chip label="Label" onRemove={onRemove}>
|
|
40
|
-
<TagNumber variant="brand" tone="solid">
|
|
40
|
+
<TagNumber variant="brand" tone="solid">
|
|
41
|
+
999
|
|
42
|
+
</TagNumber>
|
|
41
43
|
</Chip>
|
|
42
44
|
);
|
|
43
45
|
}
|
|
@@ -45,13 +47,13 @@ export function Filters({ onRemove }) {
|
|
|
45
47
|
|
|
46
48
|
### Key Props
|
|
47
49
|
|
|
48
|
-
| Prop
|
|
49
|
-
|
|
|
50
|
-
| `label`
|
|
51
|
-
| `size`
|
|
52
|
-
| `onRemove`
|
|
53
|
-
| `removeLabel` | Accessible label for the close button. Defaults to `"Remove chip"`.
|
|
54
|
-
| `disabled`
|
|
50
|
+
| Prop | Description |
|
|
51
|
+
| ------------- | -------------------------------------------------------------------------- |
|
|
52
|
+
| `label` | Primary text rendered inside the chip (truncated when needed). |
|
|
53
|
+
| `size` | `"default"` (40px tall) or `"small"` (32px tall). |
|
|
54
|
+
| `onRemove` | When provided, renders a close icon button that calls the handler. |
|
|
55
|
+
| `removeLabel` | Accessible label for the close button. Defaults to `"Remove chip"`. |
|
|
56
|
+
| `disabled` | Mutes colors, disables the close button, and removes pointer interactions. |
|
|
55
57
|
|
|
56
58
|
`children` are rendered before the label, so you can compose `TagNumber` (or any other primitive) directly inside the chip without a dedicated prop.
|
|
57
59
|
|
|
@@ -74,3 +76,33 @@ Stories live under `src/demos/Chip.stories.jsx` with size + state examples pulle
|
|
|
74
76
|
- Focus, hover, pressed, dragged, and inactive visuals are all handled via Tailwind utility classes wired to the shared design tokens.
|
|
75
77
|
- The chip container uses `focus-within` so the focus ring appears when the remove button is tabbed to.
|
|
76
78
|
- Feel free to slot other components ahead of the label; the layout simply spaces whatever you supply, so TagNumber composition stays entirely declarative.
|
|
79
|
+
|
|
80
|
+
## Native action/filter trigger
|
|
81
|
+
|
|
82
|
+
```jsx
|
|
83
|
+
import { ActionChip } from "@edux-design/chips";
|
|
84
|
+
import { TagNumber } from "@edux-design/tags";
|
|
85
|
+
import { Chevron } from "@edux-design/icons";
|
|
86
|
+
|
|
87
|
+
<ActionChip
|
|
88
|
+
label="Stages"
|
|
89
|
+
selected={selectedStages.length > 0}
|
|
90
|
+
count={
|
|
91
|
+
selectedStages.length > 0 ? (
|
|
92
|
+
<TagNumber>{selectedStages.length}</TagNumber>
|
|
93
|
+
) : null
|
|
94
|
+
}
|
|
95
|
+
endAdornment={<Chevron aria-hidden="true" />}
|
|
96
|
+
onClick={openStageFilter}
|
|
97
|
+
/>;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
ActionChip forwards native button props and an `HTMLButtonElement` ref, defaults
|
|
101
|
+
to `type="button"`, and composes with Menu/Popover triggers. `selected` is visual
|
|
102
|
+
filter state; popup expansion comes from `aria-expanded` or `data-state="open"`.
|
|
103
|
+
Set native `aria-pressed` only for actual toggle actions. Labels and slots must
|
|
104
|
+
contain noninteractive content; removal remains on the div-root Chip.
|
|
105
|
+
|
|
106
|
+
Its `default` and `small` minimum heights retain existing 40px/32px chip geometry.
|
|
107
|
+
See [ActionChip contract and examples](./docs/action-chip.md) and the
|
|
108
|
+
[read-only host inventory](./docs/action-chip-inventory.md).
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Token-driven chip that composes existing primitives (i.e. TagNumber) inside.
|
|
3
5
|
*
|
|
@@ -24,4 +26,23 @@ type ChipProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
24
26
|
removeButtonProps?: Omit<React.ComponentPropsWithRef<"button">, "type" | "disabled" | "aria-label" | "aria-labelledby" | "children">;
|
|
25
27
|
};
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
interface ActionChipProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
30
|
+
/** Visible label; children are used when label is undefined. Supply noninteractive content. */
|
|
31
|
+
label?: React.ReactNode;
|
|
32
|
+
/** Chip-specific geometry, matching Chip's existing 40px/32px minimum heights. */
|
|
33
|
+
size?: "default" | "small";
|
|
34
|
+
/** Visual filter state only. Does not imply aria-pressed or popup expansion. */
|
|
35
|
+
selected?: boolean;
|
|
36
|
+
/** Noninteractive count content before the label. The host owns its visibility and value. */
|
|
37
|
+
count?: React.ReactNode;
|
|
38
|
+
startAdornment?: React.ReactNode;
|
|
39
|
+
endAdornment?: React.ReactNode;
|
|
40
|
+
/** Use the separate div-root Chip for removal. */
|
|
41
|
+
onRemove?: never;
|
|
42
|
+
removeLabel?: never;
|
|
43
|
+
removeButtonProps?: never;
|
|
44
|
+
}
|
|
45
|
+
/** Native action/overlay trigger. The host owns filter state and popup behaviour. */
|
|
46
|
+
declare const ActionChip: React.ForwardRefExoticComponent<ActionChipProps & React.RefAttributes<HTMLButtonElement>>;
|
|
47
|
+
|
|
48
|
+
export { ActionChip, Chip };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Token-driven chip that composes existing primitives (i.e. TagNumber) inside.
|
|
3
5
|
*
|
|
@@ -24,4 +26,23 @@ type ChipProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
24
26
|
removeButtonProps?: Omit<React.ComponentPropsWithRef<"button">, "type" | "disabled" | "aria-label" | "aria-labelledby" | "children">;
|
|
25
27
|
};
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
interface ActionChipProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
30
|
+
/** Visible label; children are used when label is undefined. Supply noninteractive content. */
|
|
31
|
+
label?: React.ReactNode;
|
|
32
|
+
/** Chip-specific geometry, matching Chip's existing 40px/32px minimum heights. */
|
|
33
|
+
size?: "default" | "small";
|
|
34
|
+
/** Visual filter state only. Does not imply aria-pressed or popup expansion. */
|
|
35
|
+
selected?: boolean;
|
|
36
|
+
/** Noninteractive count content before the label. The host owns its visibility and value. */
|
|
37
|
+
count?: React.ReactNode;
|
|
38
|
+
startAdornment?: React.ReactNode;
|
|
39
|
+
endAdornment?: React.ReactNode;
|
|
40
|
+
/** Use the separate div-root Chip for removal. */
|
|
41
|
+
onRemove?: never;
|
|
42
|
+
removeLabel?: never;
|
|
43
|
+
removeButtonProps?: never;
|
|
44
|
+
}
|
|
45
|
+
/** Native action/overlay trigger. The host owns filter state and popup behaviour. */
|
|
46
|
+
declare const ActionChip: React.ForwardRefExoticComponent<ActionChipProps & React.RefAttributes<HTMLButtonElement>>;
|
|
47
|
+
|
|
48
|
+
export { ActionChip, Chip };
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
29
29
|
// src/index.js
|
|
30
30
|
var index_exports = {};
|
|
31
31
|
__export(index_exports, {
|
|
32
|
+
ActionChip: () => ActionChip,
|
|
32
33
|
Chip: () => Chip
|
|
33
34
|
});
|
|
34
35
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -111,7 +112,7 @@ var Chip = (0, import_react.forwardRef)(function Chip2({
|
|
|
111
112
|
onClick: (event) => {
|
|
112
113
|
event.stopPropagation();
|
|
113
114
|
if (disabled) return;
|
|
114
|
-
onRemoveClick
|
|
115
|
+
onRemoveClick?.(event);
|
|
115
116
|
if (!event.defaultPrevented) onRemove(event);
|
|
116
117
|
},
|
|
117
118
|
className: (0, import_utils.cx)(
|
|
@@ -131,7 +132,69 @@ var Chip = (0, import_react.forwardRef)(function Chip2({
|
|
|
131
132
|
}
|
|
132
133
|
);
|
|
133
134
|
});
|
|
135
|
+
|
|
136
|
+
// src/elements/ActionChip.tsx
|
|
137
|
+
var import_react2 = require("react");
|
|
138
|
+
var import_utils2 = require("@edux-design/utils");
|
|
139
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
140
|
+
var hasSlot = (node) => node != null && typeof node !== "boolean";
|
|
141
|
+
var sizes = {
|
|
142
|
+
default: "text-sm min-h-[40px] px-8 py-6 gap-8",
|
|
143
|
+
small: "text-xs min-h-32 px-8 py-4 gap-6"
|
|
144
|
+
};
|
|
145
|
+
var ActionChip = (0, import_react2.forwardRef)(
|
|
146
|
+
function ActionChip2({
|
|
147
|
+
label,
|
|
148
|
+
children,
|
|
149
|
+
size = "default",
|
|
150
|
+
selected = false,
|
|
151
|
+
count,
|
|
152
|
+
startAdornment,
|
|
153
|
+
endAdornment,
|
|
154
|
+
disabled = false,
|
|
155
|
+
type = "button",
|
|
156
|
+
className,
|
|
157
|
+
// Do not leak unsupported removal props onto the native button in JS hosts.
|
|
158
|
+
onRemove: _onRemove,
|
|
159
|
+
removeLabel: _removeLabel,
|
|
160
|
+
removeButtonProps: _removeButtonProps,
|
|
161
|
+
...props
|
|
162
|
+
}, ref) {
|
|
163
|
+
const expanded = props["aria-expanded"] === true || props["aria-expanded"] === "true";
|
|
164
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
165
|
+
"button",
|
|
166
|
+
{
|
|
167
|
+
...props,
|
|
168
|
+
ref,
|
|
169
|
+
type,
|
|
170
|
+
disabled,
|
|
171
|
+
"data-selected": selected ? "true" : void 0,
|
|
172
|
+
className: (0, import_utils2.cx)(
|
|
173
|
+
"inline-flex w-fit max-w-full min-w-0 items-center rounded-full border border-border-minimal bg-bg-base text-fg-base shadow-sm",
|
|
174
|
+
"transition-all duration-200 ease-out cursor-pointer",
|
|
175
|
+
"hover:border-border-primary-base hover:bg-bg-primary-subtle",
|
|
176
|
+
"active:border-border-primary-emphasis active:bg-bg-primary-subtle-90",
|
|
177
|
+
"focus-visible:shadow-focus focus-visible:border-border-emphasis focus-visible:outline-none",
|
|
178
|
+
"data-[state=open]:border-border-primary-base data-[state=open]:bg-bg-primary-subtle",
|
|
179
|
+
sizes[size],
|
|
180
|
+
(selected || expanded) && "border-border-primary-base bg-bg-primary-subtle",
|
|
181
|
+
disabled && "bg-bg-inactive text-fg-inactive border-border-subtle cursor-not-allowed pointer-events-none",
|
|
182
|
+
className
|
|
183
|
+
),
|
|
184
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "flex min-w-0 flex-1 items-center gap-6", children: [
|
|
185
|
+
hasSlot(startAdornment) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "inline-flex shrink-0 items-center", children: startAdornment }),
|
|
186
|
+
hasSlot(count) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "inline-flex shrink-0 items-center", children: count }),
|
|
187
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "flex min-w-0 items-center gap-8", children: [
|
|
188
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "min-w-0 truncate text-left", children: label === void 0 ? children : label }),
|
|
189
|
+
hasSlot(endAdornment) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "inline-flex shrink-0 items-center", children: endAdornment })
|
|
190
|
+
] })
|
|
191
|
+
] })
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
);
|
|
134
196
|
// Annotate the CommonJS export names for ESM import in node:
|
|
135
197
|
0 && (module.exports = {
|
|
198
|
+
ActionChip,
|
|
136
199
|
Chip
|
|
137
200
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -76,7 +76,7 @@ var Chip = forwardRef(function Chip2({
|
|
|
76
76
|
onClick: (event) => {
|
|
77
77
|
event.stopPropagation();
|
|
78
78
|
if (disabled) return;
|
|
79
|
-
onRemoveClick
|
|
79
|
+
onRemoveClick?.(event);
|
|
80
80
|
if (!event.defaultPrevented) onRemove(event);
|
|
81
81
|
},
|
|
82
82
|
className: cx(
|
|
@@ -96,6 +96,68 @@ var Chip = forwardRef(function Chip2({
|
|
|
96
96
|
}
|
|
97
97
|
);
|
|
98
98
|
});
|
|
99
|
+
|
|
100
|
+
// src/elements/ActionChip.tsx
|
|
101
|
+
import { forwardRef as forwardRef2 } from "react";
|
|
102
|
+
import { cx as cx2 } from "@edux-design/utils";
|
|
103
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
104
|
+
var hasSlot = (node) => node != null && typeof node !== "boolean";
|
|
105
|
+
var sizes = {
|
|
106
|
+
default: "text-sm min-h-[40px] px-8 py-6 gap-8",
|
|
107
|
+
small: "text-xs min-h-32 px-8 py-4 gap-6"
|
|
108
|
+
};
|
|
109
|
+
var ActionChip = forwardRef2(
|
|
110
|
+
function ActionChip2({
|
|
111
|
+
label,
|
|
112
|
+
children,
|
|
113
|
+
size = "default",
|
|
114
|
+
selected = false,
|
|
115
|
+
count,
|
|
116
|
+
startAdornment,
|
|
117
|
+
endAdornment,
|
|
118
|
+
disabled = false,
|
|
119
|
+
type = "button",
|
|
120
|
+
className,
|
|
121
|
+
// Do not leak unsupported removal props onto the native button in JS hosts.
|
|
122
|
+
onRemove: _onRemove,
|
|
123
|
+
removeLabel: _removeLabel,
|
|
124
|
+
removeButtonProps: _removeButtonProps,
|
|
125
|
+
...props
|
|
126
|
+
}, ref) {
|
|
127
|
+
const expanded = props["aria-expanded"] === true || props["aria-expanded"] === "true";
|
|
128
|
+
return /* @__PURE__ */ jsx2(
|
|
129
|
+
"button",
|
|
130
|
+
{
|
|
131
|
+
...props,
|
|
132
|
+
ref,
|
|
133
|
+
type,
|
|
134
|
+
disabled,
|
|
135
|
+
"data-selected": selected ? "true" : void 0,
|
|
136
|
+
className: cx2(
|
|
137
|
+
"inline-flex w-fit max-w-full min-w-0 items-center rounded-full border border-border-minimal bg-bg-base text-fg-base shadow-sm",
|
|
138
|
+
"transition-all duration-200 ease-out cursor-pointer",
|
|
139
|
+
"hover:border-border-primary-base hover:bg-bg-primary-subtle",
|
|
140
|
+
"active:border-border-primary-emphasis active:bg-bg-primary-subtle-90",
|
|
141
|
+
"focus-visible:shadow-focus focus-visible:border-border-emphasis focus-visible:outline-none",
|
|
142
|
+
"data-[state=open]:border-border-primary-base data-[state=open]:bg-bg-primary-subtle",
|
|
143
|
+
sizes[size],
|
|
144
|
+
(selected || expanded) && "border-border-primary-base bg-bg-primary-subtle",
|
|
145
|
+
disabled && "bg-bg-inactive text-fg-inactive border-border-subtle cursor-not-allowed pointer-events-none",
|
|
146
|
+
className
|
|
147
|
+
),
|
|
148
|
+
children: /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 flex-1 items-center gap-6", children: [
|
|
149
|
+
hasSlot(startAdornment) && /* @__PURE__ */ jsx2("span", { className: "inline-flex shrink-0 items-center", children: startAdornment }),
|
|
150
|
+
hasSlot(count) && /* @__PURE__ */ jsx2("span", { className: "inline-flex shrink-0 items-center", children: count }),
|
|
151
|
+
/* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-8", children: [
|
|
152
|
+
/* @__PURE__ */ jsx2("span", { className: "min-w-0 truncate text-left", children: label === void 0 ? children : label }),
|
|
153
|
+
hasSlot(endAdornment) && /* @__PURE__ */ jsx2("span", { className: "inline-flex shrink-0 items-center", children: endAdornment })
|
|
154
|
+
] })
|
|
155
|
+
] })
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
);
|
|
99
160
|
export {
|
|
161
|
+
ActionChip,
|
|
100
162
|
Chip
|
|
101
163
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# DS-20 host inventory
|
|
2
|
+
|
|
3
|
+
Read-only inventory captured 29 October 2026. No Payload source is changed by
|
|
4
|
+
this package work.
|
|
5
|
+
|
|
6
|
+
| Host | Current composition | Required behaviour |
|
|
7
|
+
| ---------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
8
|
+
| `ManuscriptFilterChip.jsx` | Div-root Chip with role, tab index, manual Enter/Space activation, TagNumber and Chevron | Native button ref/events, selected styling independent of popup expansion, count and adornment slots |
|
|
9
|
+
| `ManuScriptsHome.jsx` Stages | Controlled Menu, selected status count and filter-active visual | Native Menu trigger; count/filter state stays with host |
|
|
10
|
+
| `ManuScriptsHome.jsx` sort | Controlled Menu with dynamic sort label | Expansion visual without toggle-button semantics |
|
|
11
|
+
| `ManuscriptSeriesFilter.jsx` | Controlled Menu with selected series count and selected filter visual | Forwarded trigger ref/ARIA/events; selection remains controlled by host |
|
|
12
|
+
| `MetaDataHome.jsx` Status | Menu trigger wraps Chip in a div, count is selected count or total | Candidate native trigger adoption, preserving the host's count policy |
|
|
13
|
+
| `MetaDataHome.jsx` sort | Menu trigger wraps a dynamic-label Chip in a div | Candidate native trigger adoption |
|
|
14
|
+
|
|
15
|
+
Sources: audit round 2 README DS-20, review-1 section 3, review-3 R3-03;
|
|
16
|
+
`payload-client/src/features/Manuscripts/components/ManuscriptFilterChip.jsx`,
|
|
17
|
+
`ManuscriptSeriesFilter.jsx`, `elements/ManuScriptsHome.jsx`, and
|
|
18
|
+
`features/Metadata/pages/MetaDataHome.jsx` in the read-only Payload checkout.
|
|
19
|
+
|
|
20
|
+
The existing Chip root remains a div. Its removal button, callback composition,
|
|
21
|
+
ref types and disabled semantics are a separate contract from an action trigger.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# ActionChip
|
|
2
|
+
|
|
3
|
+
`ActionChip` is a native button for compact filter actions and Menu/Popover
|
|
4
|
+
triggers. The existing `Chip` remains a div with an optional removal button.
|
|
5
|
+
|
|
6
|
+
```tsx
|
|
7
|
+
import { ActionChip } from "@edux-design/chips";
|
|
8
|
+
import { TagNumber } from "@edux-design/tags";
|
|
9
|
+
import { Chevron } from "@edux-design/icons";
|
|
10
|
+
import { Menu, MenuList, MenuItem } from "@edux-design/menus";
|
|
11
|
+
|
|
12
|
+
<Menu
|
|
13
|
+
button={
|
|
14
|
+
<ActionChip
|
|
15
|
+
label="Stages"
|
|
16
|
+
selected={selectedStages.length > 0}
|
|
17
|
+
count={
|
|
18
|
+
selectedStages.length > 0 ? (
|
|
19
|
+
<TagNumber variant="brand" tone="solid">
|
|
20
|
+
{selectedStages.length}
|
|
21
|
+
</TagNumber>
|
|
22
|
+
) : null
|
|
23
|
+
}
|
|
24
|
+
endAdornment={<Chevron aria-hidden="true" />}
|
|
25
|
+
/>
|
|
26
|
+
}
|
|
27
|
+
>
|
|
28
|
+
<MenuList>
|
|
29
|
+
<MenuItem onClick={chooseDraft}>Draft</MenuItem>
|
|
30
|
+
</MenuList>
|
|
31
|
+
</Menu>;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The Menu clones its trigger and supplies popup ARIA, handlers and a composed
|
|
35
|
+
button ref. Popover uses `<PopoverTrigger asChild><ActionChip ... /></PopoverTrigger>`.
|
|
36
|
+
Both compositions retain native Enter/Space/click activation, handler cancellation
|
|
37
|
+
with `event.preventDefault()`, disabled behaviour, Escape dismissal and focus
|
|
38
|
+
restoration. No role, tab index or synthetic keyboard-to-click adapter is needed.
|
|
39
|
+
|
|
40
|
+
| Prop | Contract |
|
|
41
|
+
| ------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `ref` | `HTMLButtonElement`; Chip still accepts `HTMLDivElement` |
|
|
43
|
+
| `label` | Noninteractive React content; native `children` is the fallback when label is undefined |
|
|
44
|
+
| `count` | Noninteractive React content before label; host owns value, visibility and accessible naming |
|
|
45
|
+
| `startAdornment`, `endAdornment` | Noninteractive content, such as an icon or chevron |
|
|
46
|
+
| `selected` | Filter-applied visual and `data-selected="true"` only; never sets `aria-pressed` |
|
|
47
|
+
| `aria-expanded` / `data-state="open"` | Independently highlights an open popup |
|
|
48
|
+
| `aria-pressed` | Optional native attribute only when the action is an actual toggle |
|
|
49
|
+
| `disabled` | Native disabled button semantics and muted token colours |
|
|
50
|
+
| `type` | Native button type, default `"button"`; explicit submit/reset remain supported |
|
|
51
|
+
| `size` | `"default"` (40px minimum) or `"small"` (32px minimum), matching existing Chip geometry |
|
|
52
|
+
| Other native button props | Forwarded, including ARIA, form attributes and events |
|
|
53
|
+
|
|
54
|
+
`onRemove`, `removeLabel` and `removeButtonProps` are prohibited by the public
|
|
55
|
+
ActionChip type. Use `Chip` for removal. Do not place buttons, links, form fields,
|
|
56
|
+
removable Chips or other interactive descendants in any label/count/adornment
|
|
57
|
+
slot. Decorative icons should be hidden from assistive technology. Supply an
|
|
58
|
+
accessible name when there is no visible text; for a terse count, an explicit
|
|
59
|
+
`aria-label` can state the filter name and selected count together.
|
|
60
|
+
|
|
61
|
+
The chip minimum heights intentionally remain component-owned 40px and the
|
|
62
|
+
existing 32px spacing token; mapping to the control-size family would alter
|
|
63
|
+
established compact filter geometry. Existing spacing, colours, shadows, radii
|
|
64
|
+
and focus tokens are reused without changing the token package. The label
|
|
65
|
+
truncates in a constrained parent while count and adornments remain visible.
|
|
66
|
+
|
|
67
|
+
The host owns filter choices, loading/search, selection changes, popup state and
|
|
68
|
+
counts. This component does not manage pressed/open state or emit separate state
|
|
69
|
+
callbacks. The host should replace the old ManuscriptFilterChip's div keyboard
|
|
70
|
+
adapter with ActionChip when it adopts a released package; see
|
|
71
|
+
[action-chip-inventory.md](./action-chip-inventory.md) for the inspected hosts.
|
|
72
|
+
|
|
73
|
+
Verification: `src/elements/ActionChip.test.tsx`, strict public fixtures in
|
|
74
|
+
`test/action-chip-api.tsx`, CJS/SSR in `test/action-chip-public-runtime.cjs`,
|
|
75
|
+
four strict-a11y stories, and `node packages/chips/test/action-chip.browser.mjs`.
|
|
76
|
+
The browser suite compares both sizes against legacy Chip geometry in blue/teal,
|
|
77
|
+
light/dark; checks native once-only pointer/Enter/Space activation, real overlay
|
|
78
|
+
Escape/focus restoration, disabled state, and narrow long-label truncation.
|
|
79
|
+
|
|
80
|
+
The Menu composition story counts only true `onOpenChange` requests. Menu owns
|
|
81
|
+
its trigger opening policy, and may handle keyboard events before a native click
|
|
82
|
+
is emitted. Child `onClick` is not an overlay-open notification contract. The
|
|
83
|
+
standalone ActionChip tests separately verify one native click per pointer, Enter
|
|
84
|
+
or Space activation. Keyboard story sequences await Escape focus restoration
|
|
85
|
+
before sending the next activation key.
|