@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@structuralists/scaffolding",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "main": "./index.ts",
5
5
  "types": "./index.ts",
6
6
  "exports": {
@@ -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
- return {
25
- top: host.top - tip.height - GAP,
26
- left: host.left + (host.width - tip.width) / 2,
27
- };
27
+ top = host.top - tip.height - GAP;
28
+ left = host.left + (host.width - tip.width) / 2;
29
+ break;
28
30
  case 'bottom':
29
- return {
30
- top: host.bottom + GAP,
31
- left: host.left + (host.width - tip.width) / 2,
32
- };
31
+ top = host.bottom + GAP;
32
+ left = host.left + (host.width - tip.width) / 2;
33
+ break;
33
34
  case 'left':
34
- return {
35
- top: host.top + (host.height - tip.height) / 2,
36
- left: host.left - tip.width - GAP,
37
- };
35
+ top = host.top + (host.height - tip.height) / 2;
36
+ left = host.left - tip.width - GAP;
37
+ break;
38
38
  case 'right':
39
- return {
40
- top: host.top + (host.height - tip.height) / 2,
41
- left: host.right + GAP,
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
- white-space: nowrap;
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
  }