@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
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Drawer as MantineDrawer,
|
|
4
|
+
type DrawerProps as MantineDrawerProps,
|
|
5
|
+
} from "@mantine/core";
|
|
6
|
+
import {
|
|
7
|
+
filterStylingProps,
|
|
8
|
+
type RecursicaOverStyled,
|
|
9
|
+
} from "../../utils/filterStylingProps";
|
|
10
|
+
import styles from "./Panel.module.css";
|
|
2
11
|
|
|
3
|
-
|
|
12
|
+
// ============================================================
|
|
13
|
+
// PANEL (Drawer)
|
|
14
|
+
// ============================================================
|
|
4
15
|
|
|
5
|
-
|
|
6
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Recursica Panel root props. Extends Mantine Drawer.
|
|
18
|
+
*/
|
|
19
|
+
export type PanelProps = RecursicaOverStyled<MantineDrawerProps>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Recursica Panel component wrapping Mantine's Drawer.
|
|
23
|
+
*
|
|
24
|
+
* Panels slide in or expand from the edge of the screen to reveal
|
|
25
|
+
* additional content or functionality. They are commonly used to provide
|
|
26
|
+
* supplementary information, navigation options, or toolsets without
|
|
27
|
+
* cluttering the main interface.
|
|
28
|
+
*
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <Panel opened={opened} onClose={close} title="Panel Title" position="right">
|
|
31
|
+
* <Panel.Body>
|
|
32
|
+
* Content goes here
|
|
33
|
+
* </Panel.Body>
|
|
34
|
+
* </Panel>
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* Mantine Drawer sub-components available via dot-notation:
|
|
38
|
+
* - `Panel.Header` — Top section with title and close button
|
|
39
|
+
* - `Panel.Title` — Title text within the header
|
|
40
|
+
* - `Panel.CloseButton` — Close button within the header
|
|
41
|
+
* - `Panel.Body` — Scrollable body content area
|
|
42
|
+
* - `Panel.Content` — Outer content container
|
|
43
|
+
* - `Panel.Overlay` — Background overlay
|
|
44
|
+
* - `Panel.Root` — Root element for advanced composition
|
|
45
|
+
* - `Panel.Stack` — Stacked drawer context
|
|
46
|
+
*/
|
|
47
|
+
const PanelBase = function Panel({
|
|
48
|
+
overStyled = false,
|
|
49
|
+
position = "right",
|
|
50
|
+
keepMounted = true,
|
|
51
|
+
...rest
|
|
52
|
+
}: PanelProps) {
|
|
53
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
54
|
+
|
|
55
|
+
// Bind CSS module classes to Mantine's internal classNames API
|
|
56
|
+
const mergedClassNames: Partial<Record<string, string>> = {
|
|
57
|
+
content: styles.content,
|
|
58
|
+
header: styles.header,
|
|
59
|
+
title: styles.title,
|
|
60
|
+
body: styles.body,
|
|
61
|
+
inner: styles.inner,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const classNamesProp = (sanitizedProps as Record<string, unknown>).classNames;
|
|
65
|
+
if (
|
|
66
|
+
classNamesProp &&
|
|
67
|
+
typeof classNamesProp === "object" &&
|
|
68
|
+
!Array.isArray(classNamesProp)
|
|
69
|
+
) {
|
|
70
|
+
const o = classNamesProp as Record<string, string>;
|
|
71
|
+
Object.keys(o).forEach((key) => {
|
|
72
|
+
if (mergedClassNames[key]) {
|
|
73
|
+
mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
|
|
74
|
+
} else {
|
|
75
|
+
mergedClassNames[key] = o[key];
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<MantineDrawer
|
|
82
|
+
position={position} /* Recursica default: right; Mantine default: left */
|
|
83
|
+
keepMounted={keepMounted}
|
|
84
|
+
closeOnClickOutside={rest.closeOnClickOutside ?? Boolean(rest.opened)}
|
|
85
|
+
{...(sanitizedProps as unknown as MantineDrawerProps)}
|
|
86
|
+
classNames={mergedClassNames}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
7
89
|
};
|
|
90
|
+
PanelBase.displayName = "Panel";
|
|
91
|
+
|
|
92
|
+
// ============================================================
|
|
93
|
+
// PANEL FOOTER (custom — Mantine Drawer has no Footer sub-component)
|
|
94
|
+
// ============================================================
|
|
95
|
+
|
|
96
|
+
export type PanelFooterProps = RecursicaOverStyled<
|
|
97
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
98
|
+
>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Panel footer section with action buttons.
|
|
102
|
+
* Separated from the body by a divider. Remains fixed at the bottom.
|
|
103
|
+
* This is a Recursica-specific sub-component; Mantine Drawer does not
|
|
104
|
+
* natively provide a footer.
|
|
105
|
+
*/
|
|
106
|
+
export const PanelFooter = forwardRef<HTMLDivElement, PanelFooterProps>(
|
|
107
|
+
function PanelFooter({ overStyled = false, ...rest }, ref) {
|
|
108
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
109
|
+
const classNameProp = (sanitizedProps as Record<string, unknown>)
|
|
110
|
+
.className as string | undefined;
|
|
111
|
+
|
|
112
|
+
const finalClassName = classNameProp
|
|
113
|
+
? `${styles.footer} ${classNameProp}`
|
|
114
|
+
: styles.footer;
|
|
115
|
+
|
|
116
|
+
return <div ref={ref} className={finalClassName} {...sanitizedProps} />;
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
PanelFooter.displayName = "PanelFooter";
|
|
120
|
+
|
|
121
|
+
// ============================================================
|
|
122
|
+
// DOT NOTATION EXPORT
|
|
123
|
+
// ============================================================
|
|
124
|
+
|
|
125
|
+
type PanelComponent = typeof PanelBase & {
|
|
126
|
+
Root: typeof MantineDrawer.Root;
|
|
127
|
+
Overlay: typeof MantineDrawer.Overlay;
|
|
128
|
+
Content: typeof MantineDrawer.Content;
|
|
129
|
+
Header: typeof MantineDrawer.Header;
|
|
130
|
+
Title: typeof MantineDrawer.Title;
|
|
131
|
+
CloseButton: typeof MantineDrawer.CloseButton;
|
|
132
|
+
Body: typeof MantineDrawer.Body;
|
|
133
|
+
Stack: typeof MantineDrawer.Stack;
|
|
134
|
+
Footer: typeof PanelFooter;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const Panel = PanelBase as PanelComponent;
|
|
138
|
+
Panel.Root = MantineDrawer.Root;
|
|
139
|
+
Panel.Overlay = MantineDrawer.Overlay;
|
|
140
|
+
Panel.Content = MantineDrawer.Content;
|
|
141
|
+
Panel.Header = MantineDrawer.Header;
|
|
142
|
+
Panel.Title = MantineDrawer.Title;
|
|
143
|
+
Panel.CloseButton = MantineDrawer.CloseButton;
|
|
144
|
+
Panel.Body = MantineDrawer.Body;
|
|
145
|
+
Panel.Stack = MantineDrawer.Stack;
|
|
146
|
+
Panel.Footer = PanelFooter;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Tooltip – Implementation Notes
|
|
2
|
+
|
|
3
|
+
Decisions and design tweaks strictly tailored for the UI Kit's Tooltip wrapped against `@mantine/core`. This is a living document that tracks _why_ specific logic decisions exist.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Single Component API (Not Composable)
|
|
8
|
+
|
|
9
|
+
**Decision:** Unlike HoverCard (which uses `HoverCard` + `HoverCard.Target` + `HoverCard.Dropdown`), Tooltip is a single wrapper component.
|
|
10
|
+
|
|
11
|
+
**Implementation:** Mantine's `Tooltip` takes content via a `label` prop and wraps the trigger element as `children`. There are no separate Target/Dropdown sub-components. The Recursica wrapper preserves this exact API.
|
|
12
|
+
|
|
13
|
+
**Static sub-components:** `Tooltip.Floating` and `Tooltip.Group` are exposed as pass-through references to Mantine's implementations. These do not receive Recursica styling treatment — they are utility wrappers for cursor-following tooltips and shared delay grouping respectively.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 2. Token Namespace: `tooltip`
|
|
18
|
+
|
|
19
|
+
**Decision:** The CSS module exclusively uses variables from the `--recursica_ui-kit_components_tooltip_*` namespace.
|
|
20
|
+
|
|
21
|
+
**Implementation:** The Recursica token system defines the `tooltip` namespace covering:
|
|
22
|
+
|
|
23
|
+
- Geometry: border-radius, border-size, min-width, min-height, max-width, padding
|
|
24
|
+
- Typography: text_font-\* (family, size, style, weight, letter-spacing, line-height, text-decoration, text-transform)
|
|
25
|
+
- Colors (layer-aware): background, border-color, text
|
|
26
|
+
- Elevation: box-shadow
|
|
27
|
+
- Beak: beak-size (16px), beak-inset (8px)
|
|
28
|
+
|
|
29
|
+
No tokens from other component namespaces are referenced.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 3. Hardcoded Values
|
|
34
|
+
|
|
35
|
+
### `border-style: solid` (CSS module)
|
|
36
|
+
|
|
37
|
+
Mantine renders the tooltip using its `Box` component, which does not set `border-style` natively. Without this hardcoded value, the border-width and border-color tokens would have no visible effect. Same pattern as Menu and HoverCard dropdowns.
|
|
38
|
+
|
|
39
|
+
### `arrowSize` defaulted to `16` (Tooltip.tsx)
|
|
40
|
+
|
|
41
|
+
Mantine's `arrowSize` prop is a JavaScript number used for inline style calculations: it sets `width`, `height`, and a positioning offset (`-arrowSize/2`) directly on the arrow `<div>` element. These inline styles cannot be overridden via CSS without `!important`, and the positioning offset has no CSS equivalent. The beak size cannot be fully CSS-driven.
|
|
42
|
+
|
|
43
|
+
The default value `16` matches the Recursica `beak-size` token (`--recursica_ui-kit_components_tooltip_properties_beak-size: 16px`). Developers can override `arrowSize` if needed. This is documented as an open issue in `docs/COMPONENT_ISSUES.md`.
|
|
44
|
+
|
|
45
|
+
**Note:** Mantine calls this the "arrow"; Recursica calls it the "beak". The Recursica prop `withBeak` (defaulting to `true`) maps to Mantine's `withArrow`. Both are accepted; `withBeak` takes precedence.
|
|
46
|
+
|
|
47
|
+
### `multiline={true}` (Tooltip.tsx)
|
|
48
|
+
|
|
49
|
+
Mantine's `multiline` prop controls whether tooltip text wraps (`white-space: nowrap` when false). Recursica always enables multiline because the design system defines a `max-width` token (300px) — text should wrap naturally within that constraint rather than overflowing. The `multiline` prop is not exposed to developers.
|
|
50
|
+
|
|
51
|
+
### Flexbox centering (CSS module)
|
|
52
|
+
|
|
53
|
+
`display: flex; align-items: center; justify-content: center;` is applied to the `.tooltip` class. This ensures text is vertically and horizontally centered within the `min-height: 48px` container defined by the design token. Without this, text sits at the top of the tooltip.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 4. Recursica `withBeak` Prop
|
|
58
|
+
|
|
59
|
+
**Decision:** `withBeak` is the official Recursica prop for controlling beak visibility, defaulting to `true`.
|
|
60
|
+
|
|
61
|
+
**Implementation:** Both `withBeak` and Mantine's `withArrow` are accepted. Resolution order: `withBeak ?? withArrow`. When both are provided, `withBeak` takes precedence. The default of `true` means tooltips show the beak by default, matching the Recursica design intent.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 5. ClassNames Binding
|
|
66
|
+
|
|
67
|
+
**Decision:** CSS module classes are bound via the `classNames` prop on Mantine's Tooltip root.
|
|
68
|
+
|
|
69
|
+
**Implementation:** The stylesNames for Tooltip are `tooltip` (the container) and `arrow` (the beak). Both are mapped to their respective CSS module classes: `{ tooltip: styles.tooltip, arrow: styles.arrow }`. Consumer-provided `classNames` are merged additively when `overStyled` is true.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 6. Tooltip.Floating and Tooltip.Group
|
|
74
|
+
|
|
75
|
+
**Decision:** These static sub-components are direct pass-throughs to Mantine with no Recursica styling.
|
|
76
|
+
|
|
77
|
+
**Implementation:** `Tooltip.Floating` follows the cursor and uses a different rendering mechanism than standard Tooltip. `Tooltip.Group` is a utility for shared delay timing. Neither has Recursica design tokens defined, so they remain unstyled pass-throughs. If tokens are added in the future, they can be wrapped independently.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 7. Default Position Override
|
|
82
|
+
|
|
83
|
+
**Decision:** Recursica defaults `position` to `"top"`. Mantine defaults to `"bottom"`.
|
|
84
|
+
|
|
85
|
+
**Implementation:** The `position="top"` default is set on the Mantine root element before the prop spread, so developer-provided `position` values still take precedence. This aligns with Recursica's design intent for overlay components to appear above their trigger by default.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* HARDCODED VALUES:
|
|
2
|
+
- border-style: solid. Structural rendering rule for the tooltip border (Mantine uses Box
|
|
3
|
+
which may not set border-style natively). Same pattern as Menu / HoverCard.
|
|
4
|
+
- arrowSize defaults to 16 in Tooltip.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).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* ======================================
|
|
12
|
+
TOOLTIP CONTAINER
|
|
13
|
+
====================================== */
|
|
14
|
+
|
|
15
|
+
.tooltip {
|
|
16
|
+
background-color: var(
|
|
17
|
+
--recursica_ui-kit_components_tooltip_properties_colors_background
|
|
18
|
+
);
|
|
19
|
+
border-style: solid; /* HARDCODE: Mantine Box does not set border-style natively */
|
|
20
|
+
border-width: var(
|
|
21
|
+
--recursica_ui-kit_components_tooltip_properties_border-size
|
|
22
|
+
);
|
|
23
|
+
border-color: var(
|
|
24
|
+
--recursica_ui-kit_components_tooltip_properties_colors_border-color
|
|
25
|
+
);
|
|
26
|
+
border-radius: var(
|
|
27
|
+
--recursica_ui-kit_components_tooltip_properties_border-radius
|
|
28
|
+
);
|
|
29
|
+
box-shadow: var(--recursica_ui-kit_components_tooltip_properties_elevation);
|
|
30
|
+
|
|
31
|
+
min-width: var(--recursica_ui-kit_components_tooltip_properties_min-width);
|
|
32
|
+
min-height: var(--recursica_ui-kit_components_tooltip_properties_min-height);
|
|
33
|
+
max-width: var(--recursica_ui-kit_components_tooltip_properties_max-width);
|
|
34
|
+
|
|
35
|
+
padding: var(
|
|
36
|
+
--recursica_ui-kit_components_tooltip_properties_vertical-padding
|
|
37
|
+
)
|
|
38
|
+
var(--recursica_ui-kit_components_tooltip_properties_horizontal-padding);
|
|
39
|
+
|
|
40
|
+
/* Vertically center text within min-height container */
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
|
|
44
|
+
/* Typography */
|
|
45
|
+
font-family: var(
|
|
46
|
+
--recursica_ui-kit_components_tooltip_properties_text_font-family
|
|
47
|
+
);
|
|
48
|
+
font-size: var(
|
|
49
|
+
--recursica_ui-kit_components_tooltip_properties_text_font-size
|
|
50
|
+
);
|
|
51
|
+
font-style: var(
|
|
52
|
+
--recursica_ui-kit_components_tooltip_properties_text_font-style
|
|
53
|
+
);
|
|
54
|
+
font-weight: var(
|
|
55
|
+
--recursica_ui-kit_components_tooltip_properties_text_font-weight
|
|
56
|
+
);
|
|
57
|
+
letter-spacing: var(
|
|
58
|
+
--recursica_ui-kit_components_tooltip_properties_text_letter-spacing
|
|
59
|
+
);
|
|
60
|
+
line-height: var(
|
|
61
|
+
--recursica_ui-kit_components_tooltip_properties_text_line-height
|
|
62
|
+
);
|
|
63
|
+
text-decoration: var(
|
|
64
|
+
--recursica_ui-kit_components_tooltip_properties_text_text-decoration
|
|
65
|
+
);
|
|
66
|
+
text-transform: var(
|
|
67
|
+
--recursica_ui-kit_components_tooltip_properties_text_text-transform
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
color: var(--recursica_ui-kit_components_tooltip_properties_colors_text);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ======================================
|
|
74
|
+
ARROW / BEAK
|
|
75
|
+
====================================== */
|
|
76
|
+
|
|
77
|
+
.arrow {
|
|
78
|
+
border-color: var(
|
|
79
|
+
--recursica_ui-kit_components_tooltip_properties_colors_border-color
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -1,17 +1,165 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
-
import { Tooltip } from "./Tooltip";
|
|
3
|
-
import {
|
|
2
|
+
import { Tooltip, type TooltipProps } from "./Tooltip";
|
|
3
|
+
import { Button } from "../Button";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Tooltip story axes:
|
|
7
|
+
* - position: tooltip alignment relative to target
|
|
8
|
+
* - withBeak: beak (arrow) indicator on the tooltip
|
|
9
|
+
* - openDelay / closeDelay: hover timing controls
|
|
10
|
+
* - multiline: text wrapping behavior
|
|
11
|
+
* - disabled: suppress the tooltip entirely
|
|
12
|
+
* - layer: tested via the global Layer decorator (withLayer/layer args)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
type TooltipStoryArgs = Record<string, any>;
|
|
17
|
+
|
|
18
|
+
const meta: Meta = {
|
|
19
|
+
title: "UI-Kit/Tooltip",
|
|
7
20
|
component: Tooltip,
|
|
8
21
|
tags: ["autodocs"],
|
|
22
|
+
argTypes: {
|
|
23
|
+
label: {
|
|
24
|
+
control: "text",
|
|
25
|
+
description: "Content displayed inside the tooltip.",
|
|
26
|
+
},
|
|
27
|
+
position: {
|
|
28
|
+
control: "select",
|
|
29
|
+
options: [
|
|
30
|
+
"bottom",
|
|
31
|
+
"bottom-start",
|
|
32
|
+
"bottom-end",
|
|
33
|
+
"top",
|
|
34
|
+
"top-start",
|
|
35
|
+
"top-end",
|
|
36
|
+
"left",
|
|
37
|
+
"left-start",
|
|
38
|
+
"left-end",
|
|
39
|
+
"right",
|
|
40
|
+
"right-start",
|
|
41
|
+
"right-end",
|
|
42
|
+
],
|
|
43
|
+
description: "The position of the tooltip relative to the target.",
|
|
44
|
+
},
|
|
45
|
+
withBeak: {
|
|
46
|
+
control: "boolean",
|
|
47
|
+
description:
|
|
48
|
+
"Whether to display a beak (arrow) pointing to the target. Recursica equivalent of Mantine's withArrow.",
|
|
49
|
+
},
|
|
50
|
+
offset: {
|
|
51
|
+
control: "number",
|
|
52
|
+
description: "Distance in px between the tooltip and the target element.",
|
|
53
|
+
},
|
|
54
|
+
openDelay: {
|
|
55
|
+
control: "number",
|
|
56
|
+
description: "Delay in ms before the tooltip opens on hover.",
|
|
57
|
+
},
|
|
58
|
+
closeDelay: {
|
|
59
|
+
control: "number",
|
|
60
|
+
description: "Delay in ms before the tooltip closes when hover ends.",
|
|
61
|
+
},
|
|
62
|
+
disabled: {
|
|
63
|
+
control: "boolean",
|
|
64
|
+
description: "If set, the tooltip will not be rendered.",
|
|
65
|
+
},
|
|
66
|
+
opened: {
|
|
67
|
+
control: "boolean",
|
|
68
|
+
description:
|
|
69
|
+
"Force the tooltip to stay open. Useful for inspecting styling.",
|
|
70
|
+
},
|
|
71
|
+
// Hide auto-detected HTML attributes that leak from ElementProps<'div'>
|
|
72
|
+
defaultChecked: { table: { disable: true } },
|
|
73
|
+
defaultValue: { table: { disable: true } },
|
|
74
|
+
suppressContentEditableWarning: { table: { disable: true } },
|
|
75
|
+
suppressHydrationWarning: { table: { disable: true } },
|
|
76
|
+
},
|
|
77
|
+
parameters: {
|
|
78
|
+
layout: "centered",
|
|
79
|
+
docs: {
|
|
80
|
+
description: {
|
|
81
|
+
component: `
|
|
82
|
+
The \`Tooltip\` component displays a floating label when the user hovers over or focuses a target element. It wraps Mantine's \`Tooltip\` while enforcing strict Recursica design token styling.
|
|
83
|
+
|
|
84
|
+
### API
|
|
85
|
+
|
|
86
|
+
Unlike \`HoverCard\`, Tooltip is a single component — content is passed via the \`label\` prop:
|
|
87
|
+
\`\`\`tsx
|
|
88
|
+
<Tooltip label="Helpful text" withBeak>
|
|
89
|
+
<Button>Hover me</Button>
|
|
90
|
+
</Tooltip>
|
|
91
|
+
\`\`\`
|
|
92
|
+
|
|
93
|
+
### Static Sub-Components
|
|
94
|
+
- \`Tooltip.Floating\` — A tooltip that follows the cursor position
|
|
95
|
+
- \`Tooltip.Group\` — Groups multiple tooltips to share hover delay
|
|
96
|
+
|
|
97
|
+
### Key Behaviors
|
|
98
|
+
- The beak (arrow) is shown by default (\`withBeak={true}\`)
|
|
99
|
+
- Supports \`openDelay\` and \`closeDelay\` for timing control
|
|
100
|
+
- Set \`multiline\` to allow text wrapping within max-width
|
|
101
|
+
`,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
9
105
|
};
|
|
10
106
|
|
|
11
107
|
export default meta;
|
|
12
108
|
|
|
13
|
-
type Story = StoryObj<
|
|
109
|
+
type Story = StoryObj<TooltipStoryArgs>;
|
|
14
110
|
|
|
15
111
|
export const Default: Story = {
|
|
16
|
-
|
|
112
|
+
args: {
|
|
113
|
+
label: "This is a helpful tooltip",
|
|
114
|
+
position: "top",
|
|
115
|
+
withBeak: true,
|
|
116
|
+
offset: 5,
|
|
117
|
+
openDelay: 0,
|
|
118
|
+
closeDelay: 0,
|
|
119
|
+
disabled: false,
|
|
120
|
+
},
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
122
|
+
render: ({ withLayer, layer, ...args }: TooltipStoryArgs) => {
|
|
123
|
+
return (
|
|
124
|
+
<Tooltip {...(args as TooltipProps)}>
|
|
125
|
+
<Button variant="solid">Hover me</Button>
|
|
126
|
+
</Tooltip>
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const WithoutBeak: Story = {
|
|
132
|
+
args: {
|
|
133
|
+
label: "Tooltip without a beak indicator",
|
|
134
|
+
position: "top",
|
|
135
|
+
withBeak: false,
|
|
136
|
+
offset: 5,
|
|
137
|
+
},
|
|
138
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
139
|
+
render: ({ withLayer, layer, ...args }: TooltipStoryArgs) => {
|
|
140
|
+
return (
|
|
141
|
+
<Tooltip {...(args as TooltipProps)}>
|
|
142
|
+
<Button variant="outline">Without Beak</Button>
|
|
143
|
+
</Tooltip>
|
|
144
|
+
);
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const LongContent: Story = {
|
|
149
|
+
args: {
|
|
150
|
+
label:
|
|
151
|
+
"This is a longer tooltip message that demonstrates how text wraps within the maximum width defined by the design system.",
|
|
152
|
+
position: "top",
|
|
153
|
+
withBeak: true,
|
|
154
|
+
opened: true,
|
|
155
|
+
offset: 5,
|
|
156
|
+
},
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
158
|
+
render: ({ withLayer, layer, ...args }: TooltipStoryArgs) => {
|
|
159
|
+
return (
|
|
160
|
+
<Tooltip {...(args as TooltipProps)}>
|
|
161
|
+
<Button variant="solid">Long Content</Button>
|
|
162
|
+
</Tooltip>
|
|
163
|
+
);
|
|
164
|
+
},
|
|
17
165
|
};
|
|
@@ -1,7 +1,117 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
Tooltip as MantineTooltip,
|
|
3
|
+
type TooltipProps as MantineTooltipProps,
|
|
4
|
+
} from "@mantine/core";
|
|
5
|
+
import {
|
|
6
|
+
filterStylingProps,
|
|
7
|
+
type RecursicaOverStyled,
|
|
8
|
+
} from "../../utils/filterStylingProps";
|
|
9
|
+
import styles from "./Tooltip.module.css";
|
|
2
10
|
|
|
3
|
-
|
|
11
|
+
// ============================================================
|
|
12
|
+
// TOOLTIP
|
|
13
|
+
// ============================================================
|
|
4
14
|
|
|
5
|
-
|
|
6
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Recursica-specific props for Tooltip.
|
|
17
|
+
*/
|
|
18
|
+
export interface RecursicaTooltipProps {
|
|
19
|
+
/**
|
|
20
|
+
* Whether to display a beak (arrow) pointing from the tooltip to the target.
|
|
21
|
+
* This is the Recursica equivalent of Mantine's `withArrow`.
|
|
22
|
+
* When both `withBeak` and `withArrow` are provided, `withBeak` takes precedence.
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
25
|
+
withBeak?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Recursica Tooltip component wrapping Mantine's Tooltip.
|
|
30
|
+
*
|
|
31
|
+
* Displays a floating label when the user hovers over or focuses a target element.
|
|
32
|
+
* Unlike HoverCard, Tooltip is a single component (not composable) — content is
|
|
33
|
+
* passed via the `label` prop, and the trigger is passed as `children`.
|
|
34
|
+
*
|
|
35
|
+
* ```tsx
|
|
36
|
+
* <Tooltip label="Helpful information" withBeak>
|
|
37
|
+
* <Button>Hover me</Button>
|
|
38
|
+
* </Tooltip>
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* Static sub-components available via dot-notation:
|
|
42
|
+
* - `Tooltip.Floating` — Tooltip that follows the cursor
|
|
43
|
+
* - `Tooltip.Group` — Shared hover delay group for multiple tooltips
|
|
44
|
+
*/
|
|
45
|
+
export type TooltipProps = RecursicaOverStyled<
|
|
46
|
+
MantineTooltipProps & RecursicaTooltipProps
|
|
47
|
+
>;
|
|
48
|
+
|
|
49
|
+
const TooltipBase = function Tooltip({
|
|
50
|
+
overStyled = false,
|
|
51
|
+
withBeak = true,
|
|
52
|
+
...rest
|
|
53
|
+
}: TooltipProps) {
|
|
54
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
55
|
+
|
|
56
|
+
// Bind CSS module classes to Mantine's internal classNames API
|
|
57
|
+
const mergedClassNames: Partial<Record<string, string>> = {
|
|
58
|
+
tooltip: styles.tooltip,
|
|
59
|
+
arrow: styles.arrow,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const classNamesProp = (sanitizedProps as Record<string, unknown>).classNames;
|
|
63
|
+
if (
|
|
64
|
+
classNamesProp &&
|
|
65
|
+
typeof classNamesProp === "object" &&
|
|
66
|
+
!Array.isArray(classNamesProp)
|
|
67
|
+
) {
|
|
68
|
+
const o = classNamesProp as Record<string, string>;
|
|
69
|
+
Object.keys(o).forEach((key) => {
|
|
70
|
+
if (mergedClassNames[key]) {
|
|
71
|
+
mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
|
|
72
|
+
} else {
|
|
73
|
+
mergedClassNames[key] = o[key];
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// arrowSize must be a JS number prop — Mantine uses it for inline width/height
|
|
79
|
+
// and positioning offset (-arrowSize/2) calculations that cannot be CSS-driven.
|
|
80
|
+
// Default to 16 to match the Recursica beak-size token (16px).
|
|
81
|
+
const arrowSize =
|
|
82
|
+
((sanitizedProps as Record<string, unknown>).arrowSize as
|
|
83
|
+
| number
|
|
84
|
+
| undefined) ?? 16;
|
|
85
|
+
|
|
86
|
+
// Resolve withBeak (Recursica) vs withArrow (Mantine).
|
|
87
|
+
// withBeak takes precedence when both are provided.
|
|
88
|
+
const withArrow = (sanitizedProps as Record<string, unknown>).withArrow as
|
|
89
|
+
| boolean
|
|
90
|
+
| undefined;
|
|
91
|
+
const resolvedWithArrow = withBeak ?? withArrow;
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<MantineTooltip
|
|
95
|
+
position="top" /* Recursica default; Mantine defaults to "bottom" */
|
|
96
|
+
multiline /* Always allow text wrapping within max-width */
|
|
97
|
+
arrowSize={arrowSize}
|
|
98
|
+
withArrow={resolvedWithArrow}
|
|
99
|
+
classNames={mergedClassNames}
|
|
100
|
+
{...(sanitizedProps as unknown as MantineTooltipProps)}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
7
103
|
};
|
|
104
|
+
TooltipBase.displayName = "Tooltip";
|
|
105
|
+
|
|
106
|
+
// ============================================================
|
|
107
|
+
// DOT NOTATION EXPORT
|
|
108
|
+
// ============================================================
|
|
109
|
+
|
|
110
|
+
type TooltipComponent = typeof TooltipBase & {
|
|
111
|
+
Floating: typeof MantineTooltip.Floating;
|
|
112
|
+
Group: typeof MantineTooltip.Group;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const Tooltip = TooltipBase as TooltipComponent;
|
|
116
|
+
Tooltip.Floating = MantineTooltip.Floating;
|
|
117
|
+
Tooltip.Group = MantineTooltip.Group;
|