@structuralists/scaffolding 0.4.0 → 0.4.1
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/package.json
CHANGED
|
@@ -39,3 +39,53 @@ export const Placements: Story = {
|
|
|
39
39
|
</div>
|
|
40
40
|
),
|
|
41
41
|
};
|
|
42
|
+
|
|
43
|
+
const LONG_LABEL =
|
|
44
|
+
'This is a very long tooltip label that would, without a max-width and ' +
|
|
45
|
+
'viewport clamp, render as a single off-screen line and overflow the right ' +
|
|
46
|
+
'edge of the viewport — instead it now wraps and stays on-screen.';
|
|
47
|
+
|
|
48
|
+
export const LongLabel: Story = {
|
|
49
|
+
// Override the meta decorator: the triggers position themselves against
|
|
50
|
+
// the viewport edges with `position: fixed`, so the meta's 80px padding
|
|
51
|
+
// would just hide which trigger is being tested.
|
|
52
|
+
decorators: [(Story) => <Story />],
|
|
53
|
+
render: () => (
|
|
54
|
+
<>
|
|
55
|
+
{/* Each trigger is pinned flush to a viewport edge AND uses a
|
|
56
|
+
* placement that points its tip toward the same edge — the worst
|
|
57
|
+
* case for the clamp. Without the fix, every one of these would
|
|
58
|
+
* render at least partly off-screen. */}
|
|
59
|
+
<div style={{ position: 'fixed', top: 0, left: 0 }}>
|
|
60
|
+
<Tooltip label={LONG_LABEL} placement="top">
|
|
61
|
+
<Button>top-left · placement=top</Button>
|
|
62
|
+
</Tooltip>
|
|
63
|
+
</div>
|
|
64
|
+
<div style={{ position: 'fixed', top: 0, right: 0 }}>
|
|
65
|
+
<Tooltip label={LONG_LABEL} placement="top">
|
|
66
|
+
<Button>top-right · placement=top</Button>
|
|
67
|
+
</Tooltip>
|
|
68
|
+
</div>
|
|
69
|
+
<div style={{ position: 'fixed', bottom: 0, left: 0 }}>
|
|
70
|
+
<Tooltip label={LONG_LABEL} placement="bottom">
|
|
71
|
+
<Button>bottom-left · placement=bottom</Button>
|
|
72
|
+
</Tooltip>
|
|
73
|
+
</div>
|
|
74
|
+
<div style={{ position: 'fixed', bottom: 0, right: 0 }}>
|
|
75
|
+
<Tooltip label={LONG_LABEL} placement="bottom">
|
|
76
|
+
<Button>bottom-right · placement=bottom</Button>
|
|
77
|
+
</Tooltip>
|
|
78
|
+
</div>
|
|
79
|
+
<div style={{ position: 'fixed', top: '50%', left: 0 }}>
|
|
80
|
+
<Tooltip label={LONG_LABEL} placement="left">
|
|
81
|
+
<Button>flush left · placement=left</Button>
|
|
82
|
+
</Tooltip>
|
|
83
|
+
</div>
|
|
84
|
+
<div style={{ position: 'fixed', top: '50%', right: 0 }}>
|
|
85
|
+
<Tooltip label={LONG_LABEL} placement="right">
|
|
86
|
+
<Button>flush right · placement=right</Button>
|
|
87
|
+
</Tooltip>
|
|
88
|
+
</div>
|
|
89
|
+
</>
|
|
90
|
+
),
|
|
91
|
+
};
|
|
@@ -11,6 +11,7 @@ import styles from './styles.module.css';
|
|
|
11
11
|
|
|
12
12
|
const ENTER_DELAY_MS = 400;
|
|
13
13
|
const GAP = 4;
|
|
14
|
+
const VIEWPORT_MARGIN = 8;
|
|
14
15
|
|
|
15
16
|
type Position = { top: number; left: number };
|
|
16
17
|
|
|
@@ -19,28 +20,36 @@ const computePosition = (
|
|
|
19
20
|
tip: DOMRect,
|
|
20
21
|
placement: TooltipPlacement,
|
|
21
22
|
): Position => {
|
|
23
|
+
let top: number;
|
|
24
|
+
let left: number;
|
|
22
25
|
switch (placement) {
|
|
23
26
|
case 'top':
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
};
|
|
27
|
+
top = host.top - tip.height - GAP;
|
|
28
|
+
left = host.left + (host.width - tip.width) / 2;
|
|
29
|
+
break;
|
|
28
30
|
case 'bottom':
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
};
|
|
31
|
+
top = host.bottom + GAP;
|
|
32
|
+
left = host.left + (host.width - tip.width) / 2;
|
|
33
|
+
break;
|
|
33
34
|
case 'left':
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
};
|
|
35
|
+
top = host.top + (host.height - tip.height) / 2;
|
|
36
|
+
left = host.left - tip.width - GAP;
|
|
37
|
+
break;
|
|
38
38
|
case 'right':
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
};
|
|
39
|
+
top = host.top + (host.height - tip.height) / 2;
|
|
40
|
+
left = host.right + GAP;
|
|
41
|
+
break;
|
|
43
42
|
}
|
|
43
|
+
|
|
44
|
+
// Clamp into the viewport so a long label near an edge doesn't render
|
|
45
|
+
// off-screen. The tip's max-width (see styles) keeps it narrower than
|
|
46
|
+
// the viewport, so the clamp always has room on both sides.
|
|
47
|
+
const maxLeft = window.innerWidth - tip.width - VIEWPORT_MARGIN;
|
|
48
|
+
const maxTop = window.innerHeight - tip.height - VIEWPORT_MARGIN;
|
|
49
|
+
return {
|
|
50
|
+
top: Math.max(VIEWPORT_MARGIN, Math.min(top, maxTop)),
|
|
51
|
+
left: Math.max(VIEWPORT_MARGIN, Math.min(left, maxLeft)),
|
|
52
|
+
};
|
|
44
53
|
};
|
|
45
54
|
|
|
46
55
|
export const Tooltip = (props: TooltipProps) => {
|
|
@@ -21,5 +21,9 @@
|
|
|
21
21
|
font-size: var(--ui-text-small);
|
|
22
22
|
line-height: 1.2;
|
|
23
23
|
border-radius: var(--ui-radius);
|
|
24
|
-
|
|
24
|
+
/* Cap at a readable measure and never wider than the viewport (with the
|
|
25
|
+
* same margin the JS clamp uses), so long labels wrap onto multiple lines
|
|
26
|
+
* instead of rendering as one off-screen strip. */
|
|
27
|
+
max-width: min(280px, calc(100vw - 16px));
|
|
28
|
+
overflow-wrap: break-word;
|
|
25
29
|
}
|