@recursica/mantine-adapter 0.6.0 → 0.7.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 (140) hide show
  1. package/.storybook/commonArgTypes.ts +148 -0
  2. package/CHANGELOG.md +69 -0
  3. package/dist/mantine-adapter.cjs +1 -1
  4. package/dist/mantine-adapter.cjs.map +1 -1
  5. package/dist/mantine-adapter.css +1 -1
  6. package/dist/mantine-adapter.js +660 -586
  7. package/dist/mantine-adapter.js.map +1 -1
  8. package/dist/src/components/Checkbox/CheckboxGroup.d.ts +1 -1
  9. package/dist/src/components/Chip/Chip.d.ts +14 -2
  10. package/package.json +5 -2
  11. package/src/OverStyling.stories.tsx +174 -0
  12. package/src/Version.stories.tsx +56 -0
  13. package/src/components/Accordion/ACCORDION_IMPLEMENTATION_NOTES.md +48 -0
  14. package/src/components/Accordion/Accordion.module.css +261 -0
  15. package/src/components/Accordion/Accordion.stories.tsx +145 -0
  16. package/src/components/Accordion/Accordion.tsx +200 -0
  17. package/src/components/Accordion/index.ts +1 -0
  18. package/src/components/AssistiveElement/AssistiveElement.module.css +111 -0
  19. package/src/components/AssistiveElement/AssistiveElement.stories.tsx +73 -0
  20. package/src/components/AssistiveElement/AssistiveElement.tsx +91 -0
  21. package/src/components/Avatar/AVATAR_IMPLEMENTATION_NOTES.md +24 -0
  22. package/src/components/Avatar/Avatar.module.css +340 -0
  23. package/src/components/Avatar/Avatar.stories.tsx +102 -0
  24. package/src/components/Avatar/Avatar.tsx +100 -0
  25. package/src/components/Avatar/index.ts +1 -0
  26. package/src/components/Badge/BADGE_IMPLEMENTATION_NOTES.md +18 -0
  27. package/src/components/Badge/Badge.module.css +124 -0
  28. package/src/components/Badge/Badge.stories.tsx +77 -0
  29. package/src/components/Badge/Badge.tsx +66 -0
  30. package/src/components/Breadcrumb/BREADCRUMB_IMPLEMENTATION_NOTES.md +14 -0
  31. package/src/components/Breadcrumb/Breadcrumb.module.css +26 -0
  32. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +77 -0
  33. package/src/components/Breadcrumb/Breadcrumb.tsx +64 -0
  34. package/src/components/Button/Button.module.css +275 -0
  35. package/src/components/Button/Button.stories.tsx +105 -0
  36. package/src/components/Button/Button.tsx +122 -0
  37. package/src/components/Button/IMPLEMENTATION_NOTES.md +45 -0
  38. package/src/components/Button/index.ts +1 -0
  39. package/src/components/Card/CARD_IMPLEMENTATION_NOTES.md +10 -0
  40. package/src/components/Card/Card.module.css +60 -0
  41. package/src/components/Card/Card.stories.tsx +141 -0
  42. package/src/components/Card/Card.tsx +176 -0
  43. package/src/components/Checkbox/CHECKBOX_IMPLEMENTATION_NOTES.md +25 -0
  44. package/src/components/Checkbox/Checkbox.module.css +204 -0
  45. package/src/components/Checkbox/Checkbox.stories.tsx +73 -0
  46. package/src/components/Checkbox/Checkbox.tsx +111 -0
  47. package/src/components/Checkbox/CheckboxGroup.stories.tsx +138 -0
  48. package/src/components/Checkbox/CheckboxGroup.tsx +140 -0
  49. package/src/components/Chip/CHIP_IMPLEMENTATION_NOTES.md +35 -0
  50. package/src/components/Chip/Chip.module.css +218 -0
  51. package/src/components/Chip/Chip.stories.tsx +115 -0
  52. package/src/components/Chip/Chip.tsx +143 -0
  53. package/src/components/DatePicker/DatePicker.stories.tsx +22 -0
  54. package/src/components/DatePicker/DatePicker.tsx +7 -0
  55. package/src/components/Dropdown/Dropdown.stories.tsx +22 -0
  56. package/src/components/Dropdown/Dropdown.tsx +7 -0
  57. package/src/components/FileInput/FileInput.stories.tsx +22 -0
  58. package/src/components/FileInput/FileInput.tsx +7 -0
  59. package/src/components/FileUpload/FileUpload.stories.tsx +22 -0
  60. package/src/components/FileUpload/FileUpload.tsx +7 -0
  61. package/src/components/FormControlWrapper/FORMCONTROLWRAPPER_IMPLEMENTATION_NOTES.md +32 -0
  62. package/src/components/FormControlWrapper/FormControlWrapper.module.css +46 -0
  63. package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +116 -0
  64. package/src/components/FormControlWrapper/FormControlWrapper.tsx +170 -0
  65. package/src/components/HoverCard/HoverCard.stories.tsx +17 -0
  66. package/src/components/HoverCard/HoverCard.tsx +7 -0
  67. package/src/components/Label/LABEL_IMPLEMENTATION_NOTES.md +48 -0
  68. package/src/components/Label/Label.module.css +177 -0
  69. package/src/components/Label/Label.stories.tsx +123 -0
  70. package/src/components/Label/Label.tsx +132 -0
  71. package/src/components/Label/index.ts +1 -0
  72. package/src/components/Link/Link.stories.tsx +17 -0
  73. package/src/components/Link/Link.tsx +7 -0
  74. package/src/components/Loader/Loader.stories.tsx +17 -0
  75. package/src/components/Loader/Loader.tsx +7 -0
  76. package/src/components/Menu/Menu.stories.tsx +17 -0
  77. package/src/components/Menu/Menu.tsx +7 -0
  78. package/src/components/Modal/Modal.stories.tsx +17 -0
  79. package/src/components/Modal/Modal.tsx +7 -0
  80. package/src/components/NumberInput/NumberInput.stories.tsx +22 -0
  81. package/src/components/NumberInput/NumberInput.tsx +7 -0
  82. package/src/components/Pagination/Pagination.stories.tsx +17 -0
  83. package/src/components/Pagination/Pagination.tsx +7 -0
  84. package/src/components/Panel/Panel.stories.tsx +17 -0
  85. package/src/components/Panel/Panel.tsx +7 -0
  86. package/src/components/Popover/Popover.stories.tsx +17 -0
  87. package/src/components/Popover/Popover.tsx +7 -0
  88. package/src/components/Radio/Radio.stories.tsx +22 -0
  89. package/src/components/Radio/Radio.tsx +7 -0
  90. package/src/components/ReadOnlyField/READ_ONLY_FIELD_IMPLEMENTATION_NOTES.md +15 -0
  91. package/src/components/ReadOnlyField/ReadOnlyField.module.css +49 -0
  92. package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +120 -0
  93. package/src/components/ReadOnlyField/ReadOnlyField.tsx +75 -0
  94. package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +46 -0
  95. package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +85 -0
  96. package/src/components/ReadOnlyField/index.ts +3 -0
  97. package/src/components/Search/Search.stories.tsx +17 -0
  98. package/src/components/Search/Search.tsx +7 -0
  99. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +22 -0
  100. package/src/components/SegmentedControl/SegmentedControl.tsx +7 -0
  101. package/src/components/Slider/Slider.stories.tsx +22 -0
  102. package/src/components/Slider/Slider.tsx +7 -0
  103. package/src/components/Stepper/Stepper.stories.tsx +17 -0
  104. package/src/components/Stepper/Stepper.tsx +7 -0
  105. package/src/components/Switch/Switch.stories.tsx +22 -0
  106. package/src/components/Switch/Switch.tsx +7 -0
  107. package/src/components/Table/Table.stories.tsx +17 -0
  108. package/src/components/Table/Table.tsx +7 -0
  109. package/src/components/Tabs/Tabs.stories.tsx +17 -0
  110. package/src/components/Tabs/Tabs.tsx +7 -0
  111. package/src/components/Text/Text.stories.tsx +88 -0
  112. package/src/components/Text/Text.tsx +54 -0
  113. package/src/components/TextArea/TextArea.stories.tsx +22 -0
  114. package/src/components/TextArea/TextArea.tsx +7 -0
  115. package/src/components/TextField/TEXTFIELD_IMPLEMENTATION_NOTES.md +19 -0
  116. package/src/components/TextField/TextField.module.css +225 -0
  117. package/src/components/TextField/TextField.stories.tsx +162 -0
  118. package/src/components/TextField/TextField.tsx +138 -0
  119. package/src/components/TimePicker/TimePicker.stories.tsx +22 -0
  120. package/src/components/TimePicker/TimePicker.tsx +7 -0
  121. package/src/components/Timeline/Timeline.stories.tsx +17 -0
  122. package/src/components/Timeline/Timeline.tsx +7 -0
  123. package/src/components/Title/Title.stories.tsx +63 -0
  124. package/src/components/Title/Title.tsx +45 -0
  125. package/src/components/Toast/Toast.stories.tsx +17 -0
  126. package/src/components/Toast/Toast.tsx +7 -0
  127. package/src/components/Tooltip/Tooltip.stories.tsx +17 -0
  128. package/src/components/Tooltip/Tooltip.tsx +7 -0
  129. package/src/components/TransferList/TransferList.stories.tsx +17 -0
  130. package/src/components/TransferList/TransferList.tsx +7 -0
  131. package/src/components/index.ts +39 -0
  132. package/src/env.d.ts +7 -0
  133. package/src/index.ts +3 -0
  134. package/src/types/index.ts +9 -0
  135. package/src/types/mantine.d.ts +7 -0
  136. package/src/types/scss-modules.d.ts +9 -0
  137. package/src/utils/ColorSchemeWrapper.tsx +27 -0
  138. package/src/utils/copyToClipboard.ts +36 -0
  139. package/src/utils/filterStylingProps.ts +139 -0
  140. package/src/utils/index.ts +1 -0
@@ -0,0 +1,122 @@
1
+ import React, { forwardRef } from "react";
2
+ import {
3
+ Button as MantineButton,
4
+ type ButtonProps as MantineButtonProps,
5
+ } from "@mantine/core";
6
+ import {
7
+ filterStylingProps,
8
+ type RecursicaOverStyled,
9
+ } from "../../utils/filterStylingProps";
10
+ import styles from "./Button.module.css";
11
+
12
+ export interface RecursicaButtonProps {
13
+ variant?: "solid" | "outline" | "text";
14
+ size?: "default" | "small";
15
+ icon?: React.ReactNode;
16
+ }
17
+
18
+ export type ButtonProps = RecursicaOverStyled<
19
+ Omit<MantineButtonProps, "variant" | "size" | "leftSection" | "fullWidth"> &
20
+ RecursicaButtonProps
21
+ >;
22
+
23
+ function hasVisibleChildren(children: React.ReactNode): boolean {
24
+ if (children == null || children === "") return false;
25
+ if (typeof children === "string") return children.trim() !== "";
26
+ return true;
27
+ }
28
+
29
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
30
+ function Button(
31
+ {
32
+ variant = "solid",
33
+ size = "default",
34
+ icon,
35
+ children,
36
+ overStyled = false,
37
+ ...rest
38
+ },
39
+ ref,
40
+ ) {
41
+ const mapVariant = {
42
+ solid: "filled",
43
+ outline: "outline",
44
+ text: "subtle",
45
+ } as const;
46
+
47
+ const mapSize = {
48
+ default: "md",
49
+ small: "sm",
50
+ } as const;
51
+
52
+ const sanitizedProps = filterStylingProps(rest, overStyled);
53
+ const restRecord = sanitizedProps as Record<string, unknown>;
54
+
55
+ // Explicitly delete blocked semantic expansion dimension props
56
+ delete restRecord["fullWidth"];
57
+
58
+ const hasLeftSection = !!icon || !!restRecord["leftSection"];
59
+ const hasRightSection = !!restRecord["rightSection"];
60
+ const isIconOnly =
61
+ (hasLeftSection || hasRightSection) && !hasVisibleChildren(children);
62
+
63
+ if (
64
+ typeof process !== "undefined" &&
65
+ process.env.NODE_ENV !== "production" &&
66
+ isIconOnly &&
67
+ !restRecord["aria-label"]
68
+ ) {
69
+ console.warn(
70
+ '[Recursica Button] Icon-only buttons must provide an accessible name. Pass aria-label (e.g. aria-label="Submit").',
71
+ );
72
+ }
73
+
74
+ const mergedClassNames: Partial<Record<string, string>> = {
75
+ root: styles.root,
76
+ section: styles.section,
77
+ label: styles.label,
78
+ };
79
+
80
+ const classNamesProp = restRecord.classNames;
81
+ if (
82
+ classNamesProp &&
83
+ typeof classNamesProp === "object" &&
84
+ !Array.isArray(classNamesProp)
85
+ ) {
86
+ const o = classNamesProp as Partial<Record<string, string>>;
87
+ mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
88
+ mergedClassNames.section = o.section ?? styles.section;
89
+ mergedClassNames.label = o.label ?? styles.label;
90
+ }
91
+
92
+ const classNameProp = restRecord.className as string | undefined;
93
+ const finalClass = classNameProp
94
+ ? `${styles.root} ${classNameProp}`
95
+ : styles.root;
96
+
97
+ return (
98
+ <MantineButton
99
+ ref={ref}
100
+ className={finalClass}
101
+ classNames={mergedClassNames}
102
+ variant={mapVariant[variant]}
103
+ size={mapSize[size]}
104
+ leftSection={
105
+ icon != null ? (
106
+ <span className={styles.iconWrapper} aria-hidden>
107
+ {icon}
108
+ </span>
109
+ ) : undefined
110
+ }
111
+ data-variant={variant}
112
+ data-size={size}
113
+ {...(isIconOnly ? { "data-icon-only": "" } : {})}
114
+ {...sanitizedProps}
115
+ >
116
+ <span className={styles.labelText}>{children}</span>
117
+ </MantineButton>
118
+ );
119
+ },
120
+ );
121
+
122
+ Button.displayName = "Button";
@@ -0,0 +1,45 @@
1
+ # Button – implementation notes
2
+
3
+ Decisions and design tweaks for the Recursica Button that go beyond the Component Development Guide. Use this when maintaining the Button or when adding similar behavior to other components.
4
+
5
+ ---
6
+
7
+ ## Icon size: Recursica defines it
8
+
9
+ **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.
10
+
11
+ **Implementation:**
12
+
13
+ - When `icon` is provided, the Button wraps it in a single element with class `iconWrapper` before passing it to Mantine’s `leftSection`.
14
+ - In `Button.module.css`, `.iconWrapper` has explicit `width` and `height` from the Recursica tokens mapping natively based on `data-size`.
15
+ - The rule `.iconWrapper > *` sets `width: 100%`, `height: 100%`, and `object-fit: contain` so whatever the caller passes scales cleanly with the token constraints.
16
+
17
+ ---
18
+
19
+ ## Icon-only buttons: accessibility and width
20
+
21
+ **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.
22
+
23
+ **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.
24
+
25
+ **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.
26
+
27
+ ---
28
+
29
+ ## Label truncation at max-width
30
+
31
+ **Decision:** When the button hits its Recursica max-width (500px), the label truncates with an ellipsis instead of wrapping.
32
+
33
+ **Implementation:**
34
+ Mantine's `.mantine-Button-label` flex centering breaks primitive truncation logic. To combat this:
35
+
36
+ - **`.root`** has `overflow: hidden`.
37
+ - **`.root > *`** forces `min-width: 0`.
38
+ - The structural children wrap into `<span className={styles.labelText}>`.
39
+ - **`.labelText`** binds `overflow: hidden; text-overflow: ellipsis; white-space: nowrap;` creating flawless string cutoffs strictly at exact UI constraints.
40
+
41
+ ---
42
+
43
+ ## Disabled state: brand theme opacity (implicit)
44
+
45
+ **Decision:** The UI kit enforces global brand theme disabled opacities. The `.root:disabled` logic implicitly overrides visibility locally via `var(--recursica_brand_states_disabled)`.
@@ -0,0 +1 @@
1
+ export * from "./Button";
@@ -0,0 +1,10 @@
1
+ # Card Implementation Notes
2
+
3
+ ## Architecture Overrides
4
+
5
+ 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:
6
+
7
+ - `<Card.Header>` explicitly hooks `--recursica_ui-kit_components_card_properties_header-background` and corresponding padding variables.
8
+ - `<Card.Footer>` explicitly hooks `--recursica_ui-kit_components_card_properties_footer-background` and corresponding padding variables.
9
+
10
+ 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.
@@ -0,0 +1,60 @@
1
+ .root {
2
+ /* HARDCODE: Map Recursica explicitly into Mantine's variable format so internal components know the margins */
3
+ --card-padding: var(--recursica_ui-kit_components_card_properties_padding);
4
+
5
+ background-color: var(
6
+ --recursica_ui-kit_components_card_properties_colors_background
7
+ );
8
+ border-radius: var(
9
+ --recursica_ui-kit_components_card_properties_borders_border-radius
10
+ );
11
+ border: var(--recursica_ui-kit_components_card_properties_borders_border-size)
12
+ solid var(--recursica_ui-kit_components_card_properties_colors_border-color);
13
+ box-shadow: var(
14
+ --recursica_ui-kit_components_card_properties_elevations_elevation
15
+ );
16
+
17
+ padding: var(--card-padding);
18
+ min-width: var(--recursica_ui-kit_components_card_properties_min-width);
19
+ max-width: var(--recursica_ui-kit_components_card_properties_max-width);
20
+ color: var(--recursica_ui-kit_components_card_properties_colors_content);
21
+
22
+ display: flex;
23
+ flex-direction: column;
24
+ gap: var(--recursica_ui-kit_components_card_properties_section-gap);
25
+ }
26
+
27
+ .header {
28
+ background-color: var(
29
+ --recursica_ui-kit_components_card_properties_colors_header-background
30
+ );
31
+ padding: var(--recursica_ui-kit_components_card_properties_header-padding);
32
+ color: var(--recursica_ui-kit_components_card_properties_colors_title);
33
+ border-bottom: var(--recursica_ui-kit_components_card_properties_divider-size)
34
+ solid
35
+ var(--recursica_ui-kit_components_card_properties_colors_divider-color);
36
+ }
37
+
38
+ .footer {
39
+ background-color: var(
40
+ --recursica_ui-kit_components_card_properties_colors_footer-background
41
+ );
42
+ padding: var(--recursica_ui-kit_components_card_properties_footer-padding);
43
+ border-top: var(--recursica_ui-kit_components_card_properties_divider-size)
44
+ solid
45
+ var(--recursica_ui-kit_components_card_properties_colors_divider-color);
46
+ }
47
+
48
+ .section {
49
+ /* Just maps standard section spacing overrides if passed explicitly */
50
+ margin-top: var(
51
+ --recursica_ui-kit_components_card_properties_vertical-gutter
52
+ );
53
+ }
54
+
55
+ .content {
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: var(--recursica_ui-kit_components_card_properties_vertical-gutter);
59
+ padding: 0;
60
+ }
@@ -0,0 +1,141 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Card } from "./Card";
3
+ import { Layer } from "@recursica/adapter-common";
4
+ import { Button } from "../Button/Button";
5
+
6
+ const meta: Meta<typeof Card> = {
7
+ title: "UI-Kit/Card",
8
+ component: Card,
9
+ subcomponents: {
10
+ "Card.Header": Card.Header,
11
+ "Card.Content": Card.Content,
12
+ "Card.Footer": Card.Footer,
13
+ "Card.Section": Card.Section,
14
+ } as Record<string, React.ComponentType<unknown>>,
15
+ tags: ["autodocs"],
16
+ parameters: {
17
+ docs: {
18
+ description: {
19
+ component:
20
+ "The Card component acts as the foundational padded surface for grouping related information. It relies on standard internal compositional nodes (`Card.Header`, `Card.Content`, `Card.Footer`) mapped directly to the active Recursica design tokens to enforce layout gaps and margins seamlessly. Use the provided dot-notation wrappers rather than building ad-hoc generic sections.",
21
+ },
22
+ },
23
+ },
24
+ argTypes: {},
25
+ };
26
+
27
+ export default meta;
28
+
29
+ type Story = StoryObj<typeof Card>;
30
+
31
+ export const Default: Story = {
32
+ args: {},
33
+ render: ({ ...args }) => {
34
+ return (
35
+ <Layer
36
+ layer={0}
37
+ style={{
38
+ padding: "48px",
39
+ backgroundColor: "var(--recursica_brand_palettes_neutral_050)",
40
+ }}
41
+ >
42
+ <Card {...args}>
43
+ <Card.Header>
44
+ <span style={{ fontWeight: 500, fontSize: "1.125rem" }}>
45
+ Customer Activity Report
46
+ </span>
47
+ </Card.Header>
48
+ <Card.Content>
49
+ <p>
50
+ Card inner section content body. Notice how this acts as padded
51
+ content natively based on the overarching properties. Recursica's
52
+ vertical gutter governs vertical spacing between siblings in the
53
+ flex container.
54
+ </p>
55
+ <p>Another section showing the vertical gutter spacing.</p>
56
+ </Card.Content>
57
+ <Card.Footer>
58
+ <div
59
+ style={{
60
+ display: "flex",
61
+ justifyContent: "space-between",
62
+ alignItems: "center",
63
+ marginTop: "16px",
64
+ marginBottom: "8px",
65
+ }}
66
+ >
67
+ <span style={{ color: "#868e96", fontSize: "0.75rem" }}>
68
+ Generated today
69
+ </span>
70
+ <Button variant="solid">View Details</Button>
71
+ </div>
72
+ </Card.Footer>
73
+ </Card>
74
+ </Layer>
75
+ );
76
+ },
77
+ };
78
+
79
+ export const HeaderlessAndFooterless: Story = {
80
+ args: {},
81
+ render: ({ ...args }) => {
82
+ return (
83
+ <Layer
84
+ layer={0}
85
+ style={{
86
+ padding: "48px",
87
+ backgroundColor: "var(--recursica_brand_palettes_neutral_050)",
88
+ }}
89
+ >
90
+ <Card {...args}>
91
+ <Card.Content>
92
+ <span style={{ fontWeight: 500, fontSize: "1.125rem" }}>
93
+ Notice
94
+ </span>
95
+ <p>
96
+ This is a completely generic card payload dropping the Header and
97
+ Footer specific elements, simply acting as a padded elevation
98
+ boundary box directly mirroring native composability!
99
+ </p>
100
+ <Button variant="solid" mt="rec-md">
101
+ Acknowledge
102
+ </Button>
103
+ </Card.Content>
104
+ </Card>
105
+ </Layer>
106
+ );
107
+ },
108
+ };
109
+
110
+ export const LayerDemonstration: Story = {
111
+ args: {},
112
+ render: ({ ...args }) => {
113
+ return (
114
+ <div
115
+ style={{
116
+ display: "flex",
117
+ gap: "32px",
118
+ backgroundColor: "#e9ecef",
119
+ padding: "32px",
120
+ }}
121
+ >
122
+ <Layer layer={1}>
123
+ <Card {...args}>
124
+ <Card.Header>Layer 1 Wrapper</Card.Header>
125
+ <p>Content inside layer 1 card.</p>
126
+ </Card>
127
+ </Layer>
128
+
129
+ <Layer layer={2}>
130
+ <Card {...args}>
131
+ <Card.Header>Layer 2 Wrapper</Card.Header>
132
+ <p>
133
+ Content inside layer 2 card exposing a higher elevation drop
134
+ shadow inherently cascaded.
135
+ </p>
136
+ </Card>
137
+ </Layer>
138
+ </div>
139
+ );
140
+ },
141
+ };
@@ -0,0 +1,176 @@
1
+ import { forwardRef } from "react";
2
+ import {
3
+ Card as MantineCard,
4
+ type CardProps as MantineCardProps,
5
+ type CardSectionProps as MantineCardSectionProps,
6
+ } from "@mantine/core";
7
+ import {
8
+ filterStylingProps,
9
+ type RecursicaOverStyled,
10
+ } from "../../utils/filterStylingProps";
11
+ import styles from "./Card.module.css";
12
+
13
+ // ==== CARD CONTAINER ====
14
+ export type CardProps = RecursicaOverStyled<MantineCardProps>;
15
+
16
+ /**
17
+ * The root Card elevation box. It establishes the global background color, border radius, nested component gap, and outer shadow governed by the current `Layer` context.
18
+ */
19
+ const CardBase = forwardRef<HTMLDivElement, CardProps>(function Card(
20
+ { overStyled = false, ...rest },
21
+ ref,
22
+ ) {
23
+ const sanitizedProps = filterStylingProps(rest, overStyled);
24
+
25
+ const mergedClassNames: Partial<Record<string, string>> = {
26
+ root: styles.root,
27
+ };
28
+
29
+ const classNamesProp = (sanitizedProps as Record<string, unknown>).classNames;
30
+ if (
31
+ classNamesProp &&
32
+ typeof classNamesProp === "object" &&
33
+ !Array.isArray(classNamesProp)
34
+ ) {
35
+ const o = classNamesProp as Record<string, string>;
36
+ Object.keys(o).forEach((key) => {
37
+ if (mergedClassNames[key]) {
38
+ mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
39
+ } else {
40
+ mergedClassNames[key] = o[key];
41
+ }
42
+ });
43
+ }
44
+
45
+ const classNameProp = (sanitizedProps as Record<string, unknown>)
46
+ .className as string | undefined;
47
+
48
+ return (
49
+ <MantineCard
50
+ ref={ref}
51
+ className={classNameProp}
52
+ classNames={mergedClassNames}
53
+ {...(sanitizedProps as unknown as MantineCardProps)}
54
+ />
55
+ );
56
+ });
57
+ CardBase.displayName = "Card";
58
+
59
+ // ==== NATIVE MANTINE CARD.SECTION ====
60
+ export type RecursicaCardSectionProps = MantineCardSectionProps & {
61
+ children?: React.ReactNode;
62
+ };
63
+ export type CardSectionProps = RecursicaOverStyled<RecursicaCardSectionProps>;
64
+
65
+ /**
66
+ * A generalized edge-to-edge structural wrapper native to Mantine. Strips typical boundary padding via negative margins to allow content (like maps or header images) to touch the border seamlessly.
67
+ */
68
+ export const CardSection = forwardRef<HTMLDivElement, CardSectionProps>(
69
+ function CardSection({ overStyled = false, ...rest }, ref) {
70
+ const sanitizedProps = filterStylingProps(rest, overStyled);
71
+ const classNameProp = (sanitizedProps as Record<string, unknown>)
72
+ .className as string | undefined;
73
+
74
+ return (
75
+ <MantineCard.Section
76
+ ref={ref}
77
+ className={
78
+ classNameProp ? `${styles.section} ${classNameProp}` : styles.section
79
+ }
80
+ {...(sanitizedProps as unknown as MantineCardSectionProps)}
81
+ />
82
+ );
83
+ },
84
+ );
85
+ CardSection.displayName = "CardSection";
86
+
87
+ // ==== EXPLICIT CARD.HEADER ====
88
+ export type CardHeaderProps = RecursicaOverStyled<RecursicaCardSectionProps>;
89
+
90
+ /**
91
+ * Replaces standard generic Mantine `<Card.Section>` wrappers by directly injecting the strict Recursica design tokens for header sizing, background drops, and intrinsic padding limits.
92
+ */
93
+ export const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(
94
+ function CardHeader({ overStyled = false, ...rest }, ref) {
95
+ const sanitizedProps = filterStylingProps(rest, overStyled);
96
+ const classNameProp = (sanitizedProps as Record<string, unknown>)
97
+ .className as string | undefined;
98
+
99
+ return (
100
+ <MantineCard.Section
101
+ ref={ref}
102
+ className={
103
+ classNameProp ? `${styles.header} ${classNameProp}` : styles.header
104
+ }
105
+ {...(sanitizedProps as unknown as MantineCardSectionProps)}
106
+ />
107
+ );
108
+ },
109
+ );
110
+ CardHeader.displayName = "CardHeader";
111
+
112
+ // ==== EXPLICIT CARD.FOOTER ====
113
+ export type CardFooterProps = RecursicaOverStyled<RecursicaCardSectionProps>;
114
+
115
+ /**
116
+ * Counterpart to `Card.Header`, applying specific footer elevation margins, sizes, and padding natively.
117
+ */
118
+ export const CardFooter = forwardRef<HTMLDivElement, CardFooterProps>(
119
+ function CardFooter({ overStyled = false, ...rest }, ref) {
120
+ const sanitizedProps = filterStylingProps(rest, overStyled);
121
+ const classNameProp = (sanitizedProps as Record<string, unknown>)
122
+ .className as string | undefined;
123
+
124
+ return (
125
+ <MantineCard.Section
126
+ ref={ref}
127
+ className={
128
+ classNameProp ? `${styles.footer} ${classNameProp}` : styles.footer
129
+ }
130
+ {...(sanitizedProps as unknown as MantineCardSectionProps)}
131
+ />
132
+ );
133
+ },
134
+ );
135
+ CardFooter.displayName = "CardFooter";
136
+
137
+ // ==== EXPLICIT CARD.CONTENT ====
138
+ export type CardContentProps = RecursicaOverStyled<
139
+ React.HTMLAttributes<HTMLDivElement>
140
+ >;
141
+
142
+ /**
143
+ * Internal container that safely binds typography and structural flex gaps driven by the active UI kit tokens natively, preserving correct padding blocks compared to rigid `Card.Section` edges.
144
+ */
145
+ export const CardContent = forwardRef<HTMLDivElement, CardContentProps>(
146
+ function CardContent({ overStyled = false, ...rest }, ref) {
147
+ const sanitizedProps = filterStylingProps(rest, overStyled);
148
+ const classNameProp = (sanitizedProps as Record<string, unknown>)
149
+ .className as string | undefined;
150
+
151
+ return (
152
+ <div
153
+ ref={ref}
154
+ className={
155
+ classNameProp ? `${styles.content} ${classNameProp}` : styles.content
156
+ }
157
+ {...sanitizedProps}
158
+ />
159
+ );
160
+ },
161
+ );
162
+ CardContent.displayName = "CardContent";
163
+
164
+ // ==== DOT NOTATION EXPORT ====
165
+ type CardComponent = typeof CardBase & {
166
+ Section: typeof CardSection;
167
+ Header: typeof CardHeader;
168
+ Footer: typeof CardFooter;
169
+ Content: typeof CardContent;
170
+ };
171
+
172
+ export const Card = CardBase as CardComponent;
173
+ Card.Section = CardSection;
174
+ Card.Header = CardHeader;
175
+ Card.Footer = CardFooter;
176
+ Card.Content = CardContent;
@@ -0,0 +1,25 @@
1
+ # Checkbox Implementation Notes
2
+
3
+ ## Architectural Philosophy
4
+
5
+ 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.
6
+
7
+ ### Checkbox Alignment Anchoring (gap overflow hack)
8
+
9
+ 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.
10
+
11
+ We mathematically fix this alignment within `Checkbox.module.css` using explicit design variable arithmetic natively.
12
+ \`\`\`css
13
+ margin-top: calc(
14
+ (
15
+ var(--recursica_ui-kit_components_checkbox-item_properties_text_line-height) \*
16
+ var(--recursica_ui-kit_components_checkbox-item_properties_text_font-size) -
17
+ var(--recursica_ui-kit_components_checkbox_properties_size)
18
+ ) / 2
19
+ ) !important;
20
+ \`\`\`
21
+ 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!
22
+
23
+ ### Checkbox.Group Overrides
24
+
25
+ To decouple `<CheckboxGroup>` away from `<Input.Wrapper>`, we explicitly extract the raw array execution mapped correctly against our identical `RecursicaFormControlWrapperProps` schema structurally!