@tcn/ui 0.2.0 → 0.3.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 (247) hide show
  1. package/dist/divider.module-FptFV0PX.js +5 -0
  2. package/dist/divider.module-FptFV0PX.js.map +1 -0
  3. package/dist/form/field/field.js +1 -1
  4. package/dist/frame.css +1 -0
  5. package/dist/inputs/color_input/color_input.js +1 -1
  6. package/dist/inputs/color_input/color_input.js.map +1 -1
  7. package/dist/inputs/color_input/color_picker.js +1 -1
  8. package/dist/inputs/combo_box/combo_box.js +1 -1
  9. package/dist/inputs/date_picker/date_picker.js +1 -1
  10. package/dist/inputs/date_picker/date_picker_input.js +2 -2
  11. package/dist/inputs/date_picker/date_picker_input.js.map +1 -1
  12. package/dist/inputs/date_picker/date_picker_year_input.js +1 -1
  13. package/dist/inputs/date_picker/date_picker_year_input.js.map +1 -1
  14. package/dist/inputs/date_picker/date_picker_year_selector.js +1 -1
  15. package/dist/inputs/mask_input/key_capture_input.js +1 -1
  16. package/dist/inputs/mask_input/mask_input.js +1 -1
  17. package/dist/inputs/multiselect/multiselect.js +1 -1
  18. package/dist/inputs/phone_number_input/phone_number_input.js +1 -1
  19. package/dist/inputs/select/select.js +1 -1
  20. package/dist/inputs/slider/slider.js +1 -1
  21. package/dist/inputs/suggestions/suggestion_list.js +2 -2
  22. package/dist/inputs/suggestions/suggestion_list.js.map +1 -1
  23. package/dist/inputs/switch/switch.js +1 -1
  24. package/dist/inputs/unit_input/unit_input.js +1 -1
  25. package/dist/layouts/divider/divider.js +24 -23
  26. package/dist/layouts/divider/divider.js.map +1 -1
  27. package/dist/layouts/index.d.ts +6 -5
  28. package/dist/layouts/index.d.ts.map +1 -1
  29. package/dist/layouts/index.js +28 -26
  30. package/dist/layouts/index.js.map +1 -1
  31. package/dist/layouts/scaffold/scaffold.d.ts +9 -0
  32. package/dist/layouts/scaffold/scaffold.d.ts.map +1 -0
  33. package/dist/layouts/scaffold/scaffold.js +55 -0
  34. package/dist/layouts/scaffold/scaffold.js.map +1 -0
  35. package/dist/modal.css +1 -1
  36. package/dist/overlay/frame/frame.d.ts.map +1 -1
  37. package/dist/overlay/frame/frame.js +22 -5
  38. package/dist/overlay/frame/frame.js.map +1 -1
  39. package/dist/overlay/index.d.ts +9 -2
  40. package/dist/overlay/index.d.ts.map +1 -1
  41. package/dist/overlay/index.js +22 -10
  42. package/dist/overlay/index.js.map +1 -1
  43. package/dist/overlay/menu/menu.d.ts +1 -1
  44. package/dist/overlay/menu/menu.d.ts.map +1 -1
  45. package/dist/overlay/menu/menu.js +2 -2
  46. package/dist/overlay/menu/menu.js.map +1 -1
  47. package/dist/overlay/popper/base/base_popper.d.ts +11 -0
  48. package/dist/overlay/popper/base/base_popper.d.ts.map +1 -0
  49. package/dist/overlay/popper/base/base_popper.js +27 -0
  50. package/dist/overlay/popper/base/base_popper.js.map +1 -0
  51. package/dist/overlay/popper/base/dismissal_decorator.d.ts +16 -0
  52. package/dist/overlay/popper/base/dismissal_decorator.d.ts.map +1 -0
  53. package/dist/overlay/popper/base/dismissal_decorator.js +69 -0
  54. package/dist/overlay/popper/base/dismissal_decorator.js.map +1 -0
  55. package/dist/overlay/popper/context_popper.d.ts +11 -0
  56. package/dist/overlay/popper/context_popper.d.ts.map +1 -0
  57. package/dist/overlay/popper/context_popper.js +33 -0
  58. package/dist/overlay/popper/context_popper.js.map +1 -0
  59. package/dist/overlay/popper/element_popper.d.ts +7 -0
  60. package/dist/overlay/popper/element_popper.d.ts.map +1 -0
  61. package/dist/overlay/popper/element_popper.js +33 -0
  62. package/dist/overlay/popper/element_popper.js.map +1 -0
  63. package/dist/overlay/popper/hooks/use_context_trigger.d.ts +7 -0
  64. package/dist/overlay/popper/hooks/use_context_trigger.d.ts.map +1 -0
  65. package/dist/overlay/popper/hooks/use_context_trigger.js +31 -0
  66. package/dist/overlay/popper/hooks/use_context_trigger.js.map +1 -0
  67. package/dist/overlay/popper/hooks/use_hover_trigger.d.ts +6 -0
  68. package/dist/overlay/popper/hooks/use_hover_trigger.d.ts.map +1 -0
  69. package/dist/overlay/popper/hooks/use_hover_trigger.js +17 -0
  70. package/dist/overlay/popper/hooks/use_hover_trigger.js.map +1 -0
  71. package/dist/overlay/popper/hooks/use_restore_focus.d.ts +2 -0
  72. package/dist/overlay/popper/hooks/use_restore_focus.d.ts.map +1 -0
  73. package/dist/overlay/popper/hooks/use_restore_focus.js +18 -0
  74. package/dist/overlay/popper/hooks/use_restore_focus.js.map +1 -0
  75. package/dist/overlay/popper/legacy/popper.d.ts.map +1 -0
  76. package/dist/overlay/popper/{popper.js → legacy/popper.js} +6 -6
  77. package/dist/overlay/popper/legacy/popper.js.map +1 -0
  78. package/dist/overlay/popper/preview_popper.d.ts +7 -0
  79. package/dist/overlay/popper/preview_popper.d.ts.map +1 -0
  80. package/dist/overlay/popper/preview_popper.js +46 -0
  81. package/dist/overlay/popper/preview_popper.js.map +1 -0
  82. package/dist/overlay/tethered/element_tethered.d.ts +8 -0
  83. package/dist/overlay/tethered/element_tethered.d.ts.map +1 -0
  84. package/dist/overlay/tethered/element_tethered.js +33 -0
  85. package/dist/overlay/tethered/element_tethered.js.map +1 -0
  86. package/dist/overlay/tethered/hooks/calculate_position.d.ts +19 -0
  87. package/dist/overlay/tethered/hooks/calculate_position.d.ts.map +1 -0
  88. package/dist/overlay/tethered/hooks/calculate_position.js +43 -0
  89. package/dist/overlay/tethered/hooks/calculate_position.js.map +1 -0
  90. package/dist/overlay/tethered/hooks/useTether.d.ts +19 -0
  91. package/dist/overlay/tethered/hooks/useTether.d.ts.map +1 -0
  92. package/dist/overlay/tethered/hooks/useTether.js +61 -0
  93. package/dist/overlay/tethered/hooks/useTether.js.map +1 -0
  94. package/dist/overlay/tethered/tethered.d.ts +20 -0
  95. package/dist/overlay/tethered/tethered.d.ts.map +1 -0
  96. package/dist/overlay/tethered/tethered.js +59 -0
  97. package/dist/overlay/tethered/tethered.js.map +1 -0
  98. package/dist/overlay/tethered/types.d.ts +3 -0
  99. package/dist/overlay/tethered/types.d.ts.map +1 -0
  100. package/dist/overlay/tethered/types.js +2 -0
  101. package/dist/overlay/tethered/types.js.map +1 -0
  102. package/dist/popper.css +1 -1
  103. package/dist/scaffold.css +1 -0
  104. package/dist/stacks/box/box.js +1 -1
  105. package/dist/stacks/h_collapsible_box.js +1 -1
  106. package/dist/stacks/v_collapsible_box.js +1 -1
  107. package/dist/surfaces/card/card.d.ts +2 -2
  108. package/dist/surfaces/card/card.d.ts.map +1 -1
  109. package/dist/surfaces/card/card.js +7 -7
  110. package/dist/surfaces/card/card.js.map +1 -1
  111. package/dist/surfaces/index.d.ts +2 -0
  112. package/dist/surfaces/index.d.ts.map +1 -1
  113. package/dist/surfaces/index.js +22 -18
  114. package/dist/surfaces/index.js.map +1 -1
  115. package/dist/surfaces/modal/modal.d.ts +3 -3
  116. package/dist/surfaces/modal/modal.d.ts.map +1 -1
  117. package/dist/surfaces/modal/modal.js +14 -14
  118. package/dist/surfaces/modal/modal.js.map +1 -1
  119. package/dist/surfaces/panel/h_panel.js +23 -24
  120. package/dist/surfaces/panel/h_panel.js.map +1 -1
  121. package/dist/surfaces/panel/v_panel.d.ts +3 -7
  122. package/dist/surfaces/panel/v_panel.d.ts.map +1 -1
  123. package/dist/surfaces/panel/v_panel.js +12 -54
  124. package/dist/surfaces/panel/v_panel.js.map +1 -1
  125. package/dist/surfaces/pop_confirm/pop_confirm.d.ts +5 -0
  126. package/dist/surfaces/pop_confirm/pop_confirm.d.ts.map +1 -0
  127. package/dist/surfaces/pop_confirm/pop_confirm.js +37 -0
  128. package/dist/surfaces/pop_confirm/pop_confirm.js.map +1 -0
  129. package/dist/surfaces/popconfirm/pop_confirm.d.ts +5 -0
  130. package/dist/surfaces/popconfirm/pop_confirm.d.ts.map +1 -0
  131. package/dist/surfaces/popconfirm/pop_confirm.js +13 -0
  132. package/dist/surfaces/popconfirm/pop_confirm.js.map +1 -0
  133. package/dist/surfaces/popover/popover.d.ts +1 -1
  134. package/dist/surfaces/popover/popover.d.ts.map +1 -1
  135. package/dist/surfaces/popover/popover.js +1 -1
  136. package/dist/surfaces/popover/popover.js.map +1 -1
  137. package/dist/surfaces/tooltip/tooltip.d.ts +10 -0
  138. package/dist/surfaces/tooltip/tooltip.d.ts.map +1 -0
  139. package/dist/surfaces/tooltip/tooltip.js +38 -0
  140. package/dist/surfaces/tooltip/tooltip.js.map +1 -0
  141. package/dist/surfaces/window/window.d.ts +3 -3
  142. package/dist/surfaces/window/window.d.ts.map +1 -1
  143. package/dist/surfaces/window/window.js +16 -14
  144. package/dist/surfaces/window/window.js.map +1 -1
  145. package/dist/tethered.css +1 -0
  146. package/dist/themes/themes/ergo/ergo_theme.js +144 -205
  147. package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
  148. package/dist/tooltip.css +1 -1
  149. package/dist/utility_bar.css +1 -1
  150. package/dist/utils/click_away_listener.d.ts +1 -0
  151. package/dist/utils/click_away_listener.d.ts.map +1 -1
  152. package/dist/utils/click_away_listener.js +2 -1
  153. package/dist/utils/click_away_listener.js.map +1 -1
  154. package/dist/utils/index.d.ts +6 -5
  155. package/dist/utils/index.d.ts.map +1 -1
  156. package/dist/utils/index.js +26 -23
  157. package/dist/utils/index.js.map +1 -1
  158. package/dist/utils/mouse_leave_region.d.ts +8 -0
  159. package/dist/utils/mouse_leave_region.d.ts.map +1 -0
  160. package/dist/utils/mouse_leave_region.js +26 -0
  161. package/dist/utils/mouse_leave_region.js.map +1 -0
  162. package/dist/utils/types/dimensions.d.ts +11 -1
  163. package/dist/utils/types/dimensions.d.ts.map +1 -1
  164. package/package.json +3 -3
  165. package/src/inputs/color_input/color_input.tsx +1 -1
  166. package/src/inputs/date_picker/date_picker_input.tsx +1 -1
  167. package/src/inputs/date_picker/date_picker_year_input.tsx +1 -1
  168. package/src/inputs/suggestions/suggestion_list.tsx +1 -1
  169. package/src/layouts/index.ts +7 -5
  170. package/src/layouts/scaffold/scaffold.module.css +5 -0
  171. package/src/layouts/scaffold/scaffold.tsx +60 -0
  172. package/src/layouts/utility_bar/utility_bar.module.css +0 -3
  173. package/src/overlay/frame/frame.module.css +5 -0
  174. package/src/overlay/frame/frame.stories.tsx +1 -1
  175. package/src/overlay/frame/frame.tsx +19 -3
  176. package/src/overlay/index.ts +29 -2
  177. package/src/overlay/menu/menu.tsx +1 -1
  178. package/src/overlay/popper/__stories__/base_args.ts +75 -0
  179. package/src/overlay/popper/__stories__/context_popper.stories.tsx +77 -0
  180. package/src/overlay/popper/__stories__/element_popper.stories.tsx +80 -0
  181. package/src/overlay/popper/__stories__/preview_popper.stories.tsx +73 -0
  182. package/src/overlay/popper/base/base_popper.tsx +55 -0
  183. package/src/overlay/popper/base/dismissal_decorator.tsx +80 -0
  184. package/src/overlay/popper/context_popper.tsx +43 -0
  185. package/src/overlay/popper/element_popper.tsx +42 -0
  186. package/src/overlay/popper/hooks/use_context_trigger.ts +50 -0
  187. package/src/overlay/popper/hooks/use_hover_trigger.ts +24 -0
  188. package/src/overlay/popper/hooks/use_restore_focus.ts +16 -0
  189. package/src/overlay/popper/{popper.stories.tsx → legacy/popper.stories.tsx} +11 -5
  190. package/src/overlay/popper/{popper.tsx → legacy/popper.tsx} +3 -2
  191. package/src/overlay/popper/preview_popper.tsx +54 -0
  192. package/src/overlay/tethered/__stories__/element/element_tethered.stories.tsx +57 -0
  193. package/src/overlay/tethered/__stories__/element/element_tethered_stories.module.css +14 -0
  194. package/src/overlay/tethered/__stories__/shared/base_story_config.ts +52 -0
  195. package/src/overlay/tethered/__stories__/shared/components/sb_point.module.css +20 -0
  196. package/src/overlay/tethered/__stories__/shared/components/sb_point.tsx +34 -0
  197. package/src/overlay/tethered/__stories__/shared/components/sb_reference_points.tsx +54 -0
  198. package/src/overlay/tethered/__stories__/tethered/tethered.stories.tsx +90 -0
  199. package/src/overlay/tethered/__stories__/tethered/tethered_stories.module.css +25 -0
  200. package/src/overlay/tethered/element_tethered.tsx +62 -0
  201. package/src/overlay/tethered/hooks/calculate_position.ts +110 -0
  202. package/src/overlay/tethered/hooks/useTether.ts +85 -0
  203. package/src/overlay/tethered/tethered.module.css +8 -0
  204. package/src/overlay/tethered/tethered.tsx +72 -0
  205. package/src/overlay/tethered/types.ts +2 -0
  206. package/src/stacks/h_stack.stories.tsx +2 -2
  207. package/src/stacks/v_stack.stories.tsx +2 -2
  208. package/src/surfaces/card/card.stories.tsx +64 -0
  209. package/src/surfaces/card/card.tsx +4 -4
  210. package/src/surfaces/card/card_stories.module.css +13 -0
  211. package/src/surfaces/index.ts +2 -0
  212. package/src/surfaces/modal/__stories__/modal.stories.tsx +12 -1
  213. package/src/surfaces/modal/modal.module.css +2 -2
  214. package/src/surfaces/modal/modal.tsx +14 -12
  215. package/src/surfaces/panel/__stories__/panel.stories.tsx +1 -1
  216. package/src/surfaces/panel/v_panel.tsx +8 -53
  217. package/src/surfaces/pop_confirm/pop_confirm.stories.tsx +70 -0
  218. package/src/surfaces/pop_confirm/pop_confirm.tsx +30 -0
  219. package/src/surfaces/popconfirm/pop_confirm.tsx +18 -0
  220. package/src/surfaces/popover/popover.tsx +1 -1
  221. package/src/surfaces/tooltip/tooltip.stories.tsx +54 -0
  222. package/src/surfaces/tooltip/tooltip.tsx +59 -0
  223. package/src/surfaces/window/window.stories.tsx +15 -1
  224. package/src/surfaces/window/window.tsx +16 -12
  225. package/src/themes/themes/ergo/__stories__/components/tone_picker/sb_tone_picker.tsx +7 -9
  226. package/src/themes/themes/ergo/__stories__/material.stories.tsx +2 -6
  227. package/src/themes/themes/ergo/__stories__/sb_materials.module.css +29 -21
  228. package/src/themes/themes/ergo/ergo_theme.css +144 -205
  229. package/src/utils/click_away_listener.tsx +1 -1
  230. package/src/utils/index.ts +7 -5
  231. package/src/utils/mouse_leave_region.tsx +38 -0
  232. package/src/utils/types/dimensions.ts +13 -1
  233. package/tsconfig.json +3 -0
  234. package/dist/overlay/popper/popper.d.ts.map +0 -1
  235. package/dist/overlay/popper/popper.js.map +0 -1
  236. package/dist/overlay/tooltip/tooltip.d.ts +0 -7
  237. package/dist/overlay/tooltip/tooltip.d.ts.map +0 -1
  238. package/dist/overlay/tooltip/tooltip.js +0 -20
  239. package/dist/overlay/tooltip/tooltip.js.map +0 -1
  240. package/dist/panel.module-DwGKncon.js +0 -5
  241. package/dist/panel.module-DwGKncon.js.map +0 -1
  242. package/src/overlay/tooltip/tooltip.stories.tsx +0 -22
  243. package/src/overlay/tooltip/tooltip.tsx +0 -24
  244. /package/dist/{panel.css → h_panel.css} +0 -0
  245. /package/dist/overlay/popper/{popper.d.ts → legacy/popper.d.ts} +0 -0
  246. /package/src/overlay/popper/{popper.module.css → legacy/popper.module.css} +0 -0
  247. /package/src/{overlay → surfaces}/tooltip/tooltip.module.css +0 -0
@@ -0,0 +1,77 @@
1
+ import { Meta } from '@storybook/react-vite';
2
+ import React, { useRef } from 'react';
3
+ import {
4
+ ContextPopper as ContextPopperComponent,
5
+ ContextPopperProps,
6
+ } from '../context_popper.js';
7
+ import { StyleBox } from '../../../stacks/story_components/style_box.js';
8
+ import { VStack } from '../../../stacks/v_stack.js';
9
+ import { BodyText } from '../../../typography/index.js';
10
+ import { HStack } from '../../../stacks/h_stack.js';
11
+ import { SBPopperBaseArgs, SBPopperInitialArgs } from './base_args.js';
12
+ import { PopperDismissal } from '../base/dismissal_decorator.js';
13
+
14
+ type ElementPopoverStoryProps = Omit<ContextPopperProps, 'anchorElement'> & {
15
+ clickAwayRefs?: React.RefObject<HTMLElement | null>[];
16
+ };
17
+
18
+ const meta: Meta<typeof ContextPopperComponent> = {
19
+ title: 'Overlays/Popper/Context Popper',
20
+ component: ContextPopperComponent,
21
+ tags: ['autodocs'],
22
+ parameters: {
23
+ docs: {
24
+ description: {
25
+ component: 'A popper component that displays a content in a portal.',
26
+ },
27
+ },
28
+ },
29
+ argTypes: SBPopperBaseArgs,
30
+ args: {
31
+ ...SBPopperInitialArgs,
32
+ dismissals: [PopperDismissal.CLICK_AWAY, PopperDismissal.SCROLL_AWAY],
33
+ },
34
+ };
35
+
36
+ export default meta;
37
+
38
+ export function ContextPopper(props: ElementPopoverStoryProps) {
39
+ const anchorElement = useRef<HTMLButtonElement>(null);
40
+
41
+ return (
42
+ <VStack height="100%" width="100%" minHeight="600px" hAlign="center" vAlign="center">
43
+ <HStack
44
+ ref={anchorElement}
45
+ minWidth="200px"
46
+ minHeight="100px"
47
+ vAlign="center"
48
+ hAlign="center"
49
+ style={{ cursor: 'context-menu', background: 'blue', color: 'white' }}
50
+ >
51
+ Right-click me!
52
+ </HStack>
53
+ <ContextPopperComponent
54
+ anchorElement={anchorElement}
55
+ verticalAnchor={props.verticalAnchor}
56
+ verticalOrigin={props.verticalOrigin}
57
+ verticalOffset={props.verticalOffset}
58
+ horizontalAnchor={props.horizontalAnchor}
59
+ horizontalOrigin={props.horizontalOrigin}
60
+ horizontalOffset={props.horizontalOffset}
61
+ restoreFocus={props.restoreFocus}
62
+ dismissals={props.dismissals}
63
+ veil={props.veil}
64
+ >
65
+ <StyleBox
66
+ className="surface-primary"
67
+ boxShadow="0 0 10px 1px rgba(0,0,0,0.45)"
68
+ backgroundColor="white"
69
+ padding="8px"
70
+ borderRadius={'8px'}
71
+ >
72
+ <BodyText selectable>Hello World</BodyText>
73
+ </StyleBox>
74
+ </ContextPopperComponent>
75
+ </VStack>
76
+ );
77
+ }
@@ -0,0 +1,80 @@
1
+ import { Meta } from '@storybook/react-vite';
2
+ import React, { useRef, useState } from 'react';
3
+ import {
4
+ ElementPopper as ElementPopperComponent,
5
+ ElementPopperProps,
6
+ } from '../element_popper.js';
7
+ import { StyleBox } from '../../../stacks/story_components/style_box.js';
8
+ import { VStack } from '../../../stacks/v_stack.js';
9
+ import { BodyText } from '../../../typography/index.js';
10
+ import { Button } from '../../../actions/index.js';
11
+ import { SBPopperBaseArgs, SBPopperInitialArgs } from './base_args.js';
12
+
13
+ type ElementPopoverStoryProps = Omit<
14
+ ElementPopperProps,
15
+ 'anchorElement' | 'open' | 'onClose' | 'isScrollAwayException'
16
+ > & {
17
+ clickAwayRefs?: React.RefObject<HTMLElement | null>[];
18
+ isScrollAwayException?: boolean;
19
+ };
20
+
21
+ const meta: Meta<typeof ElementPopperComponent> = {
22
+ title: 'Overlays/Popper/Element Popper',
23
+ component: ElementPopperComponent,
24
+ tags: ['autodocs'],
25
+ parameters: {
26
+ docs: {
27
+ description: {
28
+ component: 'A popper component that displays a content in a portal.',
29
+ },
30
+ },
31
+ },
32
+ argTypes: SBPopperBaseArgs,
33
+ args: SBPopperInitialArgs,
34
+ };
35
+
36
+ export default meta;
37
+
38
+ export function ElementPopper(props: ElementPopoverStoryProps) {
39
+ const [isOpen, setIsOpen] = useState(false);
40
+ const anchorElement = useRef<HTMLButtonElement>(null);
41
+
42
+ function open() {
43
+ setIsOpen(true);
44
+ }
45
+
46
+ function close() {
47
+ setIsOpen(false);
48
+ }
49
+
50
+ return (
51
+ <VStack height="100%" width="100%" minHeight="600px" hAlign="center" vAlign="center">
52
+ <Button onClick={open} ref={anchorElement}>
53
+ Open
54
+ </Button>
55
+ <ElementPopperComponent
56
+ anchorElement={anchorElement}
57
+ open={isOpen}
58
+ onDismissal={close}
59
+ verticalAnchor={props.verticalAnchor}
60
+ verticalOrigin={props.verticalOrigin}
61
+ verticalOffset={props.verticalOffset}
62
+ horizontalAnchor={props.horizontalAnchor}
63
+ horizontalOrigin={props.horizontalOrigin}
64
+ horizontalOffset={props.horizontalOffset}
65
+ restoreFocus={props.restoreFocus}
66
+ dismissals={props.dismissals}
67
+ >
68
+ <StyleBox
69
+ className="surface-primary"
70
+ boxShadow="0 0 10px 1px rgba(0,0,0,0.45)"
71
+ backgroundColor="white"
72
+ padding="8px"
73
+ borderRadius={'8px'}
74
+ >
75
+ <BodyText selectable>Hello World</BodyText>
76
+ </StyleBox>
77
+ </ElementPopperComponent>
78
+ </VStack>
79
+ );
80
+ }
@@ -0,0 +1,73 @@
1
+ import { Meta } from '@storybook/react-vite';
2
+ import React, { useRef } from 'react';
3
+ import {
4
+ PreviewPopper as PreviewPopperComponent,
5
+ PreviewPopperProps,
6
+ } from '../preview_popper.js';
7
+ import { StyleBox } from '../../../stacks/story_components/style_box.js';
8
+ import { VStack } from '../../../stacks/v_stack.js';
9
+ import { BodyText } from '../../../typography/index.js';
10
+ import { SBPopperBaseArgs, SBPopperInitialArgs } from './base_args.js';
11
+
12
+ type ElementPopoverStoryProps = Omit<PreviewPopperProps, 'anchorElement'> & {
13
+ clickAwayRefs?: React.RefObject<HTMLElement | null>[];
14
+ };
15
+
16
+ const meta: Meta<typeof PreviewPopperComponent> = {
17
+ title: 'Overlays/Popper/Preview Popper',
18
+ component: PreviewPopperComponent,
19
+ tags: ['autodocs'],
20
+ parameters: {
21
+ docs: {
22
+ description: {
23
+ component: 'A popper component that displays a content in a portal.',
24
+ },
25
+ },
26
+ },
27
+ argTypes: SBPopperBaseArgs,
28
+ args: SBPopperInitialArgs,
29
+ };
30
+
31
+ export default meta;
32
+
33
+ export function SBPreviewPopper(props: ElementPopoverStoryProps) {
34
+ const anchorElement = useRef<HTMLDivElement>(null);
35
+
36
+ return (
37
+ <VStack height="100%" width="100%" minHeight="600px" hAlign="center" vAlign="center">
38
+ <StyleBox
39
+ ref={anchorElement}
40
+ backgroundColor="red"
41
+ padding="8px"
42
+ borderRadius={'8px'}
43
+ width="200px"
44
+ height="100px"
45
+ display="flex"
46
+ justifyContent="center"
47
+ alignItems="center"
48
+ >
49
+ Hover to open
50
+ </StyleBox>
51
+ <PreviewPopperComponent
52
+ anchorElement={anchorElement}
53
+ verticalAnchor={props.verticalAnchor}
54
+ verticalOrigin={props.verticalOrigin}
55
+ verticalOffset={props.verticalOffset}
56
+ horizontalAnchor={props.horizontalAnchor}
57
+ horizontalOrigin={props.horizontalOrigin}
58
+ horizontalOffset={props.horizontalOffset}
59
+ restoreFocus={props.restoreFocus}
60
+ >
61
+ <StyleBox
62
+ className="surface-primary"
63
+ boxShadow="0 0 10px 1px rgba(0,0,0,0.45)"
64
+ backgroundColor="white"
65
+ padding="8px"
66
+ borderRadius={'8px'}
67
+ >
68
+ <BodyText selectable>Hello World</BodyText>
69
+ </StyleBox>
70
+ </PreviewPopperComponent>
71
+ </VStack>
72
+ );
73
+ }
@@ -0,0 +1,55 @@
1
+ import { type PropsWithChildren } from 'react';
2
+ import { useRestoreFocus } from '../hooks/use_restore_focus.js';
3
+ import {
4
+ PopperDismissalDecorator,
5
+ type PopperDismissalDecoratorProps,
6
+ } from './dismissal_decorator.js';
7
+
8
+ export interface BasePopperOwnProps {
9
+ open: boolean;
10
+ restoreFocus?: boolean;
11
+ veil?: boolean;
12
+ }
13
+
14
+ export interface BasePopperProps
15
+ extends BasePopperOwnProps,
16
+ PopperDismissalDecoratorProps {}
17
+
18
+ export const BasePopper: React.FC<PropsWithChildren<BasePopperProps>> = ({
19
+ restoreFocus = false,
20
+ open,
21
+ onDismissal,
22
+ isException,
23
+ dismissals = [],
24
+ acceptedRefs,
25
+ // TODO: add veil
26
+ // veil = false,
27
+ children,
28
+ }) => {
29
+ useRestoreFocus(open, restoreFocus);
30
+
31
+ if (!open) return null;
32
+
33
+ // if (veil) {
34
+ // content = (
35
+ // <div
36
+ // // TODO: add ref and onClick
37
+ // // ref={veilRef}
38
+ // // onClick={e => veilRef.current === e.target && onVeilClick && onVeilClick(e)}
39
+ // >
40
+ // {content}
41
+ // </div>
42
+ // );
43
+ // }
44
+
45
+ return (
46
+ <PopperDismissalDecorator
47
+ dismissals={dismissals}
48
+ onDismissal={onDismissal}
49
+ isException={isException}
50
+ acceptedRefs={acceptedRefs}
51
+ >
52
+ {children}
53
+ </PopperDismissalDecorator>
54
+ );
55
+ };
@@ -0,0 +1,80 @@
1
+ import { forwardRef, type PropsWithChildren } from 'react';
2
+ import { ClickAwayListener } from '../../../utils/click_away_listener.js';
3
+ import { ScrollAwayListener } from '../../../utils/scroll_away_listener.js';
4
+ import { MouseLeaveRegion } from '../../../utils/mouse_leave_region.js';
5
+
6
+ export enum PopperDismissal {
7
+ CLICK_AWAY = 'clickAway',
8
+ SCROLL_AWAY = 'scrollAway',
9
+ MOUSE_LEAVE = 'mouseLeave',
10
+ VEIL_CLICK = 'veilClick',
11
+ }
12
+
13
+ export interface PopperDismissalDecoratorProps {
14
+ dismissals?: PopperDismissal[];
15
+ onDismissal?: (dismissal: PopperDismissal) => void;
16
+ isException?: (dismissal: PopperDismissal, target: HTMLElement) => boolean;
17
+ acceptedRefs?: React.RefObject<HTMLElement>[];
18
+ }
19
+
20
+ export const PopperDismissalDecorator = forwardRef<
21
+ HTMLElement,
22
+ PropsWithChildren<PopperDismissalDecoratorProps>
23
+ >(function DismissalDecorator(
24
+ { onDismissal, isException, dismissals = [], children, acceptedRefs = [] },
25
+ ref
26
+ ) {
27
+ const hasClickAway = dismissals.includes(PopperDismissal.CLICK_AWAY);
28
+ const hasScrollAway = dismissals.includes(PopperDismissal.SCROLL_AWAY);
29
+ const hasMouseLeave = dismissals.includes(PopperDismissal.MOUSE_LEAVE);
30
+ // TODO:
31
+ // const hasVeilClick = dismissals.includes(PopperDismissal.VEIL_CLICK);
32
+
33
+ const buildHandleDismissal = (dismissal: PopperDismissal) => () => {
34
+ onDismissal?.(dismissal);
35
+ };
36
+
37
+ function buildExceptionHandler(dismissal: PopperDismissal) {
38
+ if (dismissal in dismissals) {
39
+ return target => isException?.(dismissal, target) ?? false;
40
+ }
41
+ return () => false;
42
+ }
43
+
44
+ let content: React.ReactNode = (
45
+ <>
46
+ {children}
47
+ {hasMouseLeave && (
48
+ <MouseLeaveRegion
49
+ elementsRefs={acceptedRefs}
50
+ onMouseLeave={buildHandleDismissal(PopperDismissal.MOUSE_LEAVE)}
51
+ />
52
+ )}
53
+ </>
54
+ );
55
+
56
+ if (hasScrollAway) {
57
+ content = (
58
+ <ScrollAwayListener
59
+ onScrollAway={buildHandleDismissal(PopperDismissal.SCROLL_AWAY)}
60
+ isException={buildExceptionHandler(PopperDismissal.SCROLL_AWAY)}
61
+ >
62
+ {content}
63
+ </ScrollAwayListener>
64
+ );
65
+ }
66
+
67
+ if (hasClickAway) {
68
+ content = (
69
+ <ClickAwayListener
70
+ onClickAway={buildHandleDismissal(PopperDismissal.CLICK_AWAY)}
71
+ refs={acceptedRefs}
72
+ isException={buildExceptionHandler(PopperDismissal.CLICK_AWAY)}
73
+ >
74
+ {content}
75
+ </ClickAwayListener>
76
+ );
77
+ }
78
+
79
+ return content;
80
+ });
@@ -0,0 +1,43 @@
1
+ import { forwardRef, type PropsWithChildren } from 'react';
2
+ import { BasePopper, type BasePopperProps } from './base/base_popper.js';
3
+ import { Tethered, type TetheredProps } from '../tethered/tethered.js';
4
+ import { useContextTrigger } from './hooks/use_context_trigger.js';
5
+ import { PopperDismissal } from './base/dismissal_decorator.js';
6
+
7
+ export type ContextPopperProps = Omit<BasePopperProps, 'open' | 'onDismissal'> &
8
+ Omit<TetheredProps, 'anchor'> & {
9
+ anchorElement: React.RefObject<HTMLElement>;
10
+ };
11
+
12
+ export const ContextPopper = forwardRef<
13
+ HTMLDivElement,
14
+ PropsWithChildren<ContextPopperProps>
15
+ >(function ContextPopper(
16
+ {
17
+ anchorElement,
18
+ restoreFocus,
19
+ children,
20
+ acceptedRefs = [],
21
+ isException,
22
+ dismissals = [PopperDismissal.CLICK_AWAY, PopperDismissal.SCROLL_AWAY],
23
+ ...tetheredProps
24
+ },
25
+ ref
26
+ ) {
27
+ const { isOpen, close, rectangle } = useContextTrigger(anchorElement);
28
+
29
+ return (
30
+ <BasePopper
31
+ open={isOpen && rectangle != null}
32
+ onDismissal={close}
33
+ restoreFocus={restoreFocus}
34
+ dismissals={dismissals}
35
+ acceptedRefs={acceptedRefs}
36
+ isException={isException}
37
+ >
38
+ <Tethered ref={ref} anchor={rectangle} {...tetheredProps}>
39
+ {children}
40
+ </Tethered>
41
+ </BasePopper>
42
+ );
43
+ });
@@ -0,0 +1,42 @@
1
+ import { forwardRef, type PropsWithChildren } from 'react';
2
+ import { BasePopper, type BasePopperProps } from './base/base_popper.js';
3
+ import {
4
+ ElementTethered,
5
+ type ElementTetheredProps,
6
+ } from '../tethered/element_tethered.js';
7
+
8
+ export type ElementPopperProps = BasePopperProps & ElementTetheredProps;
9
+
10
+ export const ElementPopper = forwardRef<
11
+ HTMLDivElement,
12
+ PropsWithChildren<ElementPopperProps>
13
+ >(function ElementPopper(
14
+ {
15
+ restoreFocus,
16
+ open,
17
+ onDismissal,
18
+ isException,
19
+ acceptedRefs,
20
+ veil,
21
+ dismissals,
22
+ children,
23
+ ...elementTetheredProps
24
+ },
25
+ ref
26
+ ) {
27
+ return (
28
+ <BasePopper
29
+ restoreFocus={restoreFocus}
30
+ open={open}
31
+ onDismissal={onDismissal}
32
+ isException={isException}
33
+ dismissals={dismissals}
34
+ acceptedRefs={acceptedRefs}
35
+ veil={veil}
36
+ >
37
+ <ElementTethered ref={ref} {...elementTetheredProps}>
38
+ {children}
39
+ </ElementTethered>
40
+ </BasePopper>
41
+ );
42
+ });
@@ -0,0 +1,50 @@
1
+ import { useCallback, useLayoutEffect, useState } from 'react';
2
+ import type { Rectangle } from '../../../utils/types/dimensions.js';
3
+
4
+ function getContextMenuRectangle(
5
+ element: HTMLElement | null,
6
+ mouseEvent: MouseEvent
7
+ ): Rectangle | null {
8
+ if (!element) return null;
9
+
10
+ const position = { x: mouseEvent.clientX, y: mouseEvent.clientY };
11
+
12
+ return {
13
+ position,
14
+ dimensions: {
15
+ width: 8,
16
+ height: 8,
17
+ },
18
+ };
19
+ }
20
+
21
+ export function useContextTrigger(anchorElement: React.RefObject<HTMLElement>) {
22
+ const [isOpen, setIsOpen] = useState(false);
23
+ const [rectangle, setRectangle] = useState<Rectangle | null>(null);
24
+
25
+ const close = useCallback(() => {
26
+ setIsOpen(false);
27
+ }, []);
28
+
29
+ const handleContextMenu = useCallback(
30
+ (e: MouseEvent) => {
31
+ e.preventDefault();
32
+ setRectangle(getContextMenuRectangle(anchorElement.current, e));
33
+ setIsOpen(true);
34
+ },
35
+ [anchorElement]
36
+ );
37
+
38
+ useLayoutEffect(() => {
39
+ const element = anchorElement.current;
40
+ if (!element) return;
41
+
42
+ element.addEventListener('contextmenu', handleContextMenu);
43
+
44
+ return () => {
45
+ element.removeEventListener('contextmenu', handleContextMenu);
46
+ };
47
+ }, [anchorElement, handleContextMenu]);
48
+
49
+ return { isOpen, close, rectangle };
50
+ }
@@ -0,0 +1,24 @@
1
+ import { useCallback, useLayoutEffect, useState } from 'react';
2
+
3
+ export function usePreviewElement(anchorElement: React.RefObject<HTMLElement>) {
4
+ const [isOpen, setIsOpen] = useState(false);
5
+ const close = useCallback(() => {
6
+ setIsOpen(false);
7
+ }, []);
8
+
9
+ const open = useCallback(() => setIsOpen(true), []);
10
+
11
+ // Open on hover on the anchor element
12
+ useLayoutEffect(() => {
13
+ const anchor = anchorElement.current;
14
+ if (!anchor) return;
15
+
16
+ anchor.addEventListener('mouseenter', open);
17
+
18
+ return () => {
19
+ anchor.removeEventListener('mouseenter', open);
20
+ };
21
+ }, [anchorElement, open]);
22
+
23
+ return { isOpen, close, open };
24
+ }
@@ -0,0 +1,16 @@
1
+ import { useLayoutEffect, useRef } from 'react';
2
+
3
+ export function useRestoreFocus(open: boolean, restoreFocus: boolean) {
4
+ const activeElementRef = useRef<HTMLElement | null>(null);
5
+
6
+ useLayoutEffect(() => {
7
+ if (open) {
8
+ activeElementRef.current = window.document.activeElement as HTMLElement;
9
+ } else {
10
+ const restoreToElement = activeElementRef.current;
11
+ requestAnimationFrame(() => {
12
+ restoreFocus && restoreToElement?.focus();
13
+ });
14
+ }
15
+ }, [open, restoreFocus]);
16
+ }
@@ -1,11 +1,11 @@
1
1
  import { Meta } from '@storybook/react-vite';
2
- import { BodyText } from '../../typography/index.js';
2
+ import { BodyText } from '../../../typography/index.js';
3
3
  import React, { useLayoutEffect, useRef, useState } from 'react';
4
4
  import { Popper as PopperComponent, PopperProps } from './popper.js';
5
- import { HStack } from '../../stacks/h_stack.js';
6
- import { StyleBox } from '../../stacks/story_components/style_box.js';
7
- import { VStack } from '../../stacks/v_stack.js';
8
- import { ZStack } from '../../stacks/z_stack.js';
5
+ import { HStack } from '../../../stacks/h_stack.js';
6
+ import { StyleBox } from '../../../stacks/story_components/style_box.js';
7
+ import { VStack } from '../../../stacks/v_stack.js';
8
+ import { ZStack } from '../../../stacks/z_stack.js';
9
9
 
10
10
  type PopoverStoryProps = Omit<PopperProps, 'anchorElement' | 'open' | 'onClose'> & {
11
11
  clickAwayRefs?: React.RefObject<HTMLElement | null>[];
@@ -72,6 +72,11 @@ const meta: Meta<typeof PopperComponent> = {
72
72
  defaultValue: false,
73
73
  description: 'Whether to display a veil when the popper is open.',
74
74
  },
75
+ isScrollAwayException: {
76
+ control: { type: 'boolean' },
77
+ defaultValue: false,
78
+ description: 'Whether to scroll away the popper when the scroll area is scrolled.',
79
+ },
75
80
  },
76
81
  };
77
82
 
@@ -106,6 +111,7 @@ export function Popper(props: PopoverStoryProps) {
106
111
  horizontalOffset={props.horizontalOffset}
107
112
  restoreFocus={props.restoreFocus}
108
113
  veil={props.veil}
114
+ isScrollAwayException={props.isScrollAwayException}
109
115
  >
110
116
  <StyleBox
111
117
  className="surface-primary"
@@ -1,6 +1,6 @@
1
- import { ClickAwayListener, ScrollAwayListener } from '../../utils/index.js';
1
+ import { ClickAwayListener, ScrollAwayListener } from '../../../utils/index.js';
2
2
  import React, { useLayoutEffect, useRef, useState } from 'react';
3
- import { Portal } from '../portal/portal.js';
3
+ import { Portal } from '../../portal/portal.js';
4
4
  import styles from './popper.module.css';
5
5
 
6
6
  export interface PopperProps {
@@ -23,6 +23,7 @@ export interface PopperProps {
23
23
  disableClickAway?: boolean;
24
24
  }
25
25
 
26
+ // This component is being phased out in favor of ElementPopper - need to ensure parity beforehand.
26
27
  export function Popper({
27
28
  anchorElement,
28
29
  verticalAnchor = 'bottom',
@@ -0,0 +1,54 @@
1
+ import { forwardRef, useRef, type PropsWithChildren } from 'react';
2
+ import { BasePopper, type BasePopperProps } from './base/base_popper.js';
3
+ import { useForkRef } from '../../utils/index.js';
4
+ import {
5
+ ElementTethered,
6
+ type ElementTetheredProps,
7
+ } from '../tethered/element_tethered.js';
8
+ import { usePreviewElement } from './hooks/use_hover_trigger.js';
9
+ import { PopperDismissal } from './base/dismissal_decorator.js';
10
+
11
+ export type PreviewPopperProps = Omit<BasePopperProps, 'open' | 'onDismissal'> &
12
+ ElementTetheredProps;
13
+
14
+ export const PreviewPopper = forwardRef<
15
+ HTMLElement,
16
+ PropsWithChildren<PreviewPopperProps>
17
+ >(function PreviewPopper(
18
+ {
19
+ anchorElement,
20
+ restoreFocus,
21
+ children,
22
+ acceptedRefs = [],
23
+ isException,
24
+ dismissals = [PopperDismissal.MOUSE_LEAVE],
25
+ ...elementTetheredProps
26
+ },
27
+ ref
28
+ ) {
29
+ const popperRef = useRef<HTMLElement>(null);
30
+ const merged = useForkRef(ref, popperRef);
31
+
32
+ const { isOpen, close } = usePreviewElement(anchorElement);
33
+
34
+ return (
35
+ <>
36
+ <BasePopper
37
+ restoreFocus={restoreFocus}
38
+ open={isOpen}
39
+ onDismissal={close}
40
+ isException={isException}
41
+ dismissals={dismissals}
42
+ acceptedRefs={[popperRef, ...acceptedRefs]}
43
+ >
44
+ <ElementTethered
45
+ ref={merged}
46
+ anchorElement={anchorElement}
47
+ {...elementTetheredProps}
48
+ >
49
+ {children}
50
+ </ElementTethered>
51
+ </BasePopper>
52
+ </>
53
+ );
54
+ });