@recursica/mui-adapter 0.24.0 → 0.26.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 (72) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/index.d.ts +189 -28
  3. package/dist/mui-adapter.cjs +85 -85
  4. package/dist/mui-adapter.cjs.map +1 -1
  5. package/dist/mui-adapter.css +1 -1
  6. package/dist/mui-adapter.js +29694 -24393
  7. package/dist/mui-adapter.js.map +1 -1
  8. package/llms.txt +1 -0
  9. package/package.json +1 -1
  10. package/src/GlobalExemptions.modules.css +0 -6
  11. package/src/components/Accordion/Accordion.module.css +0 -8
  12. package/src/components/AssistiveElement/AssistiveElement.tsx +1 -1
  13. package/src/components/Autocomplete/Autocomplete.module.css +0 -8
  14. package/src/components/Avatar/Avatar.module.css +0 -8
  15. package/src/components/Button/BUTTON_IMPLEMENTATION_NOTES.md +12 -0
  16. package/src/components/Button/Button.module.css +6 -18
  17. package/src/components/Checkbox/Checkbox.tsx +4 -1
  18. package/src/components/Chip/Chip.module.css +0 -27
  19. package/src/components/DatePicker/DATEPICKER_IMPLEMENTATION_NOTES.md +29 -0
  20. package/src/components/DatePicker/DatePicker.icons.tsx +35 -0
  21. package/src/components/DatePicker/DatePicker.module.css +451 -42
  22. package/src/components/DatePicker/DatePicker.stories.tsx +23 -25
  23. package/src/components/DatePicker/DatePicker.tsx +229 -47
  24. package/src/components/DatePicker/USAGE.md +14 -1
  25. package/src/components/Dropdown/Dropdown.module.css +0 -7
  26. package/src/components/FileInput/FileInput.module.css +0 -21
  27. package/src/components/FileInput/FileInput.tsx +6 -0
  28. package/src/components/FileUpload/FileUpload.module.css +0 -11
  29. package/src/components/HoverCard/HoverCard.module.css +1 -7
  30. package/src/components/Label/Label.module.css +0 -6
  31. package/src/components/Link/Link.module.css +0 -13
  32. package/src/components/Menu/Menu.module.css +0 -5
  33. package/src/components/Modal/Modal.module.css +0 -11
  34. package/src/components/NumberInput/NumberInput.module.css +0 -7
  35. package/src/components/Pagination/Pagination.module.css +0 -81
  36. package/src/components/Popover/IMPLEMENTATION_NOTES.md +22 -0
  37. package/src/components/Popover/Popover.module.css +118 -0
  38. package/src/components/Popover/Popover.stories.tsx +133 -0
  39. package/src/components/Popover/Popover.tsx +275 -0
  40. package/src/components/Popover/USAGE.md +69 -0
  41. package/src/components/Popover/index.ts +1 -0
  42. package/src/components/SegmentedControl/IMPLEMENTATION_NOTES.md +6 -0
  43. package/src/components/SegmentedControl/SegmentedControl.module.css +32 -11
  44. package/src/components/SegmentedControl/SegmentedControl.tsx +18 -4
  45. package/src/components/Slider/IMPLEMENTATION_NOTES.md +29 -0
  46. package/src/components/Slider/Slider.module.css +80 -17
  47. package/src/components/Slider/Slider.stories.tsx +1 -1
  48. package/src/components/Slider/Slider.tsx +36 -1
  49. package/src/components/Stepper/IMPLEMENTATION_NOTES.md +54 -0
  50. package/src/components/Stepper/Stepper.module.css +139 -106
  51. package/src/components/Stepper/Stepper.tsx +76 -10
  52. package/src/components/Stepper/USAGE.md +4 -0
  53. package/src/components/Tabs/IMPLEMENTATION_NOTES.md +12 -0
  54. package/src/components/Tabs/Tabs.module.css +107 -24
  55. package/src/components/Tabs/Tabs.tsx +1 -0
  56. package/src/components/TextArea/TextArea.module.css +20 -11
  57. package/src/components/TextArea/TextArea.tsx +12 -23
  58. package/src/components/TextField/TextField.module.css +0 -8
  59. package/src/components/TimePicker/TimePicker.module.css +0 -16
  60. package/src/components/Timeline/IMPLEMENTATION_NOTES.md +24 -3
  61. package/src/components/Timeline/Timeline.module.css +55 -73
  62. package/src/components/Timeline/Timeline.tsx +23 -27
  63. package/src/components/Timeline/TimelineItem.tsx +38 -35
  64. package/src/components/Toast/Toast.module.css +0 -7
  65. package/src/components/Tooltip/Tooltip.module.css +0 -9
  66. package/src/components/TransferList/TRANSFERLIST_IMPLEMENTATION_NOTES.md +140 -0
  67. package/src/components/TransferList/TransferList.module.css +174 -38
  68. package/src/components/TransferList/TransferList.stories.tsx +110 -6
  69. package/src/components/TransferList/TransferList.tsx +417 -8
  70. package/src/components/TransferList/USAGE.md +37 -6
  71. package/src/components/index.ts +1 -0
  72. package/src/index.ts +3 -0
@@ -1,47 +1,229 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- import React from "react";
3
- import "./DatePicker.module.css";
4
- import { type RecursicaDatePickerProps } from "@recursica/adapter-common";
5
-
6
- export type DatePickerProps = Omit<
7
- React.HTMLAttributes<HTMLDivElement>,
8
- "value" | "onChange" | "defaultValue"
9
- > &
10
- RecursicaDatePickerProps & {
11
- disabled?: boolean;
12
- label?: React.ReactNode;
13
- assistiveText?: string;
14
- error?: React.ReactNode;
15
- required?: boolean;
16
- withAsterisk?: boolean;
17
- placeholder?: string;
18
- value?: Date | null;
19
- defaultValue?: Date | null;
20
- onChange?: (value: Date | null) => void;
21
- readOnly?: boolean;
22
- formLayout?: "stacked" | "side-by-side";
23
- leftSection?: React.ReactNode;
24
- labelWithEditIcon?: boolean;
25
- onLabelEditClick?: () => void;
26
- };
27
-
28
- export const DatePicker: React.FC<DatePickerProps> = ({
29
- disabled,
30
- label,
31
- assistiveText,
32
- error,
33
- required,
34
- withAsterisk,
35
- placeholder,
36
- value,
37
- defaultValue,
38
- onChange,
39
- readOnly,
40
- formLayout,
41
- leftSection,
42
- labelWithEditIcon,
43
- onLabelEditClick,
44
- ...props
45
- }) => {
46
- return <div {...props}>DatePicker</div>;
47
- };
1
+ import React, { forwardRef, useEffect, useState } from "react";
2
+ import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
3
+ import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
4
+ import {
5
+ DatePicker as MuiDatePicker,
6
+ type DatePickerProps as MuiDatePickerProps,
7
+ type DatePickerSlotProps,
8
+ type DatePickerSlots,
9
+ } from "@mui/x-date-pickers/DatePicker";
10
+ import dayjs, { type Dayjs } from "dayjs";
11
+ import { type ReadOnlyControlProps } from "@recursica/adapter-common";
12
+ import {
13
+ filterStylingProps,
14
+ type RecursicaOverStyled,
15
+ } from "../../utils/filterStylingProps";
16
+ import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
17
+ import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
18
+ import { CalendarIcon } from "./DatePicker.icons";
19
+ import styles from "./DatePicker.module.css";
20
+
21
+ import { type RecursicaDatePickerProps as BaseRecursicaDatePickerProps } from "@recursica/adapter-common";
22
+
23
+ // Default display/entry format (dayjs format string), matching the mantine-adapter's DatePicker.
24
+ const DATE_FORMAT = "MM/DD/YY";
25
+
26
+ function toDayjs(value: Date | null | undefined): Dayjs | null {
27
+ return value ? dayjs(value) : null;
28
+ }
29
+
30
+ export interface RecursicaDatePickerProps
31
+ extends Omit<
32
+ MuiDatePickerProps,
33
+ | "value"
34
+ | "defaultValue"
35
+ | "onChange"
36
+ | "minDate"
37
+ | "maxDate"
38
+ | "format"
39
+ | "slots"
40
+ | "slotProps"
41
+ >,
42
+ Pick<
43
+ RecursicaFormControlWrapperProps,
44
+ | "label"
45
+ | "error"
46
+ | "required"
47
+ | "id"
48
+ | "assistiveText"
49
+ | "assistiveWithIcon"
50
+ | "formLayout"
51
+ | "labelSize"
52
+ | "labelAlignment"
53
+ | "labelOptionalText"
54
+ | "labelWithEditIcon"
55
+ | "onLabelEditClick"
56
+ >,
57
+ ReadOnlyControlProps,
58
+ BaseRecursicaDatePickerProps {
59
+ /** Selected date, matching the mantine-adapter convention of a plain `Date` value. */
60
+ value?: Date | null;
61
+ /** Uncontrolled initial date. */
62
+ defaultValue?: Date | null;
63
+ /** Fires with the new date, or `null` if cleared. */
64
+ onChange?: (value: Date | null) => void;
65
+ minDate?: Date;
66
+ maxDate?: Date;
67
+ /** Overridable component slots — e.g. `slots={{ openPickerIcon: MyIcon }}` to replace the default calendar icon. */
68
+ slots?: DatePickerSlots;
69
+ slotProps?: DatePickerSlotProps;
70
+ /** Caller-provided inline style, passed through to the FormControlWrapper root. */
71
+ style?: React.CSSProperties;
72
+ }
73
+
74
+ export type DatePickerProps = RecursicaOverStyled<RecursicaDatePickerProps>;
75
+
76
+ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
77
+ function DatePicker(props, ref) {
78
+ const {
79
+ overStyled = false,
80
+ formLayout = "stacked",
81
+
82
+ // Label & Wrapper Maps
83
+ labelSize,
84
+ labelAlignment,
85
+ labelOptionalText,
86
+ labelWithEditIcon,
87
+ onLabelEditClick,
88
+
89
+ label,
90
+ assistiveText,
91
+ assistiveWithIcon,
92
+ error,
93
+ required,
94
+ id,
95
+ className,
96
+ style,
97
+ disabled,
98
+ readOnly,
99
+ readOnlyComponent,
100
+ emptyValueComponent,
101
+ value,
102
+ defaultValue,
103
+ onChange,
104
+ minDate,
105
+ maxDate,
106
+ slots,
107
+ slotProps,
108
+ ...rest
109
+ } = props;
110
+
111
+ const sanitizedProps = filterStylingProps(rest, overStyled);
112
+
113
+ // Internal Dayjs value: MUI X's DatePicker only speaks Dayjs (via AdapterDayjs); the public
114
+ // API stays plain `Date` to match the mantine-adapter convention.
115
+ const [internalValue, setInternalValue] = useState<Dayjs | null>(
116
+ () => toDayjs(value) ?? toDayjs(defaultValue) ?? null,
117
+ );
118
+
119
+ useEffect(() => {
120
+ if (value !== undefined) {
121
+ setInternalValue(toDayjs(value));
122
+ }
123
+ }, [value]);
124
+
125
+ const wrapperClass = className
126
+ ? `${styles.layoutOverride} ${className}`
127
+ : styles.layoutOverride;
128
+
129
+ const handleChange = (next: Dayjs | null) => {
130
+ setInternalValue(next);
131
+ onChange?.(next ? next.toDate() : null);
132
+ };
133
+
134
+ const readOnlyValue = (() => {
135
+ const source = value !== undefined ? value : defaultValue;
136
+ return toDayjs(source)?.format(DATE_FORMAT);
137
+ })();
138
+
139
+ // slotProps.field/desktopPaper are typed to also allow a `(ownerState) => props` function
140
+ // form; this adapter only ever merges plain objects (same assumption TextField/DatePicker's
141
+ // mantine counterpart make for consumer-supplied classNames), so narrow to that shape here.
142
+ const consumerFieldProps =
143
+ slotProps?.field && typeof slotProps.field === "object"
144
+ ? (slotProps.field as Record<string, unknown>)
145
+ : undefined;
146
+ const consumerDesktopPaperProps =
147
+ slotProps?.desktopPaper && typeof slotProps.desktopPaper === "object"
148
+ ? (slotProps.desktopPaper as Record<string, unknown>)
149
+ : undefined;
150
+
151
+ return (
152
+ <WithReadOnlyWrapper
153
+ ref={ref}
154
+ className={wrapperClass}
155
+ style={style}
156
+ controlMaxWidth={undefined}
157
+ controlMinWidth={undefined}
158
+ overStyled={overStyled as true}
159
+ formLayout={formLayout}
160
+ labelSize={labelSize}
161
+ labelAlignment={labelAlignment}
162
+ labelOptionalText={labelOptionalText}
163
+ labelWithEditIcon={labelWithEditIcon}
164
+ onLabelEditClick={onLabelEditClick}
165
+ label={label}
166
+ assistiveText={assistiveText}
167
+ assistiveWithIcon={assistiveWithIcon}
168
+ error={error}
169
+ required={required}
170
+ id={id}
171
+ readOnly={readOnly}
172
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
+ readOnlyComponent={readOnlyComponent as any}
174
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
175
+ emptyValueComponent={(emptyValueComponent as any) || undefined}
176
+ readOnlyType="text"
177
+ readOnlyValue={readOnlyValue}
178
+ readOnlyNativeProps={props}
179
+ activeComponent={
180
+ /* Naked field execution safely decoupled from MUI X's own label/error macro handling,
181
+ same convention as TimePicker.tsx. Unlike TimePicker, the popup calendar IS used here
182
+ (and token-styled below) — see DATEPICKER_IMPLEMENTATION_NOTES.md. */
183
+ <div className={styles.root} data-error={error ? "true" : undefined}>
184
+ <LocalizationProvider dateAdapter={AdapterDayjs}>
185
+ <MuiDatePicker
186
+ {...(sanitizedProps as unknown as Partial<MuiDatePickerProps>)}
187
+ value={internalValue}
188
+ onChange={handleChange}
189
+ disabled={disabled}
190
+ format={DATE_FORMAT}
191
+ minDate={toDayjs(minDate) ?? undefined}
192
+ maxDate={toDayjs(maxDate) ?? undefined}
193
+ slots={{ openPickerIcon: CalendarIcon, ...slots }}
194
+ slotProps={
195
+ {
196
+ ...slotProps,
197
+ field: {
198
+ // Calendar icon opens the picker from the left side of the field, mirroring
199
+ // the mantine-adapter's default `leftSection`; overridable via
200
+ // `slots.openPickerIcon`. Only settable per-field, not at the picker's own
201
+ // top level (MUI X has no top-level `openPickerButtonPosition` prop).
202
+ openPickerButtonPosition: "start",
203
+ ...consumerFieldProps,
204
+ className: consumerFieldProps?.className
205
+ ? `${styles.field} ${consumerFieldProps.className as string}`
206
+ : styles.field,
207
+ },
208
+ // The popup surface is portaled to document.body, outside this component's own
209
+ // DOM subtree, so — same constraint mantine-adapter's `popoverProps.classNames`
210
+ // documents — it can only be reached by handing it a className directly, not a
211
+ // descendant CSS selector.
212
+ desktopPaper: {
213
+ ...consumerDesktopPaperProps,
214
+ className: consumerDesktopPaperProps?.className
215
+ ? `${styles.dropdown} ${consumerDesktopPaperProps.className as string}`
216
+ : styles.dropdown,
217
+ },
218
+ } as DatePickerSlotProps
219
+ }
220
+ />
221
+ </LocalizationProvider>
222
+ </div>
223
+ }
224
+ />
225
+ );
226
+ },
227
+ );
228
+
229
+ DatePicker.displayName = "DatePicker";
@@ -19,7 +19,7 @@ import React from "react";
19
19
  import { DatePicker } from "@recursica/mui-adapter";
20
20
 
21
21
  export default function Demo() {
22
- return <DatePicker label="Select Date" placeholder="Pick a date" />;
22
+ return <DatePicker label="Select Date" />;
23
23
  }
24
24
  ```
25
25
 
@@ -34,3 +34,16 @@ All Recursica components in the `@recursica/mui-adapter` package adhere strictly
34
34
  > - **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.
35
35
  > - **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.
36
36
  > - **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.
37
+
38
+ ---
39
+
40
+ ## 4. Key Integration Features & Constraints
41
+
42
+ ## Structural Constraints
43
+
44
+ 1. **Value Type**: `value`/`defaultValue`/`onChange` all speak plain `Date` objects (not MUI X's native `Dayjs`), matching the mantine-adapter's DatePicker convention — conversion to/from `Dayjs` happens internally.
45
+ 2. **Read-Only Rendering**: When rendered in read-only mode, the selected date is displayed formatted as `MM/DD/YY`. If you need custom date formatting, pass a `readOnlyComponent` prop to control how the value is rendered.
46
+ 3. **Calendar Popover Styling**: The UI Kit exports no calendar-specific tokens (surface, selected day, hover, today, in-range), so the calendar reuses the closest existing tokens — see `DATEPICKER_IMPLEMENTATION_NOTES.md` for the exact mapping, mirrored 1:1 from the mantine-adapter. The header's prev/next arrows and month-view toggle are styled as Recursica `text`-variant buttons.
47
+ 4. **Typing vs. Popover Entry**: Unlike the mantine-adapter (popover-only selection), MUI X's field supports typing a date directly into its masked `MM/DD/YY` segments, in addition to picking from the popup calendar.
48
+ 5. **Default Display/Entry Format**: `MM/DD/YY` (e.g. `08/19/26`), matching the mantine-adapter; pass your own `format` (a `dayjs` format string) via the standard MUI X `DatePicker` props to override.
49
+ 6. **Default Leading Icon**: A calendar icon opens the picker from the start of the field by default; pass `slots={{ openPickerIcon: MyIcon }}` to override it.
@@ -1,10 +1,3 @@
1
- /* EXEMPTIONS:
2
- - state-specific border-size variables are ignored because a uniform 1px border is applied globally to prevent
3
- unexpected layout shift or flickering during focus, disabled, or error state transitions. */
4
- /* recursica-ignore: --recursica_ui-kit_components_dropdown_properties_border-size */
5
- /* recursica-ignore: --recursica_ui-kit_components_dropdown_variants_states_disabled_properties_border-size */
6
- /* recursica-ignore: --recursica_ui-kit_components_dropdown_variants_states_error_properties_border-size */
7
-
8
1
  /* LAYOUT SPACING OVERRIDES:
9
2
  - Sets the --form-control-margin-bottom spacing hook to map component-specific layout tokens.
10
3
  - Also sets the --dropdown-control-{max,min}-width hooks consumed inline in Dropdown.tsx, since
@@ -1,24 +1,3 @@
1
- /* EXEMPTIONS:
2
- - The disabled/error states' own border-size variables are ignored so the border never changes
3
- thickness across states and shifts the layout — same house policy as TextField/DatePicker.
4
- The flat, state-agnostic properties_border-size token below is applied uniformly instead.
5
- - There is no forge-defined focus state for file-input (no `states.focus` axis, unlike its
6
- disabled/error siblings) — the generic recursica_brand_states_focus_* ring is used instead,
7
- same fallback TextField/DatePicker use for the same reason.
8
- - properties_icon-text-gap doubles as the gap between chips: file-input has no dedicated
9
- chip-gap token (unlike file-upload's properties_item-gap).
10
- - The trailing clear-all affordance is now a real shared `Button` (icon-only, "text" variant)
11
- rather than a bespoke span, so it gets its own real button semantics/keyboard handling —
12
- same pattern as Tree's expand/collapse button. It renders through Button's own tokened
13
- color states (including disabled), so file-input's own trailing-icon color tokens — which
14
- have no "clear button is disabled/errored" equivalent state of their own anyway — are no
15
- longer consumed here. */
16
- /* recursica-ignore: --recursica_ui-kit_components_file-input_variants_states_disabled_properties_border-size */
17
- /* recursica-ignore: --recursica_ui-kit_components_file-input_variants_states_error_properties_border-size */
18
- /* recursica-ignore: --recursica_ui-kit_components_file-input_properties_colors_trailing-icon */
19
- /* recursica-ignore: --recursica_ui-kit_components_file-input_variants_states_error_properties_colors_trailing-icon */
20
- /* recursica-ignore: --recursica_ui-kit_components_file-input_variants_states_disabled_properties_colors_trailing-icon */
21
-
22
1
  /* LAYOUT SPACING OVERRIDES:
23
2
  - Sets the --form-control-margin-bottom spacing hook to map component-specific layout tokens.
24
3
  - Also sets the --file-input-control-{max,min}-width hooks consumed inline in FileInput.tsx,
@@ -387,6 +387,12 @@ export const FileInput = forwardRef<HTMLDivElement, FileInputProps>(
387
387
  e.stopPropagation();
388
388
  handleClearAll();
389
389
  }}
390
+ onKeyDown={(e) => {
391
+ if (e.key !== "Enter" && e.key !== " ") return;
392
+ e.preventDefault();
393
+ e.stopPropagation();
394
+ handleClearAll();
395
+ }}
390
396
  />
391
397
  )}
392
398
 
@@ -1,14 +1,3 @@
1
- /* EXEMPTIONS:
2
- - border-style has no usable var(...) form: the Figma-exported token's value is the literal
3
- string `"dashed"` (quotes included), which is not a valid CSS <line-style> keyword — the same
4
- class of "structural, not tokenized" property the canonical guide already treats border-style
5
- as (see .root's own HARDCODED VALUES note below and TimePicker/DatePicker's identical
6
- hardcoded `border-style: solid`). Hardcoded to the keyword `dashed` instead.
7
- - properties_icon-size has no equivalent: unlike TextField/DatePicker, file-upload has no
8
- dedicated icon-size token. Sized to visually match the Figma reference instead (see
9
- HARDCODED VALUES below). */
10
- /* recursica-ignore: --recursica_ui-kit_components_file-upload_properties_border-style */
11
-
12
1
  /* HARDCODED VALUES:
13
2
  - .root: display: flex; flex-direction: column; width: 100% — structural layout, not a design
14
3
  token concern (matches how other block-level components like Layer are laid out).
@@ -5,13 +5,7 @@
5
5
  and positioning (-arrowSize/2) calculations that cannot be CSS-driven. The default matches
6
6
  the Recursica beak-size token (16px). See COMPONENT_ISSUES.md for details.
7
7
  - All structural layout (display, position, overflow) is deferred to Mantine's native
8
- behavior. We only override visual design tokens (colors, typography, spacing, borders).
9
- /* EXEMPTIONS:
10
- - beak-size is ignored here because Mantine's popover arrows compute their sizing and positional offsets
11
- dynamically within the React rendering engine (requires inline pixel number rather than standard CSS variables).
12
- The beak size is checked in JS against a fixed value of 16px. If this variable changes, the TSX components
13
- need to be reviewed/re-compiled. */
14
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_beak-size = 16px */
8
+ behavior. We only override visual design tokens (colors, typography, spacing, borders). */
15
9
 
16
10
  /* ======================================
17
11
  DROPDOWN CONTAINER
@@ -1,9 +1,3 @@
1
- /* EXEMPTIONS:
2
- - layouts_stacked sizes width variables are ignored because stacked labels naturally occupy 100% width block-level
3
- real estate in HTML rendering, making explicit width properties redundant. Mirrors the mantine-adapter reference. */
4
- /* recursica-ignore: --recursica_ui-kit_components_label_variants_layouts_stacked_variants_sizes_default_properties_width */
5
- /* recursica-ignore: --recursica_ui-kit_components_label_variants_layouts_stacked_variants_sizes_small_properties_width */
6
-
7
1
  .root {
8
2
  /* Prevent default MUI absolute positioning logic */
9
3
  position: relative;
@@ -1,8 +1,3 @@
1
- /* EXEMPTIONS:
2
- - text_text-transform is ignored because links naturally inherit casing directly from text
3
- children rather than requiring custom CSS transforms. Mirrors the mantine-adapter reference. */
4
- /* recursica-ignore: --recursica_ui-kit_components_link_properties_text_text-transform */
5
-
6
1
  /*
7
2
  HARDCODED VALUES:
8
3
  - display: inline-flex; (Base layout to align icon and text correctly)
@@ -79,11 +74,3 @@ HARDCODED VALUES:
79
74
  /* HARDCODE: allows text truncation if bounded */
80
75
  min-width: 0;
81
76
  }
82
-
83
- /* EXEMPTIONS:
84
- text_text-transform across multiple link states (default, hover, visited, visited-hover) is ignored
85
- because links naturally inherit casing directly from text children rather than requiring custom CSS transforms. */
86
- /* recursica-ignore: --recursica_ui-kit_components_link_variants_states_default_properties_text_text-transform */
87
- /* recursica-ignore: --recursica_ui-kit_components_link_variants_states_hover_properties_text_text-transform */
88
- /* recursica-ignore: --recursica_ui-kit_components_link_variants_states_visited_properties_text_text-transform */
89
- /* recursica-ignore: --recursica_ui-kit_components_link_variants_states_visited-hover_properties_text_text-transform */
@@ -1,8 +1,3 @@
1
- /* EXEMPTIONS:
2
- - colors_supporting-text-color (selected variant) is ignored because selected menu items do not render
3
- supporting secondary text, making the selected-state override for supporting text colors obsolete. */
4
- /* recursica-ignore: --recursica_ui-kit_components_menu-item_variants_selection-states_selected_properties_colors_supporting-text-color */
5
-
6
1
  /* HARDCODED VALUES:
7
2
  - border-style: solid. Structural rendering rule for the dropdown border (Mantine uses Paper which may not set border natively).
8
3
  - NOTE: overflow is intentionally NOT set on .dropdown. Mantine renders sub-menu dropdowns
@@ -1,14 +1,3 @@
1
- /* EXEMPTIONS:
2
- - content-style and header-style parent variables represent raw JSON styling objects rather than
3
- direct CSS variables and cannot be applied directly in standard CSS files.
4
- Sub-properties are fully and individually mapped to elements natively.
5
- - header/footer background-color are ignored because .header/.footer are intentionally
6
- transparent (inheriting .content's background), matching Panel's header/footer treatment. */
7
- /* recursica-ignore: --recursica_ui-kit_components_modal_properties_content-style */
8
- /* recursica-ignore: --recursica_ui-kit_components_modal_properties_header-style */
9
- /* recursica-ignore: --recursica_ui-kit_components_modal_properties_colors_footer-background-color */
10
- /* recursica-ignore: --recursica_ui-kit_components_modal_properties_colors_header-background-color */
11
-
12
1
  .root {
13
2
  }
14
3
 
@@ -1,10 +1,3 @@
1
- /* EXEMPTIONS:
2
- - border-size variables are ignored because the input box uses a uniform static boundary to prevent
3
- unexpected layout shifts or flickering when switching between focused, disabled, or error states. */
4
- /* recursica-ignore: --recursica_ui-kit_components_number-input_properties_border-size */
5
- /* recursica-ignore: --recursica_ui-kit_components_number-input_variants_states_disabled_properties_border-size */
6
- /* recursica-ignore: --recursica_ui-kit_components_number-input_variants_states_error_properties_border-size */
7
-
8
1
  /* LAYOUT SPACING OVERRIDES:
9
2
  - Sets the --form-control-margin-bottom spacing hook to map component-specific layout tokens.
10
3
  - Also sets the --number-input-control-{max,min}-width hooks consumed inline in NumberInput.tsx,
@@ -1,84 +1,3 @@
1
- /* EXEMPTIONS:
2
- - All pagination subcomponent properties (active-pages, inactive-pages, navigation-controls) are ignored
3
- because we are not implementing custom subcomponent styling for pagination. Instead, pagination items
4
- directly inherit the unified, standard brand button components for complete UI/UX consistency, rendering
5
- these generated subcomponent tokens redundant. */
6
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_border-size */
7
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_disabled-opacity */
8
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_elevation */
9
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_height */
10
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_horizontal-padding */
11
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_hover-color */
12
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_hover-elevation */
13
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_hover-opacity */
14
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_icon */
15
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_icon-text-gap */
16
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_max-label-width */
17
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_min-width */
18
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_font-family */
19
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_font-size */
20
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_font-style */
21
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_font-weight */
22
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_letter-spacing */
23
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_line-height */
24
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_text-decoration */
25
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_text-transform */
26
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_colors_background */
27
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_colors_border-color */
28
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_colors_icon-color */
29
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_colors_text */
30
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_colors_text-hover */
31
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_border-size */
32
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_disabled-opacity */
33
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_elevation */
34
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_height */
35
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_horizontal-padding */
36
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_hover-color */
37
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_hover-elevation */
38
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_hover-opacity */
39
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_icon */
40
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_icon-text-gap */
41
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_max-label-width */
42
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_min-width */
43
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_font-family */
44
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_font-size */
45
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_font-style */
46
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_font-weight */
47
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_letter-spacing */
48
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_line-height */
49
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_text-decoration */
50
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_text-transform */
51
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_colors_background */
52
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_colors_border-color */
53
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_colors_icon-color */
54
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_colors_text */
55
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_colors_text-hover */
56
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_border-size */
57
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_disabled-opacity */
58
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_elevation */
59
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_height */
60
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_horizontal-padding */
61
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_hover-color */
62
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_hover-elevation */
63
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_hover-opacity */
64
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_icon */
65
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_icon-text-gap */
66
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_max-label-width */
67
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_min-width */
68
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_font-family */
69
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_font-size */
70
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_font-style */
71
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_font-weight */
72
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_letter-spacing */
73
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_line-height */
74
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_text-decoration */
75
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_text-transform */
76
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_colors_background */
77
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_colors_border-color */
78
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_colors_icon-color */
79
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_colors_text */
80
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_colors_text-hover */
81
-
82
1
  .root {
83
2
  display: flex;
84
3
  align-items: center;
@@ -0,0 +1,22 @@
1
+ # Popover Implementation Notes
2
+
3
+ - **Built on Mui's Tooltip, in click-controlled mode:** Rather than Mui's `Popover`/`Modal` primitives, this component reuses `@mui/material`'s `Tooltip` — the same base HoverCard and Tooltip already use in this adapter — with `disableHoverListener`/`disableFocusListener`/`disableTouchListener` all set, and `open` fully controlled by this component. This keeps the arrow/placement/token-namespace conventions identical to HoverCard and Tooltip instead of introducing a second, unrelated positioning primitive.
4
+ - **Composable API preserved via child extraction:** Like HoverCard, `Popover.Target`/`Popover.Dropdown` are never rendered themselves — `PopoverBase` walks `children` looking for those `displayName`s and extracts their inner content, then renders the target as Tooltip's single child and the dropdown as Tooltip's `title`.
5
+ - **Diverges from HoverCard on missing Target/Dropdown:** HoverCard silently falls back to an empty `<div />` if the expected child isn't found. Popover throws instead — a silent empty popover is a worse failure mode for a click-triggered disclosure than for a hover card, and the repo convention is to let structural misuse fail loudly rather than mask it.
6
+ - **Click toggle + outside-click close is custom:** Mui's Tooltip has no notion of "click to open" or "click outside to close" once its native hover/focus/touch listeners are disabled — Mantine's Popover provides both natively. This component clones the target element to attach an `onClick` toggle handler and a ref, and closes on outside click via a `mousedown` listener on `document` that ignores clicks inside either the target (`targetRef`) or the dropdown content (`dropdownRef`, a wrapper `<div>` around the dropdown children solely for this hit-test — not a styling root). Escape-to-close comes for free from Tooltip's own internal Escape handling once `onClose` is wired up.
7
+ - **`beak-size` token exemption:** Mui's Tooltip arrow is a fixed CSS shape (`1em`/`0.71em`, relative to the tooltip's own `font-size`) with no JS size prop, unlike Mantine's `arrowSize`. There's no hook to bind `--recursica_ui-kit_components_hover-card-popover_properties_beak-size` to, so it's `recursica-ignore`d here (same conceptual gap as HoverCard/Tooltip's existing exemptions for this token family, just for a different reason — those wrote a comment referencing Mantine's inline-pixel arrow calculations, which doesn't apply in this adapter; this file's comment describes the actual Mui-specific reason).
8
+ - **Arrow fill uses `color`, not `border-color`:** HoverCard's and Tooltip's `.arrow` rules in this adapter set `border-color`, which has no visual effect — Mui's Tooltip arrow is a solid rotated-square filled via `background-color: currentColor` in its `::before`, not a bordered shape. This component's `.arrow` instead sets `color` to the panel's `background-color` token, which actually renders. Intentional deviation from the HoverCard/Tooltip precedent for correctness; not fixed there as it's out of scope for this change.
9
+ - **Shared token namespace:** Same as HoverCard, this component's CSS module exclusively uses `--recursica_ui-kit_components_hover-card-popover_*` tokens (geometry, typography, elevation, layer-aware colors) — no Popover-specific token namespace exists in the schema.
10
+ - **`width` and controlled `opened`/`onChange`:** Not present in the shared `RecursicaPopoverProps` (adapter-common only defines `withBeak`), these are defined locally in `PopoverOwnProps` to match Mantine's own `PopoverProps` surface, since there is no single Mui library type that already provides them.
11
+
12
+ ## Gap larger than Mantine's on `withBeak={false}` (Matt Massey, 2026-08-19)
13
+
14
+ **Root cause:** Mui's `Tooltip` styled component ships its own hardcoded per-placement margin (`marginTop`/`marginBottom`: `14px`, or `24px` in touch mode) on the tooltip content div, applied whenever `arrow` is falsy (the `arrow` variant resets it to `margin: 0`). This stacked on top of the `offset` popper modifier already applied in `Popover.tsx` (which alone reproduces Mantine's own gap — Mantine's Popover defaults to an 8px `offset`, the same value used here), so the visible gap was `8 + 14 = 22px` instead of `8px` with `withBeak={false}`.
15
+
16
+ **Fix:** neutralize Mui's built-in margin in `Popover.module.css` via `:global(.MuiTooltip-popper[data-popper-placement]) .dropdown { margin: 0; }`, matching Mui's own rule's specificity (class + attribute + class) so it wins on source order under `injectFirst`. The `offset` modifier is now the single source of truth for the gap, matching Mantine.
17
+
18
+ ## Beak had no visible edge against the dropdown body (Matt Massey, 2026-08-19)
19
+
20
+ **Root cause:** Mantine's arrow is a bordered shape — `.arrow { border-color: ... }` — visible because Mantine's own arrow implementation sets `border-width` natively. Mui's arrow `::before` pseudo-element has no border at all by default (just `background-color: currentColor`), so setting only `color` (as this component already did, to fill the triangle) left it with zero visible edge against a similarly-colored panel.
21
+
22
+ **Fix:** added `.arrow::before { border-style: solid; border-width: ...border-size; border-color: ...colors_border-color; }`, the same border tokens the `.dropdown` container itself uses — verified live (Playwright) that the arrow now shows a visible border matching Mantine's.