@recursica/mantine-adapter 0.44.0 → 0.46.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 (35) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/index.d.ts +106 -54
  3. package/dist/mantine-adapter.cjs +2 -2
  4. package/dist/mantine-adapter.cjs.map +1 -1
  5. package/dist/mantine-adapter.css +1 -1
  6. package/dist/mantine-adapter.js +1901 -1848
  7. package/dist/mantine-adapter.js.map +1 -1
  8. package/package.json +3 -3
  9. package/src/components/Accordion/Accordion.tsx +1 -1
  10. package/src/components/Checkbox/CheckboxGroup.tsx +1 -1
  11. package/src/components/Chip/CHIP_IMPLEMENTATION_NOTES.md +8 -8
  12. package/src/components/Chip/Chip.module.css +5 -5
  13. package/src/components/Chip/Chip.stories.tsx +2 -2
  14. package/src/components/Chip/Chip.tsx +13 -13
  15. package/src/components/Chip/USAGE.md +2 -2
  16. package/src/components/Dropdown/Dropdown.module.css +1 -1
  17. package/src/components/FileInput/FILEINPUT_IMPLEMENTATION_NOTES.md +6 -4
  18. package/src/components/FileInput/FileInput.stories.tsx +3 -0
  19. package/src/components/FileInput/FileInput.tsx +11 -10
  20. package/src/components/FileInput/USAGE.md +1 -0
  21. package/src/components/FileUpload/FILEUPLOAD_IMPLEMENTATION_NOTES.md +12 -12
  22. package/src/components/FileUpload/FileUpload.tsx +9 -9
  23. package/src/components/Link/IMPLEMENTATION_NOTES.md +1 -1
  24. package/src/components/Link/Link.module.css +4 -0
  25. package/src/components/Radio/RadioGroup.tsx +1 -1
  26. package/src/components/SegmentedControl/IMPLEMENTATION_NOTES.md +8 -0
  27. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +17 -28
  28. package/src/components/SegmentedControl/SegmentedControl.tsx +29 -17
  29. package/src/components/SegmentedControl/USAGE.md +13 -0
  30. package/src/components/Switch/SwitchGroup.tsx +1 -1
  31. package/src/components/Table/TABLE_IMPLEMENTATION_NOTES.md +67 -0
  32. package/src/components/Table/Table.icons.tsx +40 -0
  33. package/src/components/Table/Table.stories.tsx +93 -0
  34. package/src/components/Table/Table.tsx +46 -8
  35. package/src/components/Table/USAGE.md +35 -0
@@ -5,7 +5,6 @@ import {
5
5
  } from "@mantine/core";
6
6
  import {
7
7
  filterStylingProps,
8
- omitUnsupportedProps,
9
8
  mergeClassNames,
10
9
  type RecursicaOverStyled,
11
10
  } from "../../utils/filterStylingProps";
@@ -22,7 +21,7 @@ export type SegmentedControlProps = RecursicaOverStyled<
22
21
  | "color"
23
22
  | "classNames"
24
23
  | "className"
25
- | "disabled"
24
+ | "data"
26
25
  > & {
27
26
  className?: string;
28
27
  classNames?: Partial<Record<string, string>>;
@@ -53,27 +52,39 @@ function useSegmentedControlClassNames(restRecord: Record<string, unknown>): {
53
52
 
54
53
  const _SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
55
54
  function SegmentedControl(
56
- { overStyled = false, orientation = "horizontal", fullWidth, ...rest },
55
+ {
56
+ overStyled = false,
57
+ orientation = "horizontal",
58
+ fullWidth,
59
+ data = [],
60
+ ...rest
61
+ },
57
62
  ref,
58
63
  ) {
59
- // Props this component intentionally doesn't support — deleted at runtime so they can't leak
60
- // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
61
- const UNSUPPORTED_PROPS = [
62
- // SegmentedControl only supports per-item disabling via the `data` array (each item may set
63
- // its own `disabled`); a top-level `disabled` is intentionally unsupported (typed as `never`
64
- // in RecursicaSegmentedControlProps) because Mantine's top-level `disabled` would disable
65
- // the whole control uniformly instead of per-item.
66
- "disabled",
67
- ] as const satisfies readonly (keyof MantineSegmentedControlProps)[];
68
-
69
- const sanitizedProps = omitUnsupportedProps(
70
- filterStylingProps(rest, overStyled) as Record<string, unknown>,
71
- UNSUPPORTED_PROPS,
72
- ) as Partial<typeof rest>;
64
+ const sanitizedProps = filterStylingProps(rest, overStyled) as Partial<
65
+ typeof rest
66
+ >;
73
67
  const restRecord = sanitizedProps as Record<string, unknown>;
74
68
 
75
69
  const stylingParams = useSegmentedControlClassNames(restRecord);
76
70
 
71
+ // Mantine's own data item has no icon slot; compose one into `label` (already a ReactNode)
72
+ // so Mantine's native innerLabel wrapper lays it out using the icon-size/gap tokens already
73
+ // wired in SegmentedControl.module.css.
74
+ const mappedData = data.map((item) =>
75
+ typeof item === "string" || !item.icon
76
+ ? item
77
+ : {
78
+ ...item,
79
+ label: (
80
+ <>
81
+ {item.icon}
82
+ {item.label}
83
+ </>
84
+ ),
85
+ },
86
+ );
87
+
77
88
  return (
78
89
  <MantineSegmentedControl
79
90
  ref={ref}
@@ -86,6 +97,7 @@ const _SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
86
97
  orientation={orientation}
87
98
  fullWidth={fullWidth}
88
99
  data-orientation={orientation}
100
+ data={mappedData}
89
101
  />
90
102
  );
91
103
  },
@@ -23,6 +23,17 @@ export default function Demo() {
23
23
  }
24
24
  ```
25
25
 
26
+ Each `data` item may also be an object with an optional `icon`, rendered ahead of the label:
27
+
28
+ ```tsx
29
+ <SegmentedControl
30
+ data={[
31
+ { value: "daily", label: "Daily", icon: <CheckIcon /> },
32
+ { value: "weekly", label: "Weekly" },
33
+ ]}
34
+ />
35
+ ```
36
+
26
37
  ---
27
38
 
28
39
  ## 3. Design System Integration
@@ -40,3 +51,5 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
40
51
  ## 4. Key Integration Features & Constraints
41
52
 
42
53
  The `variant`, `size`, `radius`, and `color` props are not available on this component, since appearance is fully controlled by the design system tokens. The active segment is shown as a floating indicator that moves behind the selected label, with a divider rendered between adjacent segments.
54
+
55
+ A top-level `disabled` disables every item at once; an individual item can still be disabled on its own via `data[].disabled`.
@@ -19,7 +19,7 @@ import { type RecursicaSwitchGroupProps as BaseRecursicaSwitchGroupProps } from
19
19
  export interface RecursicaSwitchGroupProps
20
20
  extends Omit<
21
21
  MantineSwitchGroupProps,
22
- "size" | "labelProps" | "defaultValue" | "value" | "onChange"
22
+ "size" | "labelProps" | "defaultValue" | "value"
23
23
  >,
24
24
  Omit<
25
25
  RecursicaFormControlWrapperProps,
@@ -0,0 +1,67 @@
1
+ # Table — Implementation Notes (Internal)
2
+
3
+ ## Background
4
+
5
+ Table.module.css already had full CSS for header/cell/footer sorted, disabled, currency, and
6
+ selected states, keyed off `data-sorted`/`data-disabled`/`data-currency`/`data-selected`
7
+ attributes — but nothing in Table.tsx ever set those attributes, so the states were unreachable
8
+ and their tokens showed as "unused" in the token analyzer. This pass adds the props that
9
+ actually set them; it does not add any new CSS selectors for those states.
10
+
11
+ ## Why not the 4-component split from the Forge reference implementation
12
+
13
+ Forge's Mantine Table reference (reviewed 2026-08-22) splits `Table`/`TableHeader`/`TableCell`/
14
+ `TableFooter` into four separate top-level components, each computing CSS custom properties in
15
+ JS and injecting them via inline `style`. We deliberately did not port that:
16
+
17
+ - It bypasses `filterStylingProps`/`overStyled` and computes styling values in TSX instead of
18
+ referencing tokens directly in the CSS module — both are hard "no" per
19
+ `adapter-common/docs/COMPONENT_DEV_GUIDE.md` (§1 "No custom properties set from TSX for
20
+ styling", §3.1 `overStyled`/`filterStylingProps`).
21
+ - It uses a `layer`-scoped token naming convention (`properties_colors_layer-0_...`) that
22
+ doesn't exist in this repo's tokens and isn't the sanctioned way to handle layers here (layer
23
+ is set only by wrapping in `<Layer>`, never a component prop — see the canonical guide's
24
+ "Layers" section).
25
+ - Per direction from Matt (2026-08-22): keep the existing dot-notation composition
26
+ (`Table.Th`/`Table.Td`/`Table.Tr`/`Table.Tfoot`/etc., mirroring Mantine's own API) rather than
27
+ introducing new component names — "align with the underlying UI-kit's components, but style
28
+ with Recursica."
29
+
30
+ Instead, `Table.Th`/`Table.Td`/`Table.Tr` gained real Recursica props (`sorted`, `disabled`,
31
+ `variant`, `selected`) that set `data-*` attributes on themselves, activating the CSS that was
32
+ already there.
33
+
34
+ ## Footer cells reuse `Table.Td`, not a new component
35
+
36
+ There's no separate "footer cell" component. A `Table.Td` nested inside `Table.Tfoot` picks up
37
+ the footer-specific CSS automatically via the existing `.root tfoot td` structural selector in
38
+ Table.module.css — `variant="currency"` and `disabled` work identically whether the `Table.Td`
39
+ is in `Table.Tbody` or `Table.Tfoot`.
40
+
41
+ ## Sort icon is rendered by `Table.Th` itself, not composed by the consumer
42
+
43
+ Mantine's `Table` has no sortable-header primitive (unlike MUI's `TableSortLabel`), so
44
+ `Table.Th` renders its own chevron (`Table.icons.tsx`, plain inline SVGs — no icon package
45
+ dependency, same convention as `DatePicker.icons.tsx`) when `sorted` is `"asc"`/`"desc"`, sized
46
+ via the existing `.sortIcon` CSS rule. Also sets `aria-sort` for accessibility (Mantine doesn't
47
+ set this itself). This is intentionally asymmetric with `mui-adapter`, where sorting is composed
48
+ via `Table.SortLabel` instead — see that adapter's own implementation notes.
49
+
50
+ ## Selectable rows (checkboxes)
51
+
52
+ Neither Mantine's `Table` nor `@mui/material`'s `Table` has a built-in checkbox-driven row
53
+ selection feature (confirmed 2026-08-22 — MUI's own docs treat it as a compose-it-yourself
54
+ recipe with a plain `Checkbox`; the only MUI product with built-in selection is
55
+ `@mui/x-data-grid`, not installed here). `Table.Tr`'s `selected` prop only controls the
56
+ selected-row background/token state; wiring an actual checkbox column is a separate,
57
+ not-yet-scoped ask.
58
+
59
+ ## Deliberately not changed
60
+
61
+ - **No wrapper divs** — Forge wraps the table in two divs to get a scrollable bordered
62
+ container. The canonical guide forbids wrapper divs; `Table.ScrollContainer` (already wraps
63
+ Mantine's own `Table.ScrollContainer`) is the sanctioned way to get a scrolling table.
64
+ - **Hover compositing, striping direction/row parity, row-padding semantics** — Forge diverges
65
+ from our current CSS on these (see the 2026-08-22 review), but none of them are confirmed
66
+ bugs vs. intentional design choices already baked into Figma's tokens. Left as-is; flag to
67
+ design if a change is wanted.
@@ -0,0 +1,40 @@
1
+ import React from "react";
2
+
3
+ /**
4
+ * Sort-direction indicators for `Table.Th`. Sized/colored entirely via the `.sortIcon` rule in
5
+ * Table.module.css (currentColor + the header's own text-color token) — no width/height/color
6
+ * hardcoded here on purpose, same convention as DatePicker.icons.tsx.
7
+ */
8
+ export function ChevronUpIcon(props: React.SVGProps<SVGSVGElement>) {
9
+ return (
10
+ <svg
11
+ viewBox="0 0 24 24"
12
+ fill="none"
13
+ stroke="currentColor"
14
+ strokeWidth="2"
15
+ strokeLinecap="round"
16
+ strokeLinejoin="round"
17
+ aria-hidden
18
+ {...props}
19
+ >
20
+ <polyline points="18 15 12 9 6 15" />
21
+ </svg>
22
+ );
23
+ }
24
+
25
+ export function ChevronDownIcon(props: React.SVGProps<SVGSVGElement>) {
26
+ return (
27
+ <svg
28
+ viewBox="0 0 24 24"
29
+ fill="none"
30
+ stroke="currentColor"
31
+ strokeWidth="2"
32
+ strokeLinecap="round"
33
+ strokeLinejoin="round"
34
+ aria-hidden
35
+ {...props}
36
+ >
37
+ <polyline points="6 9 12 15 18 9" />
38
+ </svg>
39
+ );
40
+ }
@@ -45,3 +45,96 @@ export const Default: Story = {
45
45
  );
46
46
  },
47
47
  };
48
+
49
+ export const SortedColumn: Story = {
50
+ render: () => {
51
+ const sorted = [...elements].sort((a, b) => a.mass - b.mass);
52
+ const rows = sorted.map((element) => (
53
+ <Table.Tr key={element.name}>
54
+ <Table.Td>{element.position}</Table.Td>
55
+ <Table.Td>{element.name}</Table.Td>
56
+ <Table.Td>{element.symbol}</Table.Td>
57
+ <Table.Td>{element.mass}</Table.Td>
58
+ </Table.Tr>
59
+ ));
60
+
61
+ return (
62
+ <Table>
63
+ <Table.Thead>
64
+ <Table.Tr>
65
+ <Table.Th>Element position</Table.Th>
66
+ <Table.Th>Element name</Table.Th>
67
+ <Table.Th>Symbol</Table.Th>
68
+ <Table.Th sorted="asc">Atomic mass</Table.Th>
69
+ </Table.Tr>
70
+ </Table.Thead>
71
+ <Table.Tbody>{rows}</Table.Tbody>
72
+ </Table>
73
+ );
74
+ },
75
+ };
76
+
77
+ export const SelectedAndDisabledRows: Story = {
78
+ render: () => (
79
+ <Table>
80
+ <Table.Thead>
81
+ <Table.Tr>
82
+ <Table.Th>Element position</Table.Th>
83
+ <Table.Th>Element name</Table.Th>
84
+ <Table.Th>Symbol</Table.Th>
85
+ <Table.Th>Atomic mass</Table.Th>
86
+ </Table.Tr>
87
+ </Table.Thead>
88
+ <Table.Tbody>
89
+ {elements.map((element, index) => (
90
+ <Table.Tr
91
+ key={element.name}
92
+ selected={index === 0}
93
+ disabled={index === elements.length - 1}
94
+ >
95
+ <Table.Td>{element.position}</Table.Td>
96
+ <Table.Td>{element.name}</Table.Td>
97
+ <Table.Td>{element.symbol}</Table.Td>
98
+ <Table.Td>{element.mass}</Table.Td>
99
+ </Table.Tr>
100
+ ))}
101
+ </Table.Tbody>
102
+ </Table>
103
+ ),
104
+ };
105
+
106
+ export const CurrencyColumnWithFooter: Story = {
107
+ render: () => {
108
+ const prices = [
109
+ { item: "Widget", price: 19.99 },
110
+ { item: "Gadget", price: 49.5 },
111
+ { item: "Gizmo", price: 9.25 },
112
+ ];
113
+ const total = prices.reduce((sum, row) => sum + row.price, 0);
114
+
115
+ return (
116
+ <Table>
117
+ <Table.Thead>
118
+ <Table.Tr>
119
+ <Table.Th>Item</Table.Th>
120
+ <Table.Th>Price</Table.Th>
121
+ </Table.Tr>
122
+ </Table.Thead>
123
+ <Table.Tbody>
124
+ {prices.map((row) => (
125
+ <Table.Tr key={row.item}>
126
+ <Table.Td>{row.item}</Table.Td>
127
+ <Table.Td variant="currency">${row.price.toFixed(2)}</Table.Td>
128
+ </Table.Tr>
129
+ ))}
130
+ </Table.Tbody>
131
+ <Table.Tfoot>
132
+ <Table.Tr>
133
+ <Table.Td>Total</Table.Td>
134
+ <Table.Td variant="currency">${total.toFixed(2)}</Table.Td>
135
+ </Table.Tr>
136
+ </Table.Tfoot>
137
+ </Table>
138
+ );
139
+ },
140
+ };
@@ -15,8 +15,14 @@ import {
15
15
  filterStylingProps,
16
16
  type RecursicaOverStyled,
17
17
  } from "../../utils/filterStylingProps";
18
- import { type RecursicaTableProps } from "@recursica/adapter-common";
18
+ import {
19
+ type RecursicaTableProps,
20
+ type RecursicaTableRowProps,
21
+ type RecursicaTableHeaderCellProps,
22
+ type RecursicaTableCellProps,
23
+ } from "@recursica/adapter-common";
19
24
  import styles from "./Table.module.css";
25
+ import { ChevronUpIcon, ChevronDownIcon } from "./Table.icons";
20
26
 
21
27
  export type TableProps = RecursicaOverStyled<
22
28
  MantineTableProps & RecursicaTableProps
@@ -80,45 +86,77 @@ export const TableTbody = forwardRef<HTMLTableSectionElement, TableTbodyProps>(
80
86
  );
81
87
  TableTbody.displayName = "TableTbody";
82
88
 
83
- export type TableTrProps = RecursicaOverStyled<MantineTableTrProps>;
89
+ export type TableTrProps = RecursicaOverStyled<
90
+ Omit<MantineTableTrProps, "children"> & RecursicaTableRowProps
91
+ >;
84
92
 
85
93
  export const TableTr = forwardRef<HTMLTableRowElement, TableTrProps>(
86
- function TableTr({ overStyled = false, ...rest }, ref) {
94
+ function TableTr(
95
+ { overStyled = false, selected = false, disabled = false, ...rest },
96
+ ref,
97
+ ) {
87
98
  const sanitizedProps = filterStylingProps(rest, overStyled);
88
99
  return (
89
100
  <MantineTable.Tr
90
101
  ref={ref}
91
102
  {...(sanitizedProps as unknown as MantineTableTrProps)}
103
+ data-selected={selected ? "true" : undefined}
104
+ data-disabled={disabled ? "true" : undefined}
92
105
  />
93
106
  );
94
107
  },
95
108
  );
96
109
  TableTr.displayName = "TableTr";
97
110
 
98
- export type TableThProps = RecursicaOverStyled<MantineTableThProps>;
111
+ export type TableThProps = RecursicaOverStyled<
112
+ Omit<MantineTableThProps, "children"> & RecursicaTableHeaderCellProps
113
+ >;
99
114
 
100
115
  export const TableTh = forwardRef<HTMLTableCellElement, TableThProps>(
101
- function TableTh({ overStyled = false, ...rest }, ref) {
116
+ function TableTh(
117
+ { overStyled = false, sorted = false, disabled = false, children, ...rest },
118
+ ref,
119
+ ) {
102
120
  const sanitizedProps = filterStylingProps(rest, overStyled);
103
121
  return (
104
122
  <MantineTable.Th
105
123
  ref={ref}
106
124
  {...(sanitizedProps as unknown as MantineTableThProps)}
107
- />
125
+ data-sorted={sorted ? "true" : undefined}
126
+ data-disabled={disabled ? "true" : undefined}
127
+ aria-sort={
128
+ sorted === "asc"
129
+ ? "ascending"
130
+ : sorted === "desc"
131
+ ? "descending"
132
+ : undefined
133
+ }
134
+ >
135
+ {children}
136
+ {sorted === "asc" && <ChevronUpIcon className={styles.sortIcon} />}
137
+ {sorted === "desc" && <ChevronDownIcon className={styles.sortIcon} />}
138
+ </MantineTable.Th>
108
139
  );
109
140
  },
110
141
  );
111
142
  TableTh.displayName = "TableTh";
112
143
 
113
- export type TableTdProps = RecursicaOverStyled<MantineTableTdProps>;
144
+ export type TableTdProps = RecursicaOverStyled<
145
+ Omit<MantineTableTdProps, "children"> & RecursicaTableCellProps
146
+ >;
114
147
 
115
148
  export const TableTd = forwardRef<HTMLTableCellElement, TableTdProps>(
116
- function TableTd({ overStyled = false, ...rest }, ref) {
149
+ function TableTd(
150
+ { overStyled = false, disabled = false, variant = "default", ...rest },
151
+ ref,
152
+ ) {
117
153
  const sanitizedProps = filterStylingProps(rest, overStyled);
118
154
  return (
119
155
  <MantineTable.Td
120
156
  ref={ref}
121
157
  {...(sanitizedProps as unknown as MantineTableTdProps)}
158
+ data-disabled={disabled ? "true" : undefined}
159
+ data-currency={variant === "currency" ? "true" : undefined}
122
160
  />
123
161
  );
124
162
  },
@@ -39,3 +39,38 @@ export default function Demo() {
39
39
  ```
40
40
 
41
41
  ---
42
+
43
+ ## 3. Row and Cell States
44
+
45
+ - **`Table.Tr`**: `selected` applies the selected-row background; `disabled` dims the row and applies the disabled cell colors to every cell in it.
46
+ - **`Table.Th`**: `sorted="asc" | "desc"` applies the sorted header style and renders the matching chevron icon; omit it (or pass `false`) for the unsorted style. `disabled` dims the header cell.
47
+ - **`Table.Td`**: `variant="currency"` applies the currency text style (for numeric/monetary columns); `disabled` dims the cell.
48
+
49
+ ```tsx
50
+ <Table>
51
+ <Table.Thead>
52
+ <Table.Tr>
53
+ <Table.Th>Name</Table.Th>
54
+ <Table.Th sorted="asc">Balance</Table.Th>
55
+ </Table.Tr>
56
+ </Table.Thead>
57
+ <Table.Tbody>
58
+ <Table.Tr selected>
59
+ <Table.Td>Jane Doe</Table.Td>
60
+ <Table.Td variant="currency">$1,204.50</Table.Td>
61
+ </Table.Tr>
62
+ <Table.Tr disabled>
63
+ <Table.Td>Inactive Account</Table.Td>
64
+ <Table.Td variant="currency">$0.00</Table.Td>
65
+ </Table.Tr>
66
+ </Table.Tbody>
67
+ <Table.Tfoot>
68
+ <Table.Tr>
69
+ <Table.Td>Total</Table.Td>
70
+ <Table.Td variant="currency">$1,204.50</Table.Td>
71
+ </Table.Tr>
72
+ </Table.Tfoot>
73
+ </Table>
74
+ ```
75
+
76
+ ---