@recursica/mantine-adapter 0.35.1 → 0.36.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 +17 -0
- package/dist/mantine-adapter.cjs +2 -2
- package/dist/mantine-adapter.cjs.map +1 -1
- package/dist/mantine-adapter.css +1 -1
- package/dist/mantine-adapter.js +1104 -1023
- package/dist/mantine-adapter.js.map +1 -1
- package/dist/src/components/Tree/Tree.d.ts +9 -5
- package/package.json +1 -1
- package/src/components/Accordion/Accordion.module.css +123 -62
- package/src/components/Accordion/Accordion.stories.tsx +65 -33
- package/src/components/Accordion/Accordion.tsx +1 -0
- package/src/components/AutoComplete/AutoComplete.module.css +50 -49
- package/src/components/AutoComplete/AutoComplete.tsx +2 -2
- package/src/components/Avatar/Avatar.module.css +7 -7
- package/src/components/Badge/Badge.module.css +8 -8
- package/src/components/Button/Button.module.css +22 -89
- package/src/components/Card/Card.module.css +3 -3
- package/src/components/Checkbox/Checkbox.module.css +54 -15
- package/src/components/Chip/Chip.module.css +27 -26
- package/src/components/DatePicker/DatePicker.module.css +25 -30
- package/src/components/Dropdown/Dropdown.module.css +47 -44
- package/src/components/Dropdown/Dropdown.tsx +2 -2
- package/src/components/FormControlWrapper/FormControlWrapper.module.css +12 -12
- package/src/components/HoverCard/HoverCard.module.css +1 -1
- package/src/components/Label/Label.module.css +7 -3
- package/src/components/Link/IMPLEMENTATION_NOTES.md +2 -9
- package/src/components/Link/Link.module.css +15 -76
- package/src/components/Link/USAGE.md +2 -9
- package/src/components/Loader/Loader.module.css +3 -3
- package/src/components/Menu/Menu.module.css +34 -41
- package/src/components/Modal/Modal.module.css +27 -11
- package/src/components/NumberInput/NumberInput.module.css +39 -37
- package/src/components/NumberInput/NumberInput.tsx +2 -2
- package/src/components/Pagination/Pagination.module.css +10 -43
- package/src/components/Panel/Panel.module.css +3 -3
- package/src/components/Popover/Popover.module.css +1 -1
- package/src/components/Radio/Radio.module.css +31 -14
- package/src/components/ReadOnlyField/ReadOnlyField.module.css +1 -1
- package/src/components/SegmentedControl/SegmentedControl.module.css +29 -29
- package/src/components/Slider/IMPLEMENTATION_NOTES.md +9 -0
- package/src/components/Slider/Slider.module.css +44 -48
- package/src/components/Stepper/Stepper.module.css +3 -3
- package/src/components/Switch/Switch.module.css +26 -10
- package/src/components/Table/Table.module.css +53 -21
- package/src/components/Tabs/Tabs.module.css +309 -170
- package/src/components/TextArea/TextArea.module.css +36 -21
- package/src/components/TextArea/TextArea.tsx +2 -2
- package/src/components/TextField/TextField.module.css +38 -35
- package/src/components/TextField/TextField.tsx +2 -2
- package/src/components/Timeline/Timeline.module.css +41 -34
- package/src/components/Toast/Toast.module.css +19 -11
- package/src/components/Tooltip/Tooltip.module.css +4 -2
- package/src/components/Tree/IMPLEMENTATION_NOTES.md +17 -0
- package/src/components/Tree/Tree.module.css +173 -39
- package/src/components/Tree/Tree.stories.tsx +81 -4
- package/src/components/Tree/Tree.tsx +117 -15
- package/src/components/Tree/USAGE.md +36 -3
|
@@ -1,43 +1,177 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_selected-text_font-style */
|
|
11
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_selected-text_font-weight */
|
|
12
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_selected-text_letter-spacing */
|
|
13
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_selected-text_line-height */
|
|
14
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_selected-text_text-decoration */
|
|
15
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_selected-text_text-transform */
|
|
16
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_unselected-text_font-family */
|
|
17
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_unselected-text_font-size */
|
|
18
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_unselected-text_font-style */
|
|
19
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_unselected-text_font-weight */
|
|
20
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_unselected-text_letter-spacing */
|
|
21
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_unselected-text_line-height */
|
|
22
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_unselected-text_text-decoration */
|
|
23
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_unselected-text_text-transform */
|
|
24
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_vertical-padding */
|
|
25
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_colors_hover-background */
|
|
26
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_colors_hover-border-color */
|
|
27
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_colors_hover-text */
|
|
28
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_colors_selected-background */
|
|
29
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_colors_selected-border-color */
|
|
30
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_colors_selected-text */
|
|
31
|
-
/* recursica-ignore: --recursica_ui-kit_components_tree_properties_colors_unselected-text */
|
|
1
|
+
/* HARDCODED VALUES:
|
|
2
|
+
* - list-style/margin/padding resets on .root/.subtree: layout resets, no corresponding
|
|
3
|
+
* design tokens (structural, not visual design values).
|
|
4
|
+
* - .expandIcon size (1em) and rotation transition: no dedicated icon-size/icon-color token
|
|
5
|
+
* exists for Tree; the glyph is drawn with `currentColor` so it always tracks the row's
|
|
6
|
+
* already-tokened text color, and sized relative to the row's own font-size.
|
|
7
|
+
* - Hover overlay uses the generic --recursica_brand_states_hover_* tokens (same technique as
|
|
8
|
+
* Menu/Accordion/Button): Tree has no component-specific hover tokens of its own.
|
|
9
|
+
*/
|
|
32
10
|
|
|
33
11
|
.root {
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 0;
|
|
14
|
+
list-style: none;
|
|
15
|
+
max-width: var(--recursica_ui-kit_components_tree_properties_max-width);
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: var(--recursica_ui-kit_components_tree_properties_item-gap);
|
|
19
|
+
|
|
20
|
+
/* Mantine computes each node's --label-offset (per-level indent) from --level-offset via its
|
|
21
|
+
own internal vars resolver (same category of override as Timeline's --mantine-spacing-xl /
|
|
22
|
+
--tl-line-width). Use !important to reliably beat Mantine's own generated rule. */
|
|
23
|
+
--level-offset: var(
|
|
24
|
+
--recursica_ui-kit_components_tree_properties_indent
|
|
25
|
+
) !important;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.subtree {
|
|
29
|
+
margin: 0;
|
|
30
|
+
padding: 0;
|
|
31
|
+
list-style: none;
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
gap: var(--recursica_ui-kit_components_tree_properties_item-gap);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.node {
|
|
38
|
+
/* Mantine's own base class already resets margin/padding/list-style and sets cursor:pointer
|
|
39
|
+
on this <li>; it also holds the subtree as a second child, so all row-level visual styling
|
|
40
|
+
(box model, colors, typography) lives on .row instead, not here. */
|
|
41
|
+
position: relative;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.row {
|
|
45
|
+
position: relative;
|
|
34
46
|
box-sizing: border-box;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
margin-left: var(--label-offset, 0px);
|
|
51
|
+
gap: var(--recursica_ui-kit_components_tree_properties_button-node-gap);
|
|
52
|
+
padding: var(--recursica_ui-kit_components_tree_properties_vertical-padding)
|
|
53
|
+
var(--recursica_ui-kit_components_tree_properties_horizontal-padding);
|
|
54
|
+
border-style: solid; /* HARDCODE: structural border rule; thickness/color are tokened */
|
|
55
|
+
border-width: var(--recursica_ui-kit_components_tree_properties_border-size);
|
|
56
|
+
border-radius: var(
|
|
57
|
+
--recursica_ui-kit_components_tree_properties_border-radius
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
font-family: var(
|
|
61
|
+
--recursica_ui-kit_components_tree_variants_selection-states_unselected_properties_text_font-family
|
|
62
|
+
);
|
|
63
|
+
font-size: var(
|
|
64
|
+
--recursica_ui-kit_components_tree_variants_selection-states_unselected_properties_text_font-size
|
|
65
|
+
);
|
|
66
|
+
font-style: var(
|
|
67
|
+
--recursica_ui-kit_components_tree_variants_selection-states_unselected_properties_text_font-style
|
|
68
|
+
);
|
|
69
|
+
font-weight: var(
|
|
70
|
+
--recursica_ui-kit_components_tree_variants_selection-states_unselected_properties_text_font-weight
|
|
71
|
+
);
|
|
72
|
+
letter-spacing: var(
|
|
73
|
+
--recursica_ui-kit_components_tree_variants_selection-states_unselected_properties_text_letter-spacing
|
|
74
|
+
);
|
|
75
|
+
line-height: var(
|
|
76
|
+
--recursica_ui-kit_components_tree_variants_selection-states_unselected_properties_text_line-height
|
|
77
|
+
);
|
|
78
|
+
text-decoration: var(
|
|
79
|
+
--recursica_ui-kit_components_tree_variants_selection-states_unselected_properties_text_text-decoration
|
|
80
|
+
);
|
|
81
|
+
text-transform: var(
|
|
82
|
+
--recursica_ui-kit_components_tree_variants_selection-states_unselected_properties_text_text-transform
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
color: var(
|
|
86
|
+
--recursica_ui-kit_components_tree_variants_selection-states_unselected_properties_colors_text-color
|
|
87
|
+
);
|
|
88
|
+
background-color: var(
|
|
89
|
+
--recursica_ui-kit_components_tree_variants_selection-states_unselected_properties_colors_background-color
|
|
90
|
+
);
|
|
91
|
+
border-color: var(
|
|
92
|
+
--recursica_ui-kit_components_tree_variants_selection-states_unselected_properties_colors_border-color
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Hover overlay via ::after pseudo-element (same technique as Menu/Accordion) */
|
|
97
|
+
.row::after {
|
|
98
|
+
content: "";
|
|
99
|
+
position: absolute;
|
|
100
|
+
inset: 0;
|
|
101
|
+
border-radius: inherit;
|
|
102
|
+
z-index: 0;
|
|
103
|
+
pointer-events: none;
|
|
104
|
+
background-color: var(--recursica_brand_states_hover_color);
|
|
105
|
+
opacity: 0;
|
|
106
|
+
transition: opacity 150ms ease;
|
|
107
|
+
}
|
|
108
|
+
.row:hover::after,
|
|
109
|
+
.row[data-hovered]::after {
|
|
110
|
+
opacity: var(--recursica_brand_states_hover_opacity);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.row[data-selected] {
|
|
114
|
+
font-family: var(
|
|
115
|
+
--recursica_ui-kit_components_tree_variants_selection-states_selected_properties_text_font-family
|
|
116
|
+
);
|
|
117
|
+
font-size: var(
|
|
118
|
+
--recursica_ui-kit_components_tree_variants_selection-states_selected_properties_text_font-size
|
|
119
|
+
);
|
|
120
|
+
font-style: var(
|
|
121
|
+
--recursica_ui-kit_components_tree_variants_selection-states_selected_properties_text_font-style
|
|
122
|
+
);
|
|
123
|
+
font-weight: var(
|
|
124
|
+
--recursica_ui-kit_components_tree_variants_selection-states_selected_properties_text_font-weight
|
|
125
|
+
);
|
|
126
|
+
letter-spacing: var(
|
|
127
|
+
--recursica_ui-kit_components_tree_variants_selection-states_selected_properties_text_letter-spacing
|
|
128
|
+
);
|
|
129
|
+
line-height: var(
|
|
130
|
+
--recursica_ui-kit_components_tree_variants_selection-states_selected_properties_text_line-height
|
|
131
|
+
);
|
|
132
|
+
text-decoration: var(
|
|
133
|
+
--recursica_ui-kit_components_tree_variants_selection-states_selected_properties_text_text-decoration
|
|
134
|
+
);
|
|
135
|
+
text-transform: var(
|
|
136
|
+
--recursica_ui-kit_components_tree_variants_selection-states_selected_properties_text_text-transform
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
color: var(
|
|
140
|
+
--recursica_ui-kit_components_tree_variants_selection-states_selected_properties_colors_text-color
|
|
141
|
+
);
|
|
142
|
+
background-color: var(
|
|
143
|
+
--recursica_ui-kit_components_tree_variants_selection-states_selected_properties_colors_background-color
|
|
144
|
+
);
|
|
145
|
+
border-color: var(
|
|
146
|
+
--recursica_ui-kit_components_tree_variants_selection-states_selected_properties_colors_border-color
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.expandIcon {
|
|
151
|
+
position: relative;
|
|
152
|
+
z-index: 1;
|
|
153
|
+
display: flex;
|
|
154
|
+
align-items: center;
|
|
155
|
+
justify-content: center;
|
|
156
|
+
flex-shrink: 0;
|
|
157
|
+
width: 1em;
|
|
158
|
+
height: 1em;
|
|
159
|
+
color: inherit;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.row[data-has-children] .expandIcon {
|
|
163
|
+
transition: transform 150ms ease;
|
|
164
|
+
}
|
|
165
|
+
.row[data-expanded] .expandIcon {
|
|
166
|
+
transform: rotate(90deg);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.label {
|
|
170
|
+
position: relative;
|
|
171
|
+
z-index: 1;
|
|
172
|
+
color: inherit;
|
|
173
|
+
font: inherit;
|
|
174
|
+
letter-spacing: inherit;
|
|
175
|
+
text-decoration: inherit;
|
|
176
|
+
text-transform: inherit;
|
|
43
177
|
}
|
|
@@ -1,14 +1,91 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
2
|
import { Tree } from "./Tree";
|
|
3
|
+
import { Layer, type RecursicaTreeNode } from "@recursica/adapter-common";
|
|
4
|
+
|
|
5
|
+
const sampleData: RecursicaTreeNode[] = [
|
|
6
|
+
{
|
|
7
|
+
value: "documents",
|
|
8
|
+
label: "Documents",
|
|
9
|
+
children: [
|
|
10
|
+
{ value: "documents/resume.pdf", label: "resume.pdf" },
|
|
11
|
+
{ value: "documents/cover-letter.docx", label: "cover-letter.docx" },
|
|
12
|
+
{
|
|
13
|
+
value: "documents/taxes",
|
|
14
|
+
label: "Taxes",
|
|
15
|
+
children: [
|
|
16
|
+
{ value: "documents/taxes/2023.pdf", label: "2023.pdf" },
|
|
17
|
+
{ value: "documents/taxes/2024.pdf", label: "2024.pdf" },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
value: "photos",
|
|
24
|
+
label: "Photos",
|
|
25
|
+
children: [
|
|
26
|
+
{ value: "photos/vacation.jpg", label: "vacation.jpg" },
|
|
27
|
+
{ value: "photos/family.jpg", label: "family.jpg" },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{ value: "readme.md", label: "readme.md" },
|
|
31
|
+
];
|
|
3
32
|
|
|
4
33
|
const meta: Meta<typeof Tree> = {
|
|
5
|
-
title: "
|
|
34
|
+
title: "UI-Kit/Tree",
|
|
6
35
|
component: Tree,
|
|
36
|
+
tags: ["autodocs"],
|
|
37
|
+
parameters: {
|
|
38
|
+
docs: {
|
|
39
|
+
description: {
|
|
40
|
+
component: `
|
|
41
|
+
The \`Tree\` component renders hierarchical \`data\` as an expandable/selectable tree, wrapping \`@mantine/core\`'s \`Tree\` with a fully custom node renderer so every visual property (row box model, selected/unselected colors and typography, indent, item spacing) comes from Recursica's \`tree\` design tokens.
|
|
42
|
+
|
|
43
|
+
Each node needs a unique \`value\` and a \`label\`; a \`children\` array (even empty) makes a node expandable.
|
|
44
|
+
`,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
7
48
|
};
|
|
8
49
|
|
|
9
50
|
export default meta;
|
|
10
|
-
type Story = StoryObj<typeof Tree>;
|
|
11
51
|
|
|
12
|
-
export const
|
|
13
|
-
|
|
52
|
+
export const Default: StoryObj<typeof Tree> = {
|
|
53
|
+
render: () => (
|
|
54
|
+
<Tree data={sampleData} initialExpandedValues={["documents"]} />
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const AllExpanded: StoryObj<typeof Tree> = {
|
|
59
|
+
render: () => <Tree data={sampleData} initialExpandedValues="*" />,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const PreSelected: StoryObj<typeof Tree> = {
|
|
63
|
+
render: () => (
|
|
64
|
+
<Tree
|
|
65
|
+
data={sampleData}
|
|
66
|
+
initialExpandedValues={["documents"]}
|
|
67
|
+
initialSelectedValues={["documents/resume.pdf"]}
|
|
68
|
+
/>
|
|
69
|
+
),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const MultipleSelection: StoryObj<typeof Tree> = {
|
|
73
|
+
render: () => (
|
|
74
|
+
<Tree
|
|
75
|
+
data={sampleData}
|
|
76
|
+
initialExpandedValues="*"
|
|
77
|
+
initialSelectedValues={["documents/resume.pdf", "photos/vacation.jpg"]}
|
|
78
|
+
multiple
|
|
79
|
+
/>
|
|
80
|
+
),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** Demonstrates the component nested inside a non-default layer — the one case where an
|
|
84
|
+
* explicit `<Layer>` wrap belongs in a story (see COMPONENT_STORYBOOK_GUIDE.md §9). */
|
|
85
|
+
export const LayerOne: StoryObj<typeof Tree> = {
|
|
86
|
+
render: () => (
|
|
87
|
+
<Layer layer={1} style={{ padding: "24px" }}>
|
|
88
|
+
<Tree data={sampleData} initialExpandedValues={["documents"]} />
|
|
89
|
+
</Layer>
|
|
90
|
+
),
|
|
14
91
|
};
|
|
@@ -1,24 +1,126 @@
|
|
|
1
|
-
import React, { forwardRef } from "react";
|
|
2
|
-
import {
|
|
1
|
+
import React, { forwardRef, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Tree as MantineTree,
|
|
4
|
+
useTree,
|
|
5
|
+
getTreeExpandedState,
|
|
6
|
+
type RenderTreeNodePayload,
|
|
7
|
+
} from "@mantine/core";
|
|
8
|
+
import {
|
|
9
|
+
filterStylingProps,
|
|
10
|
+
type RecursicaOverStyled,
|
|
11
|
+
} from "../../utils/filterStylingProps";
|
|
12
|
+
import { type RecursicaTreeProps } from "@recursica/adapter-common";
|
|
3
13
|
import styles from "./Tree.module.css";
|
|
4
14
|
|
|
5
|
-
export
|
|
6
|
-
|
|
15
|
+
export type TreeProps = RecursicaOverStyled<
|
|
16
|
+
RecursicaTreeProps & Omit<React.ComponentPropsWithoutRef<"ul">, "children">
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
/** Simple chevron glyph; rotates via `[data-expanded]` in CSS. Uses `currentColor` so it
|
|
20
|
+
* always matches the row's tokened text color — Tree has no dedicated icon-color token. */
|
|
21
|
+
function ExpandGlyph() {
|
|
22
|
+
return (
|
|
23
|
+
<svg
|
|
24
|
+
viewBox="0 0 16 16"
|
|
25
|
+
width="1em"
|
|
26
|
+
height="1em"
|
|
27
|
+
fill="none"
|
|
28
|
+
stroke="currentColor"
|
|
29
|
+
strokeWidth="2"
|
|
30
|
+
strokeLinecap="round"
|
|
31
|
+
strokeLinejoin="round"
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
>
|
|
34
|
+
<path d="M5 3l5 5-5 5" />
|
|
35
|
+
</svg>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function renderTreeNode({
|
|
40
|
+
node,
|
|
41
|
+
hasChildren,
|
|
42
|
+
expanded,
|
|
43
|
+
elementProps,
|
|
44
|
+
}: RenderTreeNodePayload) {
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
{...elementProps}
|
|
48
|
+
className={styles.row}
|
|
49
|
+
data-has-children={hasChildren || undefined}
|
|
50
|
+
data-expanded={hasChildren && expanded ? true : undefined}
|
|
51
|
+
>
|
|
52
|
+
<span className={styles.expandIcon} aria-hidden="true">
|
|
53
|
+
{hasChildren && <ExpandGlyph />}
|
|
54
|
+
</span>
|
|
55
|
+
<span className={styles.label}>{node.label}</span>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
7
58
|
}
|
|
8
59
|
|
|
9
60
|
/**
|
|
10
|
-
*
|
|
61
|
+
* Recursively renders hierarchical `data` as an expandable/selectable tree.
|
|
62
|
+
*
|
|
63
|
+
* **Recursica Abstract:**
|
|
64
|
+
* Wraps Mantine's `Tree` with a fully custom node renderer so every visual aspect (row box
|
|
65
|
+
* model, selected/unselected colors and typography, indent, item spacing) comes from
|
|
66
|
+
* Recursica's `tree` design tokens rather than Mantine's defaults.
|
|
11
67
|
*/
|
|
12
|
-
export const Tree = forwardRef<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
68
|
+
export const Tree = forwardRef<HTMLUListElement, TreeProps>(function Tree(
|
|
69
|
+
{
|
|
70
|
+
overStyled = false,
|
|
71
|
+
data,
|
|
72
|
+
initialExpandedValues,
|
|
73
|
+
initialSelectedValues,
|
|
74
|
+
multiple = false,
|
|
75
|
+
expandOnClick = true,
|
|
76
|
+
selectOnClick = true,
|
|
77
|
+
onNodeExpand,
|
|
78
|
+
onNodeCollapse,
|
|
79
|
+
onSelectedChange,
|
|
80
|
+
...rest
|
|
21
81
|
},
|
|
22
|
-
|
|
82
|
+
ref,
|
|
83
|
+
) {
|
|
84
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
85
|
+
|
|
86
|
+
const tree = useTree({
|
|
87
|
+
initialExpandedState: initialExpandedValues
|
|
88
|
+
? getTreeExpandedState(data, initialExpandedValues)
|
|
89
|
+
: undefined,
|
|
90
|
+
initialSelectedState: initialSelectedValues,
|
|
91
|
+
multiple,
|
|
92
|
+
onNodeExpand,
|
|
93
|
+
onNodeCollapse,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Mantine's `useTree` tracks selection internally but has no `onSelectedChange` callback of
|
|
97
|
+
// its own; surface one at the Recursica layer by watching the controller's selectedState.
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
onSelectedChange?.(tree.selectedState);
|
|
100
|
+
}, [tree.selectedState, onSelectedChange]);
|
|
101
|
+
|
|
102
|
+
const classNameProp = (sanitizedProps as Record<string, unknown>)
|
|
103
|
+
.className as string | undefined;
|
|
104
|
+
const rootClass = classNameProp
|
|
105
|
+
? `${styles.root} ${classNameProp}`
|
|
106
|
+
: styles.root;
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<MantineTree
|
|
110
|
+
ref={ref}
|
|
111
|
+
data={data}
|
|
112
|
+
tree={tree}
|
|
113
|
+
expandOnClick={expandOnClick}
|
|
114
|
+
selectOnClick={selectOnClick}
|
|
115
|
+
renderNode={renderTreeNode}
|
|
116
|
+
classNames={{
|
|
117
|
+
root: rootClass,
|
|
118
|
+
node: styles.node,
|
|
119
|
+
subtree: styles.subtree,
|
|
120
|
+
}}
|
|
121
|
+
{...(sanitizedProps as unknown as Record<string, unknown>)}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
});
|
|
23
125
|
|
|
24
126
|
Tree.displayName = "Tree";
|
|
@@ -25,22 +25,55 @@ export default function Demo() {
|
|
|
25
25
|
{
|
|
26
26
|
value: "1",
|
|
27
27
|
label: "Root",
|
|
28
|
-
children: [
|
|
28
|
+
children: [
|
|
29
|
+
{ value: "1.1", label: "Child A" },
|
|
30
|
+
{ value: "1.2", label: "Child B" },
|
|
31
|
+
],
|
|
29
32
|
},
|
|
33
|
+
{ value: "2", label: "Sibling" },
|
|
30
34
|
]}
|
|
31
35
|
/>
|
|
32
36
|
);
|
|
33
37
|
}
|
|
34
38
|
```
|
|
35
39
|
|
|
40
|
+
Each node needs a unique `value` and a `label`. Any node with a `children` array (even an empty one) renders an expand/collapse chevron.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 3. Controlled Selection / Expansion
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<Tree
|
|
48
|
+
data={data}
|
|
49
|
+
initialExpandedValues={["1"]}
|
|
50
|
+
initialSelectedValues={["1.1"]}
|
|
51
|
+
multiple
|
|
52
|
+
onSelectedChange={(values) => console.log("selected:", values)}
|
|
53
|
+
onNodeExpand={(value) => console.log("expanded:", value)}
|
|
54
|
+
onNodeCollapse={(value) => console.log("collapsed:", value)}
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `initialExpandedValues` accepts an array of node values, or `"*"` to start with every node expanded.
|
|
59
|
+
- `multiple` allows more than one node to be selected at once (default: single-select).
|
|
60
|
+
- `expandOnClick` / `selectOnClick` (both default `true`) control whether clicking a row toggles its expansion and/or selection.
|
|
61
|
+
|
|
36
62
|
---
|
|
37
63
|
|
|
38
|
-
##
|
|
64
|
+
## 4. Design System Integration
|
|
39
65
|
|
|
40
66
|
All Recursica components in the `@recursica/mantine-adapter` package adhere strictly to design system spacing, scaling, and behavior patterns.
|
|
41
67
|
|
|
42
68
|
> [!IMPORTANT]
|
|
43
69
|
>
|
|
44
|
-
> - **Anti-override protection**:
|
|
70
|
+
> - **Anti-override protection**: Rogue style injections (like inline `style` or arbitrary `className`) are automatically blocked by our prop layer unless `overStyled={true}` is explicitly provided.
|
|
45
71
|
> - **No Direct Layers**: Do not pass a `layer` prop to this component. To place it on a specific visual layer, wrap it in a `<Layer layer={0|1|2|3}>` component natively.
|
|
46
72
|
> - **Variables and Theming**: Styling is entirely determined by local CSS variables defined in `recursica_variables_scoped.css` and mapped in the component's CSS module.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 5. Known Constraints
|
|
77
|
+
|
|
78
|
+
- No checkbox/multi-check styling is exposed — the underlying Mantine `Tree` supports a `checked` node state, but Recursica's `tree` design tokens don't yet define a checked visual state, so it isn't part of the Recursica API.
|
|
79
|
+
- No per-node `disabled` state — the Figma UI Kit tokens don't define one, and Mantine's `Tree` data shape has no built-in `disabled` field either.
|