@recursica/adapter-mantine-v8 1.3.2 → 1.3.4

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 +15 -0
  2. package/dist/adapter-mantine-v8.cjs +1 -1
  3. package/dist/adapter-mantine-v8.cjs.map +1 -1
  4. package/dist/adapter-mantine-v8.js +338 -331
  5. package/dist/adapter-mantine-v8.js.map +1 -1
  6. package/dist/index.d.ts +5 -1
  7. package/docs/PHILOSOPHY.md +4 -0
  8. package/package.json +4 -8
  9. package/src/components/Heading/HEADING_IMPLEMENTATION_NOTES.md +9 -0
  10. package/src/components/Panel/PANEL_IMPLEMENTATION_NOTES.md +8 -0
  11. package/src/components/Panel/Panel.tsx +23 -6
  12. package/src/components/Text/TEXT_IMPLEMENTATION_NOTES.md +10 -0
  13. package/src/components/Text/Text.headings.dom.test.tsx +51 -0
  14. package/src/components/Text/Text.tsx +9 -0
  15. package/.storybook/commonArgTypes.ts +0 -148
  16. package/recursica_brand.json +0 -2610
  17. package/recursica_tokens.json +0 -769
  18. package/recursica_ui-kit.json +0 -9049
  19. package/recursica_variables_scoped.css +0 -6178
  20. package/src/Introduction.stories.tsx +0 -149
  21. package/src/components/Accordion/Accordion.stories.tsx +0 -189
  22. package/src/components/AssistiveElement/AssistiveElement.stories.tsx +0 -68
  23. package/src/components/AutoComplete/AutoComplete.stories.tsx +0 -336
  24. package/src/components/Avatar/Avatar.stories.tsx +0 -92
  25. package/src/components/Badge/Badge.stories.tsx +0 -78
  26. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +0 -114
  27. package/src/components/Button/Button.stories.tsx +0 -192
  28. package/src/components/Card/Card.stories.tsx +0 -76
  29. package/src/components/Checkbox/Checkbox.stories.tsx +0 -97
  30. package/src/components/Checkbox/CheckboxGroup.stories.tsx +0 -139
  31. package/src/components/Chip/Chip.stories.tsx +0 -128
  32. package/src/components/Container/Container.stories.tsx +0 -118
  33. package/src/components/DatePicker/DatePicker.stories.tsx +0 -158
  34. package/src/components/Dropdown/Dropdown.stories.tsx +0 -252
  35. package/src/components/FileInput/FileInput.stories.tsx +0 -313
  36. package/src/components/FileUpload/FileUpload.stories.tsx +0 -362
  37. package/src/components/Flex/Flex.stories.tsx +0 -128
  38. package/src/components/FormControlLayout/FormControlLayout.stories.tsx +0 -118
  39. package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +0 -129
  40. package/src/components/Group/Group.stories.tsx +0 -120
  41. package/src/components/Heading/Heading.stories.tsx +0 -112
  42. package/src/components/HoverCard/HoverCard.stories.tsx +0 -172
  43. package/src/components/Label/Label.stories.tsx +0 -131
  44. package/src/components/LayoutGrid/LayoutGrid.stories.tsx +0 -61
  45. package/src/components/Link/Link.stories.tsx +0 -58
  46. package/src/components/Loader/Loader.stories.tsx +0 -123
  47. package/src/components/Menu/Menu.stories.tsx +0 -370
  48. package/src/components/Modal/Modal.stories.tsx +0 -105
  49. package/src/components/NumberInput/NumberInput.stories.tsx +0 -96
  50. package/src/components/Pagination/Pagination.stories.tsx +0 -48
  51. package/src/components/Panel/Panel.stories.tsx +0 -227
  52. package/src/components/Popover/Popover.stories.tsx +0 -133
  53. package/src/components/Radio/Radio.stories.tsx +0 -93
  54. package/src/components/Radio/RadioGroup.stories.tsx +0 -117
  55. package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +0 -163
  56. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +0 -106
  57. package/src/components/Slider/Slider.stories.tsx +0 -282
  58. package/src/components/Stack/Stack.stories.tsx +0 -108
  59. package/src/components/Stepper/Stepper.stories.tsx +0 -148
  60. package/src/components/Switch/Switch.stories.tsx +0 -114
  61. package/src/components/Switch/SwitchGroup.stories.tsx +0 -132
  62. package/src/components/Table/Table.stories.tsx +0 -140
  63. package/src/components/Tabs/Tabs.stories.tsx +0 -154
  64. package/src/components/Text/Text.stories.tsx +0 -123
  65. package/src/components/TextArea/TextArea.stories.tsx +0 -94
  66. package/src/components/TextField/TextField.stories.tsx +0 -162
  67. package/src/components/TimePicker/TimePicker.stories.tsx +0 -141
  68. package/src/components/Timeline/Timeline.stories.tsx +0 -128
  69. package/src/components/Toast/Toast.stories.tsx +0 -67
  70. package/src/components/Tooltip/Tooltip.stories.tsx +0 -165
  71. package/src/components/TransferList/TransferList.stories.tsx +0 -121
  72. package/src/components/Tree/Tree.stories.tsx +0 -104
package/dist/index.d.ts CHANGED
@@ -1035,6 +1035,10 @@ export declare const Panel: PanelComponent;
1035
1035
  * </Panel>
1036
1036
  * ```
1037
1037
  *
1038
+ * Always non-modal, with no props to change it: the page behind stays usable
1039
+ * (no overlay, focus trap or scroll lock), focus returns to the opener on close,
1040
+ * Escape always closes, and outside clicks never do.
1041
+ *
1038
1042
  * Mantine Drawer sub-components available via dot-notation:
1039
1043
  * - `Panel.Header` — Top section with title and close button
1040
1044
  * - `Panel.Title` — Title text within the header
@@ -1240,7 +1244,7 @@ export declare interface RecursicaNumberInputProps extends Omit<NumberInputProps
1240
1244
  /**
1241
1245
  * Recursica Panel root props. Extends Mantine Drawer.
1242
1246
  */
1243
- export declare interface RecursicaPanelProps extends Omit<DrawerProps, "size" | "styles" | "classNames" | "style" | "position" | "opened">, RecursicaPanelProps_2 {
1247
+ export declare interface RecursicaPanelProps extends Omit<DrawerProps, "size" | "styles" | "classNames" | "style" | "position" | "opened" | "withOverlay" | "trapFocus" | "lockScroll" | "returnFocus" | "closeOnEscape" | "closeOnClickOutside">, RecursicaPanelProps_2 {
1244
1248
  }
1245
1249
 
1246
1250
  export declare interface RecursicaRadioGroupProps extends Omit<RadioGroupProps_2, "size" | "labelProps" | "defaultValue" | "value">, Omit<RecursicaFormControlWrapperProps, "controlMaxWidth" | "controlMinWidth">, ReadOnlyControlProps, RecursicaRadioGroupProps_2 {
@@ -4,6 +4,10 @@ Recursica's component architecture isn't just a wrapper; it's a strict enforcing
4
4
 
5
5
  This document serves as the governing framework for why the `adapter-mantine-v8` components are built the way they are.
6
6
 
7
+ ## Why an Adapter?
8
+
9
+ Most companies already have an existing component library or use an existing UI kit, and switching to a different kit means painful refactoring. Rather than replace Mantine, this adapter adapts to its API surface while enforcing the behaviors and styling Recursica defines for a consistent UX. You don't ship two separate UI kits, and you can migrate to Recursica systematically, over time.
10
+
7
11
  ## 1. Strict Separation of Props (The Unified Recursica Prop Layer)
8
12
 
9
13
  Recursica has a **single universal API surface** internally regardless of whether we use Mantine or another underlying UI library.
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "type": "git",
13
13
  "url": "git+https://github.com/borderux/recursica-adapter-mantine-v8.git"
14
14
  },
15
- "version": "1.3.2",
15
+ "version": "1.3.4",
16
16
  "publishConfig": {
17
17
  "access": "public"
18
18
  },
@@ -31,18 +31,14 @@
31
31
  "files": [
32
32
  "dist",
33
33
  "src",
34
+ "!src/**/*.stories.tsx",
34
35
  "CHANGELOG.md",
35
- ".storybook/commonArgTypes.ts",
36
36
  "llms.txt",
37
37
  "USAGE.md",
38
38
  "ARCHITECTURE.md",
39
39
  "SETUP.md",
40
40
  "OVERSTYLING.md",
41
- "docs/PHILOSOPHY.md",
42
- "recursica_variables_scoped.css",
43
- "recursica_brand.json",
44
- "recursica_tokens.json",
45
- "recursica_ui-kit.json"
41
+ "docs/PHILOSOPHY.md"
46
42
  ],
47
43
  "keywords": [
48
44
  "react",
@@ -86,7 +82,7 @@
86
82
  "@playwright/test": "^1.62.1",
87
83
  "@recursica/adapter-tester": "^5.3.1",
88
84
  "@recursica/recursica-postcss-vars": "^1.5.0",
89
- "@recursica/storybook-template": "^0.7.15",
85
+ "@recursica/storybook-template": "^0.7.16",
90
86
  "@recursica/token-analyzer": "^1.8.0",
91
87
  "@storybook/addon-a11y": "^10.3.3",
92
88
  "@storybook/addon-docs": "^10.3.3",
@@ -16,3 +16,12 @@ line — headings are the primary intended use case for this property. Not a des
16
16
  layout algorithm choice, so it's hardcoded rather than pulled from `recursica_variables_scoped.css`.
17
17
  Chromium/Firefox only balance up to ~6 lines, which comfortably covers heading text. No fallback
18
18
  needed — browsers that don't support the value just ignore the declaration.
19
+
20
+ ## Heading owns semantic `h1`-`h6` (Matt Massey, 2026-10-08)
21
+
22
+ **Decision:** `Heading` is the only component that renders semantic `<h1>`-`<h6>` elements. Each
23
+ `order` maps to a heading level that Recursica and Forge define and style
24
+ (`recursica_brand_typography_h{order}`).
25
+
26
+ **Implementation:** Heading's styles are fixed by the Recursica JSON/CSS. Do not add variants to
27
+ Heading. Any other kind of text belongs in `Text`.
@@ -122,3 +122,11 @@ Project-wide fix; see Chip's `CHIP_IMPLEMENTATION_NOTES.md` for the original dis
122
122
  **Decision:** The `border-radius` token is applied only to the corners on the edge that faces the page, not all four.
123
123
 
124
124
  **Implementation:** A panel with `placement="right"` (the default) rounds its upper-left and lower-left corners only; its right corners are square against the screen edge. A panel with `placement="left"` rounds its upper-right and lower-right corners only. `Panel.tsx` adds `.contentRight` / `.contentLeft` to the `content` class, and those zero the two outer corners. `top` and `bottom` placements are unchanged (all four corners rounded).
125
+
126
+ ---
127
+
128
+ ## 10. Always non-modal (Matt Massey, 2026-10-08)
129
+
130
+ **Decision:** Panel is never modal, and there are no props to change that.
131
+
132
+ **Implementation:** The house rule is that the page behind a panel stays usable. `Panel.tsx` always passes `withOverlay={false}`, `trapFocus={false}`, `lockScroll={false}`, `returnFocus`, `closeOnEscape` and `closeOnClickOutside={false}` to Mantine's Drawer, after the caller's props, and omits those keys from `RecursicaPanelProps`. Escape is independent of outside clicks and always closes the panel; clicking the page behind never does. Every adapter must match: no overlay, no focus trap, no scroll lock, no `aria-modal`, no `aria-hidden` on the rest of the page.
@@ -13,7 +13,6 @@ import {
13
13
  import {
14
14
  filterStylingProps,
15
15
  mergeClassNames,
16
- withCallerOverride,
17
16
  type RecursicaOverStyled,
18
17
  } from "../../utils/filterStylingProps";
19
18
  import styles from "./Panel.module.css";
@@ -30,7 +29,18 @@ import { type RecursicaPanelProps as BaseRecursicaPanelProps } from "@recursica/
30
29
  export interface RecursicaPanelProps
31
30
  extends Omit<
32
31
  MantineDrawerProps,
33
- "size" | "styles" | "classNames" | "style" | "position" | "opened"
32
+ | "size"
33
+ | "styles"
34
+ | "classNames"
35
+ | "style"
36
+ | "position"
37
+ | "opened"
38
+ | "withOverlay"
39
+ | "trapFocus"
40
+ | "lockScroll"
41
+ | "returnFocus"
42
+ | "closeOnEscape"
43
+ | "closeOnClickOutside"
34
44
  >,
35
45
  BaseRecursicaPanelProps {}
36
46
 
@@ -52,6 +62,10 @@ export type PanelProps = RecursicaOverStyled<RecursicaPanelProps>;
52
62
  * </Panel>
53
63
  * ```
54
64
  *
65
+ * Always non-modal, with no props to change it: the page behind stays usable
66
+ * (no overlay, focus trap or scroll lock), focus returns to the opener on close,
67
+ * Escape always closes, and outside clicks never do.
68
+ *
55
69
  * Mantine Drawer sub-components available via dot-notation:
56
70
  * - `Panel.Header` — Top section with title and close button
57
71
  * - `Panel.Title` — Title text within the header
@@ -98,10 +112,13 @@ const PanelBase = function Panel({
98
112
  size="var(--recursica_ui-kit_components_panel_properties_max-width)"
99
113
  position={placement} /* Recursica default: right; Mantine default: left */
100
114
  keepMounted={keepMounted}
101
- closeOnClickOutside={withCallerOverride(
102
- Boolean(rest.opened),
103
- rest.closeOnClickOutside,
104
- )}
115
+ /* Always non-modal: the page behind stays usable, Escape always closes */
116
+ withOverlay={false}
117
+ trapFocus={false}
118
+ lockScroll={false}
119
+ returnFocus
120
+ closeOnEscape
121
+ closeOnClickOutside={false}
105
122
  classNames={mergedClassNames}
106
123
  />
107
124
  );
@@ -10,3 +10,13 @@ line. Not a design token — it's a layout algorithm choice, so it's hardcoded r
10
10
  from `recursica_variables_scoped.css`. Chromium/Firefox only balance up to ~6 lines; longer
11
11
  paragraphs silently fall back to normal wrapping past that point. No fallback needed — browsers
12
12
  that don't support the value just ignore the declaration.
13
+
14
+ ## Text must never render `h1`-`h6` (Matt Massey, 2026-10-08)
15
+
16
+ **Decision:** `Text` must never render an `<h1>`-`<h6>` element. It is not allowed. Semantic
17
+ headings use `Heading`.
18
+
19
+ **Implementation:** `Text` covers every other kind of text in the system (body, labels, captions,
20
+ etc.). Its variants can extend what the Recursica JSON and CSS define, so a variant need not
21
+ exist in the Recursica tokens. Heading levels are the one thing `Text` can't be: they have a
22
+ fixed definition and styling from Recursica and Forge, owned by `Heading`.
@@ -0,0 +1,51 @@
1
+ import React from "react";
2
+ import { describe, it, expect, vi } from "vitest";
3
+ import { createRoot } from "react-dom/client";
4
+ import { flushSync } from "react-dom";
5
+ import { MantineProvider } from "@mantine/core";
6
+ import { Text } from "./Text";
7
+
8
+ class Boundary extends React.Component<
9
+ { children: React.ReactNode },
10
+ { message: string | null }
11
+ > {
12
+ state = { message: null as string | null };
13
+ static getDerivedStateFromError(error: Error) {
14
+ return { message: error.message };
15
+ }
16
+ render() {
17
+ return this.state.message ?? this.props.children;
18
+ }
19
+ }
20
+
21
+ function render(component: string): HTMLElement {
22
+ const container = document.createElement("div");
23
+ document.body.appendChild(container);
24
+ const props = { component } as Record<string, unknown>;
25
+ flushSync(() =>
26
+ createRoot(container).render(
27
+ <MantineProvider>
28
+ <Boundary>
29
+ <Text {...(props as object)}>Content</Text>
30
+ </Boundary>
31
+ </MantineProvider>,
32
+ ),
33
+ );
34
+ return container;
35
+ }
36
+
37
+ describe("Text heading elements", () => {
38
+ it.each(["h1", "h2", "h3", "h4", "h5", "h6"])(
39
+ "throws when component is %s",
40
+ (component) => {
41
+ vi.spyOn(console, "error").mockImplementation(() => {});
42
+ expect(render(component).textContent).toContain(
43
+ `Text cannot render <${component}>. Use <Heading> for semantic h1-h6.`,
44
+ );
45
+ },
46
+ );
47
+
48
+ it.each(["p", "span", "label"])("renders component=%s", (component) => {
49
+ expect(render(component).querySelector(component)).not.toBeNull();
50
+ });
51
+ });
@@ -18,6 +18,8 @@ export type TextProps = RecursicaOverStyled<
18
18
  "color"
19
19
  >;
20
20
 
21
+ const HEADING_ELEMENTS = ["h1", "h2", "h3", "h4", "h5", "h6"];
22
+
21
23
  const _Text = forwardRef<HTMLDivElement, TextProps>(function Text(
22
24
  {
23
25
  overStyled = false,
@@ -28,6 +30,13 @@ const _Text = forwardRef<HTMLDivElement, TextProps>(function Text(
28
30
  },
29
31
  ref,
30
32
  ) {
33
+ const component = (rest as { component?: unknown }).component;
34
+ if (typeof component === "string" && HEADING_ELEMENTS.includes(component)) {
35
+ throw new Error(
36
+ `Text cannot render <${component}>. Use <Heading> for semantic h1-h6.`,
37
+ );
38
+ }
39
+
31
40
  const sanitizedProps = filterStylingProps(rest, overStyled);
32
41
  const classNameProp = (sanitizedProps as Record<string, unknown>)
33
42
  .className as string | undefined;
@@ -1,148 +0,0 @@
1
- /**
2
- * commonArgTypes.ts
3
- *
4
- * This file centralizes the standard Storybook Controls (ArgTypes) for all UI Kit
5
- * components that natively wrap or extend the `FormControlWrapper`.
6
- *
7
- * PHILOSOPHY:
8
- * Recursica ensures strict unified design boundaries across all form inputs (TextField,
9
- * CheckboxGroup, Dropdown, etc.). Because they all share the exact same label structures,
10
- * layout variants (stacked vs side-by-side), and assistive text bindings natively supplied
11
- * by the `FormControlWrapper`, their Storybook experiences should be exactly identical.
12
- *
13
- * USAGE:
14
- * Rather than recreating these controls inside every `Component.stories.tsx` file,
15
- * developers must spread this object directly into the `argTypes` of their story default block:
16
- *
17
- * ```tsx
18
- * import { formControlArgTypes } from "../../.storybook/commonArgTypes";
19
- *
20
- * export default {
21
- * component: TextField,
22
- * argTypes: {
23
- * ...formControlArgTypes,
24
- * // Component-specific argTypes below...
25
- * }
26
- * }
27
- * ```
28
- */
29
- import type { ArgTypes } from "@storybook/react";
30
-
31
- export const formControlArgTypes: Partial<ArgTypes> = {
32
- formLayout: {
33
- control: "inline-radio",
34
- options: ["stacked", "side-by-side"],
35
- description:
36
- "Determines the structural flow layout of the Label relative to the Input.",
37
- table: {
38
- category: "Form Layout",
39
- },
40
- },
41
- labelSize: {
42
- control: "inline-radio",
43
- options: ["default", "small"],
44
- description:
45
- "Adjusts the typographical boundaries bounding the Label section.",
46
- table: {
47
- category: "Form Layout",
48
- },
49
- },
50
- labelAlignment: {
51
- control: "inline-radio",
52
- options: ["left", "right"],
53
- description:
54
- "Adjusts the explicit text alignment axis exclusively evaluating side-by-side structures.",
55
- table: {
56
- category: "Form Layout",
57
- },
58
- },
59
- labelOptionalText: {
60
- control: "text",
61
- description:
62
- "Optional secondary stylistic text injected alongside the primary Label node.",
63
- table: {
64
- category: "Form Layout",
65
- },
66
- },
67
- labelWithEditIcon: {
68
- control: "boolean",
69
- description:
70
- "Dynamically toggles interactivity hooks revealing the Edit context icon.",
71
- table: {
72
- category: "Form Layout",
73
- },
74
- },
75
- description: {
76
- table: {
77
- disable: true,
78
- },
79
- },
80
- assistiveText: {
81
- control: "text",
82
- description:
83
- "Helper instructions safely dynamically anchored below the input box natively outperforming description.",
84
- table: {
85
- category: "Form Layout",
86
- },
87
- },
88
- assistiveWithIcon: {
89
- control: "boolean",
90
- description:
91
- "Toggles native Recursica Info-styling rendering universally alongside the assistive block.",
92
- table: {
93
- category: "Form Layout",
94
- },
95
- },
96
- error: {
97
- control: "text",
98
- description:
99
- "Dynamically maps error feedback validation strings rendering explicit error states.",
100
- table: {
101
- category: "Form State",
102
- },
103
- },
104
- required: {
105
- control: "boolean",
106
- description:
107
- "Native DOM compliance flag explicitly marking the field as required natively.",
108
- table: {
109
- category: "Form State",
110
- },
111
- },
112
- disabled: {
113
- control: "boolean",
114
- description:
115
- "Globally disables interactive components bounding all click and focus logic natively.",
116
- table: {
117
- category: "Form State",
118
- },
119
- },
120
- withAsterisk: {
121
- control: "boolean",
122
- description:
123
- "Visually forces the mandatory asterisk presence completely independent of logical 'required' attribute properties.",
124
- table: {
125
- category: "Form State",
126
- },
127
- },
128
- labelActionArea: {
129
- table: {
130
- disable: true,
131
- },
132
- },
133
- onLabelEditClick: {
134
- table: {
135
- disable: true,
136
- },
137
- },
138
- controlMaxWidth: {
139
- table: {
140
- disable: true,
141
- },
142
- },
143
- controlMinWidth: {
144
- table: {
145
- disable: true,
146
- },
147
- },
148
- };