@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
@@ -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,
@@ -3,10 +3,14 @@ import { DatePickerInput, type DatePickerInputProps } from "@mantine/dates";
3
3
  import { type ReadOnlyControlProps } from "@recursica/adapter-common";
4
4
  import {
5
5
  filterStylingProps,
6
+ omitUnsupportedProps,
7
+ withCallerOverride,
8
+ mergeClassNames,
6
9
  type RecursicaOverStyled,
7
10
  } from "../../utils/filterStylingProps";
8
11
  import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
9
12
  import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
13
+ import { CalendarIcon } from "./DatePicker.icons";
10
14
  import styles from "./DatePicker.module.css";
11
15
 
12
16
  import { type RecursicaDatePickerProps as BaseRecursicaDatePickerProps } from "@recursica/adapter-common";
@@ -38,6 +42,15 @@ export interface RecursicaDatePickerProps
38
42
 
39
43
  export type DatePickerProps = RecursicaOverStyled<RecursicaDatePickerProps>;
40
44
 
45
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
46
+ // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
47
+ const UNSUPPORTED_PROPS = [
48
+ "size", // Recursica controls sizing via the `size` variant + design tokens, not raw dimensions.
49
+ "variant", // Colors/variants are token-driven; the library's native palette isn't exposed.
50
+ "radius", // Recursica does not expose the library's native corner-radius system.
51
+ "description", // Managed by FormControlWrapper via assistiveText.
52
+ ] as const satisfies readonly (keyof DatePickerInputProps)[];
53
+
41
54
  export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(
42
55
  function DatePicker(props, ref) {
43
56
  const {
@@ -66,45 +79,59 @@ export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(
66
79
  emptyValueComponent,
67
80
  value,
68
81
  defaultValue,
82
+ highlightToday = true, // Default on so today is visually marked; consumers can still override.
83
+ valueFormat = "MM/DD/YY", // Default display format; consumers can still override.
84
+ placeholder = "MM / DD / YY", // Default placeholder; consumers can still override.
85
+ leftSection, // Default leading icon computed below; consumers can still override.
69
86
  ...rest
70
87
  } = props;
71
88
 
72
- const sanitizedProps = filterStylingProps(rest, overStyled);
89
+ const sanitizedProps = omitUnsupportedProps(
90
+ filterStylingProps(rest, overStyled) as Record<string, unknown>,
91
+ UNSUPPORTED_PROPS,
92
+ );
73
93
  const restRecord = sanitizedProps as Record<string, unknown>;
74
94
 
75
- // Delete prohibited sizing hooks from bypassing variables natively
76
- delete restRecord["size"];
77
- delete restRecord["variant"];
78
- delete restRecord["radius"];
79
- delete restRecord["description"]; // Managed by FormControlWrapper via assistiveText
80
-
81
95
  // Securely map core native blocks down ensuring nested CSS modules map precisely
82
- const mergedClassNames: Partial<Record<string, string>> = {
83
- wrapper: styles.root, // The nested Input internal relative wrapper bounding box
84
- input: styles.input,
85
- section: styles.section,
86
- dropdown: styles.dropdown,
87
- day: styles.day,
88
- calendarHeader: styles.calendarHeader,
89
- };
96
+ const mergedClassNames = mergeClassNames(
97
+ {
98
+ wrapper: styles.root, // The nested Input internal relative wrapper bounding box
99
+ input: styles.input,
100
+ section: styles.section,
101
+ day: styles.day,
102
+ weekday: styles.weekday,
103
+ calendarHeader: styles.calendarHeader,
104
+ calendarHeaderControl: styles.calendarHeaderControl,
105
+ calendarHeaderLevel: styles.calendarHeaderLevel,
106
+ calendarHeaderControlIcon: styles.calendarHeaderControlIcon,
107
+ },
108
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
109
+ );
90
110
 
91
- const classNamesProp = restRecord.classNames;
92
- if (
93
- classNamesProp &&
94
- typeof classNamesProp === "object" &&
95
- !Array.isArray(classNamesProp)
96
- ) {
97
- const o = classNamesProp as Partial<Record<string, string>>;
98
- mergedClassNames.wrapper = o.wrapper
99
- ? `${styles.root} ${o.wrapper}`
100
- : styles.root;
101
- mergedClassNames.input = o.input
102
- ? `${styles.input} ${o.input}`
103
- : styles.input;
104
- mergedClassNames.section = o.section
105
- ? `${styles.section} ${o.section}`
106
- : styles.section;
107
- }
111
+ // The calendar popover surface (.dropdown) is styled by Mantine's underlying `Popover`
112
+ // component, not `DatePickerInput` itself — it only accepts classNames via `popoverProps`,
113
+ // not the top-level `classNames` map above (which only reaches the Input/Calendar parts).
114
+ const popoverPropsInput = restRecord.popoverProps;
115
+ const consumerPopoverProps =
116
+ popoverPropsInput &&
117
+ typeof popoverPropsInput === "object" &&
118
+ !Array.isArray(popoverPropsInput)
119
+ ? (popoverPropsInput as Record<string, unknown>)
120
+ : undefined;
121
+ const consumerPopoverClassNames =
122
+ consumerPopoverProps?.classNames &&
123
+ typeof consumerPopoverProps.classNames === "object" &&
124
+ !Array.isArray(consumerPopoverProps.classNames)
125
+ ? (consumerPopoverProps.classNames as Partial<Record<string, string>>)
126
+ : undefined;
127
+ delete restRecord["popoverProps"];
128
+ const mergedPopoverProps = {
129
+ ...consumerPopoverProps,
130
+ classNames: mergeClassNames(
131
+ { dropdown: styles.dropdown },
132
+ consumerPopoverClassNames,
133
+ ),
134
+ };
108
135
 
109
136
  const wrapperClass = className
110
137
  ? `${styles.layoutOverride} ${className}`
@@ -146,6 +173,7 @@ export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(
146
173
  /* Naked Input execution safely decoupled from Mantine's macro Input.Wrapper DOM hooks */
147
174
  <DatePickerInput
148
175
  ref={ref}
176
+ {...(sanitizedProps as unknown as DatePickerInputProps)}
149
177
  classNames={mergedClassNames}
150
178
  disabled={disabled}
151
179
  value={value}
@@ -154,11 +182,18 @@ export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(
154
182
  description={undefined} // Disable Mantine's native description
155
183
  error={undefined} // Disable Mantine's native error text (handled by wrapper)
156
184
  withAsterisk={false} // Handled by wrapper
185
+ highlightToday={highlightToday}
186
+ valueFormat={valueFormat}
187
+ placeholder={placeholder}
188
+ leftSection={withCallerOverride<React.ReactNode>(
189
+ <CalendarIcon />,
190
+ leftSection,
191
+ )}
157
192
  wrapperProps={{
158
193
  "data-disabled": disabled ? "true" : undefined,
159
194
  "data-error": error ? "true" : undefined,
160
195
  }}
161
- {...(sanitizedProps as unknown as DatePickerInputProps)}
196
+ popoverProps={mergedPopoverProps}
162
197
  />
163
198
  }
164
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.
@@ -5,6 +5,8 @@ import {
5
5
  } from "@mantine/core";
6
6
  import {
7
7
  filterStylingProps,
8
+ omitUnsupportedProps,
9
+ mergeClassNames,
8
10
  type RecursicaOverStyled,
9
11
  } from "../../utils/filterStylingProps";
10
12
  import styles from "./Dropdown.module.css";
@@ -37,29 +39,40 @@ export interface BareDropdownProps
37
39
 
38
40
  export type BareDropdownComponentProps = RecursicaOverStyled<BareDropdownProps>;
39
41
 
42
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
43
+ // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
44
+ const UNSUPPORTED_PROPS = [
45
+ "size", // Recursica controls sizing via the `size` variant + design tokens, not raw dimensions.
46
+ "variant", // Colors/variants are token-driven; the library's native palette isn't exposed.
47
+ "radius", // Recursica does not expose the library's native corner-radius system.
48
+ ] as const satisfies readonly (keyof MantineSelectProps)[];
49
+
40
50
  export const BareDropdown = forwardRef<
41
51
  HTMLInputElement,
42
52
  BareDropdownComponentProps
43
53
  >(function BareDropdown(props, ref) {
44
54
  const { overStyled = false, className, disabled, error, ...rest } = props;
45
- const sanitizedProps = filterStylingProps(rest, overStyled);
55
+ const sanitizedProps = omitUnsupportedProps(
56
+ filterStylingProps(rest, overStyled) as Record<string, unknown>,
57
+ UNSUPPORTED_PROPS,
58
+ );
46
59
  const restRecord = sanitizedProps as Record<string, unknown>;
47
60
 
48
- delete restRecord["size"];
49
- delete restRecord["variant"];
50
- delete restRecord["radius"];
51
-
52
- const mergedClassNames: Partial<Record<string, string>> = {
53
- wrapper: className ? `${styles.root} ${className}` : styles.root,
54
- input: styles.input,
55
- section: styles.section,
56
- dropdown: styles.dropdown,
57
- option: styles.option,
58
- };
61
+ const mergedClassNames = mergeClassNames(
62
+ {
63
+ wrapper: className ? `${styles.root} ${className}` : styles.root,
64
+ input: styles.input,
65
+ section: styles.section,
66
+ dropdown: styles.dropdown,
67
+ option: styles.option,
68
+ },
69
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
70
+ );
59
71
 
60
72
  return (
61
73
  <MantineSelect
62
74
  ref={ref}
75
+ {...(sanitizedProps as unknown as MantineSelectProps)}
63
76
  classNames={mergedClassNames}
64
77
  disabled={disabled}
65
78
  label={undefined}
@@ -77,7 +90,6 @@ export const BareDropdown = forwardRef<
77
90
  "data-error": error ? "true" : undefined,
78
91
  },
79
92
  }}
80
- {...(sanitizedProps as unknown as MantineSelectProps)}
81
93
  />
82
94
  );
83
95
  });
@@ -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