@recursica/mantine-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 +12 -0
- package/dist/mantine-adapter.cjs +1 -1
- package/dist/mantine-adapter.cjs.map +1 -1
- package/dist/mantine-adapter.css +1 -1
- package/dist/mantine-adapter.js +1095 -1081
- package/dist/mantine-adapter.js.map +1 -1
- package/dist/src/components/Button/Button.d.ts +11 -1
- package/dist/src/components/Stack/Stack.d.ts +9 -1
- package/dist/src/utils/filterStylingProps.d.ts +2 -0
- package/package.json +1 -1
- package/src/Introduction.stories.tsx +77 -103
- package/src/OverStyling.tsx +216 -171
- package/src/components/Button/Button.module.css +26 -5
- package/src/components/Button/Button.stories.tsx +55 -0
- package/src/components/Button/Button.tsx +32 -0
- package/src/components/Button/IMPLEMENTATION_NOTES.md +16 -0
- package/src/components/Flex/Flex.tsx +9 -4
- package/src/components/Group/Group.tsx +5 -3
- package/src/components/Stack/Stack.tsx +9 -5
- package/src/components/Toast/Toast.module.css +6 -0
- package/src/utils/filterStylingProps.ts +17 -1
package/src/OverStyling.tsx
CHANGED
|
@@ -1,186 +1,231 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Container,
|
|
3
|
-
Paper,
|
|
4
|
-
Title,
|
|
5
|
-
Text,
|
|
6
|
-
List,
|
|
7
|
-
Divider,
|
|
8
|
-
Group,
|
|
9
|
-
Code,
|
|
10
|
-
} from "@mantine/core";
|
|
1
|
+
import { Container, Card, Title, Text, Group, Stack } from "./components";
|
|
11
2
|
import { Button } from "./components/Button/Button";
|
|
12
3
|
|
|
13
4
|
export const OverStylingInfo = () => {
|
|
14
5
|
return (
|
|
15
|
-
<Container size="md"
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
6
|
+
<Container size="md" style={{ padding: "32px 0" }}>
|
|
7
|
+
<Card>
|
|
8
|
+
<Card.Content>
|
|
9
|
+
<Title order={1} mb="rec-md">
|
|
10
|
+
Over Styling (<code>overStyled</code>)
|
|
11
|
+
</Title>
|
|
12
|
+
<Text mb="rec-md">
|
|
13
|
+
By default, all Recursica components are strictly sandboxed. This
|
|
14
|
+
means they are protected against arbitrary styling configurations
|
|
15
|
+
(like passing generic React <code>style</code> objects, custom{" "}
|
|
16
|
+
<code>classNames</code> injections, or using deep Mantine layout
|
|
17
|
+
hooks like <code>bg</code> and <code>c</code>). This strict
|
|
18
|
+
compile-time and run-time enforcement guarantees that your design
|
|
19
|
+
system tokens remain true across your application.
|
|
20
|
+
</Text>
|
|
21
|
+
<Text mb="rec-md">
|
|
22
|
+
However, there may be edge cases where a developer absolutely must
|
|
23
|
+
modify a component beyond what the design tokens natively allow. For
|
|
24
|
+
this, we provide the <strong>escape hatch</strong> property:{" "}
|
|
25
|
+
<code>overStyled={`{true}`}</code>.
|
|
26
|
+
</Text>
|
|
35
27
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
28
|
+
<Title order={3} mb="rec-sm">
|
|
29
|
+
The Core Philosophy
|
|
30
|
+
</Title>
|
|
31
|
+
<Text mb="rec-sm">
|
|
32
|
+
**You should not over-style components.** Using{" "}
|
|
33
|
+
<code>overStyled</code> explicitly signifies that you are breaking
|
|
34
|
+
design system rules.
|
|
35
|
+
</Text>
|
|
36
|
+
<Stack
|
|
37
|
+
component="ol"
|
|
38
|
+
style={{ paddingLeft: "24px" }}
|
|
39
|
+
mb="rec-xl"
|
|
40
|
+
gap="rec-sm"
|
|
41
|
+
>
|
|
42
|
+
<li>
|
|
43
|
+
<Text>
|
|
44
|
+
<strong>Technical Debt:</strong> If over-styling is required, it
|
|
45
|
+
should be treated as a short-term workaround. Ideally, the
|
|
46
|
+
component will be refactored once the required layouts or
|
|
47
|
+
variants are officially integrated into the core Recursica
|
|
48
|
+
component library.
|
|
49
|
+
</Text>
|
|
50
|
+
</li>
|
|
51
|
+
<li>
|
|
52
|
+
<Text>
|
|
53
|
+
<strong>Auditing & Searching:</strong> Because this pattern
|
|
54
|
+
creates technical debt, we enforce the explicit{" "}
|
|
55
|
+
<code>overStyled</code> boolean. This provides a highly
|
|
56
|
+
auditable, easily searchable string. Product managers and
|
|
57
|
+
engineers can quickly grep the codebase for{" "}
|
|
58
|
+
<code>overStyled</code> (or the <code>RecursicaOverStyled</code>{" "}
|
|
59
|
+
typings) to hunt down components that don't match standard
|
|
60
|
+
patterns.
|
|
61
|
+
</Text>
|
|
62
|
+
</li>
|
|
63
|
+
<li>
|
|
64
|
+
<Text>
|
|
65
|
+
<strong>Highly Custom Components:</strong> If your application
|
|
66
|
+
genuinely requires massive custom layouts that the UI kit cannot
|
|
67
|
+
support, <strong>do not hack the Recursica component</strong>.
|
|
68
|
+
Instead, it is highly encouraged that you import the underlying
|
|
69
|
+
primitive component directly from <code>@mantine/core</code> and
|
|
70
|
+
construct your independent feature there. While you can utilize
|
|
71
|
+
raw Recursica CSS variables on these custom components, note
|
|
72
|
+
that they are not guaranteed to be accurately maintained as
|
|
73
|
+
Recursica evolves. Keep strict components strict!
|
|
74
|
+
</Text>
|
|
75
|
+
</li>
|
|
76
|
+
</Stack>
|
|
71
77
|
|
|
72
|
-
|
|
78
|
+
<Stack
|
|
79
|
+
style={{ height: 1, backgroundColor: "#eaeaea" }}
|
|
80
|
+
mb="rec-xl"
|
|
81
|
+
/>
|
|
73
82
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
83
|
+
<Title order={3} mb="rec-sm">
|
|
84
|
+
Permitted Layout Properties
|
|
85
|
+
</Title>
|
|
86
|
+
<Text mb="rec-sm">
|
|
87
|
+
Unlike deep styling bounds (colors, typography, padding,
|
|
88
|
+
dimensions), external <strong>layout spacing properties</strong>{" "}
|
|
89
|
+
like Margins (<code>m</code>, <code>mt</code>, <code>mb</code>,{" "}
|
|
90
|
+
<code>mx</code>) are safely <strong>permitted by default</strong>.
|
|
91
|
+
This allows integrators to structurally compose components alongside
|
|
92
|
+
siblings without breaching internal token boundaries.
|
|
93
|
+
</Text>
|
|
94
|
+
<Text mb="rec-xl">
|
|
95
|
+
When using layout properties, you have the flexibility to use either
|
|
96
|
+
ecosystem seamlessly:
|
|
97
|
+
</Text>
|
|
98
|
+
<Stack
|
|
99
|
+
component="ol"
|
|
100
|
+
style={{ paddingLeft: "24px" }}
|
|
101
|
+
mb="rec-md"
|
|
102
|
+
gap="rec-sm"
|
|
103
|
+
>
|
|
104
|
+
<li>
|
|
105
|
+
<Text>
|
|
106
|
+
<strong>Mantine Core Values:</strong> Passing standard Mantine
|
|
107
|
+
sizes (like <code>mt="md"</code>) passes straight through to
|
|
108
|
+
Mantine natively, allowing you to interface completely normally
|
|
109
|
+
with a parent application's existing Mantine Theme setup that
|
|
110
|
+
might fall outside Recursica's scope.
|
|
111
|
+
</Text>
|
|
112
|
+
</li>
|
|
113
|
+
<li>
|
|
114
|
+
<Text>
|
|
115
|
+
<strong>Recursica Strict Tokens:</strong> Passing our custom
|
|
116
|
+
prefixed tokens (like <code>mt="rec-md"</code>) signals our
|
|
117
|
+
internal layout interceptor to securely translate the value
|
|
118
|
+
directly to our native <code>recursica_brand_dimensions</code>{" "}
|
|
119
|
+
CSS variables. This ensures strict design token measurements
|
|
120
|
+
while sharing the exact same prop interface!
|
|
121
|
+
</Text>
|
|
122
|
+
</li>
|
|
123
|
+
</Stack>
|
|
124
|
+
<Text mb="rec-sm" overStyled fw={500}>
|
|
125
|
+
Available Recursica Layout Tokens:
|
|
126
|
+
</Text>
|
|
127
|
+
<Stack
|
|
128
|
+
component="ul"
|
|
129
|
+
style={{ paddingLeft: "24px" }}
|
|
130
|
+
mb="rec-xl"
|
|
131
|
+
gap="rec-none"
|
|
132
|
+
>
|
|
133
|
+
<li>
|
|
134
|
+
<Text>
|
|
135
|
+
<code>rec-none</code> (0px limit)
|
|
136
|
+
</Text>
|
|
137
|
+
</li>
|
|
138
|
+
<li>
|
|
139
|
+
<Text>
|
|
140
|
+
<code>rec-sm</code> (0.5x scaling)
|
|
141
|
+
</Text>
|
|
142
|
+
</li>
|
|
143
|
+
<li>
|
|
144
|
+
<Text>
|
|
145
|
+
<code>rec-default</code> (1.0x scaling)
|
|
146
|
+
</Text>
|
|
147
|
+
</li>
|
|
148
|
+
<li>
|
|
149
|
+
<Text>
|
|
150
|
+
<code>rec-md</code> (1.5x scaling)
|
|
151
|
+
</Text>
|
|
152
|
+
</li>
|
|
153
|
+
<li>
|
|
154
|
+
<Text>
|
|
155
|
+
<code>rec-lg</code> (2.0x scaling)
|
|
156
|
+
</Text>
|
|
157
|
+
</li>
|
|
158
|
+
<li>
|
|
159
|
+
<Text>
|
|
160
|
+
<code>rec-xl</code> (3.0x scaling)
|
|
161
|
+
</Text>
|
|
162
|
+
</li>
|
|
163
|
+
<li>
|
|
164
|
+
<Text>
|
|
165
|
+
<code>rec-2xl</code> (4.0x scaling)
|
|
166
|
+
</Text>
|
|
167
|
+
</li>
|
|
168
|
+
</Stack>
|
|
132
169
|
|
|
133
|
-
|
|
170
|
+
<Stack
|
|
171
|
+
style={{ height: 1, backgroundColor: "#eaeaea" }}
|
|
172
|
+
mb="rec-xl"
|
|
173
|
+
/>
|
|
134
174
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
175
|
+
<Title order={3} mb="rec-sm">
|
|
176
|
+
Primitive Layout Components Exemption
|
|
177
|
+
</Title>
|
|
178
|
+
<Text mb="rec-sm">
|
|
179
|
+
Unlike complex UI components (Buttons, Tabs, Inputs) which are
|
|
180
|
+
strictly protected, <strong>Primitive Layout Components</strong> (
|
|
181
|
+
<code>Flex</code>, <code>Stack</code>, <code>Group</code>,{" "}
|
|
182
|
+
<code>Container</code>) are entirely exempt from the{" "}
|
|
183
|
+
<code>RecursicaOverStyled</code> gatekeeper.
|
|
184
|
+
</Text>
|
|
185
|
+
<Text mb="rec-xl">
|
|
186
|
+
Because the entire functional purpose of these components is
|
|
187
|
+
structural layout composition, developers are free to pass any
|
|
188
|
+
standard Mantine width, height, padding, margin, gap, and alignment
|
|
189
|
+
property directly to them without needing to flag{" "}
|
|
190
|
+
<code>overStyled={`{true}`}</code>. The internal custom token mapper
|
|
191
|
+
(such as converting <code>gap="rec-md"</code>) is still active
|
|
192
|
+
natively on these wrappers.
|
|
193
|
+
</Text>
|
|
154
194
|
|
|
155
|
-
|
|
195
|
+
<Stack
|
|
196
|
+
style={{ height: 1, backgroundColor: "#eaeaea" }}
|
|
197
|
+
mb="rec-xl"
|
|
198
|
+
/>
|
|
156
199
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
200
|
+
<Title order={3} mb="rec-md">
|
|
201
|
+
Live Example
|
|
202
|
+
</Title>
|
|
203
|
+
<Text mb="rec-xl">
|
|
204
|
+
Below is a side-by-side comparison. The first is a standard
|
|
205
|
+
Recursica Button protected by the design tokens mapping. The second
|
|
206
|
+
flagrantly forces <code>overStyled={`{true}`}</code>, allowing
|
|
207
|
+
Mantine's native styling generics to punch right through the sandbox
|
|
208
|
+
layout.
|
|
209
|
+
</Text>
|
|
166
210
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
211
|
+
<Group gap="rec-xl">
|
|
212
|
+
<Stack gap="rec-sm">
|
|
213
|
+
<Text overStyled size="sm" c="dimmed">
|
|
214
|
+
Strict Baseline (Default)
|
|
215
|
+
</Text>
|
|
216
|
+
<Button variant="solid">Standard UI Kit Button</Button>
|
|
217
|
+
</Stack>
|
|
218
|
+
<Stack gap="rec-sm">
|
|
219
|
+
<Text overStyled size="sm" c="dimmed">
|
|
220
|
+
overStyled={`{true}`}
|
|
221
|
+
</Text>
|
|
222
|
+
<Button overStyled={true} bg="pink" c="black" radius="xl">
|
|
223
|
+
Unsafe Pink Marketing Button
|
|
224
|
+
</Button>
|
|
225
|
+
</Stack>
|
|
226
|
+
</Group>
|
|
227
|
+
</Card.Content>
|
|
228
|
+
</Card>
|
|
184
229
|
</Container>
|
|
185
230
|
);
|
|
186
231
|
};
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
overflow: hidden; /* Truncation bounding box */
|
|
7
7
|
position: relative;
|
|
8
8
|
transition: all 0.2s ease;
|
|
9
|
+
width: fit-content; /* Prevent stretching in flex columns */
|
|
9
10
|
|
|
10
11
|
/* Shared Defaults */
|
|
11
12
|
font-family: var(
|
|
@@ -35,13 +36,16 @@
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/* Internal Mantine reset & layout logic for Truncation */
|
|
38
|
-
.root >
|
|
39
|
+
.root > *:not(.loader) {
|
|
39
40
|
min-width: 0;
|
|
40
|
-
width: 100%;
|
|
41
41
|
position: relative;
|
|
42
42
|
z-index: 1;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
.loader {
|
|
46
|
+
/* Mantine absolutely positions this; we just need a hook to exclude it from the child reset */
|
|
47
|
+
}
|
|
48
|
+
|
|
45
49
|
.label {
|
|
46
50
|
display: flex;
|
|
47
51
|
align-items: center;
|
|
@@ -52,7 +56,6 @@
|
|
|
52
56
|
.labelText {
|
|
53
57
|
display: block;
|
|
54
58
|
min-width: 0;
|
|
55
|
-
width: 100%;
|
|
56
59
|
overflow: hidden;
|
|
57
60
|
text-overflow: ellipsis;
|
|
58
61
|
white-space: nowrap;
|
|
@@ -76,6 +79,11 @@
|
|
|
76
79
|
--recursica_ui-kit_components_button_variants_sizes_default_properties_horizontal-padding
|
|
77
80
|
);
|
|
78
81
|
}
|
|
82
|
+
.root[data-size="default"] .labelText {
|
|
83
|
+
max-width: var(
|
|
84
|
+
--recursica_ui-kit_components_button_variants_sizes_default_properties_max-label-width
|
|
85
|
+
);
|
|
86
|
+
}
|
|
79
87
|
.root[data-size="small"] {
|
|
80
88
|
border-radius: var(
|
|
81
89
|
--recursica_ui-kit_components_button_variants_sizes_small_properties_border-radius
|
|
@@ -111,6 +119,11 @@
|
|
|
111
119
|
)
|
|
112
120
|
);
|
|
113
121
|
}
|
|
122
|
+
.root[data-size="small"] .labelText {
|
|
123
|
+
max-width: var(
|
|
124
|
+
--recursica_ui-kit_components_button_variants_sizes_small_properties_max-label-width
|
|
125
|
+
);
|
|
126
|
+
}
|
|
114
127
|
|
|
115
128
|
/* Icon Resets */
|
|
116
129
|
.iconWrapper {
|
|
@@ -167,8 +180,16 @@
|
|
|
167
180
|
}
|
|
168
181
|
|
|
169
182
|
/* Disabled */
|
|
170
|
-
.root:disabled {
|
|
171
|
-
opacity: var(
|
|
183
|
+
.root[data-size="default"]:disabled {
|
|
184
|
+
opacity: var(
|
|
185
|
+
--recursica_ui-kit_components_button_variants_sizes_default_properties_disabled-opacity
|
|
186
|
+
);
|
|
187
|
+
cursor: not-allowed;
|
|
188
|
+
}
|
|
189
|
+
.root[data-size="small"]:disabled {
|
|
190
|
+
opacity: var(
|
|
191
|
+
--recursica_ui-kit_components_button_variants_sizes_small_properties_disabled-opacity
|
|
192
|
+
);
|
|
172
193
|
cursor: not-allowed;
|
|
173
194
|
}
|
|
174
195
|
|
|
@@ -20,6 +20,30 @@ const meta: Meta<ButtonStoryProps> = {
|
|
|
20
20
|
options: ["default", "small"],
|
|
21
21
|
description: "The size of the button",
|
|
22
22
|
},
|
|
23
|
+
loading: {
|
|
24
|
+
control: "boolean",
|
|
25
|
+
description: "Sets the button to a loading state",
|
|
26
|
+
},
|
|
27
|
+
useRecursicaLoader: {
|
|
28
|
+
control: "boolean",
|
|
29
|
+
description:
|
|
30
|
+
"Use the Recursica Loader component instead of the default Mantine loader",
|
|
31
|
+
},
|
|
32
|
+
loaderVariant: {
|
|
33
|
+
control: "select",
|
|
34
|
+
options: ["oval", "bars", "dots"],
|
|
35
|
+
description: "The visual variant of the Recursica Loader",
|
|
36
|
+
},
|
|
37
|
+
loaderSize: {
|
|
38
|
+
control: "select",
|
|
39
|
+
options: [undefined, "sm", "md", "lg", "small", "default", "large"],
|
|
40
|
+
description: "The size variant for the loader",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
args: {
|
|
44
|
+
useRecursicaLoader: true,
|
|
45
|
+
loaderVariant: "oval",
|
|
46
|
+
loaderSize: undefined,
|
|
23
47
|
},
|
|
24
48
|
};
|
|
25
49
|
|
|
@@ -112,3 +136,34 @@ export const PolymorphicAsLink: Story = {
|
|
|
112
136
|
target: "_blank",
|
|
113
137
|
},
|
|
114
138
|
};
|
|
139
|
+
|
|
140
|
+
export const TruncatedLabel: Story = {
|
|
141
|
+
args: {
|
|
142
|
+
children:
|
|
143
|
+
"This is an exceptionally long button label designed to demonstrate how the component handles text overflow by applying an ellipsis rather than breaking the layout or wrapping to multiple lines.",
|
|
144
|
+
variant: "solid",
|
|
145
|
+
size: "default",
|
|
146
|
+
},
|
|
147
|
+
render: (args: ButtonStoryProps) => (
|
|
148
|
+
<div style={{ maxWidth: "250px" }}>
|
|
149
|
+
<Button {...args} />
|
|
150
|
+
</div>
|
|
151
|
+
),
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export const Loading: Story = {
|
|
155
|
+
args: {
|
|
156
|
+
children: "Saving Changes",
|
|
157
|
+
variant: "solid",
|
|
158
|
+
size: "default",
|
|
159
|
+
loading: true,
|
|
160
|
+
},
|
|
161
|
+
parameters: {
|
|
162
|
+
docs: {
|
|
163
|
+
description: {
|
|
164
|
+
story:
|
|
165
|
+
"When `loading={true}` is applied, the Button injects the Recursica `<Loader />` component. Per Recursica design rules, placing a Button in a loading state automatically forces the `disabled={true}` state on the underlying element. This ensures the button immediately receives the brand theme disabled opacities without relying solely on semantic logic.",
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { forwardRef } from "react";
|
|
2
|
+
import { Loader } from "../Loader/Loader";
|
|
3
|
+
import type { RecursicaLoaderProps } from "../Loader/Loader";
|
|
2
4
|
import {
|
|
3
5
|
Button as MantineButton,
|
|
4
6
|
type ButtonProps as MantineButtonProps,
|
|
@@ -11,9 +13,18 @@ import {
|
|
|
11
13
|
import styles from "./Button.module.css";
|
|
12
14
|
|
|
13
15
|
export interface RecursicaButtonProps {
|
|
16
|
+
/** The visual style variant of the button */
|
|
14
17
|
variant?: "solid" | "outline" | "text";
|
|
18
|
+
/** The size of the button */
|
|
15
19
|
size?: "default" | "small";
|
|
20
|
+
/** An optional icon element to display to the left of the button text. Replaces Mantine's leftSection. */
|
|
16
21
|
icon?: React.ReactNode;
|
|
22
|
+
/** Which Recursica Loader variant to use */
|
|
23
|
+
loaderVariant?: RecursicaLoaderProps["variant"];
|
|
24
|
+
/** The size variant for the loader */
|
|
25
|
+
loaderSize?: RecursicaLoaderProps["size"];
|
|
26
|
+
/** Whether to use the Recursica loader or fallback to the Mantine loader */
|
|
27
|
+
useRecursicaLoader?: boolean;
|
|
17
28
|
}
|
|
18
29
|
|
|
19
30
|
export type ButtonProps = RecursicaOverStyled<
|
|
@@ -34,6 +45,9 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
34
45
|
icon,
|
|
35
46
|
children,
|
|
36
47
|
overStyled = false,
|
|
48
|
+
loaderVariant = "oval",
|
|
49
|
+
loaderSize,
|
|
50
|
+
useRecursicaLoader = true,
|
|
37
51
|
...rest
|
|
38
52
|
},
|
|
39
53
|
ref,
|
|
@@ -75,6 +89,7 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
75
89
|
root: styles.root,
|
|
76
90
|
section: styles.section,
|
|
77
91
|
label: styles.label,
|
|
92
|
+
loader: styles.loader,
|
|
78
93
|
};
|
|
79
94
|
|
|
80
95
|
const classNamesProp = restRecord.classNames;
|
|
@@ -94,6 +109,21 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
94
109
|
? `${styles.root} ${classNameProp}`
|
|
95
110
|
: styles.root;
|
|
96
111
|
|
|
112
|
+
const userLoaderProps = restRecord.loaderProps as
|
|
113
|
+
| Record<string, any>
|
|
114
|
+
| undefined;
|
|
115
|
+
|
|
116
|
+
const resolvedLoaderSize =
|
|
117
|
+
loaderSize ?? (size === "small" ? "small" : "default");
|
|
118
|
+
|
|
119
|
+
let mergedLoaderProps = userLoaderProps;
|
|
120
|
+
if (useRecursicaLoader) {
|
|
121
|
+
mergedLoaderProps = {
|
|
122
|
+
children: <Loader variant={loaderVariant} size={resolvedLoaderSize} />,
|
|
123
|
+
...userLoaderProps,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
97
127
|
return (
|
|
98
128
|
<MantineButton
|
|
99
129
|
ref={ref}
|
|
@@ -101,6 +131,7 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
101
131
|
classNames={mergedClassNames}
|
|
102
132
|
variant={mapVariant[variant]}
|
|
103
133
|
size={mapSize[size]}
|
|
134
|
+
loaderProps={mergedLoaderProps}
|
|
104
135
|
leftSection={
|
|
105
136
|
icon != null ? (
|
|
106
137
|
<span className={styles.iconWrapper} aria-hidden>
|
|
@@ -112,6 +143,7 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
112
143
|
data-size={size}
|
|
113
144
|
{...(isIconOnly ? { "data-icon-only": "" } : {})}
|
|
114
145
|
{...sanitizedProps}
|
|
146
|
+
disabled={!!restRecord.disabled || !!restRecord.loading}
|
|
115
147
|
>
|
|
116
148
|
<span className={styles.labelText}>{children}</span>
|
|
117
149
|
</MantineButton>
|
|
@@ -43,3 +43,19 @@ Mantine's `.mantine-Button-label` flex centering breaks primitive truncation log
|
|
|
43
43
|
## Disabled state: brand theme opacity (implicit)
|
|
44
44
|
|
|
45
45
|
**Decision:** The UI kit enforces global brand theme disabled opacities. The `.root:disabled` logic implicitly overrides visibility locally via `var(--recursica_brand_states_disabled)`.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Loader color contrast
|
|
50
|
+
|
|
51
|
+
**Decision:** When a Button is in a loading state, the `Recursica Loader` component is injected. The `Loader` component strictly defines its own colors and styles per variant, meaning it does not inherit the text color (`currentColor`) from the Button.
|
|
52
|
+
|
|
53
|
+
**Constraint:** This can lead to contrast issues (e.g., a blue dots loader inside a solid blue button). Design has explicitly decided not to address this at the moment. As such, developers using the `loading` prop must be aware that the loader's color is fixed by its internal tokens, not by the button's context.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Loading state enforces disabled state
|
|
58
|
+
|
|
59
|
+
**Decision:** When `loading={true}` is passed to the Button, the component explicitly forces `disabled={true}` natively on the underlying element.
|
|
60
|
+
|
|
61
|
+
**Implementation:** This ensures that loading buttons automatically inherit the brand theme disabled opacities (via the `:disabled` CSS pseudo-class) rather than relying solely on Mantine's native `data-disabled` dataset logic, which may not trigger the strict visual fade required by the Recursica design system.
|