@recursica/mantine-adapter 0.13.0 → 0.14.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.
- package/CHANGELOG.md +12 -0
- package/dist/mantine-adapter.cjs +1 -1
- package/dist/mantine-adapter.cjs.map +1 -1
- package/dist/mantine-adapter.css +1 -1
- package/dist/mantine-adapter.js +1304 -1059
- package/dist/mantine-adapter.js.map +1 -1
- package/dist/src/components/HoverCard/HoverCard.d.ts +55 -3
- package/dist/src/components/NumberInput/NumberInput.d.ts +10 -2
- package/dist/src/components/Panel/Panel.d.ts +57 -3
- package/dist/src/components/Tooltip/Tooltip.d.ts +42 -3
- package/package.json +1 -1
- package/src/components/Dropdown/Dropdown.module.css +0 -3
- package/src/components/HoverCard/HOVERCARD_IMPLEMENTATION_NOTES.md +77 -0
- package/src/components/HoverCard/HoverCard.module.css +84 -0
- package/src/components/HoverCard/HoverCard.stories.tsx +160 -5
- package/src/components/HoverCard/HoverCard.tsx +153 -4
- package/src/components/Loader/LOADER_IMPLEMENTATION_NOTES.md +25 -0
- package/src/components/NumberInput/NUMBER_INPUT_IMPLEMENTATION_NOTES.md +18 -0
- package/src/components/NumberInput/NumberInput.module.css +250 -0
- package/src/components/NumberInput/NumberInput.stories.tsx +84 -4
- package/src/components/NumberInput/NumberInput.tsx +144 -5
- package/src/components/Panel/PANEL_IMPLEMENTATION_NOTES.md +106 -0
- package/src/components/Panel/Panel.module.css +143 -0
- package/src/components/Panel/Panel.stories.tsx +205 -6
- package/src/components/Panel/Panel.tsx +143 -4
- package/src/components/TextArea/TextArea.module.css +0 -3
- package/src/components/TextField/TextField.module.css +0 -3
- package/src/components/Tooltip/TOOLTIP_IMPLEMENTATION_NOTES.md +85 -0
- package/src/components/Tooltip/Tooltip.module.css +81 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +154 -6
- package/src/components/Tooltip/Tooltip.tsx +114 -4
|
@@ -1,7 +1,146 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
NumberInput as MantineNumberInput,
|
|
4
|
+
type NumberInputProps as MantineNumberInputProps,
|
|
5
|
+
} from "@mantine/core";
|
|
6
|
+
import { type ReadOnlyControlProps } from "@recursica/adapter-common";
|
|
7
|
+
import {
|
|
8
|
+
filterStylingProps,
|
|
9
|
+
type RecursicaOverStyled,
|
|
10
|
+
} from "../../utils/filterStylingProps";
|
|
11
|
+
import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
|
|
12
|
+
import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
|
|
13
|
+
import styles from "./NumberInput.module.css";
|
|
2
14
|
|
|
3
|
-
export
|
|
15
|
+
export interface RecursicaNumberInputProps
|
|
16
|
+
extends Omit<
|
|
17
|
+
MantineNumberInputProps,
|
|
18
|
+
"size" | "variant" | "radius" | "wrapperProps" | "classNames" | "styles"
|
|
19
|
+
>,
|
|
20
|
+
Pick<
|
|
21
|
+
RecursicaFormControlWrapperProps,
|
|
22
|
+
| "assistiveText"
|
|
23
|
+
| "assistiveWithIcon"
|
|
24
|
+
| "formLayout"
|
|
25
|
+
| "labelSize"
|
|
26
|
+
| "labelAlignment"
|
|
27
|
+
| "labelOptionalText"
|
|
28
|
+
| "labelWithEditIcon"
|
|
29
|
+
| "onLabelEditClick"
|
|
30
|
+
>,
|
|
31
|
+
ReadOnlyControlProps {
|
|
32
|
+
/** Enables/disables the controls to increment and decrement the value */
|
|
33
|
+
hideControls?: boolean;
|
|
34
|
+
}
|
|
4
35
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
36
|
+
export type NumberInputProps = RecursicaOverStyled<RecursicaNumberInputProps>;
|
|
37
|
+
|
|
38
|
+
export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
|
|
39
|
+
function NumberInput(props, ref) {
|
|
40
|
+
const {
|
|
41
|
+
overStyled = false,
|
|
42
|
+
formLayout = "stacked",
|
|
43
|
+
|
|
44
|
+
// Label & Wrapper Maps
|
|
45
|
+
labelSize,
|
|
46
|
+
labelAlignment,
|
|
47
|
+
labelOptionalText,
|
|
48
|
+
labelWithEditIcon,
|
|
49
|
+
onLabelEditClick,
|
|
50
|
+
|
|
51
|
+
label,
|
|
52
|
+
assistiveText,
|
|
53
|
+
assistiveWithIcon,
|
|
54
|
+
error,
|
|
55
|
+
required,
|
|
56
|
+
withAsterisk,
|
|
57
|
+
id,
|
|
58
|
+
className,
|
|
59
|
+
style,
|
|
60
|
+
disabled,
|
|
61
|
+
readOnly,
|
|
62
|
+
readOnlyComponent,
|
|
63
|
+
emptyValueComponent,
|
|
64
|
+
value,
|
|
65
|
+
defaultValue,
|
|
66
|
+
hideControls = false,
|
|
67
|
+
...rest
|
|
68
|
+
} = props;
|
|
69
|
+
|
|
70
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
71
|
+
const restRecord = sanitizedProps as Record<string, unknown>;
|
|
72
|
+
|
|
73
|
+
// Delete prohibited sizing hooks from bypassing variables natively
|
|
74
|
+
delete restRecord["size"];
|
|
75
|
+
delete restRecord["variant"];
|
|
76
|
+
delete restRecord["radius"];
|
|
77
|
+
|
|
78
|
+
// Securely map core native blocks down ensuring nested CSS modules map precisely
|
|
79
|
+
const mergedClassNames: Partial<Record<string, string>> = {
|
|
80
|
+
wrapper: styles.root, // The nested Input internal relative wrapper bounding box
|
|
81
|
+
input: styles.input,
|
|
82
|
+
section: styles.section,
|
|
83
|
+
controls: styles.controls,
|
|
84
|
+
control: styles.control,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<WithReadOnlyWrapper
|
|
89
|
+
className={className}
|
|
90
|
+
style={style as React.CSSProperties}
|
|
91
|
+
controlMaxWidth="var(--recursica_ui-kit_components_number-input_properties_max-width)"
|
|
92
|
+
controlMinWidth="var(--recursica_ui-kit_components_number-input_properties_min-width)"
|
|
93
|
+
overStyled={overStyled as true}
|
|
94
|
+
formLayout={formLayout}
|
|
95
|
+
labelSize={labelSize}
|
|
96
|
+
labelAlignment={labelAlignment}
|
|
97
|
+
labelOptionalText={labelOptionalText}
|
|
98
|
+
labelWithEditIcon={labelWithEditIcon}
|
|
99
|
+
onLabelEditClick={onLabelEditClick}
|
|
100
|
+
label={label}
|
|
101
|
+
assistiveText={assistiveText}
|
|
102
|
+
assistiveWithIcon={assistiveWithIcon}
|
|
103
|
+
error={error}
|
|
104
|
+
required={required}
|
|
105
|
+
withAsterisk={withAsterisk}
|
|
106
|
+
id={id}
|
|
107
|
+
readOnly={readOnly}
|
|
108
|
+
readOnlyComponent={readOnlyComponent}
|
|
109
|
+
emptyValueComponent={emptyValueComponent}
|
|
110
|
+
readOnlyType="text"
|
|
111
|
+
readOnlyValue={
|
|
112
|
+
value !== undefined ? value?.toString() : defaultValue?.toString()
|
|
113
|
+
}
|
|
114
|
+
readOnlyNativeProps={props}
|
|
115
|
+
activeComponent={
|
|
116
|
+
/* Naked Input execution safely decoupled from Mantine's macro Input.Wrapper DOM hooks */
|
|
117
|
+
<MantineNumberInput
|
|
118
|
+
ref={ref}
|
|
119
|
+
classNames={mergedClassNames}
|
|
120
|
+
disabled={disabled}
|
|
121
|
+
value={value}
|
|
122
|
+
defaultValue={defaultValue}
|
|
123
|
+
hideControls={hideControls}
|
|
124
|
+
label={undefined}
|
|
125
|
+
description={undefined}
|
|
126
|
+
error={undefined}
|
|
127
|
+
required={undefined}
|
|
128
|
+
withAsterisk={undefined}
|
|
129
|
+
wrapperProps={{
|
|
130
|
+
"data-disabled": disabled ? "true" : undefined,
|
|
131
|
+
"data-error": error ? "true" : undefined,
|
|
132
|
+
"data-with-left-section": restRecord.leftSection
|
|
133
|
+
? "true"
|
|
134
|
+
: undefined,
|
|
135
|
+
"data-with-right-section":
|
|
136
|
+
restRecord.rightSection || !hideControls ? "true" : undefined,
|
|
137
|
+
}}
|
|
138
|
+
{...(sanitizedProps as unknown as MantineNumberInputProps)}
|
|
139
|
+
/>
|
|
140
|
+
}
|
|
141
|
+
/>
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
NumberInput.displayName = "NumberInput";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Panel – Implementation Notes
|
|
2
|
+
|
|
3
|
+
Decisions and design tweaks strictly tailored for the UI Kit's Panel wrapped against `@mantine/core`. This is a living document that tracks _why_ specific logic decisions exist.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Mapping to Mantine Drawer
|
|
8
|
+
|
|
9
|
+
**Decision:** Panel maps to Mantine's `Drawer` component, not `Paper` or `Card`.
|
|
10
|
+
|
|
11
|
+
**Implementation:** Per the Recursica design system specification, "Panels slide in or expand from the edge of the screen to reveal additional content or functionality." This is the exact behavior of Mantine's `Drawer` component, which provides:
|
|
12
|
+
|
|
13
|
+
- Slide-in animation from any screen edge
|
|
14
|
+
- Overlay/backdrop
|
|
15
|
+
- Portal rendering
|
|
16
|
+
- Focus trapping
|
|
17
|
+
- Scroll locking
|
|
18
|
+
- Close button and title in header
|
|
19
|
+
|
|
20
|
+
Paper and Card are static containers; Drawer is an overlay that matches Panel's defined behavior.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 2. Token Namespace: `panel`
|
|
25
|
+
|
|
26
|
+
**Decision:** The CSS module exclusively uses variables from the `--recursica_ui-kit_components_panel_*` namespace.
|
|
27
|
+
|
|
28
|
+
**Implementation:** The Recursica token system defines the `panel` namespace covering:
|
|
29
|
+
|
|
30
|
+
- Geometry: border-radius, border-size, min-width (200px), max-width (960px)
|
|
31
|
+
- Content padding: content-horizontal-padding (xl), content-vertical-padding (lg)
|
|
32
|
+
- Header/Footer padding: header-footer-horizontal-padding (xl), header-footer-vertical-padding (md)
|
|
33
|
+
- Spacing: header-close-gap (md), footer-button-gap (md)
|
|
34
|
+
- Divider: divider-size (1px), divider-color
|
|
35
|
+
- Elevation: elevation-3
|
|
36
|
+
- Colors (layer-aware): background, border-color, content, divider-color, header-footer-background, title
|
|
37
|
+
- Non-CSS: header-style ("h3") — see §7
|
|
38
|
+
|
|
39
|
+
No tokens from other component namespaces are referenced.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 3. ClassNames Mapping to Drawer
|
|
44
|
+
|
|
45
|
+
**Decision:** Panel maps CSS module classes to Mantine Drawer's stylesNames.
|
|
46
|
+
|
|
47
|
+
**Implementation:** The Drawer stylesNames used are:
|
|
48
|
+
|
|
49
|
+
- `content` — Outer container (background, border, elevation)
|
|
50
|
+
- `header` — Title bar with close button (padding, divider, gap)
|
|
51
|
+
- `title` — Title text color
|
|
52
|
+
- `body` — Scrollable content area (padding)
|
|
53
|
+
|
|
54
|
+
Other stylesNames (`overlay`, `root`, `inner`, `close`) are left to Mantine defaults.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 4. Default Position Override
|
|
59
|
+
|
|
60
|
+
**Decision:** Recursica defaults `position` to `"right"`. Mantine defaults to `"left"`.
|
|
61
|
+
|
|
62
|
+
**Implementation:** The `position="right"` default is set before the prop spread, so developer-provided `position` values still take precedence. Right-side panels are the most common pattern for supplementary content, settings, and detail views.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 5. Custom Panel.Footer
|
|
67
|
+
|
|
68
|
+
**Decision:** A custom `Panel.Footer` sub-component is provided. Mantine's Drawer does not have a native footer.
|
|
69
|
+
|
|
70
|
+
**Implementation:** `Panel.Footer` is a `<div>` with inline styles referencing Recursica CSS variables for:
|
|
71
|
+
|
|
72
|
+
- `header-footer-background` and `header-footer-padding` tokens
|
|
73
|
+
- Top divider using `divider-size` and `divider-color`
|
|
74
|
+
- `footer-button-gap` for action button spacing
|
|
75
|
+
- `margin-top: auto` to push the footer to the bottom
|
|
76
|
+
|
|
77
|
+
Inline styles are used instead of a CSS module class because the footer is rendered inside the Drawer's `<body>` element, and the CSS variables are applied to the body's parent container. The inline styles ensure the footer correctly references the panel tokens regardless of DOM position.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 6. Hardcoded Values
|
|
82
|
+
|
|
83
|
+
### `border-style: solid` (CSS module, `.content`)
|
|
84
|
+
|
|
85
|
+
Mantine's Drawer content does not set `border-style` natively. Without this, the border-width and border-color tokens have no visible effect. Same pattern as Card, Menu, HoverCard, Tooltip.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 7. header-style Token Reference Bug
|
|
90
|
+
|
|
91
|
+
**Decision:** The `header-style: "h3"` token currently outputs as a raw string in the UI kit JSON, breaking our PostCSS compiler when mapped as CSS variable fallbacks. We have explicitly removed these fallbacks.
|
|
92
|
+
|
|
93
|
+
**Implementation:** The design system intended for the `header-style` token to act as a proper typography reference, which would compile into fully expanded properties like `--recursica_ui-kit_components_panel_properties_header-style_fontFamily`. Because the JSON export currently passes it as the raw string `"h3"`, attempting to bind to the non-existent variables breaks the build plugin. Until the JSON export is fixed to compile proper reference nodes, we are stripping typography bindings from the `.title` class and relying purely on `color` and Mantine's defaults.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 8. Panel Types: Standard vs Scrollable
|
|
98
|
+
|
|
99
|
+
**Decision:** Both standard and scrollable types are supported natively.
|
|
100
|
+
|
|
101
|
+
**Implementation:** Per the Recursica specification:
|
|
102
|
+
|
|
103
|
+
- **Standard** — All content visible without scrolling. The default behavior when content fits.
|
|
104
|
+
- **Scrollable** — Internal scrollbar enabled when content exceeds the panel height. Header and footer CTAs remain pinned.
|
|
105
|
+
|
|
106
|
+
Mantine's Drawer handles this automatically — the `body` section scrolls when content overflows, while the `header` remains fixed. The `Panel.Footer` uses `margin-top: auto` to stay at the bottom.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/* HARDCODED VALUES:
|
|
2
|
+
- border-style: solid: Structural rendering rule for the panel border.
|
|
3
|
+
- .inner { left: 0 }: Prevents the fixed Drawer portal from being pushed off-screen in centered flex environments (like Storybook Canvas).
|
|
4
|
+
- flexbox layout on .content & .body: Required to create a vertical flex context that pushes the footer to the bottom.
|
|
5
|
+
- position: sticky & negative margins on .footer: A CSS technique used to break the footer out of the .body padding constraints so it stretches edge-to-edge, while remaining fixed at the bottom of the scroll container.
|
|
6
|
+
- z-index: 10 on .footer: Ensures the footer covers the body content as it scrolls underneath.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/* ======================================
|
|
10
|
+
PANEL CONTENT (outer drawer container)
|
|
11
|
+
====================================== */
|
|
12
|
+
|
|
13
|
+
.content {
|
|
14
|
+
background-color: var(
|
|
15
|
+
--recursica_ui-kit_components_panel_properties_colors_background
|
|
16
|
+
);
|
|
17
|
+
border-style: solid;
|
|
18
|
+
border-width: var(--recursica_ui-kit_components_panel_properties_border-size);
|
|
19
|
+
border-color: var(
|
|
20
|
+
--recursica_ui-kit_components_panel_properties_colors_border-color
|
|
21
|
+
);
|
|
22
|
+
border-radius: var(
|
|
23
|
+
--recursica_ui-kit_components_panel_properties_border-radius
|
|
24
|
+
);
|
|
25
|
+
box-shadow: var(--recursica_ui-kit_components_panel_properties_elevation);
|
|
26
|
+
color: var(--recursica_ui-kit_components_panel_properties_colors_content);
|
|
27
|
+
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
height: 100%;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.inner {
|
|
34
|
+
/* Forces the Drawer Inner container to the left edge, preventing it from inheriting
|
|
35
|
+
a centered static position in flex environments like Storybook's Canvas wrapper */
|
|
36
|
+
left: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* ======================================
|
|
40
|
+
PANEL HEADER
|
|
41
|
+
====================================== */
|
|
42
|
+
|
|
43
|
+
.header {
|
|
44
|
+
background-color: var(
|
|
45
|
+
--recursica_ui-kit_components_panel_properties_colors_header-footer-background
|
|
46
|
+
);
|
|
47
|
+
padding: var(
|
|
48
|
+
--recursica_ui-kit_components_panel_properties_header-footer-vertical-padding
|
|
49
|
+
)
|
|
50
|
+
var(
|
|
51
|
+
--recursica_ui-kit_components_panel_properties_header-footer-horizontal-padding
|
|
52
|
+
);
|
|
53
|
+
border-bottom: var(
|
|
54
|
+
--recursica_ui-kit_components_panel_properties_divider-size
|
|
55
|
+
)
|
|
56
|
+
solid
|
|
57
|
+
var(--recursica_ui-kit_components_panel_properties_colors_divider-color);
|
|
58
|
+
gap: var(--recursica_ui-kit_components_panel_properties_header-close-gap);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ======================================
|
|
62
|
+
PANEL TITLE
|
|
63
|
+
====================================== */
|
|
64
|
+
|
|
65
|
+
.title {
|
|
66
|
+
color: var(--recursica_ui-kit_components_panel_properties_colors_title);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ======================================
|
|
70
|
+
PANEL BODY
|
|
71
|
+
====================================== */
|
|
72
|
+
|
|
73
|
+
.body {
|
|
74
|
+
padding: var(
|
|
75
|
+
--recursica_ui-kit_components_panel_properties_content-vertical-padding
|
|
76
|
+
)
|
|
77
|
+
var(
|
|
78
|
+
--recursica_ui-kit_components_panel_properties_content-horizontal-padding
|
|
79
|
+
);
|
|
80
|
+
color: var(--recursica_ui-kit_components_panel_properties_colors_content);
|
|
81
|
+
|
|
82
|
+
flex: 1;
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-direction: column;
|
|
85
|
+
overflow-y: auto;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* ======================================
|
|
89
|
+
PANEL FOOTER
|
|
90
|
+
====================================== */
|
|
91
|
+
|
|
92
|
+
.footer {
|
|
93
|
+
background-color: var(
|
|
94
|
+
--recursica_ui-kit_components_panel_properties_colors_header-footer-background
|
|
95
|
+
);
|
|
96
|
+
padding: var(
|
|
97
|
+
--recursica_ui-kit_components_panel_properties_header-footer-vertical-padding
|
|
98
|
+
)
|
|
99
|
+
var(
|
|
100
|
+
--recursica_ui-kit_components_panel_properties_header-footer-horizontal-padding
|
|
101
|
+
);
|
|
102
|
+
border-top: var(--recursica_ui-kit_components_panel_properties_divider-size)
|
|
103
|
+
solid
|
|
104
|
+
var(--recursica_ui-kit_components_panel_properties_colors_divider-color);
|
|
105
|
+
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
justify-content: flex-end;
|
|
109
|
+
gap: var(--recursica_ui-kit_components_panel_properties_footer-button-gap);
|
|
110
|
+
|
|
111
|
+
/* Ensure footer sticks to the bottom of the scroll container */
|
|
112
|
+
margin-top: auto;
|
|
113
|
+
position: sticky;
|
|
114
|
+
|
|
115
|
+
/* Break out of .body padding constraints to stretch edge-to-edge */
|
|
116
|
+
bottom: calc(
|
|
117
|
+
var(
|
|
118
|
+
--recursica_ui-kit_components_panel_properties_content-vertical-padding
|
|
119
|
+
) *
|
|
120
|
+
-1
|
|
121
|
+
);
|
|
122
|
+
margin-left: calc(
|
|
123
|
+
var(
|
|
124
|
+
--recursica_ui-kit_components_panel_properties_content-horizontal-padding
|
|
125
|
+
) *
|
|
126
|
+
-1
|
|
127
|
+
);
|
|
128
|
+
margin-right: calc(
|
|
129
|
+
var(
|
|
130
|
+
--recursica_ui-kit_components_panel_properties_content-horizontal-padding
|
|
131
|
+
) *
|
|
132
|
+
-1
|
|
133
|
+
);
|
|
134
|
+
margin-bottom: calc(
|
|
135
|
+
var(
|
|
136
|
+
--recursica_ui-kit_components_panel_properties_content-vertical-padding
|
|
137
|
+
) *
|
|
138
|
+
-1
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
/* Render over scrolling content */
|
|
142
|
+
z-index: 10;
|
|
143
|
+
}
|
|
@@ -1,17 +1,216 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
1
2
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
-
import { Panel } from "./Panel";
|
|
3
|
-
import {
|
|
3
|
+
import { Panel, type PanelProps } from "./Panel";
|
|
4
|
+
import { Button } from "../Button";
|
|
5
|
+
import { Text } from "../Text/Text";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
type PanelStoryArgs = Record<string, any>;
|
|
9
|
+
|
|
10
|
+
const meta: Meta = {
|
|
11
|
+
title: "UI-Kit/Panel",
|
|
7
12
|
component: Panel,
|
|
8
13
|
tags: ["autodocs"],
|
|
14
|
+
argTypes: {
|
|
15
|
+
position: {
|
|
16
|
+
control: "select",
|
|
17
|
+
options: ["left", "right", "top", "bottom"],
|
|
18
|
+
description: "Side of the screen the panel slides in from.",
|
|
19
|
+
},
|
|
20
|
+
title: {
|
|
21
|
+
control: "text",
|
|
22
|
+
description: "Panel title displayed in the header.",
|
|
23
|
+
},
|
|
24
|
+
withOverlay: {
|
|
25
|
+
control: "boolean",
|
|
26
|
+
description: "Whether to display a background overlay.",
|
|
27
|
+
},
|
|
28
|
+
withCloseButton: {
|
|
29
|
+
control: "boolean",
|
|
30
|
+
description: "Whether to display the close button in the header.",
|
|
31
|
+
},
|
|
32
|
+
// Hide auto-detected HTML attributes
|
|
33
|
+
defaultChecked: { table: { disable: true } },
|
|
34
|
+
defaultValue: { table: { disable: true } },
|
|
35
|
+
suppressContentEditableWarning: { table: { disable: true } },
|
|
36
|
+
suppressHydrationWarning: { table: { disable: true } },
|
|
37
|
+
},
|
|
38
|
+
parameters: {
|
|
39
|
+
layout: "fullscreen",
|
|
40
|
+
docs: {
|
|
41
|
+
description: {
|
|
42
|
+
component: `
|
|
43
|
+
The \`Panel\` component slides in or expands from the edge of the screen to reveal additional content or functionality. Built on Mantine's \`Drawer\`, it enforces Recursica design tokens for styling.
|
|
44
|
+
|
|
45
|
+
### Anatomy
|
|
46
|
+
1. **Header** — Title and close icon, remains fixed on scroll
|
|
47
|
+
2. **Divider** — Separates header/footer from content
|
|
48
|
+
3. **Body (Slot)** — Scrollable content area for custom content
|
|
49
|
+
4. **Footer** — Fixed action buttons (Recursica-specific)
|
|
50
|
+
|
|
51
|
+
### Usage
|
|
52
|
+
\`\`\`tsx
|
|
53
|
+
const [opened, { open, close }] = useDisclosure(false);
|
|
54
|
+
|
|
55
|
+
<Button onClick={open}>Open Panel</Button>
|
|
56
|
+
<Panel opened={opened} onClose={close} title="Settings" position="right">
|
|
57
|
+
Content goes here
|
|
58
|
+
<Panel.Footer>
|
|
59
|
+
<Button variant="outline">Cancel</Button>
|
|
60
|
+
<Button variant="solid">Save</Button>
|
|
61
|
+
</Panel.Footer>
|
|
62
|
+
</Panel>
|
|
63
|
+
\`\`\`
|
|
64
|
+
`,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
9
68
|
};
|
|
10
69
|
|
|
11
70
|
export default meta;
|
|
12
71
|
|
|
13
|
-
type Story = StoryObj<
|
|
72
|
+
type Story = StoryObj<PanelStoryArgs>;
|
|
14
73
|
|
|
15
74
|
export const Default: Story = {
|
|
16
|
-
|
|
75
|
+
args: {
|
|
76
|
+
position: "right",
|
|
77
|
+
title: "Panel Title",
|
|
78
|
+
withOverlay: true,
|
|
79
|
+
withCloseButton: true,
|
|
80
|
+
},
|
|
81
|
+
render: () => {
|
|
82
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
83
|
+
const [opened, setOpened] = useState(false);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<>
|
|
87
|
+
<Button variant="solid" onClick={() => setOpened(true)}>
|
|
88
|
+
Open Panel
|
|
89
|
+
</Button>
|
|
90
|
+
<Panel
|
|
91
|
+
opened={opened}
|
|
92
|
+
onClose={() => setOpened(false)}
|
|
93
|
+
title="Panel Title"
|
|
94
|
+
position="right"
|
|
95
|
+
>
|
|
96
|
+
<Text>
|
|
97
|
+
This is the panel body content area. Panels slide in from the edge
|
|
98
|
+
of the screen to reveal supplementary information, navigation
|
|
99
|
+
options, or toolsets.
|
|
100
|
+
</Text>
|
|
101
|
+
<Panel.Footer>
|
|
102
|
+
<Button variant="outline" onClick={() => setOpened(false)}>
|
|
103
|
+
Cancel
|
|
104
|
+
</Button>
|
|
105
|
+
<Button variant="solid">Save</Button>
|
|
106
|
+
</Panel.Footer>
|
|
107
|
+
</Panel>
|
|
108
|
+
</>
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const LeftPosition: Story = {
|
|
114
|
+
args: {
|
|
115
|
+
position: "left",
|
|
116
|
+
title: "Navigation",
|
|
117
|
+
withOverlay: true,
|
|
118
|
+
withCloseButton: true,
|
|
119
|
+
},
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
121
|
+
render: ({ withLayer, layer, ...args }: PanelStoryArgs) => {
|
|
122
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
123
|
+
const [opened, setOpened] = useState(false);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<>
|
|
127
|
+
<Button variant="outline" onClick={() => setOpened(true)}>
|
|
128
|
+
Open Left Panel
|
|
129
|
+
</Button>
|
|
130
|
+
<Panel
|
|
131
|
+
{...(args as PanelProps)}
|
|
132
|
+
opened={opened}
|
|
133
|
+
onClose={() => setOpened(false)}
|
|
134
|
+
>
|
|
135
|
+
<Text>
|
|
136
|
+
A panel sliding in from the left, commonly used for navigation menus
|
|
137
|
+
or sidebars.
|
|
138
|
+
</Text>
|
|
139
|
+
</Panel>
|
|
140
|
+
</>
|
|
141
|
+
);
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const ScrollableContent: Story = {
|
|
146
|
+
args: {
|
|
147
|
+
position: "right",
|
|
148
|
+
title: "Scrollable Panel",
|
|
149
|
+
withOverlay: true,
|
|
150
|
+
withCloseButton: true,
|
|
151
|
+
},
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
153
|
+
render: ({ withLayer, layer, ...args }: PanelStoryArgs) => {
|
|
154
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
155
|
+
const [opened, setOpened] = useState(false);
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<>
|
|
159
|
+
<Button variant="solid" onClick={() => setOpened(true)}>
|
|
160
|
+
Open Scrollable Panel
|
|
161
|
+
</Button>
|
|
162
|
+
<Panel
|
|
163
|
+
{...(args as PanelProps)}
|
|
164
|
+
opened={opened}
|
|
165
|
+
onClose={() => setOpened(false)}
|
|
166
|
+
>
|
|
167
|
+
{Array.from({ length: 20 }).map((_, i) => (
|
|
168
|
+
<p key={i} style={{ marginBottom: "1rem" }}>
|
|
169
|
+
Paragraph {i + 1}: This is sample content to demonstrate the
|
|
170
|
+
scrollable behavior of the panel when content exceeds the viewport
|
|
171
|
+
height.
|
|
172
|
+
</p>
|
|
173
|
+
))}
|
|
174
|
+
<Panel.Footer>
|
|
175
|
+
<Button variant="outline" onClick={() => setOpened(false)}>
|
|
176
|
+
Close
|
|
177
|
+
</Button>
|
|
178
|
+
<Button variant="solid">Apply</Button>
|
|
179
|
+
</Panel.Footer>
|
|
180
|
+
</Panel>
|
|
181
|
+
</>
|
|
182
|
+
);
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const LongTitle: Story = {
|
|
187
|
+
args: {
|
|
188
|
+
position: "right",
|
|
189
|
+
title:
|
|
190
|
+
"This is a ridiculously long panel title designed to test how the header CSS handles text overflow and whether it truncates correctly or breaks the layout",
|
|
191
|
+
withOverlay: true,
|
|
192
|
+
withCloseButton: true,
|
|
193
|
+
},
|
|
194
|
+
render: ({ ...args }: PanelStoryArgs) => {
|
|
195
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
196
|
+
const [opened, setOpened] = useState(false);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<>
|
|
200
|
+
<Button variant="solid" onClick={() => setOpened(true)}>
|
|
201
|
+
Open Long Title Panel
|
|
202
|
+
</Button>
|
|
203
|
+
<Panel
|
|
204
|
+
{...(args as PanelProps)}
|
|
205
|
+
opened={opened}
|
|
206
|
+
onClose={() => setOpened(false)}
|
|
207
|
+
>
|
|
208
|
+
<Text>
|
|
209
|
+
Check the header to see if the long title is handled gracefully
|
|
210
|
+
without pushing the close button off screen.
|
|
211
|
+
</Text>
|
|
212
|
+
</Panel>
|
|
213
|
+
</>
|
|
214
|
+
);
|
|
215
|
+
},
|
|
17
216
|
};
|