@recursica/mui-adapter 0.19.0 → 0.21.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 +28 -0
- package/README.md +3 -3
- package/dist/mui-adapter.cjs +89 -58
- package/dist/mui-adapter.cjs.map +1 -1
- package/dist/mui-adapter.css +1 -1
- package/dist/mui-adapter.js +26163 -8727
- package/dist/mui-adapter.js.map +1 -1
- package/dist/src/components/Dropdown/BareDropdown.d.ts +41 -0
- package/dist/src/components/TimePicker/TimePicker.d.ts +16 -3
- package/dist/src/components/Tree/Tree.d.ts +5 -0
- package/dist/src/index.d.ts +1 -1
- package/docs/PHILOSOPHY.md +39 -0
- package/package.json +10 -3
- package/src/components/Box/USAGE.md +1 -1
- package/src/components/Button/BUTTON_IMPLEMENTATION_NOTES.md +22 -0
- package/src/components/Button/Button.module.css +10 -7
- package/src/components/Button/Button.tsx +4 -0
- package/src/components/Button/USAGE.md +1 -13
- package/src/components/Card/USAGE.md +1 -13
- package/src/components/Container/USAGE.md +1 -1
- package/src/components/Dropdown/BareDropdown.tsx +135 -0
- package/src/components/Dropdown/Dropdown.tsx +16 -0
- package/src/components/Grid/USAGE.md +6 -10
- package/src/components/Loader/USAGE.md +1 -23
- package/src/components/Menu/USAGE.md +0 -7
- package/src/components/Pagination/USAGE.md +0 -6
- package/src/components/Panel/USAGE.md +1 -58
- package/src/components/Stepper/USAGE.md +0 -7
- package/src/components/Tabs/USAGE.md +0 -7
- package/src/components/TimePicker/TIMEPICKER_IMPLEMENTATION_NOTES.md +68 -0
- package/src/components/TimePicker/TimePicker.module.css +213 -37
- package/src/components/TimePicker/TimePicker.stories.tsx +119 -4
- package/src/components/TimePicker/TimePicker.tsx +257 -8
- package/src/components/TimePicker/USAGE.md +27 -2
- package/src/components/Tree/IMPLEMENTATION_NOTES.md +28 -1
- package/src/components/Tree/Tree.module.css +114 -69
- package/src/components/Tree/Tree.stories.tsx +13 -0
- package/src/components/Tree/Tree.tsx +99 -32
- package/src/components/Tree/USAGE.md +18 -2
- package/src/index.ts +1 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
useTreeItem,
|
|
5
5
|
type UseTreeItemParameters,
|
|
6
6
|
} from "@mui/x-tree-view/useTreeItem";
|
|
7
|
-
import { useTreeItemModel } from "@mui/x-tree-view/hooks";
|
|
7
|
+
import { useTreeItemModel, useTreeItemUtils } from "@mui/x-tree-view/hooks";
|
|
8
8
|
import { TreeItemProvider } from "@mui/x-tree-view/TreeItemProvider";
|
|
9
9
|
import {
|
|
10
10
|
TreeItemRoot,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type RecursicaTreeProps,
|
|
23
23
|
type RecursicaTreeNode,
|
|
24
24
|
} from "@recursica/adapter-common";
|
|
25
|
+
import { Button } from "../Button/Button";
|
|
25
26
|
import styles from "./Tree.module.css";
|
|
26
27
|
|
|
27
28
|
export type TreeProps = RecursicaOverStyled<
|
|
@@ -40,13 +41,16 @@ function collectAllValues(nodes: RecursicaTreeNode[]): string[] {
|
|
|
40
41
|
return values;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
/**
|
|
44
|
-
* than one rotating icon (unlike Mantine's `[data-expanded]`
|
|
45
|
-
*
|
|
46
|
-
|
|
44
|
+
/** Chevron glyph; rendered inside a `Button` below. MUI renders a different component for
|
|
45
|
+
* expanded vs collapsed rather than one rotating icon (unlike Mantine's `[data-expanded]`
|
|
46
|
+
* approach), so `rotated` picks the CSS class that applies the rotation. Uses `currentColor` so
|
|
47
|
+
* it matches Button's own tokened icon color for the "text" variant. */
|
|
48
|
+
function ChevronGlyph({ rotated }: { rotated?: boolean }) {
|
|
47
49
|
return (
|
|
48
50
|
<svg
|
|
49
|
-
className={
|
|
51
|
+
className={
|
|
52
|
+
rotated ? `${styles.chevron} ${styles.chevronExpanded}` : styles.chevron
|
|
53
|
+
}
|
|
50
54
|
viewBox="0 0 16 16"
|
|
51
55
|
width="1em"
|
|
52
56
|
height="1em"
|
|
@@ -61,29 +65,47 @@ function ExpandGlyph() {
|
|
|
61
65
|
</svg>
|
|
62
66
|
);
|
|
63
67
|
}
|
|
64
|
-
|
|
68
|
+
|
|
69
|
+
/** Never independently focusable/tab-stoppable (tabIndex={-1}, aria-hidden) — expand/collapse is
|
|
70
|
+
* driven by the surrounding `TreeItemIconContainer`'s own click handling (see `CustomTreeItem`),
|
|
71
|
+
* not by this Button itself, so the row stays the only focusable element and the button never
|
|
72
|
+
* shows its own focus state. Rendered for every row, including leaf items, so row alignment
|
|
73
|
+
* stays consistent; `hidden` (visibility, not display) is used for leaf rows instead of omitting
|
|
74
|
+
* the button, so the layout space is reserved without duplicating Button's own size tokens. */
|
|
75
|
+
function ExpandToggleButton({
|
|
76
|
+
rotated,
|
|
77
|
+
hidden,
|
|
78
|
+
}: {
|
|
79
|
+
rotated?: boolean;
|
|
80
|
+
hidden?: boolean;
|
|
81
|
+
}) {
|
|
65
82
|
return (
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
stroke="currentColor"
|
|
73
|
-
strokeWidth="2"
|
|
74
|
-
strokeLinecap="round"
|
|
75
|
-
strokeLinejoin="round"
|
|
83
|
+
<Button
|
|
84
|
+
overStyled
|
|
85
|
+
variant="text"
|
|
86
|
+
size="small"
|
|
87
|
+
icon={<ChevronGlyph rotated={rotated} />}
|
|
88
|
+
aria-label="Toggle subtree"
|
|
76
89
|
aria-hidden="true"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
tabIndex={-1}
|
|
91
|
+
className={
|
|
92
|
+
hidden
|
|
93
|
+
? `${styles.expandButton} ${styles.expandButtonHidden}`
|
|
94
|
+
: styles.expandButton
|
|
95
|
+
}
|
|
96
|
+
/>
|
|
80
97
|
);
|
|
81
98
|
}
|
|
99
|
+
function ExpandGlyph() {
|
|
100
|
+
return <ExpandToggleButton />;
|
|
101
|
+
}
|
|
102
|
+
function CollapseGlyph() {
|
|
103
|
+
return <ExpandToggleButton rotated />;
|
|
104
|
+
}
|
|
82
105
|
/** Rendered for leaf items so the icon-container box (and therefore row alignment) stays
|
|
83
|
-
* consistent whether or not a row has an expand chevron
|
|
84
|
-
* implementation's always-present `.expandIcon` span. */
|
|
106
|
+
* consistent whether or not a row has an expand chevron. */
|
|
85
107
|
function EndGlyph() {
|
|
86
|
-
return <
|
|
108
|
+
return <ExpandToggleButton hidden />;
|
|
87
109
|
}
|
|
88
110
|
|
|
89
111
|
interface CustomTreeItemProps extends UseTreeItemParameters {
|
|
@@ -111,14 +133,43 @@ const CustomTreeItem = forwardRef<HTMLLIElement, CustomTreeItemProps>(
|
|
|
111
133
|
status,
|
|
112
134
|
} = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref });
|
|
113
135
|
|
|
136
|
+
// Exposes the same select/expand actions `useTreeItem`'s internal keyboard-navigation plugin
|
|
137
|
+
// uses, so we can override just the `Enter` key below without reimplementing selection.
|
|
138
|
+
const { interactions } = useTreeItemUtils({ itemId, children });
|
|
139
|
+
|
|
114
140
|
const item = useTreeItemModel<RecursicaTreeNode>(itemId);
|
|
115
141
|
|
|
142
|
+
const iconContainerProps = getIconContainerProps({
|
|
143
|
+
className: styles.iconContainer,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// `Enter` selects the focused node, full stop — never expands it, unlike
|
|
147
|
+
// `@mui/x-tree-view`'s own default (which expands an expandable node on `Enter` and only
|
|
148
|
+
// falls back to selecting leaf nodes). Setting `defaultMuiPrevented` is the documented
|
|
149
|
+
// extension point `createRootHandleKeyDown` checks before running its own handling, so this
|
|
150
|
+
// fully replaces rather than races the built-in behavior. `Space` already selects-only by
|
|
151
|
+
// default, so it needs no override.
|
|
152
|
+
const handleRootKeyDown = (
|
|
153
|
+
event: React.KeyboardEvent<HTMLLIElement> & {
|
|
154
|
+
defaultMuiPrevented?: boolean;
|
|
155
|
+
},
|
|
156
|
+
) => {
|
|
157
|
+
if (event.key !== "Enter") return;
|
|
158
|
+
event.preventDefault();
|
|
159
|
+
event.defaultMuiPrevented = true;
|
|
160
|
+
// `handleSelection` only reads modifier keys (shiftKey/ctrlKey/metaKey) off the event and
|
|
161
|
+
// forwards it on for focus bookkeeping — its `React.MouseEvent` type is narrower than
|
|
162
|
+
// necessary for what it actually uses, so a KeyboardEvent works fine at runtime here.
|
|
163
|
+
interactions.handleSelection(event as unknown as React.MouseEvent);
|
|
164
|
+
};
|
|
165
|
+
|
|
116
166
|
return (
|
|
117
167
|
<TreeItemProvider {...getContextProviderProps()}>
|
|
118
168
|
<TreeItemRoot
|
|
119
169
|
{...getRootProps({
|
|
120
170
|
...rest,
|
|
121
171
|
className: `${styles.node} ${className ?? ""}`,
|
|
172
|
+
onKeyDown: handleRootKeyDown,
|
|
122
173
|
})}
|
|
123
174
|
>
|
|
124
175
|
<TreeItemContent
|
|
@@ -126,7 +177,15 @@ const CustomTreeItem = forwardRef<HTMLLIElement, CustomTreeItemProps>(
|
|
|
126
177
|
status={status}
|
|
127
178
|
>
|
|
128
179
|
<TreeItemIconContainer
|
|
129
|
-
{...
|
|
180
|
+
{...iconContainerProps}
|
|
181
|
+
// Stops the click from also reaching `TreeItemContent`'s own click handler, which
|
|
182
|
+
// otherwise selects on *any* click inside it (including the icon container) once
|
|
183
|
+
// `expansionTrigger` is `"iconContainer"` — the button must expand/collapse
|
|
184
|
+
// without ever selecting.
|
|
185
|
+
onClick={(event) => {
|
|
186
|
+
event.stopPropagation();
|
|
187
|
+
iconContainerProps.onClick(event);
|
|
188
|
+
}}
|
|
130
189
|
>
|
|
131
190
|
<TreeItemIcon status={status} />
|
|
132
191
|
</TreeItemIconContainer>
|
|
@@ -152,6 +211,11 @@ const CustomTreeItem = forwardRef<HTMLLIElement, CustomTreeItemProps>(
|
|
|
152
211
|
* Wraps `@mui/x-tree-view`'s `RichTreeView` with a fully custom item renderer so every visual
|
|
153
212
|
* aspect (row box model, selected/unselected colors and typography, indent, item spacing)
|
|
154
213
|
* comes from Recursica's `tree` design tokens rather than MUI's defaults.
|
|
214
|
+
*
|
|
215
|
+
* **Interaction pattern (fixed, not prop-configurable):** expand/collapse and select are
|
|
216
|
+
* independent — the chevron button toggles a node's subtree only, clicking the rest of a row
|
|
217
|
+
* (or pressing `Enter`/`Space`) selects it only, and `ArrowLeft`/`ArrowRight` toggle expansion
|
|
218
|
+
* only. See `RecursicaTreeProps` for the full breakdown.
|
|
155
219
|
*/
|
|
156
220
|
export const Tree = forwardRef<HTMLUListElement, TreeProps>(function Tree(
|
|
157
221
|
{
|
|
@@ -160,8 +224,7 @@ export const Tree = forwardRef<HTMLUListElement, TreeProps>(function Tree(
|
|
|
160
224
|
initialExpandedValues,
|
|
161
225
|
initialSelectedValues,
|
|
162
226
|
multiple = false,
|
|
163
|
-
|
|
164
|
-
selectOnClick = true,
|
|
227
|
+
disabled = false,
|
|
165
228
|
onNodeExpand,
|
|
166
229
|
onNodeCollapse,
|
|
167
230
|
onSelectedChange,
|
|
@@ -195,12 +258,16 @@ export const Tree = forwardRef<HTMLUListElement, TreeProps>(function Tree(
|
|
|
195
258
|
multiple ? (initialSelectedValues ?? []) : initialSelectedValues?.[0]
|
|
196
259
|
}
|
|
197
260
|
multiSelect={multiple}
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
261
|
+
// Whole-tree `disabled` reuses `@mui/x-tree-view`'s own per-item `isItemDisabled` — marking
|
|
262
|
+
// every item disabled at once — rather than a bespoke mechanism, since MUI already handles
|
|
263
|
+
// blocking click/keyboard select/expand and dimming (`.row[data-disabled]` in
|
|
264
|
+
// Tree.module.css) for disabled items internally.
|
|
265
|
+
isItemDisabled={disabled ? () => true : undefined}
|
|
266
|
+
// Fixed, not prop-driven: only the icon container (the chevron button) expands/collapses
|
|
267
|
+
// on click; clicking anywhere else on the row selects instead. `CustomTreeItem` additionally
|
|
268
|
+
// stops the icon container's click from also bubbling into the content's own selection
|
|
269
|
+
// handler — see IMPLEMENTATION_NOTES.md.
|
|
270
|
+
expansionTrigger="iconContainer"
|
|
204
271
|
onItemExpansionToggle={(_event, itemId, isExpanded) => {
|
|
205
272
|
if (isExpanded) onNodeExpand?.(itemId);
|
|
206
273
|
else onNodeCollapse?.(itemId);
|
|
@@ -60,7 +60,24 @@ Each node needs a unique `value` and a `label`. Any node with a `children` array
|
|
|
60
60
|
|
|
61
61
|
- `initialExpandedValues` accepts an array of node values, or `"*"` to start with every node expanded.
|
|
62
62
|
- `multiple` allows more than one node to be selected at once (default: single-select).
|
|
63
|
-
|
|
63
|
+
|
|
64
|
+
### Interaction pattern
|
|
65
|
+
|
|
66
|
+
Expanding/collapsing and selecting are independent, fixed interactions (not prop-configurable):
|
|
67
|
+
|
|
68
|
+
- Clicking the expand/collapse chevron toggles that node's subtree only — it never selects.
|
|
69
|
+
- Clicking anywhere else on a row selects it — it never toggles expansion.
|
|
70
|
+
- `Enter`/`Space` on a focused node selects it, even if the node has children.
|
|
71
|
+
- `ArrowLeft`/`ArrowRight` expand/collapse the focused node only, without changing selection.
|
|
72
|
+
- `ArrowUp`/`ArrowDown` move focus between rows.
|
|
73
|
+
|
|
74
|
+
### Disabled
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
<Tree data={data} initialSelectedValues={["1.1"]} disabled />
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`disabled` disables the whole tree — no expand/collapse or select via click or keyboard, dimmed to the standard disabled opacity. A pre-selected node stays visibly selected, just dimmed along with the rest. There's no per-node disabled state (see **Known Constraints** below).
|
|
64
81
|
|
|
65
82
|
---
|
|
66
83
|
|
|
@@ -78,6 +95,5 @@ All Recursica components in the `@recursica/mui-adapter` package adhere strictly
|
|
|
78
95
|
|
|
79
96
|
## 5. Known Constraints
|
|
80
97
|
|
|
81
|
-
- **`expandOnClick`/`selectOnClick` are approximated, not exact.** `@mui/x-tree-view` doesn't expose the same two independent toggles Mantine's `Tree` does. `expandOnClick` maps to MUI's `expansionTrigger` (`"content"` vs `"iconContainer"`); `selectOnClick={false}` maps to `disableSelection` (which disables selection entirely, rather than only suppressing it on a content click while leaving some other path to select). See `IMPLEMENTATION_NOTES.md`.
|
|
82
98
|
- No checkbox/multi-check styling is exposed — `@mui/x-tree-view` supports checkbox selection, but Recursica's `tree` design tokens don't yet define a checked visual state, so it isn't part of the Recursica API.
|
|
83
99
|
- No per-node `disabled` state — the Figma UI Kit tokens don't define one, and `RecursicaTreeNode` has no `disabled` field either.
|