@recursica/mantine-adapter 0.41.0 → 0.42.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/index.d.ts +43 -3
- 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 +2633 -2312
- package/dist/mantine-adapter.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Checkbox/CheckboxGroup.tsx +32 -10
- package/src/components/FileInput/FileInput.tsx +6 -0
- package/src/components/Slider/IMPLEMENTATION_NOTES.md +1 -0
- package/src/components/Slider/Slider.module.css +34 -0
- package/src/components/Slider/Slider.stories.tsx +1 -1
- package/src/components/TextArea/TextArea.tsx +2 -6
- package/src/components/TransferList/TRANSFERLIST_IMPLEMENTATION_NOTES.md +111 -0
- package/src/components/TransferList/TransferList.module.css +180 -39
- package/src/components/TransferList/TransferList.stories.tsx +110 -6
- package/src/components/TransferList/TransferList.tsx +417 -7
- package/src/components/TransferList/USAGE.md +37 -6
|
@@ -1,9 +1,419 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { forwardRef, useCallback, useId, useMemo, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
filterStylingProps,
|
|
4
|
+
type RecursicaOverStyled,
|
|
5
|
+
} from "../../utils/filterStylingProps";
|
|
6
|
+
import { type FormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
|
|
7
|
+
import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
|
|
8
|
+
import { Badge } from "../Badge/Badge";
|
|
9
|
+
import { Button } from "../Button/Button";
|
|
10
|
+
import { TextField } from "../TextField/TextField";
|
|
11
|
+
import { Checkbox } from "../Checkbox/Checkbox";
|
|
12
|
+
import { CheckboxGroup } from "../Checkbox/CheckboxGroup";
|
|
13
|
+
import styles from "./TransferList.module.css";
|
|
3
14
|
|
|
4
|
-
|
|
5
|
-
|
|
15
|
+
import {
|
|
16
|
+
type ReadOnlyControlProps,
|
|
17
|
+
type RecursicaTransferListProps as BaseRecursicaTransferListProps,
|
|
18
|
+
type RecursicaTransferListItem,
|
|
19
|
+
type RecursicaTransferListData,
|
|
20
|
+
} from "@recursica/adapter-common";
|
|
6
21
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
22
|
+
export type { RecursicaTransferListItem, RecursicaTransferListData };
|
|
23
|
+
|
|
24
|
+
export interface RecursicaTransferListProps
|
|
25
|
+
extends Omit<
|
|
26
|
+
FormControlWrapperProps,
|
|
27
|
+
| "children"
|
|
28
|
+
| "overStyled"
|
|
29
|
+
| "controlMaxWidth"
|
|
30
|
+
| "controlMinWidth"
|
|
31
|
+
| "onChange"
|
|
32
|
+
>,
|
|
33
|
+
ReadOnlyControlProps,
|
|
34
|
+
BaseRecursicaTransferListProps {
|
|
35
|
+
/** Disables the whole control */
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type TransferListProps = RecursicaOverStyled<RecursicaTransferListProps>;
|
|
40
|
+
|
|
41
|
+
/** Single/double chevron glyphs for the transfer buttons; flipped via CSS for the "left" direction
|
|
42
|
+
* so only one path needs to be maintained, same approach as Tree's `ExpandGlyph`. */
|
|
43
|
+
function ChevronIcon({ direction }: { direction: "left" | "right" }) {
|
|
44
|
+
return (
|
|
45
|
+
<svg
|
|
46
|
+
className={styles.chevron}
|
|
47
|
+
data-direction={direction}
|
|
48
|
+
viewBox="0 0 16 16"
|
|
49
|
+
width="1em"
|
|
50
|
+
height="1em"
|
|
51
|
+
fill="none"
|
|
52
|
+
stroke="currentColor"
|
|
53
|
+
strokeWidth="2"
|
|
54
|
+
strokeLinecap="round"
|
|
55
|
+
strokeLinejoin="round"
|
|
56
|
+
aria-hidden="true"
|
|
57
|
+
>
|
|
58
|
+
<path d="M6 3l5 5-5 5" />
|
|
59
|
+
</svg>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function ChevronsIcon({ direction }: { direction: "left" | "right" }) {
|
|
64
|
+
return (
|
|
65
|
+
<svg
|
|
66
|
+
className={styles.chevron}
|
|
67
|
+
data-direction={direction}
|
|
68
|
+
viewBox="0 0 16 16"
|
|
69
|
+
width="1em"
|
|
70
|
+
height="1em"
|
|
71
|
+
fill="none"
|
|
72
|
+
stroke="currentColor"
|
|
73
|
+
strokeWidth="2"
|
|
74
|
+
strokeLinecap="round"
|
|
75
|
+
strokeLinejoin="round"
|
|
76
|
+
aria-hidden="true"
|
|
77
|
+
>
|
|
78
|
+
<path d="M3 3l5 5-5 5M9 3l5 5-5 5" />
|
|
79
|
+
</svg>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Splits items into grouped/ungrouped buckets, same shape Forge's reference groups by `group`. */
|
|
84
|
+
function groupItems(items: RecursicaTransferListItem[]) {
|
|
85
|
+
const groups: Record<string, RecursicaTransferListItem[]> = {};
|
|
86
|
+
const ungrouped: RecursicaTransferListItem[] = [];
|
|
87
|
+
items.forEach((item) => {
|
|
88
|
+
if (item.group) {
|
|
89
|
+
(groups[item.group] ??= []).push(item);
|
|
90
|
+
} else {
|
|
91
|
+
ungrouped.push(item);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return { groups, ungrouped };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const TransferList = forwardRef<HTMLDivElement, TransferListProps>(
|
|
98
|
+
function TransferList(props, ref) {
|
|
99
|
+
const {
|
|
100
|
+
overStyled = false,
|
|
101
|
+
formLayout = "stacked",
|
|
102
|
+
|
|
103
|
+
// Label & Wrapper Maps
|
|
104
|
+
labelSize,
|
|
105
|
+
labelAlignment,
|
|
106
|
+
labelOptionalText,
|
|
107
|
+
labelWithEditIcon,
|
|
108
|
+
labelActionArea,
|
|
109
|
+
onLabelEditClick,
|
|
110
|
+
|
|
111
|
+
label,
|
|
112
|
+
assistiveText,
|
|
113
|
+
description,
|
|
114
|
+
helperText,
|
|
115
|
+
assistiveWithIcon,
|
|
116
|
+
error,
|
|
117
|
+
required,
|
|
118
|
+
id: userProvidedId,
|
|
119
|
+
readOnly,
|
|
120
|
+
readOnlyComponent,
|
|
121
|
+
emptyValueComponent,
|
|
122
|
+
|
|
123
|
+
data: controlledData,
|
|
124
|
+
defaultData,
|
|
125
|
+
onChange,
|
|
126
|
+
sourceLabel = "Available",
|
|
127
|
+
targetLabel = "Selected",
|
|
128
|
+
searchable = true,
|
|
129
|
+
searchPlaceholder = "Filter items...",
|
|
130
|
+
disabled = false,
|
|
131
|
+
|
|
132
|
+
className,
|
|
133
|
+
style,
|
|
134
|
+
...rest
|
|
135
|
+
} = props;
|
|
136
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
137
|
+
|
|
138
|
+
const generatedId = useId();
|
|
139
|
+
const id = userProvidedId || `recursica-transfer-list-${generatedId}`;
|
|
140
|
+
|
|
141
|
+
// Manage internal state for uncontrolled mode
|
|
142
|
+
const [internalData, setInternalData] = useState<RecursicaTransferListData>(
|
|
143
|
+
() => defaultData ?? controlledData ?? [[], []],
|
|
144
|
+
);
|
|
145
|
+
const effectiveData =
|
|
146
|
+
controlledData !== undefined ? controlledData : internalData;
|
|
147
|
+
|
|
148
|
+
const handleChange = useCallback(
|
|
149
|
+
(newData: RecursicaTransferListData) => {
|
|
150
|
+
if (controlledData === undefined) {
|
|
151
|
+
setInternalData(newData);
|
|
152
|
+
}
|
|
153
|
+
onChange?.(newData);
|
|
154
|
+
},
|
|
155
|
+
[controlledData, onChange],
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const [sourceSearch, setSourceSearch] = useState("");
|
|
159
|
+
const [targetSearch, setTargetSearch] = useState("");
|
|
160
|
+
const [sourceSelected, setSourceSelected] = useState<Set<string>>(
|
|
161
|
+
() => new Set(),
|
|
162
|
+
);
|
|
163
|
+
const [targetSelected, setTargetSelected] = useState<Set<string>>(
|
|
164
|
+
() => new Set(),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const filteredSource = useMemo(() => {
|
|
168
|
+
if (!sourceSearch) return effectiveData[0];
|
|
169
|
+
const query = sourceSearch.toLowerCase();
|
|
170
|
+
return effectiveData[0].filter((item) =>
|
|
171
|
+
item.label.toLowerCase().includes(query),
|
|
172
|
+
);
|
|
173
|
+
}, [effectiveData, sourceSearch]);
|
|
174
|
+
|
|
175
|
+
const filteredTarget = useMemo(() => {
|
|
176
|
+
if (!targetSearch) return effectiveData[1];
|
|
177
|
+
const query = targetSearch.toLowerCase();
|
|
178
|
+
return effectiveData[1].filter((item) =>
|
|
179
|
+
item.label.toLowerCase().includes(query),
|
|
180
|
+
);
|
|
181
|
+
}, [effectiveData, targetSearch]);
|
|
182
|
+
|
|
183
|
+
const transferToTarget = useCallback(() => {
|
|
184
|
+
if (sourceSelected.size === 0) return;
|
|
185
|
+
const newSource = effectiveData[0].filter(
|
|
186
|
+
(item) => !sourceSelected.has(item.value),
|
|
187
|
+
);
|
|
188
|
+
const moved = effectiveData[0].filter((item) =>
|
|
189
|
+
sourceSelected.has(item.value),
|
|
190
|
+
);
|
|
191
|
+
setSourceSelected(new Set());
|
|
192
|
+
handleChange([newSource, [...effectiveData[1], ...moved]]);
|
|
193
|
+
}, [effectiveData, sourceSelected, handleChange]);
|
|
194
|
+
|
|
195
|
+
const transferToSource = useCallback(() => {
|
|
196
|
+
if (targetSelected.size === 0) return;
|
|
197
|
+
const newTarget = effectiveData[1].filter(
|
|
198
|
+
(item) => !targetSelected.has(item.value),
|
|
199
|
+
);
|
|
200
|
+
const moved = effectiveData[1].filter((item) =>
|
|
201
|
+
targetSelected.has(item.value),
|
|
202
|
+
);
|
|
203
|
+
setTargetSelected(new Set());
|
|
204
|
+
handleChange([[...effectiveData[0], ...moved], newTarget]);
|
|
205
|
+
}, [effectiveData, targetSelected, handleChange]);
|
|
206
|
+
|
|
207
|
+
const transferAllToTarget = useCallback(() => {
|
|
208
|
+
if (effectiveData[0].length === 0) return;
|
|
209
|
+
setSourceSelected(new Set());
|
|
210
|
+
handleChange([[], [...effectiveData[1], ...effectiveData[0]]]);
|
|
211
|
+
}, [effectiveData, handleChange]);
|
|
212
|
+
|
|
213
|
+
const transferAllToSource = useCallback(() => {
|
|
214
|
+
if (effectiveData[1].length === 0) return;
|
|
215
|
+
setTargetSelected(new Set());
|
|
216
|
+
handleChange([[...effectiveData[0], ...effectiveData[1]], []]);
|
|
217
|
+
}, [effectiveData, handleChange]);
|
|
218
|
+
|
|
219
|
+
const toggleSourceItem = useCallback((value: string) => {
|
|
220
|
+
setSourceSelected((prev) => {
|
|
221
|
+
const next = new Set(prev);
|
|
222
|
+
if (next.has(value)) next.delete(value);
|
|
223
|
+
else next.add(value);
|
|
224
|
+
return next;
|
|
225
|
+
});
|
|
226
|
+
}, []);
|
|
227
|
+
|
|
228
|
+
const toggleTargetItem = useCallback((value: string) => {
|
|
229
|
+
setTargetSelected((prev) => {
|
|
230
|
+
const next = new Set(prev);
|
|
231
|
+
if (next.has(value)) next.delete(value);
|
|
232
|
+
else next.add(value);
|
|
233
|
+
return next;
|
|
234
|
+
});
|
|
235
|
+
}, []);
|
|
236
|
+
|
|
237
|
+
const renderPane = (
|
|
238
|
+
paneId: string,
|
|
239
|
+
paneLabel: string,
|
|
240
|
+
items: RecursicaTransferListItem[],
|
|
241
|
+
allItems: RecursicaTransferListItem[],
|
|
242
|
+
selected: Set<string>,
|
|
243
|
+
onToggle: (value: string) => void,
|
|
244
|
+
search: string,
|
|
245
|
+
onSearchChange: (value: string) => void,
|
|
246
|
+
) => {
|
|
247
|
+
const { groups, ungrouped } = groupItems(items);
|
|
248
|
+
const groupNames = Object.keys(groups).sort();
|
|
249
|
+
const countText =
|
|
250
|
+
selected.size > 0
|
|
251
|
+
? `${selected.size} / ${allItems.length}`
|
|
252
|
+
: `${allItems.length}`;
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<div className={styles.pane} data-pane={paneId}>
|
|
256
|
+
<div className={styles.paneHeader}>
|
|
257
|
+
<span>{paneLabel}</span>
|
|
258
|
+
<Badge>{countText}</Badge>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
{searchable && (
|
|
262
|
+
<div className={styles.paneSearch}>
|
|
263
|
+
<TextField
|
|
264
|
+
value={search}
|
|
265
|
+
onChange={(e) => onSearchChange(e.currentTarget.value)}
|
|
266
|
+
placeholder={searchPlaceholder}
|
|
267
|
+
disabled={disabled}
|
|
268
|
+
aria-label={`Filter ${paneLabel.toLowerCase()}`}
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
|
|
273
|
+
<div className={styles.paneList}>
|
|
274
|
+
{items.length === 0 && (
|
|
275
|
+
<div className={styles.emptyState}>No items</div>
|
|
276
|
+
)}
|
|
277
|
+
|
|
278
|
+
{ungrouped.length > 0 && (
|
|
279
|
+
<CheckboxGroup>
|
|
280
|
+
{ungrouped.map((item) => (
|
|
281
|
+
<Checkbox
|
|
282
|
+
key={item.value}
|
|
283
|
+
id={`${id}-${paneId}-${item.value}`}
|
|
284
|
+
label={item.label}
|
|
285
|
+
checked={selected.has(item.value)}
|
|
286
|
+
onChange={() => onToggle(item.value)}
|
|
287
|
+
disabled={disabled}
|
|
288
|
+
/>
|
|
289
|
+
))}
|
|
290
|
+
</CheckboxGroup>
|
|
291
|
+
)}
|
|
292
|
+
|
|
293
|
+
{groupNames.map((groupName) => (
|
|
294
|
+
<CheckboxGroup
|
|
295
|
+
key={groupName}
|
|
296
|
+
label={groupName}
|
|
297
|
+
labelSize="small"
|
|
298
|
+
>
|
|
299
|
+
{groups[groupName].map((item) => (
|
|
300
|
+
<Checkbox
|
|
301
|
+
key={item.value}
|
|
302
|
+
id={`${id}-${paneId}-${item.value}`}
|
|
303
|
+
label={item.label}
|
|
304
|
+
checked={selected.has(item.value)}
|
|
305
|
+
onChange={() => onToggle(item.value)}
|
|
306
|
+
disabled={disabled}
|
|
307
|
+
/>
|
|
308
|
+
))}
|
|
309
|
+
</CheckboxGroup>
|
|
310
|
+
))}
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const wrapperClass = className
|
|
317
|
+
? `${styles.layoutOverride} ${className}`
|
|
318
|
+
: styles.layoutOverride;
|
|
319
|
+
|
|
320
|
+
return (
|
|
321
|
+
<WithReadOnlyWrapper
|
|
322
|
+
ref={ref}
|
|
323
|
+
formLayout={formLayout}
|
|
324
|
+
labelSize={labelSize}
|
|
325
|
+
labelAlignment={labelAlignment}
|
|
326
|
+
labelOptionalText={labelOptionalText}
|
|
327
|
+
labelWithEditIcon={labelWithEditIcon}
|
|
328
|
+
labelActionArea={labelActionArea}
|
|
329
|
+
onLabelEditClick={onLabelEditClick}
|
|
330
|
+
label={label}
|
|
331
|
+
assistiveText={assistiveText}
|
|
332
|
+
description={description}
|
|
333
|
+
helperText={helperText}
|
|
334
|
+
assistiveWithIcon={assistiveWithIcon}
|
|
335
|
+
error={error}
|
|
336
|
+
required={required}
|
|
337
|
+
id={id}
|
|
338
|
+
className={wrapperClass}
|
|
339
|
+
style={style}
|
|
340
|
+
overStyled={overStyled as true}
|
|
341
|
+
readOnly={readOnly}
|
|
342
|
+
readOnlyComponent={readOnlyComponent}
|
|
343
|
+
emptyValueComponent={emptyValueComponent}
|
|
344
|
+
readOnlyType="text"
|
|
345
|
+
readOnlyValue={effectiveData[1].map((item) => item.label)}
|
|
346
|
+
readOnlyNativeProps={props}
|
|
347
|
+
activeComponent={
|
|
348
|
+
<div
|
|
349
|
+
className={styles.root}
|
|
350
|
+
data-disabled={disabled ? "true" : undefined}
|
|
351
|
+
data-error={error ? "true" : undefined}
|
|
352
|
+
{...(sanitizedProps as Record<string, unknown>)}
|
|
353
|
+
>
|
|
354
|
+
<div className={styles.panes}>
|
|
355
|
+
{renderPane(
|
|
356
|
+
"source",
|
|
357
|
+
sourceLabel,
|
|
358
|
+
filteredSource,
|
|
359
|
+
effectiveData[0],
|
|
360
|
+
sourceSelected,
|
|
361
|
+
toggleSourceItem,
|
|
362
|
+
sourceSearch,
|
|
363
|
+
setSourceSearch,
|
|
364
|
+
)}
|
|
365
|
+
|
|
366
|
+
<div className={styles.transferColumn}>
|
|
367
|
+
<Button
|
|
368
|
+
variant="outline"
|
|
369
|
+
size="small"
|
|
370
|
+
icon={<ChevronsIcon direction="right" />}
|
|
371
|
+
aria-label={`Move all to ${targetLabel}`}
|
|
372
|
+
disabled={disabled || effectiveData[0].length === 0}
|
|
373
|
+
onClick={transferAllToTarget}
|
|
374
|
+
/>
|
|
375
|
+
<Button
|
|
376
|
+
variant="outline"
|
|
377
|
+
size="small"
|
|
378
|
+
icon={<ChevronIcon direction="right" />}
|
|
379
|
+
aria-label={`Move selected to ${targetLabel}`}
|
|
380
|
+
disabled={disabled || sourceSelected.size === 0}
|
|
381
|
+
onClick={transferToTarget}
|
|
382
|
+
/>
|
|
383
|
+
<Button
|
|
384
|
+
variant="outline"
|
|
385
|
+
size="small"
|
|
386
|
+
icon={<ChevronIcon direction="left" />}
|
|
387
|
+
aria-label={`Move selected to ${sourceLabel}`}
|
|
388
|
+
disabled={disabled || targetSelected.size === 0}
|
|
389
|
+
onClick={transferToSource}
|
|
390
|
+
/>
|
|
391
|
+
<Button
|
|
392
|
+
variant="outline"
|
|
393
|
+
size="small"
|
|
394
|
+
icon={<ChevronsIcon direction="left" />}
|
|
395
|
+
aria-label={`Move all to ${sourceLabel}`}
|
|
396
|
+
disabled={disabled || effectiveData[1].length === 0}
|
|
397
|
+
onClick={transferAllToSource}
|
|
398
|
+
/>
|
|
399
|
+
</div>
|
|
400
|
+
|
|
401
|
+
{renderPane(
|
|
402
|
+
"target",
|
|
403
|
+
targetLabel,
|
|
404
|
+
filteredTarget,
|
|
405
|
+
effectiveData[1],
|
|
406
|
+
targetSelected,
|
|
407
|
+
toggleTargetItem,
|
|
408
|
+
targetSearch,
|
|
409
|
+
setTargetSearch,
|
|
410
|
+
)}
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
}
|
|
414
|
+
/>
|
|
415
|
+
);
|
|
416
|
+
},
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
TransferList.displayName = "TransferList";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# TransferList - Usage Guide
|
|
2
2
|
|
|
3
|
-
This document describes how to integrate and use the `TransferList` component in your projects
|
|
3
|
+
This document describes how to integrate and use the `TransferList` component in your projects
|
|
4
|
+
using `@recursica/mantine-adapter`.
|
|
4
5
|
|
|
5
6
|
---
|
|
6
7
|
|
|
@@ -21,26 +22,56 @@ import { TransferList } from "@recursica/mantine-adapter";
|
|
|
21
22
|
export default function Demo() {
|
|
22
23
|
return (
|
|
23
24
|
<TransferList
|
|
24
|
-
|
|
25
|
+
label="Assign users"
|
|
26
|
+
sourceLabel="Available"
|
|
27
|
+
targetLabel="Selected"
|
|
28
|
+
defaultData={[
|
|
25
29
|
[
|
|
26
30
|
{ value: "1", label: "Item 1" },
|
|
27
31
|
{ value: "2", label: "Item 2" },
|
|
28
32
|
],
|
|
29
33
|
[{ value: "3", label: "Item 3" }],
|
|
30
34
|
]}
|
|
35
|
+
onChange={(data) => console.log(data)}
|
|
31
36
|
/>
|
|
32
37
|
);
|
|
33
38
|
}
|
|
34
39
|
```
|
|
35
40
|
|
|
41
|
+
Items sharing a `group` field render under a `CheckboxGroup` heading in their pane:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<TransferList
|
|
45
|
+
label="Assign ingredients"
|
|
46
|
+
defaultData={[
|
|
47
|
+
[
|
|
48
|
+
{ value: "apple", label: "Apple", group: "Fruit" },
|
|
49
|
+
{ value: "carrot", label: "Carrot", group: "Vegetable" },
|
|
50
|
+
{ value: "eagle", label: "Eagle" }, // ungrouped, renders above the groups
|
|
51
|
+
],
|
|
52
|
+
[],
|
|
53
|
+
]}
|
|
54
|
+
/>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Pass `data` instead of `defaultData` to control the selection yourself.
|
|
58
|
+
|
|
36
59
|
---
|
|
37
60
|
|
|
38
61
|
## 3. Design System Integration
|
|
39
62
|
|
|
40
|
-
All Recursica components in the `@recursica/mantine-adapter` package adhere strictly to design
|
|
63
|
+
All Recursica components in the `@recursica/mantine-adapter` package adhere strictly to design
|
|
64
|
+
system spacing, scaling, and behavior patterns.
|
|
41
65
|
|
|
42
66
|
> [!IMPORTANT]
|
|
43
67
|
>
|
|
44
|
-
> - **Anti-override protection**:
|
|
45
|
-
>
|
|
46
|
-
>
|
|
68
|
+
> - **Anti-override protection**: Rogue style injections (like inline `style` or arbitrary
|
|
69
|
+
> `className`) are automatically blocked by our prop layer unless `overStyled={true}` is
|
|
70
|
+
> explicitly provided.
|
|
71
|
+
> - **No Direct Layers**: Do not pass a `layer` prop to this component. To place it on a specific
|
|
72
|
+
> visual layer, wrap it in a `<Layer layer={0|1|2|3}>` component natively.
|
|
73
|
+
> - **Variables and Theming**: Styling is entirely determined by local CSS variables defined in
|
|
74
|
+
> `recursica_variables_scoped.css` and mapped in the component's CSS module.
|
|
75
|
+
> - **Form control layout**: `label`, `formLayout` (`stacked`/`side-by-side`), `required`,
|
|
76
|
+
> `assistiveText`/`description`/`helperText`, and `error` all route through the shared
|
|
77
|
+
> `FormControlWrapper`, same as every other form control.
|