@recursica/mantine-adapter 0.45.0 → 0.47.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 (32) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/index.d.ts +72 -52
  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 +1945 -1934
  7. package/dist/mantine-adapter.js.map +1 -1
  8. package/package.json +2 -2
  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/Radio/RadioGroup.tsx +1 -1
  24. package/src/components/SegmentedControl/IMPLEMENTATION_NOTES.md +8 -0
  25. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +17 -28
  26. package/src/components/SegmentedControl/SegmentedControl.tsx +29 -17
  27. package/src/components/SegmentedControl/USAGE.md +13 -0
  28. package/src/components/Slider/IMPLEMENTATION_NOTES.md +13 -0
  29. package/src/components/Slider/Slider.stories.tsx +44 -0
  30. package/src/components/Slider/Slider.tsx +23 -4
  31. package/src/components/Slider/USAGE.md +1 -1
  32. package/src/components/Switch/SwitchGroup.tsx +1 -1
@@ -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`.
@@ -57,3 +57,16 @@ This document contains specific design decisions, architectural constraints, and
57
57
  - The thumb's focus box-shadow layers the ring on top of its existing `thumb-elevation` shadow rather than replacing it.
58
58
  - `.sliderMark[data-filled]` has no default-state "-active" step-indicator token anymore (only `disabled`/`error` define one); it reuses `colors_track-active` so filled marks stay visually tied to the filled portion of the track. Worth a design review if a distinct filled-mark color is expected.
59
59
  - `markLabel` was already mapped in the `classNames` prop but had no matching `.sliderMarkLabel` rule in this file, so Mantine silently fell back to its own default theme grey instead of any recursica token. Added a `.sliderMarkLabel` rule reusing the min-max-label typography tokens with `color: inherit`, matching the mui-adapter's equivalent.
60
+
61
+ ## 7. Formatted Current Value, Label Overrides, Trailing Icon
62
+
63
+ **Decision:** The floating `.currentValue` display (see §5) always rendered the raw numeric value, even when `tooltipLabel` was a formatter function — a caller mapping values onto custom text (e.g. 0-4 → XS/S/M/L/XL) got the formatted tooltip while dragging but the raw number next to the track otherwise.
64
+ **Implementation:**
65
+
66
+ - `.currentValue` now runs `resolvedValue` through `tooltipLabel` when it's a function, so both displays agree. No change when `tooltipLabel` is absent or a static node — still the raw number.
67
+ - `minLabel`/`maxLabel` (new `adapter-common` props) override the `.minMaxGuide` text at either end of the track, falling back to the numeric `min`/`max`.
68
+ - `trailingIcon` (new `adapter-common` prop) renders a second icon on the opposite side of the track from the existing `icon`, reusing the same `.iconWrapper` styling (disabled/error/focus states already target `.iconWrapper` generically, so no new CSS was needed).
69
+
70
+ ## 8. No Dual-Thumb / Range Support
71
+
72
+ **Decision:** Requested (a caller passing `[number, number]` for `value`/`onChange`, backed by Mantine's separate `RangeSlider` component), declined — no current use case needs it. `Slider` stays single-thumb only; `value`/`onChange` remain typed as `number`.
@@ -146,6 +146,50 @@ export const WithMarks: Story = {
146
146
  },
147
147
  };
148
148
 
149
+ export const WithIconsAndLabels: Story = {
150
+ args: {
151
+ label: "Volume",
152
+ assistiveText:
153
+ "Icons flank the track; min/max labels replace the raw bounds.",
154
+ defaultValue: 60,
155
+ minLabel: "Quiet",
156
+ maxLabel: "Loud",
157
+ tooltipLabel: (value: number) => `${value}%`,
158
+ icon: (
159
+ <svg
160
+ xmlns="http://www.w3.org/2000/svg"
161
+ width="16"
162
+ height="16"
163
+ viewBox="0 0 24 24"
164
+ fill="none"
165
+ stroke="currentColor"
166
+ strokeWidth="2"
167
+ strokeLinecap="round"
168
+ strokeLinejoin="round"
169
+ >
170
+ <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
171
+ </svg>
172
+ ),
173
+ trailingIcon: (
174
+ <svg
175
+ xmlns="http://www.w3.org/2000/svg"
176
+ width="16"
177
+ height="16"
178
+ viewBox="0 0 24 24"
179
+ fill="none"
180
+ stroke="currentColor"
181
+ strokeWidth="2"
182
+ strokeLinecap="round"
183
+ strokeLinejoin="round"
184
+ >
185
+ <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
186
+ <path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path>
187
+ <path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path>
188
+ </svg>
189
+ ),
190
+ },
191
+ };
192
+
149
193
  export const FormLayouts: Story = {
150
194
  render: () => (
151
195
  <div
@@ -51,7 +51,8 @@ const SliderReadOnlyValue: React.FC<{ value: number }> = ({ value }) => {
51
51
  * Recursica Slider component wrapping Mantine's Slider.
52
52
  *
53
53
  * Implements a bidirectional text input field next to the slider track, responsive layouts,
54
- * custom typography-bound min/max labels, an optional leading icon, and an explicit read-only layout.
54
+ * custom typography-bound min/max labels (optionally overridden via `minLabel`/`maxLabel`),
55
+ * optional leading/trailing icons, and an explicit read-only layout.
55
56
  */
56
57
  export const Slider = forwardRef<HTMLDivElement, SliderProps>(
57
58
  function Slider(props, ref) {
@@ -84,10 +85,13 @@ export const Slider = forwardRef<HTMLDivElement, SliderProps>(
84
85
  value,
85
86
  defaultValue,
86
87
  icon,
88
+ trailingIcon,
87
89
  showInput = false,
88
90
  showMinMaxLabels = true,
89
91
  min = 0,
90
92
  max = 100,
93
+ minLabel,
94
+ maxLabel,
91
95
  step = 1,
92
96
  onChange,
93
97
  onChangeEnd,
@@ -176,6 +180,19 @@ export const Slider = forwardRef<HTMLDivElement, SliderProps>(
176
180
  </span>
177
181
  ) : null;
178
182
 
183
+ const trailingIconEl = trailingIcon ? (
184
+ <span className={styles.iconWrapper} aria-hidden>
185
+ {trailingIcon}
186
+ </span>
187
+ ) : null;
188
+
189
+ // Duplicates the raw numeric value next to the track by default; when `tooltipLabel` is a
190
+ // formatter, reuse it here too so both displays agree instead of one showing raw numbers.
191
+ const displayValue =
192
+ typeof tooltipLabel === "function"
193
+ ? tooltipLabel(resolvedValue)
194
+ : resolvedValue;
195
+
179
196
  return (
180
197
  <WithReadOnlyWrapper
181
198
  ref={ref}
@@ -214,7 +231,7 @@ export const Slider = forwardRef<HTMLDivElement, SliderProps>(
214
231
  {leadingIcon}
215
232
 
216
233
  {showMinMaxLabels && (
217
- <span className={styles.minMaxGuide}>{min}</span>
234
+ <span className={styles.minMaxGuide}>{minLabel ?? min}</span>
218
235
  )}
219
236
 
220
237
  <div className={styles.sliderTrackWrapper}>
@@ -234,10 +251,10 @@ export const Slider = forwardRef<HTMLDivElement, SliderProps>(
234
251
 
235
252
  <div className={styles.rightGuideContainer}>
236
253
  {!showInput && (
237
- <span className={styles.currentValue}>{resolvedValue}</span>
254
+ <span className={styles.currentValue}>{displayValue}</span>
238
255
  )}
239
256
  {showMinMaxLabels && (
240
- <span className={styles.minMaxGuide}>{max}</span>
257
+ <span className={styles.minMaxGuide}>{maxLabel ?? max}</span>
241
258
  )}
242
259
  </div>
243
260
 
@@ -255,6 +272,8 @@ export const Slider = forwardRef<HTMLDivElement, SliderProps>(
255
272
  data-error={error ? "true" : undefined}
256
273
  />
257
274
  )}
275
+
276
+ {trailingIconEl}
258
277
  </div>
259
278
  }
260
279
  />
@@ -39,4 +39,4 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
39
39
 
40
40
  ## 4. Key Integration Features & Constraints
41
41
 
42
- The `label` prop is passed through to the surrounding form label rather than Mantine's dragging tooltip; use `tooltipLabel` to set the label shown while dragging. When `showInput` is enabled, a numeric text input is rendered alongside the track and stays in sync with the slider's value. Set `showMinMaxLabels` to `false` to hide the min/max guides shown at either end of the track. Otherwise, the current value is displayed near the track instead.
42
+ The `label` prop is passed through to the surrounding form label rather than Mantine's dragging tooltip; use `tooltipLabel` to set the label shown while dragging. When `showInput` is enabled, a numeric text input is rendered alongside the track and stays in sync with the slider's value. Set `showMinMaxLabels` to `false` to hide the min/max guides shown at either end of the track. Otherwise, the current value is displayed near the track instead — pass `tooltipLabel` as a formatter function (`(value) => ReactNode`) and that same formatter is reused for this display, instead of always showing the raw number. `minLabel`/`maxLabel` override the text shown at either end of the track (defaults to the numeric `min`/`max`). `icon` renders a leading icon next to the track; `trailingIcon` renders one on the opposite side.
@@ -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,