@recursica/mui-adapter 0.24.0 → 0.26.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/index.d.ts +189 -28
  3. package/dist/mui-adapter.cjs +85 -85
  4. package/dist/mui-adapter.cjs.map +1 -1
  5. package/dist/mui-adapter.css +1 -1
  6. package/dist/mui-adapter.js +29694 -24393
  7. package/dist/mui-adapter.js.map +1 -1
  8. package/llms.txt +1 -0
  9. package/package.json +1 -1
  10. package/src/GlobalExemptions.modules.css +0 -6
  11. package/src/components/Accordion/Accordion.module.css +0 -8
  12. package/src/components/AssistiveElement/AssistiveElement.tsx +1 -1
  13. package/src/components/Autocomplete/Autocomplete.module.css +0 -8
  14. package/src/components/Avatar/Avatar.module.css +0 -8
  15. package/src/components/Button/BUTTON_IMPLEMENTATION_NOTES.md +12 -0
  16. package/src/components/Button/Button.module.css +6 -18
  17. package/src/components/Checkbox/Checkbox.tsx +4 -1
  18. package/src/components/Chip/Chip.module.css +0 -27
  19. package/src/components/DatePicker/DATEPICKER_IMPLEMENTATION_NOTES.md +29 -0
  20. package/src/components/DatePicker/DatePicker.icons.tsx +35 -0
  21. package/src/components/DatePicker/DatePicker.module.css +451 -42
  22. package/src/components/DatePicker/DatePicker.stories.tsx +23 -25
  23. package/src/components/DatePicker/DatePicker.tsx +229 -47
  24. package/src/components/DatePicker/USAGE.md +14 -1
  25. package/src/components/Dropdown/Dropdown.module.css +0 -7
  26. package/src/components/FileInput/FileInput.module.css +0 -21
  27. package/src/components/FileInput/FileInput.tsx +6 -0
  28. package/src/components/FileUpload/FileUpload.module.css +0 -11
  29. package/src/components/HoverCard/HoverCard.module.css +1 -7
  30. package/src/components/Label/Label.module.css +0 -6
  31. package/src/components/Link/Link.module.css +0 -13
  32. package/src/components/Menu/Menu.module.css +0 -5
  33. package/src/components/Modal/Modal.module.css +0 -11
  34. package/src/components/NumberInput/NumberInput.module.css +0 -7
  35. package/src/components/Pagination/Pagination.module.css +0 -81
  36. package/src/components/Popover/IMPLEMENTATION_NOTES.md +22 -0
  37. package/src/components/Popover/Popover.module.css +118 -0
  38. package/src/components/Popover/Popover.stories.tsx +133 -0
  39. package/src/components/Popover/Popover.tsx +275 -0
  40. package/src/components/Popover/USAGE.md +69 -0
  41. package/src/components/Popover/index.ts +1 -0
  42. package/src/components/SegmentedControl/IMPLEMENTATION_NOTES.md +6 -0
  43. package/src/components/SegmentedControl/SegmentedControl.module.css +32 -11
  44. package/src/components/SegmentedControl/SegmentedControl.tsx +18 -4
  45. package/src/components/Slider/IMPLEMENTATION_NOTES.md +29 -0
  46. package/src/components/Slider/Slider.module.css +80 -17
  47. package/src/components/Slider/Slider.stories.tsx +1 -1
  48. package/src/components/Slider/Slider.tsx +36 -1
  49. package/src/components/Stepper/IMPLEMENTATION_NOTES.md +54 -0
  50. package/src/components/Stepper/Stepper.module.css +139 -106
  51. package/src/components/Stepper/Stepper.tsx +76 -10
  52. package/src/components/Stepper/USAGE.md +4 -0
  53. package/src/components/Tabs/IMPLEMENTATION_NOTES.md +12 -0
  54. package/src/components/Tabs/Tabs.module.css +107 -24
  55. package/src/components/Tabs/Tabs.tsx +1 -0
  56. package/src/components/TextArea/TextArea.module.css +20 -11
  57. package/src/components/TextArea/TextArea.tsx +12 -23
  58. package/src/components/TextField/TextField.module.css +0 -8
  59. package/src/components/TimePicker/TimePicker.module.css +0 -16
  60. package/src/components/Timeline/IMPLEMENTATION_NOTES.md +24 -3
  61. package/src/components/Timeline/Timeline.module.css +55 -73
  62. package/src/components/Timeline/Timeline.tsx +23 -27
  63. package/src/components/Timeline/TimelineItem.tsx +38 -35
  64. package/src/components/Toast/Toast.module.css +0 -7
  65. package/src/components/Tooltip/Tooltip.module.css +0 -9
  66. package/src/components/TransferList/TRANSFERLIST_IMPLEMENTATION_NOTES.md +140 -0
  67. package/src/components/TransferList/TransferList.module.css +174 -38
  68. package/src/components/TransferList/TransferList.stories.tsx +110 -6
  69. package/src/components/TransferList/TransferList.tsx +417 -8
  70. package/src/components/TransferList/USAGE.md +37 -6
  71. package/src/components/index.ts +1 -0
  72. package/src/index.ts +3 -0
@@ -1,17 +1,121 @@
1
+ import React from "react";
1
2
  import type { Meta, StoryObj } from "@storybook/react";
2
3
  import { TransferList } from "./TransferList";
3
- import { ComingSoon } from "@recursica/storybook-template";
4
+ import { formControlArgTypes } from "../../../.storybook/commonArgTypes";
4
5
 
5
- const meta: Meta<typeof TransferList> = {
6
- title: "UI-Kit/🚧 TransferList",
6
+ type TransferListStoryProps = React.ComponentProps<typeof TransferList>;
7
+
8
+ const SAMPLE_DATA: TransferListStoryProps["defaultData"] = [
9
+ [
10
+ { value: "alpha", label: "Alpha" },
11
+ { value: "bravo", label: "Bravo" },
12
+ { value: "charlie", label: "Charlie" },
13
+ { value: "delta", label: "Delta" },
14
+ { value: "echo", label: "Echo" },
15
+ ],
16
+ [{ value: "foxtrot", label: "Foxtrot" }],
17
+ ];
18
+
19
+ const GROUPED_DATA: TransferListStoryProps["defaultData"] = [
20
+ [
21
+ { value: "apple", label: "Apple", group: "Fruit" },
22
+ { value: "banana", label: "Banana", group: "Fruit" },
23
+ { value: "carrot", label: "Carrot", group: "Vegetable" },
24
+ { value: "daikon", label: "Daikon", group: "Vegetable" },
25
+ { value: "eagle", label: "Eagle" },
26
+ ],
27
+ [],
28
+ ];
29
+
30
+ const meta: Meta<TransferListStoryProps> = {
31
+ title: "UI-Kit/TransferList",
7
32
  component: TransferList,
8
33
  tags: ["autodocs"],
34
+ parameters: {
35
+ docs: {
36
+ description: {
37
+ component:
38
+ "TransferList (dual listbox) lets users move items between two lists. Composes FormControlWrapper, TextField, Checkbox, CheckboxGroup, Badge, and Button.",
39
+ },
40
+ },
41
+ },
42
+ args: {
43
+ label: "Assign users",
44
+ assistiveText: "Move users into the selected list.",
45
+ defaultData: SAMPLE_DATA,
46
+ disabled: false,
47
+ required: false,
48
+ },
49
+ argTypes: {
50
+ disabled: {
51
+ control: "boolean",
52
+ },
53
+ ...formControlArgTypes,
54
+ sourceLabel: {
55
+ control: "text",
56
+ },
57
+ targetLabel: {
58
+ control: "text",
59
+ },
60
+ searchable: {
61
+ control: "boolean",
62
+ },
63
+ searchPlaceholder: {
64
+ control: "text",
65
+ },
66
+ },
9
67
  };
10
68
 
11
69
  export default meta;
12
70
 
13
- type Story = StoryObj<typeof TransferList>;
71
+ type Story = StoryObj<TransferListStoryProps>;
72
+
73
+ export const Default: Story = {};
74
+
75
+ export const Grouped: Story = {
76
+ args: {
77
+ label: "Assign ingredients",
78
+ defaultData: GROUPED_DATA,
79
+ },
80
+ };
81
+
82
+ export const SideBySide: Story = {
83
+ args: {
84
+ formLayout: "side-by-side",
85
+ },
86
+ };
87
+
88
+ export const NoSearch: Story = {
89
+ args: {
90
+ label: "Assign users (no filtering)",
91
+ searchable: false,
92
+ },
93
+ };
94
+
95
+ export const StaticError: Story = {
96
+ args: {
97
+ error: "Select at least one user.",
98
+ defaultData: [[], SAMPLE_DATA![0]],
99
+ },
100
+ };
101
+
102
+ export const StaticDisabled: Story = {
103
+ args: {
104
+ disabled: true,
105
+ },
106
+ };
107
+
108
+ export const Empty: Story = {
109
+ args: {
110
+ label: "Assign users",
111
+ defaultData: [[], []],
112
+ },
113
+ };
14
114
 
15
- export const Default: Story = {
16
- render: () => <ComingSoon componentName="TransferList" />,
115
+ export const ReadOnly: Story = {
116
+ args: {
117
+ label: "Assigned users",
118
+ defaultData: [[], SAMPLE_DATA![0]],
119
+ readOnly: true,
120
+ },
17
121
  };
@@ -1,10 +1,419 @@
1
- import React from "react";
2
- import "./TransferList.module.css";
3
- import { type RecursicaTransferListProps } from "@recursica/adapter-common";
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";
4
14
 
5
- export type TransferListProps = React.HTMLAttributes<HTMLDivElement> &
6
- RecursicaTransferListProps;
15
+ import {
16
+ type ReadOnlyControlProps,
17
+ type RecursicaTransferListProps as BaseRecursicaTransferListProps,
18
+ type RecursicaTransferListItem,
19
+ type RecursicaTransferListData,
20
+ } from "@recursica/adapter-common";
7
21
 
8
- export const TransferList: React.FC<TransferListProps> = (props) => {
9
- return <div {...props}>TransferList</div>;
10
- };
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
+ disabled={disabled}
338
+ id={id}
339
+ className={wrapperClass}
340
+ style={style}
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 using `@recursica/mui-adapter`.
3
+ This document describes how to integrate and use the `TransferList` component in your projects
4
+ using `@recursica/mui-adapter`.
4
5
 
5
6
  ---
6
7
 
@@ -21,26 +22,56 @@ import { TransferList } from "@recursica/mui-adapter";
21
22
  export default function Demo() {
22
23
  return (
23
24
  <TransferList
24
- data={[
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/mui-adapter` package adhere strictly to design system spacing, scaling, and behavior patterns.
63
+ All Recursica components in the `@recursica/mui-adapter` package adhere strictly to design system
64
+ spacing, scaling, and behavior patterns.
41
65
 
42
66
  > [!IMPORTANT]
43
67
  >
44
- > - **Anti-override protection**: Rogues style injections (like inline `style` or arbitrary `className`) are automatically blocked by our prop layer unless `overStyled={true}` is explicitly provided.
45
- > - **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
- > - **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.
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.
@@ -28,6 +28,7 @@ export * from "./Modal";
28
28
  export * from "./NumberInput";
29
29
  export * from "./Pagination";
30
30
  export * from "./Panel";
31
+ export * from "./Popover";
31
32
  export * from "./Radio";
32
33
  export * from "./ReadOnlyField";
33
34
  export * from "./SegmentedControl";
package/src/index.ts CHANGED
@@ -111,6 +111,9 @@ export const Panel = wrapComponent(
111
111
  export const PanelFooter = wrapComponent(
112
112
  rawComponents.PanelFooter,
113
113
  ) as typeof rawComponents.PanelFooter;
114
+ export const Popover = wrapComponent(
115
+ rawComponents.Popover,
116
+ ) as typeof rawComponents.Popover;
114
117
  export const Radio = wrapComponent(
115
118
  rawComponents.Radio,
116
119
  ) as typeof rawComponents.Radio;