@recursica/mantine-adapter 0.36.0 → 0.38.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 (63) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +3 -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 +2352 -2099
  7. package/dist/mantine-adapter.js.map +1 -1
  8. package/dist/src/components/Dropdown/BareDropdown.d.ts +19 -0
  9. package/dist/src/components/TimePicker/TimePicker.d.ts +9 -3
  10. package/dist/src/components/Tree/Tree.d.ts +5 -0
  11. package/dist/src/index.d.ts +1 -1
  12. package/docs/PHILOSOPHY.md +39 -0
  13. package/package.json +3 -2
  14. package/src/components/Accordion/USAGE.md +2 -41
  15. package/src/components/AutoComplete/USAGE.md +2 -18
  16. package/src/components/Avatar/USAGE.md +0 -27
  17. package/src/components/Badge/USAGE.md +3 -6
  18. package/src/components/Breadcrumb/USAGE.md +1 -5
  19. package/src/components/Button/Button.tsx +5 -0
  20. package/src/components/Button/IMPLEMENTATION_NOTES.md +8 -0
  21. package/src/components/Button/USAGE.md +5 -25
  22. package/src/components/Card/USAGE.md +4 -12
  23. package/src/components/Checkbox/USAGE.md +4 -20
  24. package/src/components/Chip/USAGE.md +3 -32
  25. package/src/components/DatePicker/USAGE.md +2 -12
  26. package/src/components/Dropdown/BareDropdown.tsx +85 -0
  27. package/src/components/Dropdown/Dropdown.tsx +12 -3
  28. package/src/components/Dropdown/USAGE.md +2 -6
  29. package/src/components/Flex/USAGE.md +1 -1
  30. package/src/components/FormControlWrapper/USAGE.md +3 -31
  31. package/src/components/Grid/USAGE.md +1 -1
  32. package/src/components/Group/USAGE.md +1 -1
  33. package/src/components/HoverCard/USAGE.md +3 -72
  34. package/src/components/Label/USAGE.md +8 -48
  35. package/src/components/Link/USAGE.md +4 -10
  36. package/src/components/Loader/USAGE.md +4 -23
  37. package/src/components/Menu/USAGE.md +3 -73
  38. package/src/components/Modal/USAGE.md +3 -3
  39. package/src/components/NumberInput/USAGE.md +5 -8
  40. package/src/components/Pagination/USAGE.md +0 -19
  41. package/src/components/Panel/USAGE.md +6 -95
  42. package/src/components/Popover/USAGE.md +6 -66
  43. package/src/components/ReadOnlyField/USAGE.md +2 -10
  44. package/src/components/SegmentedControl/USAGE.md +1 -15
  45. package/src/components/Slider/USAGE.md +1 -45
  46. package/src/components/Stack/USAGE.md +1 -1
  47. package/src/components/Switch/USAGE.md +1 -24
  48. package/src/components/TextArea/USAGE.md +2 -2
  49. package/src/components/TextField/USAGE.md +1 -17
  50. package/src/components/TimePicker/TIMEPICKER_IMPLEMENTATION_NOTES.md +72 -0
  51. package/src/components/TimePicker/TimePicker.module.css +225 -41
  52. package/src/components/TimePicker/TimePicker.stories.tsx +105 -4
  53. package/src/components/TimePicker/TimePicker.tsx +287 -7
  54. package/src/components/TimePicker/USAGE.md +23 -2
  55. package/src/components/Timeline/USAGE.md +2 -10
  56. package/src/components/Toast/USAGE.md +3 -33
  57. package/src/components/Tooltip/USAGE.md +7 -51
  58. package/src/components/Tree/IMPLEMENTATION_NOTES.md +31 -3
  59. package/src/components/Tree/Tree.module.css +84 -40
  60. package/src/components/Tree/Tree.stories.tsx +13 -0
  61. package/src/components/Tree/Tree.tsx +116 -27
  62. package/src/components/Tree/USAGE.md +18 -1
  63. package/src/index.ts +1 -0
@@ -0,0 +1,19 @@
1
+ import { SelectProps as MantineSelectProps } from '@mantine/core';
2
+ import { RecursicaOverStyled } from '../../utils/filterStylingProps';
3
+ /**
4
+ * Bare, unwrapped Select — no `FormControlWrapper`/`WithReadOnlyWrapper`, no label/assistiveText/
5
+ * error/required. Tied to the same `Dropdown.module.css` variables/classes as the public
6
+ * `Dropdown` component, so it looks identical, but is meant to be embedded inside another
7
+ * component that already owns its own `FormControlWrapper` (e.g. `TimePicker`'s AM/PM control) —
8
+ * nesting the full `Dropdown` there would double up `FormControl`/`FormControlLayout` wrapping.
9
+ *
10
+ * Not exported from this folder's `index.ts` — internal use only. Import it directly:
11
+ * `import { BareDropdown } from "../Dropdown/BareDropdown"`.
12
+ */
13
+ export interface BareDropdownProps extends Omit<MantineSelectProps, "size" | "variant" | "radius" | "wrapperProps" | "label" | "description" | "error"> {
14
+ data: MantineSelectProps["data"];
15
+ /** Applies the error visual state (via `data-error`) — no error message is rendered here. */
16
+ error?: boolean;
17
+ }
18
+ export type BareDropdownComponentProps = RecursicaOverStyled<BareDropdownProps>;
19
+ export declare const BareDropdown: import('react').ForwardRefExoticComponent<BareDropdownComponentProps & import('react').RefAttributes<HTMLInputElement>>;
@@ -1,4 +1,10 @@
1
1
  import { default as React } from 'react';
2
- import { RecursicaTimePickerProps } from '@recursica/adapter-common';
3
- export type TimePickerProps = React.HTMLAttributes<HTMLDivElement> & RecursicaTimePickerProps;
4
- export declare const TimePicker: React.FC<TimePickerProps>;
2
+ import { TimePickerProps as MantineTimePickerProps } from '@mantine/dates';
3
+ import { InputWrapperProps } from '@mantine/core';
4
+ import { ReadOnlyControlProps, RecursicaTimePickerProps as BaseRecursicaTimePickerProps } from '@recursica/adapter-common';
5
+ import { RecursicaOverStyled } from '../../utils/filterStylingProps';
6
+ import { RecursicaFormControlWrapperProps } from '../FormControlWrapper/FormControlWrapper';
7
+ export interface RecursicaTimePickerProps extends Omit<MantineTimePickerProps, "size" | "variant" | "radius" | "wrapperProps" | "format" | "min" | "max" | "amPmInputLabel" | "amPmLabels" | "amPmSelectProps" | "amPmRef" | "withDropdown" | "presets" | "maxDropdownContentHeight" | "scrollAreaProps" | "reverseTimeControlsList" | "popoverProps">, Pick<InputWrapperProps, "label" | "error" | "required" | "withAsterisk" | "id">, Omit<RecursicaFormControlWrapperProps, "controlMaxWidth" | "controlMinWidth">, ReadOnlyControlProps, BaseRecursicaTimePickerProps {
8
+ }
9
+ export type TimePickerProps = RecursicaOverStyled<RecursicaTimePickerProps>;
10
+ export declare const TimePicker: React.ForwardRefExoticComponent<TimePickerProps & React.RefAttributes<HTMLDivElement>>;
@@ -9,5 +9,10 @@ export type TreeProps = RecursicaOverStyled<RecursicaTreeProps & Omit<React.Comp
9
9
  * Wraps Mantine's `Tree` with a fully custom node renderer so every visual aspect (row box
10
10
  * model, selected/unselected colors and typography, indent, item spacing) comes from
11
11
  * Recursica's `tree` design tokens rather than Mantine's defaults.
12
+ *
13
+ * **Interaction pattern (fixed, not prop-configurable):** expand/collapse and select are
14
+ * independent — the chevron button toggles a node's subtree only, clicking the rest of a row
15
+ * (or pressing `Enter`/`Space`) selects it only, and `ArrowLeft`/`ArrowRight` toggle expansion
16
+ * only. See `RecursicaTreeProps` for the full breakdown.
12
17
  */
13
18
  export declare const Tree: React.ForwardRefExoticComponent<TreeProps & React.RefAttributes<HTMLUListElement>>;
@@ -217,4 +217,4 @@ export declare const Toast: typeof rawComponents.Toast;
217
217
  export declare const Tooltip: typeof rawComponents.Tooltip;
218
218
  export declare const TransferList: typeof rawComponents.TransferList;
219
219
  export declare const Tree: typeof rawComponents.Tree;
220
- export type { RecursicaCheckboxGroupProps, RecursicaDatePickerProps, RecursicaDropdownProps, RecursicaNumberInputProps, RecursicaRadioGroupProps, RecursicaReadOnlyFieldProps, RecursicaReadOnlyTextFieldProps, RecursicaSliderProps, RecursicaSwitchGroupProps, RecursicaTextAreaProps, RecursicaTextFieldProps, } from './components';
220
+ export type { RecursicaCheckboxGroupProps, RecursicaDatePickerProps, RecursicaDropdownProps, RecursicaNumberInputProps, RecursicaRadioGroupProps, RecursicaReadOnlyFieldProps, RecursicaReadOnlyTextFieldProps, RecursicaSliderProps, RecursicaSwitchGroupProps, RecursicaTextAreaProps, RecursicaTextFieldProps, RecursicaTimePickerProps, } from './components';
@@ -0,0 +1,39 @@
1
+ # Recursica Mantine Adapter: Core Philosophy
2
+
3
+ Recursica's component architecture isn't just a wrapper; it's a strict enforcing layer over Mantine's massive API surface. Our primary goal is to ensure consistency, eliminate "design system rot," and provide clear boundaries for application developers using the UI Kit.
4
+
5
+ This document serves as the governing framework for why the `mantine-adapter` components are built the way they are.
6
+
7
+ ## 1. Strict Separation of Props (The Unified Recursica Prop Layer)
8
+
9
+ Recursica has a **single universal API surface** internally regardless of whether we use Mantine or another underlying UI library.
10
+
11
+ - We decouple our visual properties natively. Instead of mapping perfectly to Mantine's variants `(solid, outline)`, we intentionally use Recursica's semantic and behavioral structures (e.g., `<Badge variant="alert" />`).
12
+ - We intentionally omit and strip complex underlying parameters if they collide with or circumvent our UI tokens (like stripping `--size` out of Mantine Badge when Recursica enforces a universal single size).
13
+
14
+ ## 2. Component Wrappers (Leaving Mantine Alone)
15
+
16
+ We actively avoid mutating or patching Mantine source code or deeply hooking into the Mantine Theme object to apply our token system.
17
+
18
+ - We rely on standard DOM `module.css` bridging with strictly targeted `className`/`classNames` overrides.
19
+ - This creates total decoupled isolation: updating Mantine natively will not fracture our styles, and we avoid dealing with deep Emotion/styled-component theme clashing logic.
20
+
21
+ ## 3. The `overStyled` Property
22
+
23
+ Mantine encourages deep styling access by injecting properties like `p` (padding), `bg` (background), `c` (color), or `styles`/`classNames` directly into component tags.
24
+
25
+ - By default, **Recursica components block all arbitrary styling vectors**. `className` maps, system styles, and inline logic are proactively stripped before they hit Mantine using central utility functions.
26
+ - **Why?** To prevent the design system from deteriorating over time as developers write one-off hotfixes into their TSX rendering blocks.
27
+ - **The Caveat:** We allow _external DOM layout positioning props_ (e.g., margin `m`, `mt`, `mb`, etc.) to pass through so developers can still structure components organically within their parent layouts.
28
+
29
+ ### Escape Hatches
30
+
31
+ If a developer _strictly must_ heavily alter a component, they are required to explicitly declare `<Component overStyled={true} />`. This immediately raises a visible red flag during code reviews.
32
+
33
+ ## 4. Expectations for External Developers (Modifying Recursica)
34
+
35
+ If a developer finds that a component does not fit their needs and styling must be modified, their path of execution should follow these principles sequentially:
36
+
37
+ 1. **Leverage Native Mantine First:** If a Recursica component lacks the functionality or styling variant needed for a highly custom edge case (e.g., a massive marketing hero button), do not try to forcibly hack the Recursica component. Instead, import the raw underlying `Button` component directly from `@mantine/core` and style it manually. Use Recursica for standard systematic needs, and native libraries for isolated custom one-offs.
38
+ 2. **Accept `overStyled` as Technical Debt:** If you must override the Recursica component immediately but intend to roll it back, use `overStyled={true}`. The expectation is that `overStyled` uses will eventually be replaced once the actual Recursica Figma variants are natively updated to accommodate your usecase, at which point `overStyled={true}` can be safely removed.
39
+ 3. **Contribute to the Kit:** Avoid building private custom wrappers around Recursica components. If the system is missing a variant, that is a shared project deficit—raise a concern and have the variant integrated directly into the universal token libraries!
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.36.0",
16
+ "version": "0.38.0",
17
17
  "type": "module",
18
18
  "main": "./dist/mantine-adapter.cjs",
19
19
  "module": "./dist/mantine-adapter.js",
@@ -35,7 +35,8 @@
35
35
  "USAGE.md",
36
36
  "ARCHITECTURE.md",
37
37
  "SETUP.md",
38
- "OVERSTYLING.md"
38
+ "OVERSTYLING.md",
39
+ "docs/PHILOSOPHY.md"
39
40
  ],
40
41
  "keywords": [
41
42
  "react",
@@ -46,45 +46,6 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
46
46
 
47
47
  ## 4. Key Integration Features & Constraints
48
48
 
49
- ## 1. Hybrid Composition API (Smart Rendering Flow)
49
+ ### Composition Patterns
50
50
 
51
- **Decision:** We fundamentally maintain the exact library composition API structure (`<Accordion>`, `<Accordion.Item>`, `<Accordion.Control>`, `<Accordion.Panel>`) while actively supporting an auto-completing flattened prop schema matching the unified Recursica API (`title`, `leftIcon`, `divider`).
52
- **Implementation:** Avoid rigid raw parameter dumps. Mantine dynamically injects explicit `id` logic, keyboard ARIA mapping, and focus tracking correctly across `Control` to `Panel` DOM connections natively. By exposing the hierarchical mapping 1:1, Recursica safely adopts these capabilities. However, to strictly support Recursica's unified prop mapping interface:
53
-
54
- - **Auto-Construction:** If integrators natively pass `title` and/or `leftIcon` props into `<AccordionItem>`, the component structurally auto-generates the internal `AccordionControl` sub-wrappers mapping the text and SVG natively, whilst treating `children` implicitly as the Panel contents.
55
- - **Graceful Falldown:** If `title` is heavily omitted, the node immediately falls backward into raw Mantine composability expecting integrators mapped `<Accordion.Control>` entirely manually.
56
-
57
- ---
58
-
59
- ## 2. Default Configuration Reset (`unstyled`)
60
-
61
- **Decision:** We strip Mantine's inner styles away completely from Accordion mappings by leveraging React's default `variant="unstyled"`.
62
- **Implementation:** In `Accordion.tsx`, `<MantineAccordion>` binds `variant="unstyled"`. This effectively deletes Mantine's precomputed padding, borders, and shadow mappings allowing our targeted `classNames` inside `Accordion.module.css` to become the exact source of foundational truth without "fighting" `!important` tags or unpredictable flex-layouts inherited globally.
63
-
64
- ---
65
-
66
- ## 3. Strict SVG Icons Wrapper (`.iconLeftWrapper`)
67
-
68
- **Decision:** Identical logic enforced as seen within Buttons: SVG scales dynamically inside `.mantine-leftSection` based on SVGs internal definition boundaries potentially corrupting header gaps.
69
- **Implementation:** `AccordionControl` captures `<span className={styles.iconLeftWrapper} aria-hidden>` forcing `object-fit: contain` mapped exactly to the `properties_icon-left-size` Recursica dimension token forcing integrator SVG overrides inline perfectly.
70
-
71
- ---
72
-
73
- ## 4. Transparent Global Hover Fixes
74
-
75
- **Decision:** We nullify internal Mantine button hover actions and exclusively utilize Recursica's hover structure natively.
76
- **Implementation:** We construct `.control::after` pseudo-objects dynamically pulling our `hover-color` & `hover-opacity` bindings. Mantine's native action sets `.control:hover { background-color: var(...) }` dynamically causing internal layer overlaps. We enforce `background-color: transparent` strictly overriding it, preserving our pseudo-overlay layer-cascade cleanly.
77
-
78
- ---
79
-
80
- ## 5. Active Target Hooks (`[data-active]`)
81
-
82
- **Decision:** Collapsed and expanded state tracking requires separate background maps across `AccordionItem`.
83
- **Implementation:** Rather than syncing React `useState` hooks matching `Accordion.value`, we defer to Mantine's inherent DOM mapping: `.item[data-active]` implicitly triggers exactly when Mantine registers an expansion state swap changing values down dynamically on the element layer, perfectly binding to `--recursica_..._background-expanded`.
84
-
85
- ---
86
-
87
- ## 6. Nullifying Isolated State Bounds (`open` boolean)
88
-
89
- **Decision:** We do not bind isolated `open={true}` state properties natively on individual `<AccordionItem>` configurations.
90
- **Implementation:** Recursica natively dictates an item-level `open` tracking mapping. However, internally mapping boolean flags structurally across specific tree nodes heavily corrupts Mantine's DOM layout algorithms mapping parent-driven transition listeners. Mantine forces all expanded-height logic to run symmetrically off the `<Accordion value="...">` string matching array to accurately bind ARIA transitions. We explicitly ignore isolated item `<AccordionItem open={...}>` booleans to shield the rendering sequence cleanly.
51
+ `Accordion.Item` supports two ways of providing content: pass `title` (and optionally `leftIcon`) directly on `Accordion.Item` for a simple control/panel pair, with `children` treated as the panel content; or omit `title` and compose manually with `Accordion.Control` and `Accordion.Panel` for full control over the control's contents.
@@ -45,22 +45,6 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
45
45
 
46
46
  ## 4. Key Integration Features & Constraints
47
47
 
48
- ## Form Control Wrapping
48
+ ### Known Limitation: Active Option Highlight
49
49
 
50
- The `AutoComplete` component is wrapped using the `WithReadOnlyWrapper` to seamlessly bridge standard `InputWrapperProps` attributes (like `label`, `error`, `assistiveText`) directly onto the macro Recursica `<FormControlWrapper>`.
51
-
52
- ## CSS Layout Execution
53
-
54
- The baseline structure maps identical `HARDCODED VALUES` as standard text inputs (`border-width: 1px`, `display: flex`). The `.root` dynamically overrides the `--input-left-section-size` and `--input-right-section-size` to accurately allocate whitespace for prepended or appended icons natively matching the underlying UI token layout system securely.
55
-
56
- ## Dropdown Styling
57
-
58
- The Mantine `<Autocomplete>` dropdown menu and options are styled strictly using native UI-Kit variables mapping border radii, shadows, and base colors (`.dropdown` and `.option`).
59
-
60
- ## State Cascade Architecture
61
-
62
- Focus, errors, and disabled visual states are enforced explicitly via the outer `<FormControlWrapper>` boundary emitting context down structurally (`[data-error]`, `[data-disabled]`) and evaluated efficiently against scoped nested selectors natively inside `AutoComplete.module.css`.
63
-
64
- ## Missing Active Option Color
65
-
66
- Currently, there is no explicit JSON token for the background color of an active/hovered option in the AutoComplete dropdown. We temporarily map `.option:hover` and `.option[data-combobox-active]` to the `--recursica_ui-kit_components_autocomplete_variants_states_focus_properties_colors_background` variable. Because this variable maps to the base field background, the highlight is currently invisible. This will be updated once the correct token is added to the UI kit.
50
+ The background highlight for a hovered or active option in the dropdown is not currently visible. This will be addressed in a future update.
@@ -36,30 +36,3 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
36
36
  > - **Anti-override protection**: Rogues style injections (like inline `style` or arbitrary `className`) are automatically blocked by our prop layer unless `overStyled={true}` is explicitly provided.
37
37
  > - **No Direct Layers**: Do not pass a `layer` prop to this component. To place it on a specific visual layer, wrap it in a `<Layer layer={0|1|2|3}>` component natively.
38
38
  > - **Variables and Theming**: Styling is entirely determined by local CSS variables defined in `recursica_variables_scoped.css` and mapped in the component's CSS module.
39
-
40
- ---
41
-
42
- ## 4. Key Integration Features & Constraints
43
-
44
- ## Architecture Decisions
45
-
46
- The `Avatar` component is an adapter over Mantine's `Avatar`. To ensure adherence to the `COMPONENT_GUIDE_WALKTHROUGH.md`:
47
-
48
- - We do not wrap `MantineAvatar` in any custom standard `div` elements, preserving DOM structure.
49
- - All styles strictly pull from explicit `--recursica_ui-kit_components_avatar_*` CSS tokens.
50
-
51
- ## Structural Workarounds
52
-
53
- ### Implicit `data-style`
54
-
55
- Mantine's Avatar implicitly renders an image, an icon, or a text node based on the properties passed (`src`, `var`, `children`).
56
- Recursica Tokens split Avatar styling distinctly across three separate categories: `image`, `icon`, and `text`.
57
- To correctly map these variables, our React component observes standard prop states and manually injects a `data-style="image|icon|text"` onto the root. The `Avatar.module.css` explicitly gates padding and generic sizing modifiers under these `data-style` attributes.
58
-
59
- ### Flex Layout & Internal Spans
60
-
61
- Since Avatar children (icons or initials) require robust centering that might differ heavily across Recursica size mappings, all child content defaults to being wrapped in `span` elements (either `.textWrapper` or `.iconWrapper`). These spans enforce 100% height and flex formatting independent of the Mantine container constraints.
62
-
63
- ### CSS Reset Hacks
64
-
65
- Noticeable `/* HARDCODE: ... */` hacks are deployed within `.root` to completely zero-out Mantine's `--avatar-bg` and internal variables statically since Recursica handles background-colors inherently via the CSS variants cascade.
@@ -43,16 +43,13 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
43
43
 
44
44
  ## 4. Key Integration Features & Constraints
45
45
 
46
- ## 1. Stripped Size Properties
46
+ ## 1. Sizing
47
47
 
48
- As logged in `COMPONENT_ISSUES.md`, there are currently no Figma variables mapped for different `size` variants (`small`, `default`, etc.). The component explicitly `Omit`s the Mantine `size` property from its signature to prevent integrators from attempting to drive sizes that do not exist in the tokens.
48
+ Badge does not currently support a `size` prop; badges render at a single fixed size.
49
49
 
50
50
  ## 2. Intent-Based Variants
51
51
 
52
- Mantine supports multiple visual variants (`outline`, `filled`, `light`). However, the existing variable schema for `Badge` only defines "Styles" which act as intents (`alert`, `primary-color`, `success`, `warning`).
53
-
54
- - Default is arbitrarily mapped to `primary-color` as we lack a pure `neutral` schema right now.
55
- - `variant` mapped to underlying Mantine prop has been hardcoded to `filled`, since the Recursica coloring fully replaces the Mantine DOM.
52
+ Badge exposes a `variant` prop with intent-based values (`alert`, `primary-color`, `success`, `warning`) rather than Mantine's `outline`/`filled`/`light` style options. The default variant is `primary-color`.
56
53
 
57
54
  ## 3. The `overStyled` Prop
58
55
 
@@ -47,11 +47,7 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
47
47
 
48
48
  ## Missing Variants and Sizes
49
49
 
50
- The `Breadcrumb` currently does not establish any size (`xs`, `sm`, etc.) or variant variables in the underlying design tokens (`recursica_ui-kit.json`). Only basic structural definitions for `padding` and `item-gap` exist. Because of this, the `size` and `variant` properties have been explicitly omitted from the passed mantine props.
51
-
52
- ## Gap Styling
53
-
54
- We attach `gap` to `.root` directly within `.module.css`. Mantine's inner `separator` divs can natively accept our CSS variables for structural layout.
50
+ Breadcrumb does not currently support `size` or `variant` props.
55
51
 
56
52
  ## Composition and Separators
57
53
 
@@ -101,6 +101,11 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
101
101
  const finalClass = classNameProp
102
102
  ? `${styles.root} ${classNameProp}`
103
103
  : styles.root;
104
+ // className is merged explicitly above — don't let the {...sanitizedProps} spread below
105
+ // silently overwrite finalClass with just the caller's own class (same bug class as
106
+ // mantine-adapter's Dropdown/BareDropdown.tsx had). Masked here today since `classNames.root`
107
+ // (a separate prop, unaffected) also carries styles.root — but a real latent bug regardless.
108
+ delete restRecord["className"];
104
109
 
105
110
  const userLoaderProps = restRecord.loaderProps as
106
111
  | Record<string, unknown>
@@ -59,3 +59,11 @@ Mantine's `.mantine-Button-label` flex centering breaks primitive truncation log
59
59
  **Decision:** When `loading={true}` is passed to the Button, the component explicitly forces `disabled={true}` natively on the underlying element.
60
60
 
61
61
  **Implementation:** This ensures that loading buttons automatically inherit the brand theme disabled opacities (via the `:disabled` CSS pseudo-class) rather than relying solely on Mantine's native `data-disabled` dataset logic, which may not trigger the strict visual fade required by the Recursica design system.
62
+
63
+ ---
64
+
65
+ ## `className` overwrite bug (Matt Massey, 2026-08-08)
66
+
67
+ **Bug:** with `overStyled` and a custom `className` (surfaced by Tree embedding a `Button` for its expand chevron — see `Tree/IMPLEMENTATION_NOTES.md`), `finalClass` (`` `${styles.root} ${classNameProp}` ``) was set explicitly on `<MantineButton>`, but `{...sanitizedProps}` was spread _after_ it — and `sanitizedProps` still contained the original, unmodified `className` key, since it had only been _read_, never deleted. The later spread would silently overwrite `finalClass` with just the caller's own class. Same bug class as `Dropdown.tsx`/`BareDropdown.tsx` had.
68
+
69
+ **Why it wasn't visible here:** `classNames={{root: mergedClassNames.root, ...}}` is a _separate_ Mantine prop from the plain `className` string, unaffected by the overwrite, and `mergedClassNames.root` always includes `styles.root` independently — so the root element kept its Recursica styling regardless of the bug. mui-adapter's equivalent `Button.tsx` has no such secondary path (`@mui/material` only has a plain `className`), so the identical mistake there was fully visible (chevron rendered in MUI's own default color). Fixed in both regardless, since this is a real latent bug independent of whether it happens to be masked today.
@@ -45,37 +45,19 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
45
45
 
46
46
  ## Icon size: Recursica defines it
47
47
 
48
- **Decision:** Icon size is **not** left to the developer. Recursica defines it via the design tokens; the Button enforces it so callers cannot pass an arbitrarily sized icon.
49
-
50
- **Implementation:**
51
-
52
- - When `icon` is provided, the Button wraps it in a single element with class `iconWrapper` before passing it to Mantine’s `leftSection`.
53
- - In `Button.module.css`, `.iconWrapper` has explicit `width` and `height` from the Recursica tokens mapping natively based on `data-size`.
54
- - The rule `.iconWrapper > *` sets `width: 100%`, `height: 100%`, and `object-fit: contain` so whatever the caller passes scales cleanly with the token constraints.
48
+ Icon size is **not** left to the developer. Recursica defines it via the design tokens, so callers cannot pass an arbitrarily sized icon — whatever is passed in `icon` is scaled to fit the token-defined dimensions for the button's size.
55
49
 
56
50
  ---
57
51
 
58
- ## Icon-only buttons: accessibility and width
59
-
60
- **Decision:** When the button has an icon and no visible label (icon-only), callers must provide an accessible name, and the button must not show extra space to the right of the icon.
61
-
62
- **Accessibility:** We document that icon-only buttons must pass `aria-label` (e.g. `aria-label="Submit"`). In development we log a console warning if `icon` is set, `children` is empty, and `aria-label` is missing.
52
+ ## Icon-only buttons: accessibility
63
53
 
64
- **Width:** Mantine’s layout natively applies structural section gaps. We detect icon-only and set a `data-icon-only` hook so that `Button.module.css` zeros out the sections spacing allowing the button to precisely hit `min-width` perfectly centered.
54
+ When the button has an icon and no visible label (icon-only), callers must provide an accessible name via `aria-label` (e.g. `aria-label="Submit"`). In development, a console warning is logged if `icon` is set, `children` is empty, and `aria-label` is missing.
65
55
 
66
56
  ---
67
57
 
68
58
  ## Label truncation at max-width
69
59
 
70
- **Decision:** When the button hits its Recursica max-width (500px), the label truncates with an ellipsis instead of wrapping.
71
-
72
- **Implementation:**
73
- Mantine's `.mantine-Button-label` flex centering breaks primitive truncation logic. To combat this:
74
-
75
- - **`.root`** has `overflow: hidden`.
76
- - **`.root > *`** forces `min-width: 0`.
77
- - The structural children wrap into `<span className={styles.labelText}>`.
78
- - **`.labelText`** binds `overflow: hidden; text-overflow: ellipsis; white-space: nowrap;` creating flawless string cutoffs strictly at exact UI constraints.
60
+ When the button hits its Recursica max-width (500px), the label truncates with an ellipsis instead of wrapping.
79
61
 
80
62
  ---
81
63
 
@@ -95,6 +77,4 @@ Mantine's `.mantine-Button-label` flex centering breaks primitive truncation log
95
77
 
96
78
  ## Loading state enforces disabled state
97
79
 
98
- **Decision:** When `loading={true}` is passed to the Button, the component explicitly forces `disabled={true}` natively on the underlying element.
99
-
100
- **Implementation:** This ensures that loading buttons automatically inherit the brand theme disabled opacities (via the `:disabled` CSS pseudo-class) rather than relying solely on Mantine's native `data-disabled` dataset logic, which may not trigger the strict visual fade required by the Recursica design system.
80
+ When `loading={true}` is passed to the Button, it is also treated as disabled — the button cannot be interacted with while loading.
@@ -54,18 +54,10 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
54
54
 
55
55
  ## 4. Key Integration Features & Constraints
56
56
 
57
- ## Architecture Overrides
57
+ ## Header & Footer Styling
58
58
 
59
- Because Mantine natively constructs `Card` bounding boxes with generic inner `<Card.Section>` elements that depend on explicit user styling (and lack implicit native designations for "Header" vs "Footer"), we implemented an explicit component wrapper departure:
59
+ `Card.Header` and `Card.Footer` automatically receive their background color and padding from the design system; no manual styling is needed.
60
60
 
61
- - `<Card.Header>` explicitly hooks `--recursica_ui-kit_components_card_properties_header-background` and corresponding padding variables.
62
- - `<Card.Footer>` explicitly hooks `--recursica_ui-kit_components_card_properties_footer-background` and corresponding padding variables.
61
+ ## Layout Alignment
63
62
 
64
- Mantine's generic `<Card.Section>` calculates negative margins implicitly. Because of this, it is crucial that our local CSS module declares `--card-padding: var(--recursica_ui-kit_components_card_properties_padding)` directly on `.root` so that all generic or explicit section wrappers natively stretch across the bounding box properly.
65
-
66
- ## Layout Alignment Exceptions
67
-
68
- To allow Cards to fit cleanly inside dynamic/flex layouts (like dashboard panels, grid tracks, or sidebar layout segments), the Card wrapper implements a custom gatekeeper bypass for outer styling properties:
69
-
70
- - Exposes a safe subset of flexbox/dimensions styling properties (`flex`, `flexGrow`, `flexShrink`, `flexBasis`, `grow`, `h`, `height`) on the root `<Card>` component to allow proper sizing alongside layout siblings.
71
- - Sets `<Card.Content>` to `flex-grow: 1;` by default via CSS modules. Since the root `<Card>` has `display: flex; flex-direction: column;`, this makes the content area expand to fill all vertical space, pushing `<Card.Footer>` to align at the absolute bottom of the bounding box.
63
+ `Card` accepts a safe subset of flexbox/sizing props (`flex`, `flexGrow`, `flexShrink`, `flexBasis`, `grow`, `h`, `height`) so it can participate cleanly in flex or grid layouts (dashboard panels, grid tracks, sidebar segments, etc.). `Card.Content` grows to fill available vertical space, which keeps `Card.Footer` pinned to the bottom of the card.
@@ -39,26 +39,10 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
39
39
 
40
40
  ## 4. Key Integration Features & Constraints
41
41
 
42
- ## Architectural Philosophy
42
+ ### Checkbox Alignment with Multi-line Labels
43
43
 
44
- The `Checkbox` primitive requires aggressive structural modifications to decouple Mantine's built-in arrays (`<Checkbox.Group>`) tying the raw DOM nodes seamlessly back into Recursica's unified form definitions flawlessly.
44
+ Checkbox visually aligns to the first line of a wrapping, multi-line label.
45
45
 
46
- ### Checkbox Alignment Anchoring (gap overflow hack)
46
+ ### Checkbox.Group
47
47
 
48
- By default, placing a Checkbox alongside a deeply wrapping multi-line label causes native geometric drifts natively. Because Mantine aligns the Checkbox graphic to the top of standard `display: flex` boxes, its optical center will appear natively skewed slightly _too high_ against the very first typographic text-line.
49
-
50
- We mathematically fix this alignment within `Checkbox.module.css` using explicit design variable arithmetic natively.
51
- \`\`\`css
52
- margin-top: calc(
53
- (
54
- var(--recursica_ui-kit_components_checkbox-item_properties_text_line-height) \*
55
- var(--recursica_ui-kit_components_checkbox-item_properties_text_font-size) -
56
- var(--recursica_ui-kit_components_checkbox_properties_size)
57
- ) / 2
58
- ) !important;
59
- \`\`\`
60
- This calculation ensures that the optical center of the `.inner` checkmark vector perfectly snaps onto the relative center of the text's line-height, permanently solving pixel-drifts natively!
61
-
62
- ### Checkbox.Group Overrides
63
-
64
- To decouple `<CheckboxGroup>` away from `<Input.Wrapper>`, we explicitly extract the raw array execution mapped correctly against our identical `RecursicaFormControlWrapperProps` schema structurally!
48
+ `Checkbox.Group` integrates with the same label, description, and error props as other Recursica form components.
@@ -39,39 +39,10 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
39
39
 
40
40
  ## 4. Key Integration Features & Constraints
41
41
 
42
- ## Architecture decisions
43
-
44
- ### Mantine DOM Structure & Label Overrides
45
-
46
- Mantine's `<Chip>` behaves like an input element (`radio` or `checkbox`). Under the hood, it renders:
47
-
48
- 1. `.mantine-Chip-root` (wrapper)
49
- 2. `input` (hidden visual structure)
50
- 3. `.mantine-Chip-label` (The actual visible button-like pill)
51
-
52
- Because the `.label` is the primary visual surface and handles Mantine's built-in `:hover` and active states, we direct our Recursica styling natively to `.label`.
53
-
54
- ### Icon and Remove Implementations
55
-
56
- To achieve this without breaking Mantine's `Chip` input architecture, we wrapped the internal `children` using a standard `span` DOM strategy:
57
-
58
- ```tsx
59
- <span className={styles.innerWrapper}>
60
- {icon}
61
- <span className={styles.children}>{children}</span>
62
- {onRemove}
63
- </span>
64
- ```
65
-
66
- #### Intermediate Children Wrapper Span display fix:
67
-
68
- Mantine internally wraps the children passed to `Chip` in a default `<span>` which has `display: inline`. This intermediate `span` inherits the `line-height` of the `.label` container, causing the computed height of the Chip to be ~2px taller than expected. To address this, `.label > span:not(.mantineIconWrapper)` is targeted to force `display: inline-flex; align-items: center;` on that intermediate wrapper `span`, allowing it to collapse perfectly to the `16px` height of the `innerWrapper`.
69
-
70
42
  ### Accessibility of Remove Action
71
43
 
72
- Because the Chip fundamentally functions as a `<label>` linked to an `<input>`, placing a raw interactive element like `<button>` directly inside the standard Chip sub-tree violates nested interactive element ARIA constraints in strict validators.
73
- To accommodate this, the visual "close" icon uses a `<span>` element configured with `role="button"` and `tabIndex={0}` to hook into standard keyboard activations without triggering generic nested `<form>` conflicts native to Mantine's baseline constraints.
44
+ The remove/close action is keyboard accessible — it can be activated via keyboard as well as mouse click.
74
45
 
75
- ### Removing Sizing Properties
46
+ ### Sizing
76
47
 
77
- During implementation, the parsed Figma design tokens natively exported specific height/padding vectors dynamically (e.g., `--recursica_ui-kit_components_chip_properties_icon-size`) rather than explicit string variants (`sm`, `md`, `lg`). Therefore, we omitted `size` conceptually from the `RecursicaChipProps` wrapper to lock down size evaluation natively against the active layer variables.
48
+ Chip does not support a `size` prop; chips render at a fixed size.
@@ -39,17 +39,7 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
39
39
 
40
40
  ## 4. Key Integration Features & Constraints
41
41
 
42
- ## Architecture Overview
43
-
44
- The `DatePicker` component is a wrapper around the `@mantine/dates` `DatePickerInput` component, implementing the `FormControlWrapper` macro structure for Recursica. This ensures that the component visually and structurally aligns with standard Recursica input primitives.
45
-
46
42
  ## Structural Constraints
47
43
 
48
- 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.
49
- 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.
50
- 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.
51
-
52
- ## Styling Quirks
53
-
54
- - The `DatePickerInput` mimics Mantine's `Input` structure natively (`.input`, `.wrapper`, `.section`). We attach our `styles.input` and `styles.root` classes exactly like `TextField`.
55
- - The global layout margin override (`.layoutOverride`) utilizes `--form-control-margin-bottom` driven by specific stacked/side-by-side design tokens.
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.
@@ -0,0 +1,85 @@
1
+ import { forwardRef } from "react";
2
+ import {
3
+ Select as MantineSelect,
4
+ type SelectProps as MantineSelectProps,
5
+ } from "@mantine/core";
6
+ import {
7
+ filterStylingProps,
8
+ type RecursicaOverStyled,
9
+ } from "../../utils/filterStylingProps";
10
+ import styles from "./Dropdown.module.css";
11
+
12
+ /**
13
+ * Bare, unwrapped Select — no `FormControlWrapper`/`WithReadOnlyWrapper`, no label/assistiveText/
14
+ * error/required. Tied to the same `Dropdown.module.css` variables/classes as the public
15
+ * `Dropdown` component, so it looks identical, but is meant to be embedded inside another
16
+ * component that already owns its own `FormControlWrapper` (e.g. `TimePicker`'s AM/PM control) —
17
+ * nesting the full `Dropdown` there would double up `FormControl`/`FormControlLayout` wrapping.
18
+ *
19
+ * Not exported from this folder's `index.ts` — internal use only. Import it directly:
20
+ * `import { BareDropdown } from "../Dropdown/BareDropdown"`.
21
+ */
22
+ export interface BareDropdownProps
23
+ extends Omit<
24
+ MantineSelectProps,
25
+ | "size"
26
+ | "variant"
27
+ | "radius"
28
+ | "wrapperProps"
29
+ | "label"
30
+ | "description"
31
+ | "error"
32
+ > {
33
+ data: MantineSelectProps["data"];
34
+ /** Applies the error visual state (via `data-error`) — no error message is rendered here. */
35
+ error?: boolean;
36
+ }
37
+
38
+ export type BareDropdownComponentProps = RecursicaOverStyled<BareDropdownProps>;
39
+
40
+ export const BareDropdown = forwardRef<
41
+ HTMLInputElement,
42
+ BareDropdownComponentProps
43
+ >(function BareDropdown(props, ref) {
44
+ const { overStyled = false, className, disabled, error, ...rest } = props;
45
+ const sanitizedProps = filterStylingProps(rest, overStyled);
46
+ const restRecord = sanitizedProps as Record<string, unknown>;
47
+
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
+ };
59
+
60
+ return (
61
+ <MantineSelect
62
+ ref={ref}
63
+ classNames={mergedClassNames}
64
+ disabled={disabled}
65
+ label={undefined}
66
+ description={undefined}
67
+ error={undefined}
68
+ // `wrapperProps` targets Mantine's *outer* `Input.Wrapper` (the label/description/error
69
+ // stacking element) — a different, ancestor element from the "wrapper" styles-api slot that
70
+ // actually carries `styles.root`'s border/background. Dropdown.module.css's error/disabled
71
+ // rules (`.root[data-error]`/`[data-disabled]`) need the attribute on that inner element, so
72
+ // `attributes.wrapper` (which targets the same slot as `classNames.wrapper`/`styles.wrapper`)
73
+ // is the correct hook here, not `wrapperProps`. See TIMEPICKER_IMPLEMENTATION_NOTES.md.
74
+ attributes={{
75
+ wrapper: {
76
+ "data-disabled": disabled ? "true" : undefined,
77
+ "data-error": error ? "true" : undefined,
78
+ },
79
+ }}
80
+ {...(sanitizedProps as unknown as MantineSelectProps)}
81
+ />
82
+ );
83
+ });
84
+
85
+ BareDropdown.displayName = "BareDropdown";
@@ -156,9 +156,18 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
156
156
  error={undefined}
157
157
  required={undefined}
158
158
  withAsterisk={undefined}
159
- wrapperProps={{
160
- "data-disabled": disabled ? "true" : undefined,
161
- "data-error": error ? "true" : undefined,
159
+ // `wrapperProps` targets Mantine's *outer* `Input.Wrapper` (the label/description/error
160
+ // stacking element) — a different, ancestor element from the "wrapper" styles-api slot
161
+ // that actually carries `styles.root`'s border/background. Dropdown.module.css's error/
162
+ // disabled rules (`.root[data-error]`/`[data-disabled]`) need the attribute on that
163
+ // inner element, so `attributes.wrapper` (which targets the same slot as
164
+ // `classNames.wrapper`) is the correct hook — `wrapperProps` here meant the error state
165
+ // never actually applied a border color.
166
+ attributes={{
167
+ wrapper: {
168
+ "data-disabled": disabled ? "true" : undefined,
169
+ "data-error": error ? "true" : undefined,
170
+ },
162
171
  }}
163
172
  {...(sanitizedProps as unknown as MantineSelectProps)}
164
173
  />
@@ -42,10 +42,6 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
42
42
 
43
43
  ---
44
44
 
45
- ## 4. Key Integration Features & Constraints
45
+ ## 4. Notes
46
46
 
47
- The `Dropdown` component is mapped explicitly to Mantine's `<Select>` following the exact same strict encapsulation rules as `TextField`.
48
-
49
- 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`.
50
- 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.
51
- 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`.
47
+ - `Dropdown` is styled independently from `TextField`; even though they look similar, they are themed using separate design tokens.
@@ -45,4 +45,4 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
45
45
  ## 4. Key Integration Features & Constraints
46
46
 
47
47
  The `Flex` component is a generic unopinionated flex layout wrapper mapped directly to Mantine's `Flex`.
48
- It currently does not require any custom logical layouts or CSS workarounds since it serves only to provide absolute, raw manipulation of standard CSS flex properties. All spacing props (gap, align, justify, direction, wrap) pass safely through via the `filterStylingProps` layout-property allowance, with `rec-` dimension tokens scaling transparently mapped to standard gap limits.
48
+ All spacing props (`gap`, `align`, `justify`, `direction`, `wrap`) pass through as normal, with `rec-` dimension tokens mapped transparently to standard gap values.