@recursica/mantine-adapter 0.41.0 → 0.42.1

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 (47) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/index.d.ts +43 -3
  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 +2580 -2218
  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/AutoComplete/AutoComplete.module.css +0 -7
  12. package/src/components/Avatar/Avatar.module.css +0 -8
  13. package/src/components/Checkbox/CheckboxGroup.tsx +32 -10
  14. package/src/components/Chip/Chip.module.css +0 -16
  15. package/src/components/DatePicker/DATEPICKER_IMPLEMENTATION_NOTES.md +14 -1
  16. package/src/components/DatePicker/DatePicker.icons.tsx +27 -0
  17. package/src/components/DatePicker/DatePicker.module.css +256 -7
  18. package/src/components/DatePicker/DatePicker.stories.tsx +0 -7
  19. package/src/components/DatePicker/DatePicker.tsx +37 -1
  20. package/src/components/DatePicker/USAGE.md +6 -2
  21. package/src/components/Dropdown/Dropdown.module.css +0 -7
  22. package/src/components/FileInput/FileInput.module.css +0 -21
  23. package/src/components/FileInput/FileInput.tsx +6 -0
  24. package/src/components/FileUpload/FileUpload.module.css +0 -11
  25. package/src/components/HoverCard/HoverCard.module.css +1 -7
  26. package/src/components/Label/Label.module.css +0 -6
  27. package/src/components/Link/Link.module.css +1 -6
  28. package/src/components/Menu/Menu.module.css +0 -6
  29. package/src/components/Modal/Modal.module.css +0 -11
  30. package/src/components/NumberInput/NumberInput.module.css +0 -7
  31. package/src/components/Pagination/Pagination.module.css +0 -81
  32. package/src/components/SegmentedControl/SegmentedControl.module.css +0 -1
  33. package/src/components/Slider/IMPLEMENTATION_NOTES.md +1 -0
  34. package/src/components/Slider/Slider.module.css +34 -0
  35. package/src/components/Slider/Slider.stories.tsx +1 -1
  36. package/src/components/TextArea/TextArea.module.css +0 -7
  37. package/src/components/TextArea/TextArea.tsx +2 -6
  38. package/src/components/TextField/TextField.module.css +0 -7
  39. package/src/components/TimePicker/TimePicker.module.css +0 -16
  40. package/src/components/Timeline/Timeline.module.css +0 -6
  41. package/src/components/Toast/Toast.module.css +0 -7
  42. package/src/components/Tooltip/Tooltip.module.css +0 -9
  43. package/src/components/TransferList/TRANSFERLIST_IMPLEMENTATION_NOTES.md +111 -0
  44. package/src/components/TransferList/TransferList.module.css +174 -41
  45. package/src/components/TransferList/TransferList.stories.tsx +110 -6
  46. package/src/components/TransferList/TransferList.tsx +417 -7
  47. package/src/components/TransferList/USAGE.md +37 -6
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.41.0",
16
+ "version": "0.42.1",
17
17
  "type": "module",
18
18
  "main": "./dist/mantine-adapter.cjs",
19
19
  "module": "./dist/mantine-adapter.js",
@@ -5,9 +5,3 @@
5
5
  * recursica_variables_scoped.css but are not used by any specific component.
6
6
  * Isolating them here keeps individual component stylesheets clean and focused.
7
7
  */
8
-
9
- /* ==== GLOBAL ICON STYLE ====
10
- Global layout option specifying the overall SVG rendering style (solid/outline).
11
- Note to Forge Devs: style is not a CSS property or variable type and cannot be applied in standard CSS stylesheets.
12
- This variable is ignored in the UI adapters and should be reviewed by the Forge team. */
13
- /* recursica-ignore: --recursica_ui-kit_globals_icon_style */
@@ -1,11 +1,3 @@
1
- /* EXEMPTIONS:
2
- - accordion-header open-appearance background-color/border-color are ignored because Forge does
3
- not apply a visual swap on expand, even though the token schema defines "open" appearance
4
- colors distinct from "closed" (see .control below, which intentionally keeps a constant
5
- background/border across collapsed/expanded). */
6
- /* recursica-ignore: --recursica_ui-kit_components_accordion-header_variants_appearance_open_properties_colors_background-color */
7
- /* recursica-ignore: --recursica_ui-kit_components_accordion-header_variants_appearance_open_properties_colors_border-color */
8
-
9
1
  /* ==== ACCORDION CONTAINER ==== */
10
2
  .root {
11
3
  box-sizing: border-box;
@@ -1,10 +1,3 @@
1
- /* EXEMPTIONS:
2
- - border-size variables are ignored because the input box uses a uniform static boundary to prevent
3
- unexpected layout shifts or flickering when switching between focused, disabled, or error states. */
4
- /* recursica-ignore: --recursica_ui-kit_components_autocomplete_properties_border-size */
5
- /* recursica-ignore: --recursica_ui-kit_components_autocomplete_variants_states_disabled_properties_border-size */
6
- /* recursica-ignore: --recursica_ui-kit_components_autocomplete_variants_states_error_properties_border-size */
7
-
8
1
  /* LAYOUT SPACING OVERRIDES:
9
2
  - Sets the --form-control-margin-bottom spacing hook to map component-specific layout tokens.
10
3
  - Also sets the --autocomplete-control-{max,min}-width hooks consumed inline in AutoComplete.tsx,
@@ -7,14 +7,6 @@ HARDCODED VALUES:
7
7
  - Image has no explicit size tokens other than root size, assume it scales 100%
8
8
  */
9
9
 
10
- /* EXEMPTIONS:
11
- The generic size variables below are defined in Figma but unused.
12
- We explicitly style the avatar's dimensions using the dedicated width and height tokens
13
- (e.g., small_properties_width and small_properties_height) for precise component layout. */
14
- /* recursica-ignore: --recursica_ui-kit_components_avatar_variants_sizes_default_properties_size */
15
- /* recursica-ignore: --recursica_ui-kit_components_avatar_variants_sizes_large_properties_size */
16
- /* recursica-ignore: --recursica_ui-kit_components_avatar_variants_sizes_small_properties_size */
17
-
18
10
  .root {
19
11
  box-sizing: border-box;
20
12
  margin: 0;
@@ -68,6 +68,16 @@ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
68
68
  // Delete prohibited sizing hooks from bypassing the variables
69
69
  delete restRecord["size"];
70
70
 
71
+ // Mantine's own `Checkbox.Group` always establishes a context whose `value` defaults to
72
+ // `[]` (see @mantine/core's `useUncontrolled({ finalValue: [] })`), and its `Checkbox` spreads
73
+ // that context-derived `checked` *after* the individually-passed `checked` prop — so any
74
+ // `Checkbox` nested in a real `Checkbox.Group`, even one given neither `value` nor
75
+ // `defaultValue`, has its own `checked` prop silently forced to `false`. Callers who only want
76
+ // this component for its layout (item-gap spacing), not array-tracked selection — e.g.
77
+ // `TransferList`'s ungrouped rows, which pass each `Checkbox` its own controlled `checked` —
78
+ // must skip Mantine's real group primitive entirely rather than hand it an empty value.
79
+ const isArrayControlled = value !== undefined || defaultValue !== undefined;
80
+
71
81
  return (
72
82
  <WithReadOnlyWrapper
73
83
  className={className}
@@ -97,18 +107,30 @@ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
97
107
  readOnlyValue={value !== undefined ? value : defaultValue}
98
108
  readOnlyNativeProps={props}
99
109
  activeComponent={
100
- <MantineCheckbox.Group
101
- ref={ref}
102
- /* Natively bind local disabled lock dynamically */
103
- {...(sanitizedProps as unknown as MantineCheckboxGroupProps)}
104
- disabled={readOnly || (restRecord as any).disabled}
105
- value={value as any}
106
- defaultValue={defaultValue as any}
107
- >
108
- <div className={styles.groupRoot} data-layout={formLayout}>
110
+ isArrayControlled ? (
111
+ <MantineCheckbox.Group
112
+ ref={ref}
113
+ /* Natively bind local disabled lock dynamically */
114
+ {...(sanitizedProps as unknown as MantineCheckboxGroupProps)}
115
+ disabled={readOnly || (restRecord as any).disabled}
116
+ value={value as any}
117
+ defaultValue={defaultValue as any}
118
+ >
119
+ <div className={styles.groupRoot} data-layout={formLayout}>
120
+ {children}
121
+ </div>
122
+ </MantineCheckbox.Group>
123
+ ) : (
124
+ <div
125
+ ref={ref}
126
+ role="group"
127
+ {...(sanitizedProps as Record<string, unknown>)}
128
+ className={styles.groupRoot}
129
+ data-layout={formLayout}
130
+ >
109
131
  {children}
110
132
  </div>
111
- </MantineCheckbox.Group>
133
+ )
112
134
  }
113
135
  />
114
136
  );
@@ -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)
@@ -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
+ }
@@ -1,10 +1,3 @@
1
- /* EXEMPTIONS:
2
- - border-size variables are ignored because a uniform 1px border is applied globally to prevent
3
- unexpected layout shift or flickering during focused, disabled, or error state transitions. */
4
- /* recursica-ignore: --recursica_ui-kit_components_date-picker_properties_border-size */
5
- /* recursica-ignore: --recursica_ui-kit_components_date-picker_variants_states_disabled_properties_border-size */
6
- /* recursica-ignore: --recursica_ui-kit_components_date-picker_variants_states_error_properties_border-size */
7
-
8
1
  /* LAYOUT SPACING OVERRIDES:
9
2
  - Sets the --form-control-margin-bottom spacing hook to map component-specific layout tokens. */
10
3
  .layoutOverride {
@@ -117,6 +110,14 @@
117
110
  );
118
111
 
119
112
  outline: none;
113
+
114
+ /* Single-line enforcement: DatePickerInput renders its value/placeholder as a <button>'s text
115
+ content (not a native <input>), so it wraps by default like any other block text — clip it to
116
+ one line with ellipsis instead, same convention as FileInput's .value. */
117
+ white-space: nowrap;
118
+ text-overflow: ellipsis;
119
+ overflow-x: clip;
120
+ overflow-y: visible;
120
121
  }
121
122
 
122
123
  .input::placeholder {
@@ -263,10 +264,257 @@
263
264
  );
264
265
  }
265
266
 
267
+ /* Month label + prev/next nav controls: styled as Recursica's own "text" button variant (small
268
+ size) rather than bare popover text — same border/background/color/typography tokens Button
269
+ uses for data-variant="text", since these are functionally buttons inside the popover. */
270
+ .calendarHeaderControl,
271
+ .calendarHeaderLevel {
272
+ position: relative;
273
+ border-style: solid;
274
+ border-width: var(
275
+ --recursica_ui-kit_components_button_variants_styles_text_properties_border-size
276
+ );
277
+ border-color: var(
278
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_border-color
279
+ );
280
+ background-color: var(
281
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_background-color
282
+ );
283
+ box-shadow: var(
284
+ --recursica_ui-kit_components_button_variants_styles_text_properties_elevation
285
+ );
286
+ color: var(
287
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_text-color
288
+ );
289
+ font-family: var(
290
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_text_font-family
291
+ );
292
+ font-size: var(
293
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_text_font-size
294
+ );
295
+ font-style: var(
296
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_text_font-style
297
+ );
298
+ font-weight: var(
299
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_text_font-weight
300
+ );
301
+ letter-spacing: var(
302
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_text_letter-spacing
303
+ );
304
+ text-decoration: var(
305
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_text_text-decoration
306
+ );
307
+ text-transform: var(
308
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_text_text-transform
309
+ );
310
+ transition: all 0.2s ease;
311
+ }
312
+
313
+ /* Prev/next nav: icon-only button sizing (small). */
314
+ .calendarHeaderControl {
315
+ min-width: var(
316
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_min-width
317
+ );
318
+ height: var(
319
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_height
320
+ );
321
+ border-radius: var(
322
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_border-radius
323
+ );
324
+ padding: 0
325
+ var(
326
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_horizontal-padding
327
+ );
328
+ }
329
+
330
+ /* Month/year label: label-content button sizing (small). */
331
+ .calendarHeaderLevel {
332
+ height: var(
333
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_height
334
+ );
335
+ border-radius: var(
336
+ --recursica_ui-kit_components_button_variants_content_label_variants_sizes_small_properties_border-radius
337
+ );
338
+ padding: 0
339
+ var(
340
+ --recursica_ui-kit_components_button_variants_content_label_variants_sizes_small_properties_horizontal-padding
341
+ );
342
+ }
343
+
344
+ /* Neutralize Mantine's own native :hover background-color (gray-0/dark-5) so only our
345
+ token-driven overlay below is visible — same reasoning as .day above. */
346
+ .calendarHeaderControl:hover:not(:disabled, [data-disabled]),
347
+ .calendarHeaderLevel:hover:not(:disabled, [data-disabled]) {
348
+ background-color: transparent;
349
+ }
350
+
351
+ .calendarHeaderControl::after,
352
+ .calendarHeaderLevel::after {
353
+ content: "";
354
+ position: absolute;
355
+ inset: 0;
356
+ border-radius: inherit;
357
+ z-index: -1;
358
+ pointer-events: none;
359
+ background-color: var(--recursica_brand_states_hover_color);
360
+ opacity: 0;
361
+ transition: opacity 150ms ease;
362
+ }
363
+
364
+ .calendarHeaderControl:hover:not(:disabled, [data-disabled])::after,
365
+ .calendarHeaderLevel:hover:not(:disabled, [data-disabled])::after {
366
+ opacity: var(--recursica_brand_states_hover_opacity);
367
+ }
368
+
369
+ .calendarHeaderControl:disabled,
370
+ .calendarHeaderControl[data-disabled],
371
+ .calendarHeaderLevel:disabled,
372
+ .calendarHeaderLevel[data-disabled] {
373
+ opacity: var(
374
+ --recursica_ui-kit_components_button_variants_styles_text_variants_states_disabled_properties_opacity
375
+ );
376
+ box-shadow: var(
377
+ --recursica_ui-kit_components_button_variants_styles_text_variants_states_disabled_properties_elevation
378
+ );
379
+ }
380
+
381
+ /* Focus ring, same convention as Button's own :focus-visible treatment. */
382
+ .calendarHeaderControl:focus-visible,
383
+ .calendarHeaderLevel:focus-visible {
384
+ outline: none;
385
+ box-shadow:
386
+ 0 0 0 var(--recursica_brand_states_focus_border-size)
387
+ var(--recursica_brand_states_focus_color),
388
+ 0 0 var(--recursica_brand_states_focus_blur)
389
+ var(--recursica_brand_states_focus_margin)
390
+ var(--recursica_brand_states_focus_color);
391
+ }
392
+
393
+ .calendarHeaderControlIcon {
394
+ color: var(
395
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_icon-color
396
+ );
397
+ width: var(
398
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_icon
399
+ );
400
+ height: var(
401
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_icon
402
+ );
403
+ }
404
+
405
+ /* Weekday row (Mo, Tu, We...) */
406
+ .weekday {
407
+ color: var(
408
+ --recursica_ui-kit_components_hover-card-popover_properties_colors_content
409
+ );
410
+ }
411
+
266
412
  .day {
413
+ position: relative;
414
+ color: var(
415
+ --recursica_ui-kit_components_hover-card-popover_properties_colors_content
416
+ );
417
+ border-radius: var(--recursica_brand_dimensions_border-radii_default);
418
+ }
419
+
420
+ /* Weekend days: Mantine's default colors them red; the UI Kit has no such concept, so they're
421
+ intentionally kept identical to any other day rather than left on Mantine's un-tokenized default. */
422
+ .day[data-weekend] {
267
423
  color: var(
268
424
  --recursica_ui-kit_components_hover-card-popover_properties_colors_content
269
425
  );
426
+ }
427
+
428
+ /* Today marker: reuse the popover border-size/color tokens already used for the dropdown
429
+ surface itself, since the UI Kit exports no dedicated "today" indicator token. */
430
+ .day[data-today][data-highlight-today]:not([data-selected], [data-in-range]) {
431
+ border-style: solid;
432
+ border-width: var(
433
+ --recursica_ui-kit_components_hover-card-popover_properties_border-size
434
+ );
435
+ border-color: var(
436
+ --recursica_ui-kit_components_hover-card-popover_properties_colors_border-color
437
+ );
438
+ }
439
+
440
+ /* Outside-month & disabled days: dim with the generic disabled-opacity token used everywhere else
441
+ in the design system for elements with no dedicated disabled token. */
442
+ .day[data-outside],
443
+ .day[data-disabled] {
444
+ opacity: var(--recursica_brand_states_disabled);
445
+ }
446
+
447
+ .day[data-disabled] {
448
+ cursor: not-allowed;
449
+ }
450
+
451
+ /* Unlike Button (a plain button with no built-in library hover), Mantine's Day ships its own
452
+ native :hover background-color (gray-0/dark-5) — neutralize it so only our token-driven
453
+ overlay below is visible. Same specificity as Mantine's own selector, so this relies on our
454
+ CSS module loading after @mantine/dates' stylesheet to win the tie (true today; see
455
+ DATEPICKER_IMPLEMENTATION_NOTES.md if that load order ever changes). */
456
+ .day:hover:not(:disabled, [data-disabled], [data-static]) {
457
+ background-color: transparent;
458
+ }
459
+
460
+ /* Hover overlay, same generic mechanism used by Button/Menu for components without a dedicated
461
+ hover token. Unlike Button, the day number here is a bare text node (Mantine's Day renders it
462
+ directly, with no wrapping span to z-index above the overlay), so this sits at z-index: -1
463
+ instead — still scoped to .day's own positioned box, just painted behind the digit rather than
464
+ in front of it. */
465
+ .day::after {
466
+ content: "";
467
+ position: absolute;
468
+ inset: 0;
469
+ border-radius: inherit;
470
+ z-index: -1;
471
+ pointer-events: none;
472
+ background-color: var(--recursica_brand_states_hover_color);
473
+ opacity: 0;
474
+ transition: opacity 150ms ease;
475
+ }
476
+
477
+ .day:hover:not(:disabled, [data-disabled], [data-static])::after {
478
+ opacity: var(--recursica_brand_states_hover_opacity);
479
+ }
480
+
481
+ /* Range selection (type="range"): the UI Kit exports no dedicated in-range token, so the generic
482
+ overlay token — meant for exactly this kind of translucent tint — fills the band between the
483
+ two selected endpoints. Painted via ::before (behind the day number and the ::after hover
484
+ overlay) so the fill's opacity never dims the text itself. */
485
+ .day[data-in-range] {
486
+ border-radius: 0;
487
+ }
488
+
489
+ .day[data-in-range]::before {
490
+ content: "";
491
+ position: absolute;
492
+ inset: 0;
493
+ z-index: -1;
494
+ pointer-events: none;
495
+ background-color: var(--recursica_brand_states_overlay_color);
496
+ opacity: var(--recursica_brand_states_overlay_opacity);
497
+ }
498
+
499
+ .day[data-first-in-range] {
500
+ border-radius: 0;
501
+ border-start-start-radius: var(
502
+ --recursica_brand_dimensions_border-radii_default
503
+ );
504
+ border-end-start-radius: var(
505
+ --recursica_brand_dimensions_border-radii_default
506
+ );
507
+ }
508
+
509
+ .day[data-last-in-range] {
510
+ border-radius: 0;
511
+ border-end-end-radius: var(--recursica_brand_dimensions_border-radii_default);
512
+ border-start-end-radius: var(
513
+ --recursica_brand_dimensions_border-radii_default
514
+ );
515
+ }
516
+
517
+ .day[data-first-in-range][data-last-in-range] {
270
518
  border-radius: var(--recursica_brand_dimensions_border-radii_default);
271
519
  }
272
520
 
@@ -278,4 +526,5 @@
278
526
  color: var(
279
527
  --recursica_ui-kit_components_button_variants_styles_solid_properties_colors_text-color
280
528
  );
529
+ opacity: 1;
281
530
  }
@@ -65,7 +65,6 @@ export const Default: Story = {
65
65
  args: {
66
66
  disabled: false,
67
67
  label: "Project Deadline",
68
- placeholder: "Select a deadline...",
69
68
  assistiveText: "Specify the absolute cutoff for code submission.",
70
69
  },
71
70
  };
@@ -73,7 +72,6 @@ export const Default: Story = {
73
72
  export const FormsSideBySide: Story = {
74
73
  args: {
75
74
  label: "Incident Start Date",
76
- placeholder: "Pick date...",
77
75
  assistiveText: "When did the incident originally occur?",
78
76
  formLayout: "side-by-side",
79
77
  },
@@ -82,7 +80,6 @@ export const FormsSideBySide: Story = {
82
80
  export const WithLeadingIcon: Story = {
83
81
  args: {
84
82
  label: "Launch Date",
85
- placeholder: "Select launch date...",
86
83
  leftSection: (
87
84
  <svg
88
85
  width="24"
@@ -106,7 +103,6 @@ export const WithLeadingIcon: Story = {
106
103
  export const Disabled: Story = {
107
104
  args: {
108
105
  label: "Disabled Date Range",
109
- placeholder: "Disabled selection...",
110
106
  disabled: true,
111
107
  },
112
108
  };
@@ -114,7 +110,6 @@ export const Disabled: Story = {
114
110
  export const ErrorState: Story = {
115
111
  args: {
116
112
  label: "Execution Date",
117
- placeholder: "Pick a valid date...",
118
113
  error: "The chosen date conflicts with an existing deployment freeze.",
119
114
  required: true,
120
115
  },
@@ -123,7 +118,6 @@ export const ErrorState: Story = {
123
118
  export const StaticReadOnly: Story = {
124
119
  args: {
125
120
  label: "Static ReadOnly Review",
126
- placeholder: "Ignored...",
127
121
  value: new Date("2026-05-21"),
128
122
  readOnly: true,
129
123
  },
@@ -132,7 +126,6 @@ export const StaticReadOnly: Story = {
132
126
  export const EditableReadOnly: Story = {
133
127
  args: {
134
128
  label: "Editable ReadOnly Review",
135
- placeholder: "Ignored until active...",
136
129
  defaultValue: new Date("2026-06-01"),
137
130
  readOnly: true,
138
131
  labelWithEditIcon: true,
@@ -7,6 +7,7 @@ import {
7
7
  } from "../../utils/filterStylingProps";
8
8
  import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
9
9
  import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
10
+ import { CalendarIcon } from "./DatePicker.icons";
10
11
  import styles from "./DatePicker.module.css";
11
12
 
12
13
  import { type RecursicaDatePickerProps as BaseRecursicaDatePickerProps } from "@recursica/adapter-common";
@@ -83,9 +84,12 @@ export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(
83
84
  wrapper: styles.root, // The nested Input internal relative wrapper bounding box
84
85
  input: styles.input,
85
86
  section: styles.section,
86
- dropdown: styles.dropdown,
87
87
  day: styles.day,
88
+ weekday: styles.weekday,
88
89
  calendarHeader: styles.calendarHeader,
90
+ calendarHeaderControl: styles.calendarHeaderControl,
91
+ calendarHeaderLevel: styles.calendarHeaderLevel,
92
+ calendarHeaderControlIcon: styles.calendarHeaderControlIcon,
89
93
  };
90
94
 
91
95
  const classNamesProp = restRecord.classNames;
@@ -106,6 +110,33 @@ export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(
106
110
  : styles.section;
107
111
  }
108
112
 
113
+ // The calendar popover surface (.dropdown) is styled by Mantine's underlying `Popover`
114
+ // component, not `DatePickerInput` itself — it only accepts classNames via `popoverProps`,
115
+ // not the top-level `classNames` map above (which only reaches the Input/Calendar parts).
116
+ const popoverPropsInput = restRecord.popoverProps;
117
+ const consumerPopoverProps =
118
+ popoverPropsInput &&
119
+ typeof popoverPropsInput === "object" &&
120
+ !Array.isArray(popoverPropsInput)
121
+ ? (popoverPropsInput as Record<string, unknown>)
122
+ : undefined;
123
+ const consumerPopoverClassNames =
124
+ consumerPopoverProps?.classNames &&
125
+ typeof consumerPopoverProps.classNames === "object" &&
126
+ !Array.isArray(consumerPopoverProps.classNames)
127
+ ? (consumerPopoverProps.classNames as Partial<Record<string, string>>)
128
+ : undefined;
129
+ delete restRecord["popoverProps"];
130
+ const mergedPopoverProps = {
131
+ ...consumerPopoverProps,
132
+ classNames: {
133
+ ...consumerPopoverClassNames,
134
+ dropdown: consumerPopoverClassNames?.dropdown
135
+ ? `${styles.dropdown} ${consumerPopoverClassNames.dropdown}`
136
+ : styles.dropdown,
137
+ },
138
+ };
139
+
109
140
  const wrapperClass = className
110
141
  ? `${styles.layoutOverride} ${className}`
111
142
  : styles.layoutOverride;
@@ -158,6 +189,11 @@ export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(
158
189
  "data-disabled": disabled ? "true" : undefined,
159
190
  "data-error": error ? "true" : undefined,
160
191
  }}
192
+ highlightToday // Default on so today is visually marked; consumers can still override via rest
193
+ valueFormat="MM/DD/YY" // Default display format; consumers can still override via rest
194
+ placeholder="MM / DD / YY" // Default placeholder; consumers can still override via rest
195
+ leftSection={<CalendarIcon />} // Default leading icon; consumers can still override via rest
196
+ popoverProps={mergedPopoverProps}
161
197
  {...(sanitizedProps as unknown as DatePickerInputProps)}
162
198
  />
163
199
  }
@@ -19,7 +19,7 @@ import React from "react";
19
19
  import { DatePicker } from "@recursica/mantine-adapter";
20
20
 
21
21
  export default function Demo() {
22
- return <DatePicker label="Select Date" placeholder="Pick a date" />;
22
+ return <DatePicker label="Select Date" />;
23
23
  }
24
24
  ```
25
25
 
@@ -42,4 +42,8 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
42
42
  ## Structural Constraints
43
43
 
44
44
  1. **Read-Only Rendering**: When rendered in read-only mode, the selected date is displayed via a plain string conversion of the value. If you need custom date formatting, pass a `readOnlyComponent` prop to control how the value is rendered.
45
- 2. **Calendar Popover Styling**: The calendar popover is not yet fully styled by design tokens.
45
+ 2. **Calendar Popover Styling**: The UI Kit exports no calendar-specific tokens (surface, selected day, hover, today, weekend, in-range), so the calendar reuses the closest existing tokens — see `DATEPICKER_IMPLEMENTATION_NOTES.md` for the exact mapping. All calendar states are token-driven; none are left on Mantine's un-tokenized defaults. The header's prev/next arrows and month/year label are styled as Recursica `text`-variant buttons.
46
+ 3. **Today Highlighting**: `highlightToday` defaults to `true` so the current day is always visually marked; pass `highlightToday={false}` to turn it off.
47
+ 4. **Default Value Format**: Displayed values default to `MM/DD/YY` (e.g. `08/19/26`); pass your own `valueFormat` (a `dayjs` format string) to override.
48
+ 5. **Default Leading Icon**: A calendar icon is shown in the left section by default; pass your own `leftSection` to override it.
49
+ 6. **Default Placeholder**: Defaults to `"MM / DD / YY"` when not set; pass your own `placeholder` to override it.
@@ -1,10 +1,3 @@
1
- /* EXEMPTIONS:
2
- - border-size variables are ignored because a uniform 1px border is applied globally to prevent
3
- unexpected layout shift or flickering during focus, disabled, or error state transitions. */
4
- /* recursica-ignore: --recursica_ui-kit_components_dropdown_properties_border-size */
5
- /* recursica-ignore: --recursica_ui-kit_components_dropdown_variants_states_disabled_properties_border-size */
6
- /* recursica-ignore: --recursica_ui-kit_components_dropdown_variants_states_error_properties_border-size */
7
-
8
1
  /* LAYOUT SPACING OVERRIDES:
9
2
  - Sets the --form-control-margin-bottom spacing hook to map component-specific layout tokens.
10
3
  - Also sets the --dropdown-control-{max,min}-width hooks consumed inline in Dropdown.tsx, since