@recursica/mantine-adapter 0.42.0 → 0.43.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 (86) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/index.d.ts +93 -15
  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 +3546 -3250
  7. package/dist/mantine-adapter.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/GlobalExemptions.modules.css +0 -6
  10. package/src/components/Accordion/Accordion.module.css +0 -8
  11. package/src/components/Accordion/Accordion.tsx +32 -33
  12. package/src/components/AssistiveElement/AssistiveElement.tsx +1 -1
  13. package/src/components/AutoComplete/AutoComplete.module.css +0 -7
  14. package/src/components/AutoComplete/AutoComplete.tsx +26 -38
  15. package/src/components/Avatar/Avatar.module.css +0 -8
  16. package/src/components/Avatar/Avatar.tsx +22 -23
  17. package/src/components/Badge/Badge.tsx +12 -18
  18. package/src/components/Breadcrumb/Breadcrumb.tsx +10 -18
  19. package/src/components/Button/Button.tsx +31 -30
  20. package/src/components/Card/Card.tsx +48 -27
  21. package/src/components/Checkbox/Checkbox.tsx +29 -42
  22. package/src/components/Checkbox/CheckboxGroup.tsx +12 -5
  23. package/src/components/Chip/Chip.module.css +0 -16
  24. package/src/components/Chip/Chip.tsx +34 -27
  25. package/src/components/DatePicker/DATEPICKER_IMPLEMENTATION_NOTES.md +14 -1
  26. package/src/components/DatePicker/DatePicker.icons.tsx +27 -0
  27. package/src/components/DatePicker/DatePicker.module.css +256 -7
  28. package/src/components/DatePicker/DatePicker.stories.tsx +0 -7
  29. package/src/components/DatePicker/DatePicker.tsx +68 -33
  30. package/src/components/DatePicker/USAGE.md +6 -2
  31. package/src/components/Dropdown/BareDropdown.tsx +25 -13
  32. package/src/components/Dropdown/Dropdown.module.css +0 -7
  33. package/src/components/Dropdown/Dropdown.tsx +25 -38
  34. package/src/components/FileInput/FileInput.module.css +0 -21
  35. package/src/components/FileInput/FileInput.tsx +1 -1
  36. package/src/components/FileUpload/FileUpload.module.css +0 -11
  37. package/src/components/FileUpload/FileUpload.tsx +1 -1
  38. package/src/components/HoverCard/HoverCard.module.css +1 -7
  39. package/src/components/HoverCard/HoverCard.tsx +18 -34
  40. package/src/components/Label/Label.module.css +0 -6
  41. package/src/components/Label/Label.tsx +20 -21
  42. package/src/components/Link/Link.module.css +1 -6
  43. package/src/components/Link/Link.tsx +6 -14
  44. package/src/components/Loader/Loader.tsx +8 -16
  45. package/src/components/Menu/Menu.module.css +0 -6
  46. package/src/components/Menu/Menu.tsx +55 -48
  47. package/src/components/Modal/Modal.module.css +0 -11
  48. package/src/components/Modal/Modal.tsx +134 -96
  49. package/src/components/NumberInput/NumberInput.module.css +0 -7
  50. package/src/components/NumberInput/NumberInput.tsx +14 -7
  51. package/src/components/Pagination/Pagination.module.css +0 -81
  52. package/src/components/Pagination/Pagination.tsx +25 -34
  53. package/src/components/Panel/Panel.tsx +19 -26
  54. package/src/components/Popover/Popover.tsx +19 -31
  55. package/src/components/Radio/Radio.tsx +32 -43
  56. package/src/components/Radio/RadioGroup.tsx +11 -4
  57. package/src/components/ReadOnlyField/ReadOnlyField.tsx +1 -1
  58. package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +1 -1
  59. package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +1 -1
  60. package/src/components/SegmentedControl/SegmentedControl.module.css +0 -1
  61. package/src/components/SegmentedControl/SegmentedControl.tsx +29 -33
  62. package/src/components/Slider/Slider.tsx +30 -43
  63. package/src/components/Stepper/Stepper.tsx +20 -13
  64. package/src/components/Switch/Switch.tsx +45 -45
  65. package/src/components/Switch/SwitchGroup.tsx +11 -4
  66. package/src/components/Table/Table.tsx +1 -1
  67. package/src/components/Tabs/Tabs.tsx +13 -6
  68. package/src/components/Text/Text.tsx +1 -1
  69. package/src/components/TextArea/TextArea.module.css +0 -7
  70. package/src/components/TextArea/TextArea.tsx +21 -25
  71. package/src/components/TextField/TextField.module.css +0 -7
  72. package/src/components/TextField/TextField.tsx +23 -30
  73. package/src/components/TimePicker/TimePicker.module.css +0 -16
  74. package/src/components/TimePicker/TimePicker.tsx +26 -12
  75. package/src/components/Timeline/Timeline.module.css +0 -6
  76. package/src/components/Timeline/Timeline.tsx +7 -21
  77. package/src/components/Timeline/TimelineItem.tsx +14 -26
  78. package/src/components/Title/Title.tsx +12 -2
  79. package/src/components/Toast/Toast.module.css +0 -7
  80. package/src/components/Toast/Toast.tsx +32 -31
  81. package/src/components/Tooltip/Tooltip.module.css +0 -9
  82. package/src/components/Tooltip/Tooltip.tsx +18 -34
  83. package/src/components/TransferList/TransferList.module.css +0 -8
  84. package/src/components/TransferList/TransferList.tsx +1 -1
  85. package/src/components/Tree/Tree.tsx +8 -8
  86. package/src/utils/filterStylingProps.ts +4 -0
@@ -7,6 +7,8 @@ import {
7
7
  } from "@mantine/core";
8
8
  import {
9
9
  filterStylingProps,
10
+ omitUnsupportedProps,
11
+ mergeClassNames,
10
12
  type RecursicaOverStyled,
11
13
  } from "../../utils/filterStylingProps";
12
14
  import styles from "./Card.module.css";
@@ -32,11 +34,23 @@ export type CardProps = RecursicaOverStyled<
32
34
  /**
33
35
  * The root Card elevation box. It establishes the global background color, border radius, nested component gap, and outer shadow governed by the current `Layer` context.
34
36
  */
37
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
38
+ // through even if a caller forces them via plain JavaScript. `Card.module.css` unconditionally
39
+ // drives these from Recursica UI Kit tokens on `.root`; the native prop is never consulted.
40
+ const UNSUPPORTED_PROPS = [
41
+ "radius", // Border radius is token-driven (`--recursica_..._borders_border-radius`), not caller-set.
42
+ "withBorder", // Border is always applied via tokens on `.root`; not an optional caller toggle.
43
+ "padding", // Padding is token-driven (`--card-padding`), not Mantine's spacing-scale prop.
44
+ ] as const satisfies readonly (keyof MantineCardProps)[];
45
+
35
46
  const CardBase = forwardRef<HTMLDivElement, CardProps>(function Card(
36
47
  { overStyled = false, ...rest },
37
48
  ref,
38
49
  ) {
39
- const sanitizedProps = filterStylingProps(rest, overStyled);
50
+ const sanitizedProps = omitUnsupportedProps(
51
+ filterStylingProps(rest, overStyled),
52
+ UNSUPPORTED_PROPS,
53
+ );
40
54
 
41
55
  // Expose outer layout/flexbox properties on Card to allow structuring alongside siblings
42
56
  const safeCardLayoutKeys = [
@@ -56,25 +70,14 @@ const CardBase = forwardRef<HTMLDivElement, CardProps>(function Card(
56
70
  }
57
71
  });
58
72
 
59
- const mergedClassNames: Partial<Record<string, string>> = {
60
- root: styles.root,
61
- };
62
-
63
- const classNamesProp = (sanitizedProps as Record<string, unknown>).classNames;
64
- if (
65
- classNamesProp &&
66
- typeof classNamesProp === "object" &&
67
- !Array.isArray(classNamesProp)
68
- ) {
69
- const o = classNamesProp as Record<string, string>;
70
- Object.keys(o).forEach((key) => {
71
- if (mergedClassNames[key]) {
72
- mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
73
- } else {
74
- mergedClassNames[key] = o[key];
75
- }
76
- });
77
- }
73
+ const mergedClassNames = mergeClassNames(
74
+ {
75
+ root: styles.root,
76
+ },
77
+ (sanitizedProps as Record<string, unknown>).classNames as
78
+ | Partial<Record<string, string>>
79
+ | undefined,
80
+ );
78
81
 
79
82
  const classNameProp = (sanitizedProps as Record<string, unknown>)
80
83
  .className as string | undefined;
@@ -82,9 +85,9 @@ const CardBase = forwardRef<HTMLDivElement, CardProps>(function Card(
82
85
  return (
83
86
  <MantineCard
84
87
  ref={ref}
88
+ {...(sanitizedProps as unknown as MantineCardProps)}
85
89
  className={classNameProp}
86
90
  classNames={mergedClassNames}
87
- {...(sanitizedProps as unknown as MantineCardProps)}
88
91
  />
89
92
  );
90
93
  });
@@ -106,10 +109,10 @@ export const CardSection = forwardRef<HTMLDivElement, CardSectionProps>(
106
109
  return (
107
110
  <MantineCard.Section
108
111
  ref={ref}
112
+ {...(sanitizedProps as unknown as MantineCardSectionProps)}
109
113
  className={
110
114
  classNameProp ? `${styles.section} ${classNameProp}` : styles.section
111
115
  }
112
- {...(sanitizedProps as unknown as MantineCardSectionProps)}
113
116
  />
114
117
  );
115
118
  },
@@ -122,19 +125,32 @@ export type CardHeaderProps = RecursicaOverStyled<RecursicaCardSectionProps>;
122
125
  /**
123
126
  * Replaces standard generic Mantine `<Card.Section>` wrappers by directly injecting the strict Recursica design tokens for header sizing, background drops, and intrinsic padding limits.
124
127
  */
128
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
129
+ // through even if a caller forces them via plain JavaScript. `CardHeaderProps` already omits all
130
+ // native Mantine section props from the public type, but that alone doesn't stop a forced-typed
131
+ // caller from smuggling one through `...rest`; `.header` in Card.module.css unconditionally
132
+ // drives background/padding/border from Recursica tokens, so these native toggles are meaningless.
133
+ const HEADER_FOOTER_UNSUPPORTED_PROPS = [
134
+ "withBorder", // The divider border is always applied via tokens on `.header`/`.footer`.
135
+ "inheritPadding", // Padding is fixed by the `.header`/`.footer` token-driven padding, not Mantine's default.
136
+ ] as const satisfies readonly (keyof MantineCardSectionProps)[];
137
+
125
138
  export const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(
126
139
  function CardHeader({ overStyled = false, ...rest }, ref) {
127
- const sanitizedProps = filterStylingProps(rest, overStyled);
140
+ const sanitizedProps = omitUnsupportedProps(
141
+ filterStylingProps(rest, overStyled),
142
+ HEADER_FOOTER_UNSUPPORTED_PROPS,
143
+ );
128
144
  const classNameProp = (sanitizedProps as Record<string, unknown>)
129
145
  .className as string | undefined;
130
146
 
131
147
  return (
132
148
  <MantineCard.Section
133
149
  ref={ref}
150
+ {...(sanitizedProps as unknown as MantineCardSectionProps)}
134
151
  className={
135
152
  classNameProp ? `${styles.header} ${classNameProp}` : styles.header
136
153
  }
137
- {...(sanitizedProps as unknown as MantineCardSectionProps)}
138
154
  />
139
155
  );
140
156
  },
@@ -149,17 +165,22 @@ export type CardFooterProps = RecursicaOverStyled<RecursicaCardSectionProps>;
149
165
  */
150
166
  export const CardFooter = forwardRef<HTMLDivElement, CardFooterProps>(
151
167
  function CardFooter({ overStyled = false, ...rest }, ref) {
152
- const sanitizedProps = filterStylingProps(rest, overStyled);
168
+ // Reuses HEADER_FOOTER_UNSUPPORTED_PROPS (declared above with CardHeader) — same rationale
169
+ // applies: `.footer` in Card.module.css unconditionally drives padding/border from tokens.
170
+ const sanitizedProps = omitUnsupportedProps(
171
+ filterStylingProps(rest, overStyled),
172
+ HEADER_FOOTER_UNSUPPORTED_PROPS,
173
+ );
153
174
  const classNameProp = (sanitizedProps as Record<string, unknown>)
154
175
  .className as string | undefined;
155
176
 
156
177
  return (
157
178
  <MantineCard.Section
158
179
  ref={ref}
180
+ {...(sanitizedProps as unknown as MantineCardSectionProps)}
159
181
  className={
160
182
  classNameProp ? `${styles.footer} ${classNameProp}` : styles.footer
161
183
  }
162
- {...(sanitizedProps as unknown as MantineCardSectionProps)}
163
184
  />
164
185
  );
165
186
  },
@@ -183,10 +204,10 @@ export const CardContent = forwardRef<HTMLDivElement, CardContentProps>(
183
204
  return (
184
205
  <div
185
206
  ref={ref}
207
+ {...sanitizedProps}
186
208
  className={
187
209
  classNameProp ? `${styles.content} ${classNameProp}` : styles.content
188
210
  }
189
- {...sanitizedProps}
190
211
  />
191
212
  );
192
213
  },
@@ -7,6 +7,8 @@ import { type ReadOnlyControlProps } from "@recursica/adapter-common";
7
7
  import { CheckboxGroup } from "./CheckboxGroup";
8
8
  import {
9
9
  filterStylingProps,
10
+ omitUnsupportedProps,
11
+ mergeClassNames,
10
12
  type RecursicaOverStyled,
11
13
  } from "../../utils/filterStylingProps";
12
14
  import {
@@ -56,49 +58,34 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
56
58
  ...rest
57
59
  } = props;
58
60
  // Checkbox doesn't use Label onLabelEditClick natively since it isn't mapped inside FormControlWrapper intrinsically.
59
- const sanitizedProps = filterStylingProps(rest, overStyled);
61
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
62
+ // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
63
+ const UNSUPPORTED_PROPS = [
64
+ "size", // Recursica controls sizing via Checkbox.module.css variables, not Mantine's native size scale.
65
+ "color", // Colors are token-driven via Checkbox.module.css; Mantine's native palette isn't exposed.
66
+ "radius", // Corner radius is fixed by Recursica tokens in Checkbox.module.css, not caller-configurable.
67
+ "variant", // Checkbox has a single Recursica-defined visual treatment; Mantine's variant isn't exposed.
68
+ "iconColor", // Icon color is token-driven via Checkbox.module.css, not a native Mantine override.
69
+ ] as const satisfies readonly (keyof MantineCheckboxProps)[];
70
+
71
+ const sanitizedProps = omitUnsupportedProps(
72
+ filterStylingProps(rest, overStyled) as Record<string, unknown>,
73
+ UNSUPPORTED_PROPS,
74
+ ) as Partial<typeof rest>;
60
75
  const restRecord = sanitizedProps as Record<string, unknown>;
61
76
 
62
- // Actively delete dimension bindings that bypass the abstraction
63
- delete restRecord["size"];
64
- delete restRecord["color"];
65
- delete restRecord["radius"];
66
- delete restRecord["variant"];
67
- delete restRecord["iconColor"];
68
-
69
- const mergedClassNames: Partial<Record<string, string>> = {
70
- root: styles.root,
71
- body: styles.body,
72
- inner: styles.inner,
73
- input: styles.input,
74
- icon: styles.icon,
75
- labelWrapper: styles.labelWrapper,
76
- label: styles.label,
77
- };
78
-
79
- const classNamesProp = restRecord.classNames;
80
- if (
81
- classNamesProp &&
82
- typeof classNamesProp === "object" &&
83
- !Array.isArray(classNamesProp)
84
- ) {
85
- const o = classNamesProp as Partial<Record<string, string>>;
86
- mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
87
- mergedClassNames.body = o.body ? `${styles.body} ${o.body}` : styles.body;
88
- mergedClassNames.inner = o.inner
89
- ? `${styles.inner} ${o.inner}`
90
- : styles.inner;
91
- mergedClassNames.input = o.input
92
- ? `${styles.input} ${o.input}`
93
- : styles.input;
94
- mergedClassNames.icon = o.icon ? `${styles.icon} ${o.icon}` : styles.icon;
95
- mergedClassNames.labelWrapper = o.labelWrapper
96
- ? `${styles.labelWrapper} ${o.labelWrapper}`
97
- : styles.labelWrapper;
98
- mergedClassNames.label = o.label
99
- ? `${styles.label} ${o.label}`
100
- : styles.label;
101
- }
77
+ const mergedClassNames = mergeClassNames(
78
+ {
79
+ root: styles.root,
80
+ body: styles.body,
81
+ inner: styles.inner,
82
+ input: styles.input,
83
+ icon: styles.icon,
84
+ labelWrapper: styles.labelWrapper,
85
+ label: styles.label,
86
+ },
87
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
88
+ );
102
89
 
103
90
  const classNameProp = restRecord.className as string | undefined;
104
91
  const finalClass = classNameProp
@@ -136,10 +123,10 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
136
123
  const checkboxNode = (
137
124
  <MantineCheckbox
138
125
  ref={ref}
126
+ {...(sanitizedProps as unknown as MantineCheckboxProps)}
139
127
  className={finalClass}
140
128
  classNames={mergedClassNames}
141
129
  disabled={readOnly || disabled}
142
- {...(sanitizedProps as unknown as MantineCheckboxProps)}
143
130
  />
144
131
  );
145
132
 
@@ -7,6 +7,7 @@ import {
7
7
  import { type ReadOnlyControlProps } from "@recursica/adapter-common";
8
8
  import {
9
9
  filterStylingProps,
10
+ omitUnsupportedProps,
10
11
  type RecursicaOverStyled,
11
12
  } from "../../utils/filterStylingProps";
12
13
  import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
@@ -62,11 +63,17 @@ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
62
63
  defaultValue,
63
64
  ...rest
64
65
  } = props;
65
- const sanitizedProps = filterStylingProps(rest, overStyled);
66
- const restRecord = sanitizedProps as Record<string, unknown>;
66
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
67
+ // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
68
+ const UNSUPPORTED_PROPS = [
69
+ "size", // Recursica controls sizing via Checkbox.module.css variables, not Mantine's native size scale.
70
+ ] as const satisfies readonly (keyof MantineCheckboxGroupProps)[];
67
71
 
68
- // Delete prohibited sizing hooks from bypassing the variables
69
- delete restRecord["size"];
72
+ const sanitizedProps = omitUnsupportedProps(
73
+ filterStylingProps(rest, overStyled) as Record<string, unknown>,
74
+ UNSUPPORTED_PROPS,
75
+ ) as Partial<typeof rest>;
76
+ const restRecord = sanitizedProps as Record<string, unknown>;
70
77
 
71
78
  // Mantine's own `Checkbox.Group` always establishes a context whose `value` defaults to
72
79
  // `[]` (see @mantine/core's `useUncontrolled({ finalValue: [] })`), and its `Checkbox` spreads
@@ -123,8 +130,8 @@ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
123
130
  ) : (
124
131
  <div
125
132
  ref={ref}
126
- role="group"
127
133
  {...(sanitizedProps as Record<string, unknown>)}
134
+ role="group"
128
135
  className={styles.groupRoot}
129
136
  data-layout={formLayout}
130
137
  >
@@ -1,19 +1,3 @@
1
- /* EXEMPTIONS:
2
- - close-icon-color, leading-icon-color, and select-specific state icon variables are ignored because
3
- Chip uses explicit state color definitions (e.g. unselected, selected, error, error-selected variant colors)
4
- to drive pixel-perfect hover and click transitions, rather than generic single-color overrides.
5
- - text-size is ignored since actual chip sizing is dynamically driven by precise vertical padding and line-heights. */
6
- /* recursica-ignore: --recursica_ui-kit_components_chip_properties_close-icon-color */
7
- /* recursica-ignore: --recursica_ui-kit_components_chip_properties_leading-icon-color */
8
- /* recursica-ignore: --recursica_ui-kit_components_chip_properties_text-size */
9
- /* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_unselected_variants_states_error_properties_colors_leading-icon-color */
10
- /* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_unselected_variants_states_error_properties_colors_selected-icon-color */
11
- /* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_unselected_variants_states_error_properties_colors_icon-color */
12
- /* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_selected_variants_states_error_properties_colors_icon-color */
13
- /* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_selected_variants_states_error_properties_colors_leading-icon-color */
14
- /* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_selected_properties_colors_leading-icon-color */
15
- /* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_unselected_properties_colors_selected-icon-color */
16
-
17
1
  /* HARDCODED VALUES
18
2
  border-style: solid; (Baseline reset)
19
3
  box-sizing: border-box; (Baseline reset)
@@ -5,6 +5,8 @@ import {
5
5
  } from "@mantine/core";
6
6
  import {
7
7
  filterStylingProps,
8
+ mergeClassNames,
9
+ withCallerOverride,
8
10
  type RecursicaOverStyled,
9
11
  } from "../../utils/filterStylingProps";
10
12
  import styles from "./Chip.module.css";
@@ -45,6 +47,9 @@ export const Chip = forwardRef<HTMLInputElement, ChipProps>(function Chip(
45
47
  removeIconRef,
46
48
  children,
47
49
  overStyled = false,
50
+ wrapperProps,
51
+ tabIndex,
52
+ "aria-hidden": ariaHidden,
48
53
  ...rest
49
54
  },
50
55
  ref,
@@ -52,26 +57,16 @@ export const Chip = forwardRef<HTMLInputElement, ChipProps>(function Chip(
52
57
  const sanitizedProps = filterStylingProps(rest, overStyled);
53
58
  const restRecord = sanitizedProps as Record<string, unknown>;
54
59
 
55
- const mergedClassNames: Partial<Record<string, string>> = {
56
- root: styles.root,
57
- label: styles.label,
58
- input: styles.input,
59
- iconWrapper: styles.mantineIconWrapper,
60
- checkIcon: styles.checkIcon,
61
- };
62
-
63
- const classNamesProp = restRecord.classNames;
64
- if (
65
- classNamesProp &&
66
- typeof classNamesProp === "object" &&
67
- !Array.isArray(classNamesProp)
68
- ) {
69
- const o = classNamesProp as Partial<Record<string, string>>;
70
- mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
71
- mergedClassNames.label = o.label
72
- ? `${styles.label} ${o.label}`
73
- : styles.label;
74
- }
60
+ const mergedClassNames = mergeClassNames(
61
+ {
62
+ root: styles.root,
63
+ label: styles.label,
64
+ input: styles.input,
65
+ iconWrapper: styles.mantineIconWrapper,
66
+ checkIcon: styles.checkIcon,
67
+ },
68
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
69
+ );
75
70
 
76
71
  const classNameProp = restRecord.className as string | undefined;
77
72
  const finalClass = classNameProp
@@ -90,18 +85,30 @@ export const Chip = forwardRef<HTMLInputElement, ChipProps>(function Chip(
90
85
  restRecord.onClick !== undefined ||
91
86
  restRecord.onChange !== undefined;
92
87
 
88
+ // Our own computed default for Mantine's `wrapperProps` — flags the internal error/interactive
89
+ // CSS hooks. This is a real Mantine `ChipProps` slot a caller could legitimately pass their own
90
+ // value for, so it must merge via `withCallerOverride`, not silently clobber theirs.
91
+ const computedWrapperProps = {
92
+ ...(dataError !== undefined ? { "data-error": "" } : {}),
93
+ ...(isInteractive ? { "data-interactive": "" } : {}),
94
+ } as NonNullable<MantineChipProps["wrapperProps"]>;
95
+
93
96
  return (
94
97
  <MantineChip
95
98
  ref={ref}
99
+ {...sanitizedProps}
96
100
  className={finalClass}
97
101
  classNames={mergedClassNames}
98
- wrapperProps={{
99
- ...(dataError !== undefined ? { "data-error": "" } : {}),
100
- ...(isInteractive ? { "data-interactive": "" } : {}),
101
- }}
102
- {...(isIconOnly ? { "data-icon-only": "" } : {})}
103
- {...(!isInteractive ? { tabIndex: -1, "aria-hidden": true } : {})}
104
- {...sanitizedProps}
102
+ wrapperProps={withCallerOverride(computedWrapperProps, wrapperProps)}
103
+ data-icon-only={isIconOnly ? "" : undefined}
104
+ tabIndex={withCallerOverride<number | undefined>(
105
+ isInteractive ? undefined : -1,
106
+ tabIndex,
107
+ )}
108
+ aria-hidden={withCallerOverride<boolean | "true" | "false" | undefined>(
109
+ isInteractive ? undefined : true,
110
+ ariaHidden,
111
+ )}
105
112
  >
106
113
  <span className={styles.innerWrapper}>
107
114
  {icon && (
@@ -8,9 +8,22 @@ The `DatePicker` component is a wrapper around the `@mantine/dates` `DatePickerI
8
8
 
9
9
  1. **Naked Input Usage**: We intentionally pass `label={undefined}`, `description={undefined}`, and `error={undefined}` to the Mantine `DatePickerInput` component. This suppresses Mantine's internal macro form wrapping and ensures that only our `WithReadOnlyWrapper` > `FormControlWrapper` orchestrates labels, description text, and ARIA state error boundaries.
10
10
  2. **Read-Only Implementation**: Since the value type for `DatePickerInput` can be a date object, string, or array, the `WithReadOnlyWrapper` attempts to safely cast the output value using standard `String(value)`. For production apps utilizing heavy date formatting logic, developers can pass a custom `readOnlyComponent` explicitly to bypass this default cast.
11
- 3. **Calendar Portal/Dropdown (Figma Token Issue)**: The Recursica UI Kit's `date-picker` component in Figma fails to export any explicit structural or color properties for the calendar popover itself (e.g. elevation, surface background, selected day colors). To solve this organically within the framework without breaking strict token adherence, we manually override the Mantine `.dropdown` and `.day[data-selected]` classes using `--recursica_ui-kit_components_hover-card-popover` tokens for elevation/padding/surfaces, and `--recursica_ui-kit_components_button_variants_styles_solid` tokens for the selected primary blue day. This guarantees strict visual adherence to the system until explicitly mapped tokens are provided in the UI Kit.
11
+ 3. **Calendar Portal/Dropdown (Figma Token Issue)**: The Recursica UI Kit's `date-picker` component in Figma fails to export any explicit structural or color properties for the calendar popover itself (e.g. elevation, surface background, selected day colors, hover, today, in-range). To solve this organically within the framework without breaking strict token adherence, we manually map every calendar state to the closest existing Recursica token instead of leaving any of it on Mantine's own un-tokenized defaults:
12
+ - **Surface** (`.dropdown`) & **selected day** (`.day[data-selected]`): `--recursica_ui-kit_components_hover-card-popover` tokens for elevation/padding/surface, `--recursica_ui-kit_components_button_variants_styles_solid` tokens for the selected day fill (unchanged from before).
13
+ - **Header content, weekday row** (`.calendarHeader`, `.weekday`): the hover-card-popover content-color token — previously unmapped, so these leaked Mantine's own text colors.
14
+ - **Header nav controls & month/year label** (`.calendarHeaderControl`, `.calendarHeaderLevel`, `.calendarHeaderControlIcon`): these are functionally buttons, so they're now styled with `--recursica_ui-kit_components_button_variants_styles_text` tokens (border/background/color/elevation) plus small-size `Button` sizing tokens (height, icon-only vs. label-content border-radius/padding/min-width, icon size, typography) — the same tokens `Button`'s `data-variant="text"` uses. Disabled opacity/elevation and the focus-visible ring also mirror `Button`'s own treatment instead of the generic brand tokens used elsewhere in this file.
15
+ - **Hover** (any day cell, header nav, header level button): the generic `--recursica_brand_states_hover_color`/`_hover_opacity` pair, via the same `::after`-overlay technique used by `Button`/`Menu` — the standard convention for components with no dedicated hover token. Unlike `Button`, the day number is a bare text node with no wrapping span to `z-index` above the overlay, so here the overlay sits at `z-index: -1` instead of the text getting `z-index: 1` — same visual effect, opposite side of the layering. Also unlike `Button` (a plain button with no library-native hover CSS), Mantine's `Day`/`CalendarHeaderControl`/`CalendarHeaderLevel` all ship their own native `:hover { background-color: ... }` rule — an explicit `background-color: transparent` reset on `:hover` was needed first, or the overlay would just layer on top of Mantine's own gray-0/dark-5 fill instead of replacing it. (Caught in review: the first pass added the overlay without this reset, and the visible hover color was indistinguishable from Mantine's plain default — confirmed via computed-style diffing, not just eyeballing the screenshot.)
16
+ - **Disabled / outside-month days**: the generic `--recursica_brand_states_disabled` opacity token — the same one used repo-wide (Button, Chip, Tree, Pagination, Accordion) for elements with no dedicated disabled token. **Disabled header nav/label controls** use `Button`'s own text-variant disabled opacity/elevation tokens instead, since they're now styled as `Button`.
17
+ - **Today marker**: no dedicated token exists, so it reuses the hover-card-popover border-size/color tokens already used for the dropdown surface's own border (previously hardcoded `1px`). `highlightToday` is now defaulted to `true` in `DatePicker.tsx` (Mantine ships it `false`, meaning today was never marked at all before this pass).
18
+ - **Weekend days**: Mantine's default colors these red; the UI Kit has no such concept, so weekend text is explicitly forced back to the same color as any other day rather than left on that unrelated default.
19
+ - **Range selection** (`.day[data-in-range]`/`[data-first-in-range]`/`[data-last-in-range]`): styled with the generic `--recursica_brand_states_overlay_color`/`_overlay_opacity` pair — previously unused anywhere in the codebase, but its name and shape (a translucent tint token) is exactly this use case. **Not currently reachable through our public API**: `RecursicaDatePickerProps` doesn't discriminate on `type`, so `value`/`defaultValue` are typed for single-date selection only — `type="range"` fails typecheck for consumers. This CSS is forward-compatible groundwork, not a demoed feature; proper range-mode typing would be a separate, larger follow-up. Also flagged as a judgment call on the token pairing itself: first usage anywhere, no prior precedent or design-team confirmation.
12
20
 
13
21
  ## Styling Quirks
14
22
 
15
23
  - The `DatePickerInput` mimics Mantine's `Input` structure natively (`.input`, `.wrapper`, `.section`). We attach our `styles.input` and `styles.root` classes exactly like `TextField`.
16
24
  - The global layout margin override (`.layoutOverride`) utilizes `--form-control-margin-bottom` driven by specific stacked/side-by-side design tokens.
25
+ - **Popover `classNames` gotcha (bug fix)**: `.dropdown` was previously passed via the top-level `classNames` prop alongside `wrapper`/`input`/`section`/`day`/etc., which silently did nothing — the calendar surface is rendered by Mantine's internal `Popover`, whose style API is a separate namespace only reachable via `popoverProps.classNames` (`PickerInputBaseProps.popoverProps: Partial<Omit<PopoverProps, 'children'>>`). The `.dropdown` CSS (background/border/border-radius/box-shadow/padding/font-family, matching `Popover`'s own `.dropdown` rules 1:1) was never actually applied until this was fixed — confirmed via computed styles before/after (`box-shadow: none` → the real elevation token). `DatePicker.tsx` now merges any consumer-supplied `popoverProps`/`popoverProps.classNames` with our default instead of overwriting them.
26
+ - **Default value format**: `valueFormat="MM/DD/YY"` (a `dayjs` format string, Mantine's default is `"MMMM D, YYYY"`), set before the `sanitizedProps` spread so consumers can still override via their own `valueFormat` prop.
27
+ - **Default leading icon**: `leftSection={<CalendarIcon />}` (new `DatePicker.icons.tsx`, following the existing local-inline-SVG convention used by `Pagination.icons.tsx` — no icon library is a dependency of either adapter) is set the same way, overridable via `leftSection`.
28
+ - **Default placeholder**: `placeholder="MM / DD / YY"`, set the same way (before `sanitizedProps`, so consumers can override).
29
+ - **Single-line enforcement (bug fix)**: `DatePickerInput` renders its value/placeholder as a `<button>`'s text content, not a native `<input>` — it wraps like any other block text by default. A long placeholder (e.g. the original story's `"Select a deadline..."`) wrapped to two lines, inflating the field's height well past its `min-height` token and making the field visually look narrower than its actual `width` token value even though the box itself was always the right size. Fixed with `white-space: nowrap`/`text-overflow: ellipsis`/`overflow-x: clip`/`overflow-y: visible` on `.input`, the same convention `FileInput.module.css`'s `.value` uses.
@@ -0,0 +1,27 @@
1
+ import React from "react";
2
+
3
+ /**
4
+ * Default leading icon for DatePicker's left section. Consumers can override it by passing
5
+ * their own `leftSection` prop (see DatePicker.tsx). Sized/colored entirely via the generic
6
+ * `.section :global(svg)` rule in DatePicker.module.css, same as the icon in the
7
+ * WithLeadingIcon story — no width/height/color hardcoded here on purpose.
8
+ */
9
+ export function CalendarIcon(props: React.SVGProps<SVGSVGElement>) {
10
+ return (
11
+ <svg
12
+ viewBox="0 0 24 24"
13
+ fill="none"
14
+ stroke="currentColor"
15
+ strokeWidth="2"
16
+ strokeLinecap="round"
17
+ strokeLinejoin="round"
18
+ aria-hidden
19
+ {...props}
20
+ >
21
+ <rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
22
+ <line x1="16" y1="2" x2="16" y2="6" />
23
+ <line x1="8" y1="2" x2="8" y2="6" />
24
+ <line x1="3" y1="10" x2="21" y2="10" />
25
+ </svg>
26
+ );
27
+ }