@recursica/mantine-adapter 0.43.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "url": "git+https://github.com/borderux/recursica.git",
14
14
  "directory": "packages/mantine-adapter"
15
15
  },
16
- "version": "0.43.0",
16
+ "version": "0.44.0",
17
17
  "type": "module",
18
18
  "main": "./dist/mantine-adapter.cjs",
19
19
  "module": "./dist/mantine-adapter.js",
@@ -23,4 +23,6 @@
23
23
  display: flex;
24
24
  align-items: center;
25
25
  justify-content: center;
26
+ /* Neutralize Mantine's built-in margin-inline (--mantine-spacing-xs default) so .root's gap alone controls spacing */
27
+ margin-inline: 0;
26
28
  }
@@ -56,12 +56,6 @@ export const Default: Story = {
56
56
  },
57
57
  };
58
58
 
59
- export const StaticExample: Story = {
60
- args: {
61
- items: ["Store", "Electronics", "Computers", "Laptops"],
62
- },
63
- };
64
-
65
59
  export const CustomSeparator: Story = {
66
60
  args: {
67
61
  items: ["Root", "Branch", "Leaf"],
@@ -35,6 +35,21 @@ export const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>(
35
35
  | undefined,
36
36
  );
37
37
 
38
+ // Mantine's Breadcrumbs, unlike MUI's, doesn't hide the separator from screen
39
+ // readers by default — it's a plain text node. Match MUI's built-in aria-hidden,
40
+ // letting a caller override it via attributes.separator["aria-hidden"] if needed.
41
+ const callerAttributes = (sanitizedProps as Record<string, unknown>)
42
+ .attributes as
43
+ | Partial<Record<string, Record<string, unknown>>>
44
+ | undefined;
45
+ const mergedAttributes = {
46
+ ...callerAttributes,
47
+ separator: {
48
+ "aria-hidden": true,
49
+ ...callerAttributes?.separator,
50
+ },
51
+ };
52
+
38
53
  const classNameProp = (sanitizedProps as Record<string, unknown>)
39
54
  .className as string | undefined;
40
55
  const finalClass = classNameProp
@@ -47,6 +62,7 @@ export const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>(
47
62
  {...(sanitizedProps as unknown as MantineBreadcrumbsProps)}
48
63
  className={finalClass}
49
64
  classNames={mergedClassNames}
65
+ attributes={mergedAttributes}
50
66
  />
51
67
  );
52
68
  },
@@ -5,3 +5,5 @@ The `Dropdown` component is mapped explicitly to Mantine's `<Select>` following
5
5
  1. **Naked Primitive Mapping:** Mantine's `Select` natively executes macro-label generation. To decouple it, we explicitly disable internal labels (`label={undefined}`) and inject it purely inside our generic `FormControlWrapper`.
6
6
  2. **Strict Dropdown Design Tokens:** The adapter implements strictly sandboxed styling utilizing only `--recursica_ui-kit_components_dropdown_...` variables. It explicitly does NOT inherit general `text-field` tokens despite geometric similarities, ensuring dropdown menus can be themed independently.
7
7
  3. **Dropdown Appendages:** To correctly map Mantine's detached Popover `.dropdown` and list `.option` items, we targeted focus and geometric bindings appending standard padding structures matched to the dropdown height overrides dynamically into our `Dropdown.module.css`.
8
+ 4. **Popup Vertical Padding:** The gap above/below the option list inside `.dropdown` is explicitly set from the same `--recursica_..._dropdown_properties_vertical-padding` token the closed control uses — it was previously left to Mantine's own untokenized `--combobox-padding` (4px) default.
9
+ 5. **Clear Button Styling:** Mantine's native clear button (`clearButtonProps`, shown when `clearable` + a value are both present) is a bare `CloseButton` with its own hardcoded gray icon/hover styling by default. `Dropdown.tsx` merges in a `.clearButton` class (Dropdown.module.css) that overrides it with the same icon-size/trailing-icon-color/focus-ring tokens as the rest of the right section, using the double-class-selector specificity trick (same as Chip.module.css's `.root.root`) to beat Mantine's own CSS module rule without `!important`.
@@ -169,6 +169,39 @@
169
169
  );
170
170
  }
171
171
 
172
+ /* Mantine's own clear button (rendered via `clearButtonProps`, see Dropdown.tsx) is a bare
173
+ `CloseButton` by default — its own module CSS hardcodes a gray icon color and a circular gray
174
+ hover background, neither of which is Recursica-token-driven. The double class selector (same
175
+ specificity-boosting trick as Chip.module.css's `.root.root`) beats that default without
176
+ needing `!important`. Same icon-size/trailing-icon tokens as the rest of this section, same
177
+ focus-visible ring technique as Chip's own `.removeIcon` — the established inline icon-button
178
+ pattern elsewhere in this adapter. */
179
+ .clearButton.clearButton {
180
+ width: var(--recursica_ui-kit_components_dropdown_properties_icon-size);
181
+ height: var(--recursica_ui-kit_components_dropdown_properties_icon-size);
182
+ min-width: var(--recursica_ui-kit_components_dropdown_properties_icon-size);
183
+ min-height: var(--recursica_ui-kit_components_dropdown_properties_icon-size);
184
+ border-radius: 50%;
185
+ background: transparent;
186
+ color: var(
187
+ --recursica_ui-kit_components_dropdown_properties_colors_trailing-icon
188
+ );
189
+ outline: none;
190
+ }
191
+
192
+ .clearButton.clearButton:hover {
193
+ background: transparent;
194
+ }
195
+
196
+ .clearButton.clearButton:focus-visible {
197
+ box-shadow:
198
+ 0 0 0 var(--recursica_brand_states_focus_border-size)
199
+ var(--recursica_brand_states_focus_color),
200
+ 0 0 var(--recursica_brand_states_focus_blur)
201
+ var(--recursica_brand_states_focus_margin)
202
+ var(--recursica_brand_states_focus_color);
203
+ }
204
+
172
205
  /* -------------------------------------
173
206
  STATE CASCADE ARCHITECTURE
174
207
  -------------------------------------- */
@@ -254,6 +287,17 @@
254
287
  --recursica_ui-kit_components_dropdown_properties_border-radius
255
288
  );
256
289
  overflow: hidden;
290
+
291
+ /* The visible top/bottom gap before/after the option list was previously left to Mantine's own
292
+ untokenized `--combobox-padding` default (4px) — wire it to the same dropdown vertical-padding
293
+ token the closed control itself uses instead (mirrors the mui-adapter fix to the equivalent
294
+ gap there, which came from MUI's own untokenized 8px `.MuiList-padding` default). */
295
+ padding-top: var(
296
+ --recursica_ui-kit_components_dropdown_properties_vertical-padding
297
+ );
298
+ padding-bottom: var(
299
+ --recursica_ui-kit_components_dropdown_properties_vertical-padding
300
+ );
257
301
  }
258
302
 
259
303
  .option {
@@ -277,10 +321,8 @@
277
321
  cursor: pointer;
278
322
  }
279
323
 
280
- /* No per-option selected/hovered tokens exist in the schema; use the generic overlay tint
281
- (same technique as Table's row hover) to highlight the active option. */
282
- .option[data-selected="true"],
283
- .option[data-combobox-selected="true"],
324
+ /* No per-option hovered token exists in the schema; use the generic overlay tint (same technique
325
+ as Table's row hover) to highlight a hovered-but-not-selected option. */
284
326
  .option[data-hovered="true"],
285
327
  .option:hover {
286
328
  background-color: color-mix(
@@ -290,3 +332,18 @@
290
332
  transparent
291
333
  );
292
334
  }
335
+
336
+ /* No dedicated dropdown-option "selected" token exists in the schema either, but the menu-item
337
+ component's selected-state colors are the closest real token family for the same concept (a
338
+ selected row in a list) — reused here rather than the neutral hover tint above, which is what
339
+ previously made the selected option read as grey instead of Recursica's brand color. Matches
340
+ the mui-adapter fix for the same gap. */
341
+ .option[data-selected="true"],
342
+ .option[data-combobox-selected="true"] {
343
+ background-color: var(
344
+ --recursica_ui-kit_components_menu-item_variants_selection-states_selected_properties_colors_background-color
345
+ );
346
+ color: var(
347
+ --recursica_ui-kit_components_menu-item_variants_selection-states_selected_properties_colors_text-color
348
+ );
349
+ }
@@ -65,6 +65,27 @@ export const SearchableClearable: Story = {
65
65
  },
66
66
  };
67
67
 
68
+ export const WithLeadingIcon: Story = {
69
+ args: {
70
+ label: "Destination",
71
+ leftSection: (
72
+ <svg
73
+ width="24"
74
+ height="24"
75
+ viewBox="0 0 24 24"
76
+ fill="none"
77
+ stroke="currentColor"
78
+ strokeWidth="2"
79
+ strokeLinecap="round"
80
+ strokeLinejoin="round"
81
+ >
82
+ <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
83
+ <circle cx="12" cy="10" r="3"></circle>
84
+ </svg>
85
+ ),
86
+ },
87
+ };
88
+
68
89
  export const StaticError: Story = {
69
90
  args: {
70
91
  error: "You must choose a valid destination.",
@@ -68,6 +68,7 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
68
68
  value,
69
69
  defaultValue,
70
70
  data,
71
+ clearButtonProps,
71
72
  ...rest
72
73
  } = props;
73
74
  const sanitizedProps = omitUnsupportedProps(
@@ -76,6 +77,21 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
76
77
  );
77
78
  const restRecord = sanitizedProps as Record<string, unknown>;
78
79
 
80
+ // Mantine's own clear button (rendered when `clearable` + a value are both present) otherwise
81
+ // renders unstyled — bare `CloseButton` defaults, no Recursica icon-button treatment. Merge in
82
+ // our own class (see `.clearButton` in Dropdown.module.css) alongside anything the caller
83
+ // already passed, same merge shape as `mergeClassNames` but for a single `className` string
84
+ // rather than a per-slot classNames map.
85
+ const consumerClearButtonProps = clearButtonProps as
86
+ | Record<string, unknown>
87
+ | undefined;
88
+ const mergedClearButtonProps = {
89
+ ...consumerClearButtonProps,
90
+ className: consumerClearButtonProps?.className
91
+ ? `${styles.clearButton} ${consumerClearButtonProps.className as string}`
92
+ : styles.clearButton,
93
+ };
94
+
79
95
  // Securely map core native blocks down ensuring nested CSS modules map precisely
80
96
  const mergedClassNames = mergeClassNames(
81
97
  {
@@ -139,6 +155,7 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
139
155
  value={value}
140
156
  defaultValue={defaultValue}
141
157
  data={(data as unknown as MantineSelectProps["data"]) || []}
158
+ clearButtonProps={mergedClearButtonProps}
142
159
  label={undefined}
143
160
  description={undefined}
144
161
  error={undefined}
@@ -45,3 +45,4 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
45
45
  ## 4. Notes
46
46
 
47
47
  - `Dropdown` is styled independently from `TextField`; even though they look similar, they are themed using separate design tokens.
48
+ - Pass `leftSection` for a leading icon, and `clearable` (with a value present) to show a clear button — both render using the dropdown's own icon-color tokens.
@@ -199,7 +199,9 @@ export const Default: Story = {
199
199
  position: "bottom-start",
200
200
  withArrow: false,
201
201
  offset: 5,
202
- opened: undefined,
202
+ // Rendered open by default so this story is diffable against the MUI adapter
203
+ // without an interaction step.
204
+ opened: true,
203
205
  },
204
206
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
205
207
  render: ({ withLayer, layer, ...args }: MenuStoryArgs) => {
@@ -230,6 +232,9 @@ export const Default: Story = {
230
232
  export const WithDisabledItems: Story = {
231
233
  args: {
232
234
  position: "bottom-start",
235
+ // Rendered open by default so this story is diffable against the MUI adapter
236
+ // without an interaction step.
237
+ opened: true,
233
238
  },
234
239
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
235
240
  render: ({ withLayer, layer, ...args }: MenuStoryArgs) => {
@@ -258,6 +263,9 @@ export const WithSubmenus: Story = {
258
263
  args: {
259
264
  position: "bottom-start",
260
265
  width: 200,
266
+ // Rendered open by default so this story is diffable against the MUI adapter
267
+ // without an interaction step.
268
+ opened: true,
261
269
  },
262
270
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
263
271
  render: ({ withLayer, layer, ...args }: MenuStoryArgs) => {