@tcn/ui 0.1.1 → 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 (317) hide show
  1. package/README.md +38 -3
  2. package/dist/divider.module-FptFV0PX.js +5 -0
  3. package/dist/divider.module-FptFV0PX.js.map +1 -0
  4. package/dist/draggable.css +1 -0
  5. package/dist/feedback/progress/progress_bar.js +1 -1
  6. package/dist/form/field/common/field_description.js +1 -1
  7. package/dist/form/field/common/field_error.js +1 -1
  8. package/dist/form/field/common/field_label.js +1 -1
  9. package/dist/form/field/field.js +1 -1
  10. package/dist/frame.css +1 -0
  11. package/dist/inputs/color_input/color_input.js +1 -1
  12. package/dist/inputs/color_input/color_input.js.map +1 -1
  13. package/dist/inputs/color_input/color_picker.js +1 -1
  14. package/dist/inputs/combo_box/combo_box.js +1 -1
  15. package/dist/inputs/date_picker/date_picker.js +1 -1
  16. package/dist/inputs/date_picker/date_picker_date.js +1 -1
  17. package/dist/inputs/date_picker/date_picker_day.js +1 -1
  18. package/dist/inputs/date_picker/date_picker_input.js +2 -2
  19. package/dist/inputs/date_picker/date_picker_input.js.map +1 -1
  20. package/dist/inputs/date_picker/date_picker_time_selector.js +1 -1
  21. package/dist/inputs/date_picker/date_picker_year_input.js +1 -1
  22. package/dist/inputs/date_picker/date_picker_year_input.js.map +1 -1
  23. package/dist/inputs/date_picker/date_picker_year_selector.js +2 -2
  24. package/dist/inputs/mask_input/key_capture_input.js +1 -1
  25. package/dist/inputs/mask_input/mask_input.js +1 -1
  26. package/dist/inputs/multiselect/multiselect.js +1 -1
  27. package/dist/inputs/phone_number_input/phone_number_input.d.ts +2 -0
  28. package/dist/inputs/phone_number_input/phone_number_input.d.ts.map +1 -1
  29. package/dist/inputs/phone_number_input/phone_number_input.js +160 -154
  30. package/dist/inputs/phone_number_input/phone_number_input.js.map +1 -1
  31. package/dist/inputs/select/select.js +1 -1
  32. package/dist/inputs/slider/slider.js +1 -1
  33. package/dist/inputs/suggestions/suggestion_list.js +3 -3
  34. package/dist/inputs/suggestions/suggestion_list.js.map +1 -1
  35. package/dist/inputs/switch/switch.js +1 -1
  36. package/dist/inputs/textarea/textarea.d.ts +2 -2
  37. package/dist/inputs/textarea/textarea.d.ts.map +1 -1
  38. package/dist/inputs/textarea/textarea.js.map +1 -1
  39. package/dist/inputs/unit_input/unit_input.js +1 -1
  40. package/dist/layouts/divider/divider.js +24 -23
  41. package/dist/layouts/divider/divider.js.map +1 -1
  42. package/dist/layouts/header/header.d.ts.map +1 -1
  43. package/dist/layouts/header/header.js.map +1 -1
  44. package/dist/layouts/index.d.ts +6 -5
  45. package/dist/layouts/index.d.ts.map +1 -1
  46. package/dist/layouts/index.js +28 -26
  47. package/dist/layouts/index.js.map +1 -1
  48. package/dist/layouts/scaffold/scaffold.d.ts +9 -0
  49. package/dist/layouts/scaffold/scaffold.d.ts.map +1 -0
  50. package/dist/layouts/scaffold/scaffold.js +55 -0
  51. package/dist/layouts/scaffold/scaffold.js.map +1 -0
  52. package/dist/modal.css +1 -1
  53. package/dist/overlay/frame/frame.d.ts +11 -0
  54. package/dist/overlay/frame/frame.d.ts.map +1 -0
  55. package/dist/overlay/frame/frame.js +35 -0
  56. package/dist/overlay/frame/frame.js.map +1 -0
  57. package/dist/overlay/index.d.ts +10 -2
  58. package/dist/overlay/index.d.ts.map +1 -1
  59. package/dist/overlay/index.js +22 -8
  60. package/dist/overlay/index.js.map +1 -1
  61. package/dist/overlay/menu/menu.d.ts +1 -1
  62. package/dist/overlay/menu/menu.d.ts.map +1 -1
  63. package/dist/overlay/menu/menu.js +2 -2
  64. package/dist/overlay/menu/menu.js.map +1 -1
  65. package/dist/overlay/popper/base/base_popper.d.ts +11 -0
  66. package/dist/overlay/popper/base/base_popper.d.ts.map +1 -0
  67. package/dist/overlay/popper/base/base_popper.js +27 -0
  68. package/dist/overlay/popper/base/base_popper.js.map +1 -0
  69. package/dist/overlay/popper/base/dismissal_decorator.d.ts +16 -0
  70. package/dist/overlay/popper/base/dismissal_decorator.d.ts.map +1 -0
  71. package/dist/overlay/popper/base/dismissal_decorator.js +69 -0
  72. package/dist/overlay/popper/base/dismissal_decorator.js.map +1 -0
  73. package/dist/overlay/popper/context_popper.d.ts +11 -0
  74. package/dist/overlay/popper/context_popper.d.ts.map +1 -0
  75. package/dist/overlay/popper/context_popper.js +33 -0
  76. package/dist/overlay/popper/context_popper.js.map +1 -0
  77. package/dist/overlay/popper/element_popper.d.ts +7 -0
  78. package/dist/overlay/popper/element_popper.d.ts.map +1 -0
  79. package/dist/overlay/popper/element_popper.js +33 -0
  80. package/dist/overlay/popper/element_popper.js.map +1 -0
  81. package/dist/overlay/popper/hooks/use_context_trigger.d.ts +7 -0
  82. package/dist/overlay/popper/hooks/use_context_trigger.d.ts.map +1 -0
  83. package/dist/overlay/popper/hooks/use_context_trigger.js +31 -0
  84. package/dist/overlay/popper/hooks/use_context_trigger.js.map +1 -0
  85. package/dist/overlay/popper/hooks/use_hover_trigger.d.ts +6 -0
  86. package/dist/overlay/popper/hooks/use_hover_trigger.d.ts.map +1 -0
  87. package/dist/overlay/popper/hooks/use_hover_trigger.js +17 -0
  88. package/dist/overlay/popper/hooks/use_hover_trigger.js.map +1 -0
  89. package/dist/overlay/popper/hooks/use_restore_focus.d.ts +2 -0
  90. package/dist/overlay/popper/hooks/use_restore_focus.d.ts.map +1 -0
  91. package/dist/overlay/popper/hooks/use_restore_focus.js +18 -0
  92. package/dist/overlay/popper/hooks/use_restore_focus.js.map +1 -0
  93. package/dist/overlay/popper/legacy/popper.d.ts.map +1 -0
  94. package/dist/overlay/popper/{popper.js → legacy/popper.js} +6 -6
  95. package/dist/overlay/popper/legacy/popper.js.map +1 -0
  96. package/dist/overlay/popper/preview_popper.d.ts +7 -0
  97. package/dist/overlay/popper/preview_popper.d.ts.map +1 -0
  98. package/dist/overlay/popper/preview_popper.js +46 -0
  99. package/dist/overlay/popper/preview_popper.js.map +1 -0
  100. package/dist/overlay/tethered/element_tethered.d.ts +8 -0
  101. package/dist/overlay/tethered/element_tethered.d.ts.map +1 -0
  102. package/dist/overlay/tethered/element_tethered.js +33 -0
  103. package/dist/overlay/tethered/element_tethered.js.map +1 -0
  104. package/dist/overlay/tethered/hooks/calculate_position.d.ts +19 -0
  105. package/dist/overlay/tethered/hooks/calculate_position.d.ts.map +1 -0
  106. package/dist/overlay/tethered/hooks/calculate_position.js +43 -0
  107. package/dist/overlay/tethered/hooks/calculate_position.js.map +1 -0
  108. package/dist/overlay/tethered/hooks/useTether.d.ts +19 -0
  109. package/dist/overlay/tethered/hooks/useTether.d.ts.map +1 -0
  110. package/dist/overlay/tethered/hooks/useTether.js +61 -0
  111. package/dist/overlay/tethered/hooks/useTether.js.map +1 -0
  112. package/dist/overlay/tethered/tethered.d.ts +20 -0
  113. package/dist/overlay/tethered/tethered.d.ts.map +1 -0
  114. package/dist/overlay/tethered/tethered.js +59 -0
  115. package/dist/overlay/tethered/tethered.js.map +1 -0
  116. package/dist/overlay/tethered/types.d.ts +3 -0
  117. package/dist/overlay/tethered/types.d.ts.map +1 -0
  118. package/dist/overlay/tethered/types.js +2 -0
  119. package/dist/overlay/tethered/types.js.map +1 -0
  120. package/dist/popper.css +1 -1
  121. package/dist/scaffold.css +1 -0
  122. package/dist/stacks/box/box.d.ts +1 -1
  123. package/dist/stacks/box/box.d.ts.map +1 -1
  124. package/dist/stacks/box/box.js +1 -1
  125. package/dist/stacks/box/box.js.map +1 -1
  126. package/dist/stacks/h_collapsible_box.js +1 -1
  127. package/dist/stacks/v_collapsible_box.js +1 -1
  128. package/dist/surfaces/card/card.d.ts +2 -2
  129. package/dist/surfaces/card/card.d.ts.map +1 -1
  130. package/dist/surfaces/card/card.js +7 -7
  131. package/dist/surfaces/card/card.js.map +1 -1
  132. package/dist/surfaces/index.d.ts +4 -2
  133. package/dist/surfaces/index.d.ts.map +1 -1
  134. package/dist/surfaces/index.js +26 -22
  135. package/dist/surfaces/index.js.map +1 -1
  136. package/dist/surfaces/modal/modal.d.ts +4 -3
  137. package/dist/surfaces/modal/modal.d.ts.map +1 -1
  138. package/dist/surfaces/modal/modal.js +12 -11
  139. package/dist/surfaces/modal/modal.js.map +1 -1
  140. package/dist/surfaces/panel/h_panel.js +23 -24
  141. package/dist/surfaces/panel/h_panel.js.map +1 -1
  142. package/dist/surfaces/panel/v_panel.d.ts +3 -7
  143. package/dist/surfaces/panel/v_panel.d.ts.map +1 -1
  144. package/dist/surfaces/panel/v_panel.js +12 -54
  145. package/dist/surfaces/panel/v_panel.js.map +1 -1
  146. package/dist/surfaces/pop_confirm/pop_confirm.d.ts +5 -0
  147. package/dist/surfaces/pop_confirm/pop_confirm.d.ts.map +1 -0
  148. package/dist/surfaces/pop_confirm/pop_confirm.js +37 -0
  149. package/dist/surfaces/pop_confirm/pop_confirm.js.map +1 -0
  150. package/dist/surfaces/popconfirm/pop_confirm.d.ts +5 -0
  151. package/dist/surfaces/popconfirm/pop_confirm.d.ts.map +1 -0
  152. package/dist/surfaces/popconfirm/pop_confirm.js +13 -0
  153. package/dist/surfaces/popconfirm/pop_confirm.js.map +1 -0
  154. package/dist/surfaces/popover/popover.d.ts +1 -1
  155. package/dist/surfaces/popover/popover.d.ts.map +1 -1
  156. package/dist/surfaces/popover/popover.js +1 -1
  157. package/dist/surfaces/popover/popover.js.map +1 -1
  158. package/dist/surfaces/tooltip/tooltip.d.ts +10 -0
  159. package/dist/surfaces/tooltip/tooltip.d.ts.map +1 -0
  160. package/dist/surfaces/tooltip/tooltip.js +38 -0
  161. package/dist/surfaces/tooltip/tooltip.js.map +1 -0
  162. package/dist/surfaces/window/window.d.ts +4 -3
  163. package/dist/surfaces/window/window.d.ts.map +1 -1
  164. package/dist/surfaces/window/window.js +18 -6
  165. package/dist/surfaces/window/window.js.map +1 -1
  166. package/dist/tethered.css +1 -0
  167. package/dist/themes/themes/ergo/ergo_theme.js +166 -158
  168. package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
  169. package/dist/tooltip.css +1 -1
  170. package/dist/typography/title/title.d.ts +2 -1
  171. package/dist/typography/title/title.d.ts.map +1 -1
  172. package/dist/typography/title/title.js +23 -22
  173. package/dist/typography/title/title.js.map +1 -1
  174. package/dist/utility_bar.css +1 -1
  175. package/dist/utils/click_away_listener.d.ts +1 -0
  176. package/dist/utils/click_away_listener.d.ts.map +1 -1
  177. package/dist/utils/click_away_listener.js +2 -1
  178. package/dist/utils/click_away_listener.js.map +1 -1
  179. package/dist/utils/dnd/context.d.ts +4 -0
  180. package/dist/utils/dnd/context.d.ts.map +1 -0
  181. package/dist/utils/dnd/context.js +20 -0
  182. package/dist/utils/dnd/context.js.map +1 -0
  183. package/dist/utils/dnd/draggable/draggable.d.ts +7 -0
  184. package/dist/utils/dnd/draggable/draggable.d.ts.map +1 -0
  185. package/dist/utils/dnd/draggable/draggable.js +27 -0
  186. package/dist/utils/dnd/draggable/draggable.js.map +1 -0
  187. package/dist/utils/dnd/handle.d.ts +6 -0
  188. package/dist/utils/dnd/handle.d.ts.map +1 -0
  189. package/dist/utils/dnd/handle.js +22 -0
  190. package/dist/utils/dnd/handle.js.map +1 -0
  191. package/dist/utils/dnd/hooks/use_drag_container.d.ts +7 -0
  192. package/dist/utils/dnd/hooks/use_drag_container.d.ts.map +1 -0
  193. package/dist/utils/dnd/hooks/use_drag_container.js +30 -0
  194. package/dist/utils/dnd/hooks/use_drag_container.js.map +1 -0
  195. package/dist/utils/{hooks → dnd/hooks}/use_draggable.d.ts +3 -3
  196. package/dist/utils/dnd/hooks/use_draggable.d.ts.map +1 -0
  197. package/dist/utils/dnd/hooks/use_draggable.js +41 -0
  198. package/dist/utils/dnd/hooks/use_draggable.js.map +1 -0
  199. package/dist/utils/dnd/types.d.ts +10 -0
  200. package/dist/utils/dnd/types.d.ts.map +1 -0
  201. package/dist/utils/dnd/types.js +2 -0
  202. package/dist/utils/dnd/types.js.map +1 -0
  203. package/dist/utils/index.d.ts +6 -5
  204. package/dist/utils/index.d.ts.map +1 -1
  205. package/dist/utils/index.js +26 -23
  206. package/dist/utils/index.js.map +1 -1
  207. package/dist/utils/mouse_leave_region.d.ts +8 -0
  208. package/dist/utils/mouse_leave_region.d.ts.map +1 -0
  209. package/dist/utils/mouse_leave_region.js +26 -0
  210. package/dist/utils/mouse_leave_region.js.map +1 -0
  211. package/dist/utils/types/dimensions.d.ts +11 -1
  212. package/dist/utils/types/dimensions.d.ts.map +1 -1
  213. package/package.json +9 -3
  214. package/src/inputs/color_input/color_input.tsx +1 -1
  215. package/src/inputs/date_picker/date_picker_input.tsx +1 -1
  216. package/src/inputs/date_picker/date_picker_year_input.tsx +1 -1
  217. package/src/inputs/phone_number_input/phone_number_input.tsx +8 -0
  218. package/src/inputs/suggestions/suggestion_list.tsx +1 -1
  219. package/src/inputs/textarea/textarea.tsx +2 -2
  220. package/src/layouts/header/header.tsx +0 -1
  221. package/src/layouts/index.ts +7 -5
  222. package/src/layouts/scaffold/scaffold.module.css +5 -0
  223. package/src/layouts/scaffold/scaffold.tsx +60 -0
  224. package/src/layouts/utility_bar/utility_bar.module.css +0 -3
  225. package/src/overlay/frame/frame.module.css +5 -0
  226. package/src/overlay/frame/frame.stories.tsx +40 -0
  227. package/src/overlay/frame/frame.tsx +50 -0
  228. package/src/overlay/frame/frame_stories.module.css +14 -0
  229. package/src/overlay/index.ts +30 -2
  230. package/src/overlay/menu/menu.tsx +1 -1
  231. package/src/overlay/popper/__stories__/base_args.ts +75 -0
  232. package/src/overlay/popper/__stories__/context_popper.stories.tsx +77 -0
  233. package/src/overlay/popper/__stories__/element_popper.stories.tsx +80 -0
  234. package/src/overlay/popper/__stories__/preview_popper.stories.tsx +73 -0
  235. package/src/overlay/popper/base/base_popper.tsx +55 -0
  236. package/src/overlay/popper/base/dismissal_decorator.tsx +80 -0
  237. package/src/overlay/popper/context_popper.tsx +43 -0
  238. package/src/overlay/popper/element_popper.tsx +42 -0
  239. package/src/overlay/popper/hooks/use_context_trigger.ts +50 -0
  240. package/src/overlay/popper/hooks/use_hover_trigger.ts +24 -0
  241. package/src/overlay/popper/hooks/use_restore_focus.ts +16 -0
  242. package/src/overlay/popper/{popper.stories.tsx → legacy/popper.stories.tsx} +11 -5
  243. package/src/overlay/popper/{popper.tsx → legacy/popper.tsx} +3 -2
  244. package/src/overlay/popper/preview_popper.tsx +54 -0
  245. package/src/overlay/tethered/__stories__/element/element_tethered.stories.tsx +57 -0
  246. package/src/overlay/tethered/__stories__/element/element_tethered_stories.module.css +14 -0
  247. package/src/overlay/tethered/__stories__/shared/base_story_config.ts +52 -0
  248. package/src/overlay/tethered/__stories__/shared/components/sb_point.module.css +20 -0
  249. package/src/overlay/tethered/__stories__/shared/components/sb_point.tsx +34 -0
  250. package/src/overlay/tethered/__stories__/shared/components/sb_reference_points.tsx +54 -0
  251. package/src/overlay/tethered/__stories__/tethered/tethered.stories.tsx +90 -0
  252. package/src/overlay/tethered/__stories__/tethered/tethered_stories.module.css +25 -0
  253. package/src/overlay/tethered/element_tethered.tsx +62 -0
  254. package/src/overlay/tethered/hooks/calculate_position.ts +110 -0
  255. package/src/overlay/tethered/hooks/useTether.ts +85 -0
  256. package/src/overlay/tethered/tethered.module.css +8 -0
  257. package/src/overlay/tethered/tethered.tsx +72 -0
  258. package/src/overlay/tethered/types.ts +2 -0
  259. package/src/stacks/box/box.tsx +8 -2
  260. package/src/stacks/h_stack.stories.tsx +2 -2
  261. package/src/stacks/v_stack.stories.tsx +2 -2
  262. package/src/surfaces/card/card.stories.tsx +64 -0
  263. package/src/surfaces/card/card.tsx +4 -4
  264. package/src/surfaces/card/card_stories.module.css +13 -0
  265. package/src/surfaces/index.ts +4 -2
  266. package/src/surfaces/modal/__stories__/modal.stories.tsx +31 -28
  267. package/src/surfaces/modal/modal.module.css +2 -2
  268. package/src/surfaces/modal/modal.tsx +16 -11
  269. package/src/surfaces/panel/__stories__/panel.stories.tsx +1 -1
  270. package/src/surfaces/panel/v_panel.tsx +8 -53
  271. package/src/surfaces/pop_confirm/pop_confirm.stories.tsx +70 -0
  272. package/src/surfaces/pop_confirm/pop_confirm.tsx +30 -0
  273. package/src/surfaces/popconfirm/pop_confirm.tsx +18 -0
  274. package/src/surfaces/popover/popover.tsx +1 -1
  275. package/src/surfaces/tooltip/tooltip.stories.tsx +54 -0
  276. package/src/surfaces/tooltip/tooltip.tsx +59 -0
  277. package/src/surfaces/window/window.stories.tsx +51 -4
  278. package/src/surfaces/window/window.tsx +19 -7
  279. package/src/themes/themes/ergo/__stories__/components/tone_picker/sb_tone_picker.tsx +7 -9
  280. package/src/themes/themes/ergo/__stories__/material.stories.tsx +2 -6
  281. package/src/themes/themes/ergo/__stories__/sb_materials.module.css +29 -21
  282. package/src/themes/themes/ergo/ergo_theme.css +166 -158
  283. package/src/typography/title/title.tsx +22 -18
  284. package/src/utils/click_away_listener.tsx +1 -1
  285. package/src/utils/dnd/__stories__/draggable.stories.tsx +48 -0
  286. package/src/utils/dnd/__stories__/draggable_stories.module.css +21 -0
  287. package/src/utils/{__stories__ → dnd/__stories__}/use_draggable.stories.tsx +15 -10
  288. package/src/utils/dnd/context.ts +24 -0
  289. package/src/utils/dnd/draggable/draggable.module.css +8 -0
  290. package/src/utils/dnd/draggable/draggable.tsx +42 -0
  291. package/src/utils/dnd/handle.tsx +32 -0
  292. package/src/utils/dnd/hooks/use_drag_container.ts +42 -0
  293. package/src/utils/{hooks → dnd/hooks}/use_draggable.ts +23 -17
  294. package/src/utils/dnd/types.ts +6 -0
  295. package/src/utils/index.ts +7 -5
  296. package/src/utils/mouse_leave_region.tsx +38 -0
  297. package/src/utils/types/dimensions.ts +13 -1
  298. package/tsconfig.json +3 -0
  299. package/dist/overlay/popper/popper.d.ts.map +0 -1
  300. package/dist/overlay/popper/popper.js.map +0 -1
  301. package/dist/overlay/tooltip/tooltip.d.ts +0 -7
  302. package/dist/overlay/tooltip/tooltip.d.ts.map +0 -1
  303. package/dist/overlay/tooltip/tooltip.js +0 -20
  304. package/dist/overlay/tooltip/tooltip.js.map +0 -1
  305. package/dist/panel.module-DwGKncon.js +0 -5
  306. package/dist/panel.module-DwGKncon.js.map +0 -1
  307. package/dist/title.module-B16de2jd.js +0 -5
  308. package/dist/title.module-B16de2jd.js.map +0 -1
  309. package/dist/utils/hooks/use_draggable.d.ts.map +0 -1
  310. package/dist/utils/hooks/use_draggable.js +0 -30
  311. package/dist/utils/hooks/use_draggable.js.map +0 -1
  312. package/src/overlay/tooltip/tooltip.stories.tsx +0 -22
  313. package/src/overlay/tooltip/tooltip.tsx +0 -24
  314. /package/dist/{panel.css → h_panel.css} +0 -0
  315. /package/dist/overlay/popper/{popper.d.ts → legacy/popper.d.ts} +0 -0
  316. /package/src/overlay/popper/{popper.module.css → legacy/popper.module.css} +0 -0
  317. /package/src/{overlay → surfaces}/tooltip/tooltip.module.css +0 -0
@@ -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
+ });
@@ -0,0 +1,57 @@
1
+ import { useRef } from 'react';
2
+ import { HStack, VStack } from '../../../../stacks/index.js';
3
+ import { ElementTethered, type ElementTetheredProps } from '../../element_tethered.js';
4
+ import { tetheredArgTypes, tetheredArgs } from '../shared/base_story_config.js';
5
+
6
+ import styles from './element_tethered_stories.module.css';
7
+
8
+ export default {
9
+ title: 'Overlays/Element Tethered',
10
+ component: ElementTethered,
11
+ tags: ['autodocs'],
12
+ argTypes: tetheredArgTypes,
13
+ args: tetheredArgs,
14
+ };
15
+
16
+ type ElementTetheredStoryProps = Omit<ElementTetheredProps, 'anchorElement'>;
17
+
18
+ export const Default = (args: ElementTetheredStoryProps) => {
19
+ const anchorElement = useRef<HTMLDivElement>(null);
20
+
21
+ return (
22
+ <VStack height="100%" width="100%" hAlign="center" vAlign="center">
23
+ <VStack
24
+ className={styles.container}
25
+ hAlign="center"
26
+ vAlign="center"
27
+ height="500px"
28
+ width="500px"
29
+ >
30
+ <HStack
31
+ padding="16px"
32
+ width="auto"
33
+ height="auto"
34
+ ref={anchorElement}
35
+ className={styles.anchor}
36
+ hAlign="center"
37
+ vAlign="center"
38
+ >
39
+ Anchor Element
40
+ </HStack>
41
+
42
+ <ElementTethered anchorElement={anchorElement} {...args}>
43
+ <HStack
44
+ className={styles.tethered}
45
+ width="auto"
46
+ height="auto"
47
+ padding="16px"
48
+ hAlign="center"
49
+ vAlign="center"
50
+ >
51
+ Tethered to Anchor Element
52
+ </HStack>
53
+ </ElementTethered>
54
+ </VStack>
55
+ </VStack>
56
+ );
57
+ };
@@ -0,0 +1,14 @@
1
+ .container {
2
+ background-color: #f0eee7; /* eggshell */
3
+ }
4
+
5
+ .anchor {
6
+ background: white;
7
+ border-radius: 8px;
8
+ }
9
+
10
+ .tethered {
11
+ background: white;
12
+ border-radius: 8px;
13
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
14
+ }
@@ -0,0 +1,52 @@
1
+ import type { ArgTypes } from '@storybook/react-vite';
2
+ import type { TetheredProps } from '../../tethered.js';
3
+
4
+ type TetheredStoryArgs = Pick<
5
+ TetheredProps,
6
+ | 'verticalAnchor'
7
+ | 'horizontalAnchor'
8
+ | 'verticalOrigin'
9
+ | 'horizontalOrigin'
10
+ | 'verticalOffset'
11
+ | 'horizontalOffset'
12
+ >;
13
+
14
+ export const tetheredArgTypes: ArgTypes<TetheredStoryArgs> = {
15
+ verticalAnchor: {
16
+ options: ['top', 'center', 'bottom'],
17
+ control: { type: 'radio' },
18
+ description: 'The anchor to position the popper vertically.',
19
+ },
20
+ horizontalAnchor: {
21
+ options: ['start', 'center', 'end'],
22
+ control: { type: 'radio' },
23
+ description: 'The anchor to position the popper horizontally.',
24
+ },
25
+ verticalOrigin: {
26
+ options: ['top', 'center', 'bottom'],
27
+ control: { type: 'radio' },
28
+ description: 'The origin to position the popper vertically.',
29
+ },
30
+ horizontalOrigin: {
31
+ options: ['start', 'center', 'end'],
32
+ control: { type: 'radio' },
33
+ description: 'The origin to position the popper horizontally.',
34
+ },
35
+ verticalOffset: {
36
+ control: { type: 'number' },
37
+ description: 'The offset to position the popper vertically.',
38
+ },
39
+ horizontalOffset: {
40
+ control: { type: 'number' },
41
+ description: 'The offset to position the popper horizontally.',
42
+ },
43
+ };
44
+
45
+ export const tetheredArgs: TetheredStoryArgs = {
46
+ verticalAnchor: 'top',
47
+ horizontalAnchor: 'center',
48
+ verticalOrigin: 'bottom',
49
+ horizontalOrigin: 'center',
50
+ verticalOffset: 0,
51
+ horizontalOffset: 0,
52
+ };
@@ -0,0 +1,20 @@
1
+ .point {
2
+ position: absolute;
3
+ left: var(--point-x);
4
+ top: var(--point-y);
5
+ border-radius: 50%;
6
+ background: black;
7
+ pointer-events: none;
8
+ opacity: 0.5;
9
+ translate: -50% -50%;
10
+ width: var(--point-size);
11
+ height: var(--point-size);
12
+ }
13
+
14
+ .point[data-selected="true"] {
15
+ width: calc(var(--point-size) * 1.5);
16
+ height: calc(var(--point-size) * 1.5);
17
+ opacity: 1;
18
+ background: var(--accent-color);
19
+ border: 1px solid white;
20
+ }
@@ -0,0 +1,34 @@
1
+ import { Box, type BoxProps } from '../../../../../stacks/index.js';
2
+ import type { Position } from '../../../../../utils/index.js';
3
+
4
+ import styles from './sb_point.module.css';
5
+
6
+ export interface SB_TetheredPointProps extends BoxProps {
7
+ point: Position;
8
+ isSelected: boolean;
9
+ size?: number;
10
+ }
11
+
12
+ export const SB_TetheredPoint: React.FC<SB_TetheredPointProps> = ({
13
+ point,
14
+ isSelected,
15
+ style,
16
+ size = 8,
17
+ ...rest
18
+ }) => {
19
+ return (
20
+ <Box
21
+ className={styles.point}
22
+ data-selected={isSelected}
23
+ style={
24
+ {
25
+ ...style,
26
+ '--point-size': `${size}px`,
27
+ '--point-x': `${point.x}px`,
28
+ '--point-y': `${point.y}px`,
29
+ } as React.CSSProperties
30
+ }
31
+ {...rest}
32
+ />
33
+ );
34
+ };
@@ -0,0 +1,54 @@
1
+ import type { Dimensions } from '../../../../../utils/index.js';
2
+ import type { VerticalTether, HorizontalTether } from '../../../types.js';
3
+ import { SB_TetheredPoint } from './sb_point.js';
4
+
5
+ type PointValue = `${VerticalTether}-${HorizontalTether}`;
6
+
7
+ type SBReferencePoint = {
8
+ x: number;
9
+ y: number;
10
+ value: PointValue;
11
+ };
12
+
13
+ const mid = (value: number) => value / 2;
14
+
15
+ const getPoints = (dimensions: Dimensions): SBReferencePoint[] => {
16
+ const mWidth = mid(dimensions.width);
17
+ const mHeight = mid(dimensions.height);
18
+ const { width, height } = dimensions;
19
+ return [
20
+ { x: 0, y: 0, value: 'top-start' },
21
+ { x: mWidth, y: 0, value: 'top-center' },
22
+ { x: width, y: 0, value: 'top-end' },
23
+ { x: 0, y: mHeight, value: 'center-start' },
24
+ { x: mWidth, y: mHeight, value: 'center-center' },
25
+ { x: width, y: mHeight, value: 'center-end' },
26
+ { x: 0, y: height, value: 'bottom-start' },
27
+ { x: mWidth, y: height, value: 'bottom-center' },
28
+ { x: width, y: height, value: 'bottom-end' },
29
+ ];
30
+ };
31
+
32
+ const getAnchorValue = (
33
+ verticalAnchor: VerticalTether,
34
+ horizontalAnchor: HorizontalTether
35
+ ): PointValue => {
36
+ return `${verticalAnchor}-${horizontalAnchor}`;
37
+ };
38
+
39
+ export const SBReferencePoints: React.FC<{
40
+ dimensions: Dimensions;
41
+ vertical: VerticalTether;
42
+ horizontal: HorizontalTether;
43
+ }> = ({ dimensions, vertical, horizontal }) => {
44
+ const anchorPoints = getPoints(dimensions);
45
+ return (
46
+ <>
47
+ {anchorPoints.map((point, index) => {
48
+ const value = getAnchorValue(vertical, horizontal);
49
+ const isSelected = value === point.value;
50
+ return <SB_TetheredPoint key={index} point={point} isSelected={isSelected} />;
51
+ })}
52
+ </>
53
+ );
54
+ };