@recursica/mantine-adapter 0.36.1 → 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 (61) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/mantine-adapter.cjs +2 -2
  3. package/dist/mantine-adapter.cjs.map +1 -1
  4. package/dist/mantine-adapter.css +1 -1
  5. package/dist/mantine-adapter.js +2352 -2099
  6. package/dist/mantine-adapter.js.map +1 -1
  7. package/dist/src/components/Dropdown/BareDropdown.d.ts +19 -0
  8. package/dist/src/components/TimePicker/TimePicker.d.ts +9 -3
  9. package/dist/src/components/Tree/Tree.d.ts +5 -0
  10. package/dist/src/index.d.ts +1 -1
  11. package/package.json +1 -1
  12. package/src/components/Accordion/USAGE.md +2 -41
  13. package/src/components/AutoComplete/USAGE.md +2 -18
  14. package/src/components/Avatar/USAGE.md +0 -27
  15. package/src/components/Badge/USAGE.md +3 -6
  16. package/src/components/Breadcrumb/USAGE.md +1 -5
  17. package/src/components/Button/Button.tsx +5 -0
  18. package/src/components/Button/IMPLEMENTATION_NOTES.md +8 -0
  19. package/src/components/Button/USAGE.md +5 -25
  20. package/src/components/Card/USAGE.md +4 -12
  21. package/src/components/Checkbox/USAGE.md +4 -20
  22. package/src/components/Chip/USAGE.md +3 -32
  23. package/src/components/DatePicker/USAGE.md +2 -12
  24. package/src/components/Dropdown/BareDropdown.tsx +85 -0
  25. package/src/components/Dropdown/Dropdown.tsx +12 -3
  26. package/src/components/Dropdown/USAGE.md +2 -6
  27. package/src/components/Flex/USAGE.md +1 -1
  28. package/src/components/FormControlWrapper/USAGE.md +3 -31
  29. package/src/components/Grid/USAGE.md +1 -1
  30. package/src/components/Group/USAGE.md +1 -1
  31. package/src/components/HoverCard/USAGE.md +3 -72
  32. package/src/components/Label/USAGE.md +8 -48
  33. package/src/components/Link/USAGE.md +4 -10
  34. package/src/components/Loader/USAGE.md +4 -23
  35. package/src/components/Menu/USAGE.md +3 -73
  36. package/src/components/Modal/USAGE.md +3 -3
  37. package/src/components/NumberInput/USAGE.md +5 -8
  38. package/src/components/Pagination/USAGE.md +0 -19
  39. package/src/components/Panel/USAGE.md +6 -95
  40. package/src/components/Popover/USAGE.md +6 -66
  41. package/src/components/ReadOnlyField/USAGE.md +2 -10
  42. package/src/components/SegmentedControl/USAGE.md +1 -15
  43. package/src/components/Slider/USAGE.md +1 -45
  44. package/src/components/Stack/USAGE.md +1 -1
  45. package/src/components/Switch/USAGE.md +1 -24
  46. package/src/components/TextArea/USAGE.md +2 -2
  47. package/src/components/TextField/USAGE.md +1 -17
  48. package/src/components/TimePicker/TIMEPICKER_IMPLEMENTATION_NOTES.md +72 -0
  49. package/src/components/TimePicker/TimePicker.module.css +225 -41
  50. package/src/components/TimePicker/TimePicker.stories.tsx +105 -4
  51. package/src/components/TimePicker/TimePicker.tsx +287 -7
  52. package/src/components/TimePicker/USAGE.md +23 -2
  53. package/src/components/Timeline/USAGE.md +2 -10
  54. package/src/components/Toast/USAGE.md +3 -33
  55. package/src/components/Tooltip/USAGE.md +7 -51
  56. package/src/components/Tree/IMPLEMENTATION_NOTES.md +31 -3
  57. package/src/components/Tree/Tree.module.css +84 -40
  58. package/src/components/Tree/Tree.stories.tsx +13 -0
  59. package/src/components/Tree/Tree.tsx +116 -27
  60. package/src/components/Tree/USAGE.md +18 -1
  61. 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';
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.1",
16
+ "version": "0.38.0",
17
17
  "type": "module",
18
18
  "main": "./dist/mantine-adapter.cjs",
19
19
  "module": "./dist/mantine-adapter.js",
@@ -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.
@@ -41,35 +41,7 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
41
41
 
42
42
  ---
43
43
 
44
- ## 4. Key Integration Features & Constraints
44
+ ## 4. Notes
45
45
 
46
- ## Architectural Philosophy
47
-
48
- The `FormControlWrapper` is the ultimate structural replacement for Mantine's built-in `Input.Wrapper`. By abandoning Mantine's opinionated wrappers entirely across the design system, we centralize all label tracking, error rendering, ARIA generation, and grid layouts natively inside this single component.
49
-
50
- ### 1. Bypassing `Input.Wrapper`
51
-
52
- Under the hood of Mantine, elements like `TextInput` heavily rely on `Input.Wrapper`. We actively discourage their use. The central tenet of Recursica Forms is to strip the UI primitive back to its "naked" form (e.g. `<Input />`, `<Checkbox />`) and encapsulate it manually inside `<FormControlWrapper>`.
53
-
54
- - **Why?** It enforces complete layout mastery. It natively enables `formLayout="side-by-side"` and completely disables Mantine's margin collisions without requiring messy CSS hacks.
55
-
56
- ### 2. The `cloneElement` ARIA Map
57
-
58
- Because we tore out Mantine's `InputContext` provider (which natively glued error strings to `<input>` tags using React Context), we explicitly utilize `React.cloneElement` on the nested children inside this wrapper.
59
-
60
- - `aria-describedby` and `aria-errormessage` are dynamically generated using `React.useId()` and physically injected back onto the provided child node. Screen readers rely strictly on this mapping to announce the assistive fields correctly.
61
-
62
- ### 3. Strict `AssistiveElement` Coupling
63
-
64
- We completely abandoned generic `<Input.Description>` tags. The `FormControlWrapper` directly renders `<AssistiveElement>` primitives, parsing them seamlessly mapping them to `"error"` or `"help"` variants automatically depending on the component's internal state machine.
65
-
66
- ### 4. Dynamic Geometric Variable Payloads
67
-
68
- Because `FormControlWrapper` acts as an agnostic grid box encompassing raw primitives (like `TextField`, `Select`), it initially stretches `100%` across horizontal bounds.
69
-
70
- - **The Bug:** If a child `TextField` carries its own hardcoded `max-width` token, it stops expanding early, but the wrapper and `<Label>` keep expanding, causing right-aligned labels to aggressively float past the field to the screen's edge dynamically.
71
- - **The Variable Payload Resolution:** Instead of destroying grids with `width: fit-content` arrays, primitive components are required to pass their local `max-width` tokens UP to the wrapper explicitly via React `style`:
72
- ```tsx
73
- <FormControlWrapper style={{ "--form-control-max-width": "var(--...)" }}>
74
- ```
75
- The wrapper natively respects `max-width: var(--form-control-max-width, 100%)`. This structurally unifies the bounding caps so right-aligned labels flawlessly snap tightly to the explicit boundary edge of the specific primitive it is wrapping.
46
+ - `FormControlWrapper` renders the label, error, and help text around the child input; screen-reader associations between the input and its assistive text are handled automatically.
47
+ - The wrapper stretches to fill its container by default. If the wrapped input has its own maximum width, pass a matching `--form-control-max-width` CSS variable via the `style` prop so the label and assistive text align to the same width as the input.
@@ -36,7 +36,7 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
36
36
 
37
37
  > [!IMPORTANT]
38
38
  >
39
- > - **Anti-override protection**: `Grid` is a primitive layout component (see [OVERSTYLING.md](../../../OVERSTYLING.md)) and is exempt from the `RecursicaOverStyled` gatekeeper, so standard Mantine layout props pass through freely.
39
+ > - **Anti-override protection**: `Grid` is a primitive layout component (see [OVERSTYLING.md](../../../OVERSTYLING.md)), so standard Mantine layout props pass through freely without needing `overStyled`.
40
40
  > - **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.
41
41
  > - **Variables and Theming**: Spacing is entirely determined by the `rec-*` token scale, mapped transparently to standard Mantine gutter values.
42
42