@recursica/mui-adapter 0.5.0 → 0.6.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 (159) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/mui-adapter.cjs +74 -1
  3. package/dist/mui-adapter.cjs.map +1 -1
  4. package/dist/mui-adapter.css +1 -1
  5. package/dist/mui-adapter.js +6652 -204
  6. package/dist/mui-adapter.js.map +1 -1
  7. package/dist/src/components/Accordion/Accordion.d.ts +63 -2
  8. package/dist/src/components/AssistiveElement/AssistiveElement.d.ts +9 -2
  9. package/dist/src/components/Autocomplete/Autocomplete.d.ts +16 -2
  10. package/dist/src/components/Avatar/Avatar.d.ts +14 -2
  11. package/dist/src/components/Badge/Badge.d.ts +13 -3
  12. package/dist/src/components/Breadcrumb/Breadcrumb.d.ts +10 -3
  13. package/dist/src/components/Button/Button.d.ts +1 -1
  14. package/dist/src/components/Card/Card.d.ts +40 -3
  15. package/dist/src/components/Checkbox/Checkbox.d.ts +15 -2
  16. package/dist/src/components/Checkbox/CheckboxGroup.d.ts +16 -0
  17. package/dist/src/components/Checkbox/index.d.ts +5 -1
  18. package/dist/src/components/Chip/Chip.d.ts +18 -2
  19. package/dist/src/components/Dropdown/Dropdown.d.ts +23 -3
  20. package/dist/src/components/FormControlLayout/FormControlLayout.d.ts +10 -2
  21. package/dist/src/components/FormControlWrapper/FormControlWrapper.d.ts +24 -2
  22. package/dist/src/components/HoverCard/HoverCard.d.ts +55 -3
  23. package/dist/src/components/Label/Label.d.ts +13 -2
  24. package/dist/src/components/Link/Link.d.ts +1 -1
  25. package/dist/src/components/Menu/Menu.d.ts +99 -3
  26. package/dist/src/components/Modal/Modal.d.ts +36 -2
  27. package/dist/src/components/NumberInput/NumberInput.d.ts +14 -2
  28. package/dist/src/components/Pagination/Pagination.d.ts +44 -2
  29. package/dist/src/components/Pagination/Pagination.icons.d.ts +8 -0
  30. package/dist/src/components/Panel/Panel.d.ts +65 -3
  31. package/dist/src/components/Radio/Radio.d.ts +13 -3
  32. package/dist/src/components/Radio/RadioGroup.d.ts +13 -0
  33. package/dist/src/components/ReadOnlyField/ReadOnlyBooleanField.d.ts +5 -0
  34. package/dist/src/components/ReadOnlyField/ReadOnlyField.d.ts +7 -2
  35. package/dist/src/components/ReadOnlyField/ReadOnlySwitchField.d.ts +5 -0
  36. package/dist/src/components/ReadOnlyField/ReadOnlyTextField.d.ts +8 -0
  37. package/dist/src/components/ReadOnlyField/WithReadOnlyWrapper.d.ts +28 -0
  38. package/dist/src/components/ReadOnlyField/index.d.ts +1 -0
  39. package/dist/src/components/SegmentedControl/SegmentedControl.d.ts +31 -3
  40. package/dist/src/components/Slider/Slider.d.ts +28 -2
  41. package/dist/src/components/Stepper/Stepper.d.ts +21 -2
  42. package/dist/src/components/Switch/Switch.d.ts +10 -3
  43. package/dist/src/components/Switch/SwitchGroup.d.ts +13 -0
  44. package/dist/src/components/Tabs/Tabs.d.ts +35 -3
  45. package/dist/src/components/Text/Text.d.ts +1 -1
  46. package/dist/src/components/TextArea/TextArea.d.ts +18 -2
  47. package/dist/src/components/TextField/TextField.d.ts +16 -2
  48. package/dist/src/components/Timeline/Timeline.d.ts +14 -2
  49. package/dist/src/components/Timeline/TimelineItem.d.ts +29 -0
  50. package/dist/src/components/Title/Title.d.ts +1 -1
  51. package/dist/src/components/Toast/Toast.d.ts +25 -2
  52. package/dist/src/components/Tooltip/Tooltip.d.ts +42 -3
  53. package/dist/src/components/Typography/Typography.d.ts +1 -1
  54. package/package.json +9 -2
  55. package/src/components/Accordion/Accordion.module.css +295 -56
  56. package/src/components/Accordion/Accordion.stories.tsx +149 -21
  57. package/src/components/Accordion/Accordion.tsx +264 -5
  58. package/src/components/AssistiveElement/AssistiveElement.module.css +111 -20
  59. package/src/components/AssistiveElement/AssistiveElement.stories.tsx +76 -19
  60. package/src/components/AssistiveElement/AssistiveElement.tsx +85 -5
  61. package/src/components/Autocomplete/Autocomplete.module.css +320 -42
  62. package/src/components/Autocomplete/Autocomplete.stories.tsx +180 -19
  63. package/src/components/Autocomplete/Autocomplete.tsx +199 -6
  64. package/src/components/Avatar/Avatar.module.css +343 -73
  65. package/src/components/Avatar/Avatar.stories.tsx +94 -22
  66. package/src/components/Avatar/Avatar.tsx +110 -6
  67. package/src/components/Badge/Badge.module.css +127 -29
  68. package/src/components/Badge/Badge.stories.tsx +65 -22
  69. package/src/components/Badge/Badge.tsx +70 -6
  70. package/src/components/Breadcrumb/Breadcrumb.module.css +25 -5
  71. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +57 -22
  72. package/src/components/Breadcrumb/Breadcrumb.tsx +62 -6
  73. package/src/components/Button/Button.module.css +3 -3
  74. package/src/components/Card/Card.module.css +118 -38
  75. package/src/components/Card/Card.stories.tsx +117 -19
  76. package/src/components/Card/Card.tsx +179 -5
  77. package/src/components/Checkbox/Checkbox.module.css +232 -39
  78. package/src/components/Checkbox/Checkbox.stories.tsx +81 -19
  79. package/src/components/Checkbox/Checkbox.tsx +146 -5
  80. package/src/components/Checkbox/CheckboxGroup.tsx +117 -0
  81. package/src/components/Checkbox/index.ts +6 -1
  82. package/src/components/Chip/Chip.module.css +228 -43
  83. package/src/components/Chip/Chip.stories.tsx +101 -21
  84. package/src/components/Chip/Chip.tsx +138 -6
  85. package/src/components/Dropdown/Dropdown.module.css +291 -41
  86. package/src/components/Dropdown/Dropdown.stories.tsx +80 -21
  87. package/src/components/Dropdown/Dropdown.tsx +182 -6
  88. package/src/components/FormControlLayout/FormControlLayout.module.css +92 -0
  89. package/src/components/FormControlLayout/FormControlLayout.stories.tsx +74 -19
  90. package/src/components/FormControlLayout/FormControlLayout.tsx +58 -4
  91. package/src/components/FormControlWrapper/FormControlWrapper.module.css +96 -0
  92. package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +122 -19
  93. package/src/components/FormControlWrapper/FormControlWrapper.tsx +145 -6
  94. package/src/components/HoverCard/HoverCard.module.css +90 -22
  95. package/src/components/HoverCard/HoverCard.stories.tsx +158 -21
  96. package/src/components/HoverCard/HoverCard.tsx +150 -5
  97. package/src/components/Label/Label.module.css +136 -35
  98. package/src/components/Label/Label.stories.tsx +98 -17
  99. package/src/components/Label/Label.tsx +88 -5
  100. package/src/components/Menu/Menu.module.css +285 -51
  101. package/src/components/Menu/Menu.stories.tsx +307 -21
  102. package/src/components/Menu/Menu.tsx +318 -5
  103. package/src/components/Modal/Modal.module.css +171 -35
  104. package/src/components/Modal/Modal.stories.tsx +66 -22
  105. package/src/components/Modal/Modal.tsx +216 -4
  106. package/src/components/NumberInput/NumberInput.module.css +276 -42
  107. package/src/components/NumberInput/NumberInput.stories.tsx +83 -20
  108. package/src/components/NumberInput/NumberInput.tsx +148 -6
  109. package/src/components/Pagination/Pagination.icons.tsx +66 -0
  110. package/src/components/Pagination/Pagination.module.css +246 -20
  111. package/src/components/Pagination/Pagination.stories.tsx +42 -21
  112. package/src/components/Pagination/Pagination.tsx +193 -5
  113. package/src/components/Panel/Panel.module.css +206 -38
  114. package/src/components/Panel/Panel.stories.tsx +214 -22
  115. package/src/components/Panel/Panel.tsx +153 -5
  116. package/src/components/Radio/Radio.module.css +220 -37
  117. package/src/components/Radio/Radio.stories.tsx +77 -19
  118. package/src/components/Radio/Radio.tsx +178 -5
  119. package/src/components/Radio/RadioGroup.tsx +111 -0
  120. package/src/components/ReadOnlyField/ReadOnlyBooleanField.tsx +21 -0
  121. package/src/components/ReadOnlyField/ReadOnlyField.module.css +53 -15
  122. package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +175 -16
  123. package/src/components/ReadOnlyField/ReadOnlyField.tsx +109 -5
  124. package/src/components/ReadOnlyField/ReadOnlySwitchField.tsx +21 -0
  125. package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +48 -0
  126. package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +93 -0
  127. package/src/components/ReadOnlyField/index.ts +1 -0
  128. package/src/components/SegmentedControl/SegmentedControl.module.css +203 -47
  129. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +102 -20
  130. package/src/components/SegmentedControl/SegmentedControl.tsx +110 -6
  131. package/src/components/Slider/Slider.module.css +542 -86
  132. package/src/components/Slider/Slider.stories.tsx +157 -19
  133. package/src/components/Slider/Slider.tsx +282 -5
  134. package/src/components/Stepper/Stepper.module.css +410 -71
  135. package/src/components/Stepper/Stepper.stories.tsx +132 -19
  136. package/src/components/Stepper/Stepper.tsx +145 -6
  137. package/src/components/Switch/Switch.module.css +238 -39
  138. package/src/components/Switch/Switch.stories.tsx +98 -19
  139. package/src/components/Switch/Switch.tsx +153 -6
  140. package/src/components/Switch/SwitchGroup.tsx +111 -0
  141. package/src/components/Tabs/Tabs.module.css +422 -79
  142. package/src/components/Tabs/Tabs.stories.tsx +138 -19
  143. package/src/components/Tabs/Tabs.tsx +103 -6
  144. package/src/components/TextArea/TextArea.module.css +155 -32
  145. package/src/components/TextArea/TextArea.stories.tsx +80 -21
  146. package/src/components/TextArea/TextArea.tsx +156 -6
  147. package/src/components/TextField/TextField.module.css +261 -42
  148. package/src/components/TextField/TextField.stories.tsx +168 -15
  149. package/src/components/TextField/TextField.tsx +171 -6
  150. package/src/components/Timeline/Timeline.module.css +365 -76
  151. package/src/components/Timeline/Timeline.stories.tsx +114 -21
  152. package/src/components/Timeline/Timeline.tsx +80 -5
  153. package/src/components/Timeline/TimelineItem.tsx +101 -0
  154. package/src/components/Toast/Toast.module.css +166 -28
  155. package/src/components/Toast/Toast.stories.tsx +54 -22
  156. package/src/components/Toast/Toast.tsx +90 -5
  157. package/src/components/Tooltip/Tooltip.module.css +89 -24
  158. package/src/components/Tooltip/Tooltip.stories.tsx +152 -22
  159. package/src/components/Tooltip/Tooltip.tsx +114 -5
@@ -1,35 +1,138 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import React from "react";
1
3
  import type { Meta, StoryObj } from "@storybook/react";
2
- import { FormControlWrapper } from "./FormControlWrapper";
3
- import { ComingSoon } from "@recursica/storybook-template";
4
+ import {
5
+ FormControlWrapper,
6
+ type FormControlWrapperProps,
7
+ } from "./FormControlWrapper";
8
+ import { TextField } from "../TextField/TextField";
9
+ import { formControlArgTypes } from "../../../.storybook/commonArgTypes";
4
10
 
5
- const meta: Meta<typeof FormControlWrapper> = {
6
- title: "UI-Kit/🚧 FormControlWrapper",
11
+ type WrapperStoryProps = FormControlWrapperProps;
12
+
13
+ const meta: Meta<WrapperStoryProps> = {
14
+ title: "UI-Kit/FormControlWrapper",
7
15
  component: FormControlWrapper,
8
16
  tags: ["autodocs"],
9
17
  parameters: {
18
+ docs: {
19
+ description: {
20
+ component:
21
+ "The `FormControlWrapper` is the ultimate structural replacement for built-in wrapper layouts. By centralizing label tracking, error rendering, ARIA generation, and grid layouts natively inside this single component, we achieve pixel-perfect layout compliance while retaining MUI's internal `useFormControl` Context natively.\n\n### Usage with Naked Primitives\nThis component wraps 'naked' elements directly. The demonstration stories below utilize `<TextField>` as a native display vehicle, since `<TextField>` natively pipes all its properties structurally back into this wrapper.",
22
+ },
23
+ },
10
24
  controls: {
11
25
  include: [
12
- "layer",
13
- "withLayer",
14
- "children",
15
- "component",
16
- "variant",
17
- "size",
18
- "icon",
19
- "disabled",
20
- "href",
21
- "onClick",
22
- "onChange",
23
- "value",
24
- "checked",
26
+ "error",
27
+ "assistiveText",
28
+ "assistiveWithIcon",
29
+ "required",
30
+ "formLayout",
31
+ "labelSize",
32
+ "labelAlignment",
33
+ "labelOptionalText",
34
+ "labelWithEditIcon",
35
+ "labelActionArea",
25
36
  ],
26
37
  },
27
38
  },
39
+ argTypes: {
40
+ ...formControlArgTypes,
41
+ error: {
42
+ control: "text",
43
+ description:
44
+ "Error string driving native assistive component and validation markers.",
45
+ },
46
+ assistiveText: {
47
+ control: "text",
48
+ description:
49
+ "Helper instructions safely dynamically anchored below the input box.",
50
+ },
51
+ assistiveWithIcon: {
52
+ control: "boolean",
53
+ },
54
+ required: {
55
+ control: "boolean",
56
+ },
57
+ },
28
58
  };
29
59
 
30
60
  export default meta;
31
- type Story = StoryObj<typeof FormControlWrapper>;
61
+
62
+ type Story = StoryObj<WrapperStoryProps>;
63
+
64
+ const renderWithTextField = ({ ...args }: any) => (
65
+ // We explicitly cast args to 'any' here because MUI wrapper defines Wrapper anatomy,
66
+ // but TextField expects them for the full TextInput anatomy, causing a TS intersection mismatch.
67
+ <TextField placeholder="Form Control primitive mapped..." {...args} />
68
+ );
32
69
 
33
70
  export const Default: Story = {
34
- render: () => <ComingSoon componentName="FormControlWrapper" />,
71
+ args: {
72
+ label: "Account Username",
73
+ formLayout: "stacked",
74
+ assistiveText: "Validation occurs immediately natively.",
75
+ },
76
+ render: renderWithTextField,
77
+ };
78
+
79
+ export const VisualErrorState: Story = {
80
+ args: {
81
+ label: "Encryption Protocol",
82
+ formLayout: "stacked",
83
+ error: "Strict validation limits reached. Handshake rejected securely.",
84
+ },
85
+ render: renderWithTextField,
86
+ };
87
+
88
+ export const RequiredArchitecture: Story = {
89
+ args: {
90
+ label: "Root Password",
91
+ formLayout: "side-by-side",
92
+ required: true,
93
+ assistiveText: "Bypass string structure required to initiate protocol.",
94
+ },
95
+ render: renderWithTextField,
96
+ };
97
+
98
+ export const WithoutAssistiveIcons: Story = {
99
+ args: {
100
+ label: "Server Domain",
101
+ assistiveText:
102
+ "A standard text boundary without default native icon parameters bounding.",
103
+ assistiveWithIcon: false,
104
+ },
105
+ render: renderWithTextField,
106
+ };
107
+
108
+ export const NativeChildrenDirectly: Story = {
109
+ parameters: {
110
+ docs: {
111
+ description: {
112
+ story:
113
+ "Bypassing the TextField map to show exactly how native `<input>` hooks execute inside the raw wrapper natively perfectly.",
114
+ },
115
+ },
116
+ },
117
+ args: {
118
+ label: "Raw HTML Checkbox",
119
+ formLayout: "side-by-side",
120
+ assistiveText: "This wraps a raw HTML input tag mapping correctly.",
121
+ },
122
+ render: ({ ...args }: any) => (
123
+ <div
124
+ style={{
125
+ display: "flex",
126
+ gap: "10px",
127
+ alignItems: "center",
128
+ }}
129
+ >
130
+ <FormControlWrapper {...args}>
131
+ <input
132
+ type="checkbox"
133
+ style={{ margin: 0, width: "16px", height: "16px" }}
134
+ />
135
+ </FormControlWrapper>
136
+ </div>
137
+ ),
35
138
  };
@@ -1,9 +1,148 @@
1
1
  import React from "react";
2
+ import { FormControl, type FormControlProps } from "@mui/material";
3
+ import { filterStylingProps } from "../../utils/filterStylingProps";
4
+ import { Label } from "../Label/Label";
5
+ import { AssistiveElement } from "../AssistiveElement/AssistiveElement";
6
+ import { FormControlLayout } from "../FormControlLayout/FormControlLayout";
7
+ import styles from "./FormControlWrapper.module.css";
2
8
 
3
- export type FormControlWrapperProps = React.HTMLAttributes<HTMLDivElement>;
9
+ export interface RecursicaFormControlWrapperProps
10
+ extends Omit<FormControlProps, "margin" | "variant" | "size" | "color"> {
11
+ overStyled?: boolean;
12
+ label?: React.ReactNode;
13
+ /** Primary assistive description bound natively to the component footer. Maps to MUI's 'helperText' dynamically. */
14
+ assistiveText?: React.ReactNode;
15
+ /** Secondary parameter fallback identical to assistiveText to match MUI / Mantine native APIs safely. */
16
+ description?: React.ReactNode;
17
+ /** Fallback mapping for MUI's native helperText */
18
+ helperText?: React.ReactNode;
19
+ assistiveWithIcon?: boolean;
4
20
 
5
- export const FormControlWrapper: React.FC<FormControlWrapperProps> = (
6
- props,
7
- ) => {
8
- return <div {...props}>FormControlWrapper</div>;
9
- };
21
+ formLayout?: "stacked" | "side-by-side";
22
+ labelSize?: "small" | "default" | "md";
23
+ labelAlignment?: "left" | "right"; // Currently functionally maps to left automatically natively
24
+ labelOptionalText?: React.ReactNode | true;
25
+ labelWithEditIcon?: boolean;
26
+ onLabelEditClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
27
+ labelActionArea?: React.ReactNode;
28
+
29
+ /** Allows layout mapping to limit the input's bounding box without impacting the label */
30
+ controlMaxWidth?: string;
31
+ controlMinWidth?: string;
32
+ }
33
+
34
+ export type FormControlWrapperProps = RecursicaFormControlWrapperProps;
35
+
36
+ export const FormControlWrapper = React.forwardRef<
37
+ HTMLDivElement,
38
+ FormControlWrapperProps
39
+ >(function FormControlWrapper(props, ref) {
40
+ const {
41
+ overStyled = false,
42
+ label,
43
+ assistiveText,
44
+ description,
45
+ helperText,
46
+ assistiveWithIcon = true,
47
+ error,
48
+ required,
49
+ disabled,
50
+ focused,
51
+ id,
52
+
53
+ formLayout = "stacked",
54
+ labelSize = "default",
55
+ labelOptionalText,
56
+ labelWithEditIcon,
57
+ onLabelEditClick,
58
+ labelActionArea,
59
+
60
+ controlMaxWidth,
61
+ controlMinWidth,
62
+
63
+ className,
64
+ style,
65
+ children,
66
+ ...rest
67
+ } = props;
68
+
69
+ const sanitizedProps = filterStylingProps(rest, overStyled);
70
+
71
+ // Map descriptions fallback exactly like Mantine
72
+ const finalAssistiveText = assistiveText || description || helperText;
73
+
74
+ // ARIA Bindings
75
+ const labelId = id ? `${id}-label` : undefined;
76
+ const descriptionId = id ? `${id}-description` : undefined;
77
+
78
+ // Wrap children safely cloning aria hooks down natively
79
+ const mappedChildren = React.Children.map(children, (child) => {
80
+ if (React.isValidElement(child)) {
81
+ return React.cloneElement(child, {
82
+ "aria-labelledby": labelId,
83
+ "aria-describedby": finalAssistiveText ? descriptionId : undefined,
84
+ } as React.HTMLAttributes<HTMLElement>);
85
+ }
86
+ return child;
87
+ });
88
+
89
+ return (
90
+ <FormControl
91
+ ref={ref}
92
+ error={!!error}
93
+ required={required}
94
+ disabled={disabled}
95
+ focused={focused}
96
+ className={className ? `${styles.root} ${className}` : styles.root}
97
+ style={style}
98
+ {...(sanitizedProps as FormControlProps)}
99
+ >
100
+ <FormControlLayout
101
+ formLayout={formLayout}
102
+ labelSize={labelSize}
103
+ leftSection={
104
+ label && (
105
+ <Label
106
+ id={labelId}
107
+ htmlFor={id}
108
+ required={required}
109
+ labelOptionalText={labelOptionalText}
110
+ labelWithEditIcon={labelWithEditIcon}
111
+ onLabelEditClick={onLabelEditClick}
112
+ labelActionArea={labelActionArea}
113
+ >
114
+ {label}
115
+ </Label>
116
+ )
117
+ }
118
+ >
119
+ <div
120
+ className={styles.inputContainer}
121
+ style={
122
+ {
123
+ "--form-control-max-width": controlMaxWidth,
124
+ "--form-control-min-width": controlMinWidth,
125
+ } as React.CSSProperties
126
+ }
127
+ >
128
+ {/* Natively wrap children into flex box */}
129
+ {mappedChildren}
130
+
131
+ {/* Append native assistive block dynamically below the input */}
132
+ {(finalAssistiveText || error) && (
133
+ <AssistiveElement
134
+ id={descriptionId}
135
+ assistiveVariant={error ? "error" : "help"}
136
+ assistiveWithIcon={assistiveWithIcon}
137
+ className={styles.assistive}
138
+ >
139
+ {error && typeof error === "string" ? error : finalAssistiveText}
140
+ </AssistiveElement>
141
+ )}
142
+ </div>
143
+ </FormControlLayout>
144
+ </FormControl>
145
+ );
146
+ });
147
+
148
+ FormControlWrapper.displayName = "FormControlWrapper";
@@ -1,23 +1,91 @@
1
+ /* HARDCODED VALUES:
2
+ - border-style: solid. Structural rendering rule for the dropdown border (Mantine uses Paper
3
+ which may not set border-style natively). Same pattern as Menu.
4
+ - arrowSize defaults to 16 in HoverCard.tsx. Mantine uses arrowSize for inline width/height
5
+ and positioning (-arrowSize/2) calculations that cannot be CSS-driven. The default matches
6
+ the Recursica beak-size token (16px). See COMPONENT_ISSUES.md for details.
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).
1
9
  /* EXEMPTIONS:
2
- This component is a placeholder stub. Once fully implemented, its specific
3
- tokens will be fully wired up to direct styles. */
4
-
5
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_beak-size */
6
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_border-radius */
7
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_border-size */
8
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_colors_background */
9
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_colors_border-color */
10
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_colors_content */
11
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_content-text_font-family */
12
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_content-text_font-size */
13
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_content-text_font-style */
14
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_content-text_font-weight */
15
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_content-text_letter-spacing */
16
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_content-text_line-height */
17
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_content-text_text-decoration */
18
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_content-text_text-transform */
19
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_elevation */
20
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_horizontal-padding */
21
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_max-width */
22
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_min-width */
23
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_vertical-padding */
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 */
15
+
16
+ /* ======================================
17
+ DROPDOWN CONTAINER
18
+ ====================================== */
19
+
20
+ .dropdown {
21
+ background-color: var(
22
+ --recursica_ui-kit_components_hover-card-popover_properties_colors_background
23
+ );
24
+ border-style: solid; /* HARDCODE: Mantine Paper does not set border-style natively */
25
+ border-width: var(
26
+ --recursica_ui-kit_components_hover-card-popover_properties_border-size
27
+ );
28
+ border-color: var(
29
+ --recursica_ui-kit_components_hover-card-popover_properties_colors_border-color
30
+ );
31
+ border-radius: var(
32
+ --recursica_ui-kit_components_hover-card-popover_properties_border-radius
33
+ );
34
+ box-shadow: var(
35
+ --recursica_ui-kit_components_hover-card-popover_properties_elevation
36
+ );
37
+
38
+ min-width: var(
39
+ --recursica_ui-kit_components_hover-card-popover_properties_min-width
40
+ );
41
+ max-width: var(
42
+ --recursica_ui-kit_components_hover-card-popover_properties_max-width
43
+ );
44
+
45
+ padding: var(
46
+ --recursica_ui-kit_components_hover-card-popover_properties_vertical-padding
47
+ )
48
+ var(
49
+ --recursica_ui-kit_components_hover-card-popover_properties_horizontal-padding
50
+ );
51
+
52
+ /* Typography */
53
+ font-family: var(
54
+ --recursica_ui-kit_components_hover-card-popover_properties_content-text_font-family
55
+ );
56
+ font-size: var(
57
+ --recursica_ui-kit_components_hover-card-popover_properties_content-text_font-size
58
+ );
59
+ font-style: var(
60
+ --recursica_ui-kit_components_hover-card-popover_properties_content-text_font-style
61
+ );
62
+ font-weight: var(
63
+ --recursica_ui-kit_components_hover-card-popover_properties_content-text_font-weight
64
+ );
65
+ letter-spacing: var(
66
+ --recursica_ui-kit_components_hover-card-popover_properties_content-text_letter-spacing
67
+ );
68
+ line-height: var(
69
+ --recursica_ui-kit_components_hover-card-popover_properties_content-text_line-height
70
+ );
71
+ text-decoration: var(
72
+ --recursica_ui-kit_components_hover-card-popover_properties_content-text_text-decoration
73
+ );
74
+ text-transform: var(
75
+ --recursica_ui-kit_components_hover-card-popover_properties_content-text_text-transform
76
+ );
77
+
78
+ color: var(
79
+ --recursica_ui-kit_components_hover-card-popover_properties_colors_content
80
+ );
81
+ }
82
+
83
+ /* ======================================
84
+ ARROW / BEAK
85
+ ====================================== */
86
+
87
+ .arrow {
88
+ border-color: var(
89
+ --recursica_ui-kit_components_hover-card-popover_properties_colors_border-color
90
+ );
91
+ }
@@ -1,35 +1,172 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
2
  import { HoverCard } from "./HoverCard";
3
- import { ComingSoon } from "@recursica/storybook-template";
3
+ import { Button } from "../Button";
4
+ import { Text } from "../Text/Text";
5
+ import { Group } from "../Group/Group";
6
+ import { Stack } from "../Stack/Stack";
7
+ import { Avatar } from "../Avatar";
4
8
 
5
- const meta: Meta<typeof HoverCard> = {
6
- title: "UI-Kit/🚧 HoverCard",
9
+ /**
10
+ * HoverCard story axes:
11
+ * - position: dropdown alignment relative to target
12
+ * - withArrow: arrow indicator on the dropdown
13
+ * - openDelay / closeDelay: hover timing controls
14
+ * - content variations: plain text, rich content (avatar + text + actions)
15
+ * - layer: tested via the global Layer decorator (withLayer/layer args)
16
+ */
17
+
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ type HoverCardStoryArgs = Record<string, any>;
20
+
21
+ const meta: Meta = {
22
+ title: "UI-Kit/HoverCard",
7
23
  component: HoverCard,
8
24
  tags: ["autodocs"],
9
- parameters: {
10
- controls: {
11
- include: [
12
- "layer",
13
- "withLayer",
14
- "children",
15
- "component",
16
- "variant",
17
- "size",
18
- "icon",
19
- "disabled",
20
- "href",
21
- "onClick",
22
- "onChange",
23
- "value",
24
- "checked",
25
+ argTypes: {
26
+ position: {
27
+ control: "select",
28
+ options: [
29
+ "bottom",
30
+ "bottom-start",
31
+ "bottom-end",
32
+ "top",
33
+ "top-start",
34
+ "top-end",
35
+ "left",
36
+ "left-start",
37
+ "left-end",
38
+ "right",
39
+ "right-start",
40
+ "right-end",
25
41
  ],
42
+ description: "The position of the dropdown relative to the target.",
43
+ },
44
+ withBeak: {
45
+ control: "boolean",
46
+ description:
47
+ "Whether to display a beak (arrow) pointing to the target. Recursica equivalent of Mantine's withArrow.",
48
+ },
49
+ offset: {
50
+ control: "number",
51
+ description:
52
+ "Distance in px between the dropdown and the target element.",
53
+ },
54
+ openDelay: {
55
+ control: "number",
56
+ description: "Delay in ms before the dropdown opens on hover.",
57
+ },
58
+ closeDelay: {
59
+ control: "number",
60
+ description: "Delay in ms before the dropdown closes when hover ends.",
61
+ },
62
+ disabled: {
63
+ control: "boolean",
64
+ description: "If set, the hover card dropdown will not be rendered.",
65
+ },
66
+ },
67
+ parameters: {
68
+ layout: "centered",
69
+ docs: {
70
+ description: {
71
+ component: `
72
+ The \`HoverCard\` component displays a popover-style dropdown when the user hovers over a target element. It wraps Mantine's composable \`HoverCard\` while enforcing strict Recursica design token styling.
73
+
74
+ ### Composable API
75
+
76
+ HoverCard uses a dot-notation composition pattern:
77
+ - \`<HoverCard>\` — Root container managing open/close state on hover
78
+ - \`<HoverCard.Target>\` — Wrapper for the trigger element (must be a single element supporting ref)
79
+ - \`<HoverCard.Dropdown>\` — The popup panel displaying content on hover
80
+
81
+ ### Key Behaviors
82
+ - The dropdown opens when the user hovers over the target and closes when they move away
83
+ - \`openDelay\` and \`closeDelay\` control the timing of open/close transitions
84
+ - The dropdown stays open while the user hovers over it, allowing interaction with its content
85
+ `,
86
+ },
26
87
  },
27
88
  },
28
89
  };
29
90
 
30
91
  export default meta;
31
- type Story = StoryObj<typeof HoverCard>;
92
+
93
+ type Story = StoryObj<HoverCardStoryArgs>;
32
94
 
33
95
  export const Default: Story = {
34
- render: () => <ComingSoon componentName="HoverCard" />,
96
+ args: {
97
+ position: "top",
98
+ withBeak: true,
99
+ offset: 5,
100
+ openDelay: 0,
101
+ closeDelay: 150,
102
+ },
103
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
104
+ render: ({ withLayer, layer, ...args }: HoverCardStoryArgs) => {
105
+ return (
106
+ <HoverCard {...args}>
107
+ <HoverCard.Target>
108
+ <Button variant="solid">Hover me</Button>
109
+ </HoverCard.Target>
110
+
111
+ <HoverCard.Dropdown>
112
+ <Text>
113
+ This is a hover card with informational content that appears when
114
+ you hover over the target element.
115
+ </Text>
116
+ </HoverCard.Dropdown>
117
+ </HoverCard>
118
+ );
119
+ },
120
+ };
121
+
122
+ export const WithoutBeak: Story = {
123
+ args: {
124
+ position: "top",
125
+ withBeak: false,
126
+ offset: 5,
127
+ },
128
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
129
+ render: ({ withLayer, layer, ...args }: HoverCardStoryArgs) => {
130
+ return (
131
+ <HoverCard {...args}>
132
+ <HoverCard.Target>
133
+ <Button variant="outline">Without Beak</Button>
134
+ </HoverCard.Target>
135
+
136
+ <HoverCard.Dropdown>
137
+ <Text>
138
+ This hover card has the beak disabled, showing a clean dropdown
139
+ without the pointing indicator.
140
+ </Text>
141
+ </HoverCard.Dropdown>
142
+ </HoverCard>
143
+ );
144
+ },
145
+ };
146
+
147
+ export const RichContent: Story = {
148
+ args: {
149
+ position: "top",
150
+ offset: 5,
151
+ },
152
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
153
+ render: ({ withLayer, layer, ...args }: HoverCardStoryArgs) => {
154
+ return (
155
+ <HoverCard {...args}>
156
+ <HoverCard.Target>
157
+ <Button variant="solid">User Profile</Button>
158
+ </HoverCard.Target>
159
+
160
+ <HoverCard.Dropdown>
161
+ <Group>
162
+ <Avatar src={null} alt="User avatar" />
163
+ <Stack>
164
+ <Text>Jane Doe</Text>
165
+ <Text>Software Engineer at Recursica</Text>
166
+ </Stack>
167
+ </Group>
168
+ </HoverCard.Dropdown>
169
+ </HoverCard>
170
+ );
171
+ },
35
172
  };