@fibery/ui-kit 1.36.2 → 1.38.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 (296) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +8 -5
  3. package/src/action-badge.tsx +5 -4
  4. package/src/actions-menu/actions-menu-confirmation.tsx +1 -12
  5. package/src/actions-menu/actions-menu-item.tsx +19 -7
  6. package/src/actions-menu/actions-menu-props.tsx +4 -0
  7. package/src/actions-menu/actions-menu-sub-command-menu.tsx +7 -4
  8. package/src/actions-menu/actions-menu-sub-menu.tsx +9 -3
  9. package/src/actions-menu/actions-menu.tsx +24 -8
  10. package/src/actions-panel.tsx +12 -2
  11. package/src/antd/ant-modal.tsx +17 -2
  12. package/src/antd/input.tsx +4 -3
  13. package/src/app-icon-with-fallback.tsx +5 -8
  14. package/src/app-icon-wrapper.tsx +4 -4
  15. package/src/appIcons.json +20 -0
  16. package/src/axis-header.tsx +136 -76
  17. package/src/badge.tsx +14 -7
  18. package/src/beta-badge.tsx +12 -8
  19. package/src/box.tsx +1 -1
  20. package/src/breadcrumb.tsx +78 -0
  21. package/src/button/actions-button-compact.tsx +5 -2
  22. package/src/button/base-button.tsx +7 -4
  23. package/src/button/button-group.tsx +1 -1
  24. package/src/button/button.tsx +120 -2
  25. package/src/button/icon-button.tsx +13 -2
  26. package/src/button/select-button.tsx +27 -8
  27. package/src/card-container.tsx +36 -69
  28. package/src/card-hidden-link.tsx +52 -0
  29. package/src/collapsible-section.tsx +13 -3
  30. package/src/color-picker/index.js +65 -60
  31. package/src/color-picker/swatch.tsx +29 -14
  32. package/src/command-menu/index.tsx +5 -5
  33. package/src/context-menu/index.tsx +10 -1
  34. package/src/count-badge.tsx +57 -0
  35. package/src/date-picker/date-range-input.tsx +1 -1
  36. package/src/date-picker/date-range-picker.tsx +6 -3
  37. package/src/date-picker/fake-date-input.tsx +1 -1
  38. package/src/date-picker/relative-date-picker.tsx +7 -3
  39. package/src/date-picker/single-date-input.tsx +1 -1
  40. package/src/date-picker/single-date-picker.tsx +8 -5
  41. package/src/date-picker/styles.ts +32 -26
  42. package/src/date-picker/types.ts +3 -1
  43. package/src/date-picker/utils.ts +9 -2
  44. package/src/day-select/day-select.tsx +21 -75
  45. package/src/day-select/iso-week-day-select.tsx +1 -1
  46. package/src/day-select/mount-day-select.tsx +2 -2
  47. package/src/day-select/week-day-select.tsx +1 -5
  48. package/src/design-system.ts +91 -51
  49. package/src/dropdown-menu/index.tsx +8 -1
  50. package/src/emoji-picker/app-icon-picker.tsx +6 -4
  51. package/src/emoji-picker/emoji-picker-content-with-color.tsx +3 -9
  52. package/src/emoji-picker/emoji-picker-preview-footer.tsx +1 -1
  53. package/src/emoji-picker/emoji-picker.tsx +3 -0
  54. package/src/emoji-picker/emoji.tsx +2 -1
  55. package/src/emoji-picker/icon-emoji-picker.tsx +83 -68
  56. package/src/emoji-picker/primitives/footer.tsx +0 -1
  57. package/src/emoji-picker/primitives/header.tsx +1 -0
  58. package/src/emoji-picker/primitives/search.tsx +12 -13
  59. package/src/emoji-picker/primitives/skin-tone.tsx +1 -0
  60. package/src/emoji-picker/use-responsive-emoji-size.ts +11 -0
  61. package/src/emoji-picker/utils/emoji-set.ts +2 -1
  62. package/src/empty-state/empty-state.tsx +6 -0
  63. package/src/error-alert/error-alert.tsx +19 -7
  64. package/src/field-app-icon.tsx +29 -0
  65. package/src/field-container.tsx +4 -2
  66. package/src/field-icon.tsx +29 -0
  67. package/src/field-type-icon.tsx +82 -0
  68. package/src/file-item.tsx +164 -47
  69. package/src/guide-link.tsx +4 -3
  70. package/src/hint.tsx +10 -1
  71. package/src/html-styles.ts +23 -13
  72. package/src/icons/ast/ActivityFilled.ts +8 -0
  73. package/src/icons/ast/AiAssistantFilled.ts +8 -0
  74. package/src/icons/ast/AiFiberyBuild.ts +8 -0
  75. package/src/icons/ast/Anthropic.ts +1 -1
  76. package/src/icons/ast/Apple.ts +8 -0
  77. package/src/icons/ast/ArrowBottom.ts +1 -1
  78. package/src/icons/ast/ArrowForward.ts +1 -1
  79. package/src/icons/ast/ArrowLeft.ts +1 -1
  80. package/src/icons/ast/ArrowRight.ts +1 -1
  81. package/src/icons/ast/ArrowTop.ts +1 -1
  82. package/src/icons/ast/CollapseAllHorizontal.ts +8 -0
  83. package/src/icons/ast/Crosshair.ts +8 -0
  84. package/src/icons/ast/DateRange.ts +1 -1
  85. package/src/icons/ast/Document.ts +8 -0
  86. package/src/icons/ast/EntityCreate.ts +8 -0
  87. package/src/icons/ast/EntityCreateFilled.ts +8 -0
  88. package/src/icons/ast/ExpandAll.ts +8 -0
  89. package/src/icons/ast/ExpandAllHorizontal.ts +8 -0
  90. package/src/icons/ast/FiberyDesktopApp.ts +8 -0
  91. package/src/icons/ast/FileDownload.ts +8 -0
  92. package/src/icons/ast/GlobeUnpublish.ts +8 -0
  93. package/src/icons/ast/GraduationCapFilled.ts +8 -0
  94. package/src/icons/ast/Home.ts +1 -1
  95. package/src/icons/ast/HomeFilled.ts +8 -0
  96. package/src/icons/ast/ImportFilled.ts +8 -0
  97. package/src/icons/ast/IntegrationsFilled.ts +8 -0
  98. package/src/icons/ast/KeyboardHideDown.ts +8 -0
  99. package/src/icons/ast/LayoutMultiple.ts +8 -0
  100. package/src/icons/ast/LocationEdit.ts +8 -0
  101. package/src/icons/ast/MessagePlus.ts +8 -0
  102. package/src/icons/ast/OpenAi.ts +1 -1
  103. package/src/icons/ast/RicheditorHistoryRedo.ts +1 -1
  104. package/src/icons/ast/RicheditorHistoryUndo.ts +1 -1
  105. package/src/icons/ast/RicheditorMarkHighlight.ts +8 -0
  106. package/src/icons/ast/RicheditorMarkStrike.ts +1 -1
  107. package/src/icons/ast/RicheditorMarkUnderline.ts +1 -1
  108. package/src/icons/ast/SearchFilled.ts +8 -0
  109. package/src/icons/ast/SettingsFilled.ts +8 -0
  110. package/src/icons/ast/SlideMenuFilled.ts +8 -0
  111. package/src/icons/ast/SpaceApp.ts +8 -0
  112. package/src/icons/ast/SpaceGenerate.ts +8 -0
  113. package/src/icons/ast/SpaceScratch.ts +8 -0
  114. package/src/icons/ast/SpaceTemplate.ts +8 -0
  115. package/src/icons/ast/TableLayout.ts +8 -0
  116. package/src/icons/ast/TemplatesFilled.ts +8 -0
  117. package/src/icons/ast/ThumbsDown.ts +8 -0
  118. package/src/icons/ast/ThumbsUp.ts +8 -0
  119. package/src/icons/ast/TypeDate.ts +1 -1
  120. package/src/icons/ast/ViewDashboard.ts +8 -0
  121. package/src/icons/ast/WarningTriangleFilled.ts +8 -0
  122. package/src/icons/ast/Windows.ts +8 -0
  123. package/src/icons/ast/index.tsx +37 -0
  124. package/src/icons/react/ActivityFilled.tsx +13 -0
  125. package/src/icons/react/AiAssistantFilled.tsx +13 -0
  126. package/src/icons/react/AiFiberyBuild.tsx +13 -0
  127. package/src/icons/react/Apple.tsx +13 -0
  128. package/src/icons/react/CollapseAllHorizontal.tsx +13 -0
  129. package/src/icons/react/Crosshair.tsx +13 -0
  130. package/src/icons/react/Document.tsx +13 -0
  131. package/src/icons/react/EntityCreate.tsx +13 -0
  132. package/src/icons/react/EntityCreateFilled.tsx +13 -0
  133. package/src/icons/react/ExpandAll.tsx +13 -0
  134. package/src/icons/react/ExpandAllHorizontal.tsx +13 -0
  135. package/src/icons/react/FiberyDesktopApp.tsx +13 -0
  136. package/src/icons/react/FileDownload.tsx +13 -0
  137. package/src/icons/react/GlobeUnpublish.tsx +13 -0
  138. package/src/icons/react/GraduationCapFilled.tsx +13 -0
  139. package/src/icons/react/HomeFilled.tsx +13 -0
  140. package/src/icons/react/ImportFilled.tsx +13 -0
  141. package/src/icons/react/IntegrationsFilled.tsx +13 -0
  142. package/src/icons/react/KeyboardHideDown.tsx +13 -0
  143. package/src/icons/react/LayoutMultiple.tsx +13 -0
  144. package/src/icons/react/LocationEdit.tsx +13 -0
  145. package/src/icons/react/MessagePlus.tsx +13 -0
  146. package/src/icons/react/RicheditorMarkHighlight.tsx +13 -0
  147. package/src/icons/react/SearchFilled.tsx +13 -0
  148. package/src/icons/react/SettingsFilled.tsx +13 -0
  149. package/src/icons/react/SlideMenuFilled.tsx +13 -0
  150. package/src/icons/react/SpaceApp.tsx +13 -0
  151. package/src/icons/react/SpaceGenerate.tsx +13 -0
  152. package/src/icons/react/SpaceScratch.tsx +13 -0
  153. package/src/icons/react/SpaceTemplate.tsx +13 -0
  154. package/src/icons/react/TableLayout.tsx +13 -0
  155. package/src/icons/react/TemplatesFilled.tsx +13 -0
  156. package/src/icons/react/ThumbsDown.tsx +13 -0
  157. package/src/icons/react/ThumbsUp.tsx +13 -0
  158. package/src/icons/react/ViewDashboard.tsx +13 -0
  159. package/src/icons/react/WarningTriangleFilled.tsx +13 -0
  160. package/src/icons/react/Windows.tsx +13 -0
  161. package/src/icons/react/index.tsx +37 -0
  162. package/src/icons/svg/activity-filled.svg +4 -0
  163. package/src/icons/svg/ai-assistant-filled.svg +3 -0
  164. package/src/icons/svg/ai-fibery-build.svg +24 -0
  165. package/src/icons/svg/anthropic.svg +2 -14
  166. package/src/icons/svg/apple.svg +4 -0
  167. package/src/icons/svg/arrow-bottom.svg +1 -1
  168. package/src/icons/svg/arrow-forward.svg +1 -1
  169. package/src/icons/svg/arrow-left.svg +1 -1
  170. package/src/icons/svg/arrow-right.svg +1 -1
  171. package/src/icons/svg/arrow-top.svg +1 -1
  172. package/src/icons/svg/collapse-all-horizontal.svg +3 -0
  173. package/src/icons/svg/crosshair.svg +4 -0
  174. package/src/icons/svg/date-range.svg +3 -3
  175. package/src/icons/svg/document.svg +4 -0
  176. package/src/icons/svg/entity-create-filled.svg +4 -0
  177. package/src/icons/svg/entity-create.svg +3 -0
  178. package/src/icons/svg/expand-all-horizontal.svg +3 -0
  179. package/src/icons/svg/expand-all.svg +3 -0
  180. package/src/icons/svg/fibery-desktop-app.svg +101 -0
  181. package/src/icons/svg/file-download.svg +4 -0
  182. package/src/icons/svg/globe-unpublish.svg +4 -0
  183. package/src/icons/svg/go-to.svg +1 -3
  184. package/src/icons/svg/graduation-cap-filled.svg +4 -0
  185. package/src/icons/svg/home-filled.svg +3 -0
  186. package/src/icons/svg/home.svg +4 -2
  187. package/src/icons/svg/import-filled.svg +4 -0
  188. package/src/icons/svg/integrations-filled.svg +4 -0
  189. package/src/icons/svg/keyboard-hide-down.svg +1 -0
  190. package/src/icons/svg/layout-multiple.svg +3 -0
  191. package/src/icons/svg/location-edit.svg +13 -0
  192. package/src/icons/svg/message-plus.svg +4 -0
  193. package/src/icons/svg/open-ai.svg +2 -7
  194. package/src/icons/svg/open-as-page.svg +2 -3
  195. package/src/icons/svg/richeditor/history/redo.svg +3 -2
  196. package/src/icons/svg/richeditor/history/undo.svg +3 -2
  197. package/src/icons/svg/richeditor/mark/code.svg +4 -5
  198. package/src/icons/svg/richeditor/mark/em.svg +3 -2
  199. package/src/icons/svg/richeditor/mark/highlight.svg +3 -0
  200. package/src/icons/svg/richeditor/mark/strike.svg +3 -2
  201. package/src/icons/svg/richeditor/mark/strong.svg +3 -2
  202. package/src/icons/svg/richeditor/mark/underline.svg +4 -2
  203. package/src/icons/svg/search-filled.svg +3 -0
  204. package/src/icons/svg/settings-filled.svg +3 -0
  205. package/src/icons/svg/slide-menu-filled.svg +1 -0
  206. package/src/icons/svg/space-app.svg +3 -0
  207. package/src/icons/svg/space-generate.svg +3 -0
  208. package/src/icons/svg/space-scratch.svg +3 -0
  209. package/src/icons/svg/space-template.svg +3 -0
  210. package/src/icons/svg/table-layout.svg +3 -0
  211. package/src/icons/svg/templates-filled.svg +6 -0
  212. package/src/icons/svg/thumbs-down.svg +3 -0
  213. package/src/icons/svg/thumbs-up.svg +3 -0
  214. package/src/icons/svg/type/date.svg +3 -2
  215. package/src/icons/svg/view-dashboard.svg +5 -0
  216. package/src/icons/svg/warning-triangle-filled.svg +3 -0
  217. package/src/icons/svg/windows.svg +15 -0
  218. package/src/images-gallery/images-gallery.tsx +254 -0
  219. package/src/images-gallery/zoom.tsx +112 -0
  220. package/src/integration-compact-info-button.tsx +3 -1
  221. package/src/is-in-popup.ts +1 -1
  222. package/src/layout-styles.ts +43 -0
  223. package/src/lists/actions-menu-row-surface.tsx +3 -1
  224. package/src/lists/list-row-surface.tsx +9 -2
  225. package/src/loaders.tsx +6 -6
  226. package/src/logo.tsx +16 -9
  227. package/src/mobile-keyboard-aware-popup.tsx +87 -0
  228. package/src/mobile-styles.ts +10 -0
  229. package/src/modal-menu/contexts/modal-menu-context.tsx +22 -0
  230. package/src/modal-menu/contexts/modal-submenu-context.tsx +16 -0
  231. package/src/modal-menu/index.tsx +14 -0
  232. package/src/modal-menu/modal-menu-content.tsx +68 -0
  233. package/src/modal-menu/modal-menu-group.tsx +10 -0
  234. package/src/modal-menu/modal-menu-item.tsx +37 -0
  235. package/src/modal-menu/modal-menu-label.tsx +24 -0
  236. package/src/modal-menu/modal-menu-root.tsx +57 -0
  237. package/src/modal-menu/modal-menu-separator.tsx +14 -0
  238. package/src/modal-menu/modal-menu-trigger.tsx +37 -0
  239. package/src/modal-menu/modal-submenu-content.tsx +49 -0
  240. package/src/modal-menu/modal-submenu-trigger.tsx +34 -0
  241. package/src/modal-menu/modal-submenu.tsx +17 -0
  242. package/src/new-badge.tsx +4 -7
  243. package/src/number-input/text-area-auto-sized.tsx +47 -0
  244. package/src/number-input/text-input-auto-sized.tsx +49 -0
  245. package/src/online-users.tsx +3 -3
  246. package/src/palette.ts +50 -50
  247. package/src/popover/index.tsx +687 -0
  248. package/src/popover/mobile-popup-context.tsx +29 -0
  249. package/src/{popup → popover}/modifiers.tsx +0 -7
  250. package/src/{popup → popover}/popup-modifiers-context.ts +3 -3
  251. package/src/reactions/reaction-button.tsx +30 -45
  252. package/src/reactions/reaction-picker.tsx +39 -45
  253. package/src/rich-text-embed-error.tsx +2 -2
  254. package/src/select/{custom-select-partials → components}/menu.tsx +13 -7
  255. package/src/select/{custom-select-partials → components}/no-option-message.tsx +0 -1
  256. package/src/select/constants.ts +1 -0
  257. package/src/select/index.tsx +143 -148
  258. package/src/select/select-in-popover.tsx +119 -138
  259. package/src/select/styles.ts +60 -24
  260. package/src/shortcut-badge.tsx +1 -0
  261. package/src/tab-nav/tab-nav.tsx +47 -28
  262. package/src/theme-provider.tsx +9 -1
  263. package/src/toast/primitives.tsx +8 -1
  264. package/src/toggle-button/round-toggle-button.tsx +23 -0
  265. package/src/toggle-button/toggle-button-group.tsx +34 -0
  266. package/src/toggle-button/toggle-button.tsx +164 -0
  267. package/src/toggle.tsx +3 -3
  268. package/src/tooltip-if-overflown.tsx +9 -3
  269. package/src/tooltip.tsx +2 -2
  270. package/src/unit/index.tsx +9 -1
  271. package/src/unit/loading-unit-value.tsx +1 -0
  272. package/src/unit/maybe-unit-value.tsx +3 -3
  273. package/src/unit/primitive.tsx +58 -42
  274. package/src/unit/styles.ts +10 -14
  275. package/src/unit/types.ts +4 -2
  276. package/src/unit/unit-button-group.tsx +6 -11
  277. package/src/unit/unit-content.tsx +13 -5
  278. package/src/unit/unit-with-title.tsx +6 -1
  279. package/src/unit/unit-with-tooltip.tsx +24 -9
  280. package/src/use-is-phone.tsx +37 -0
  281. package/src/use-is-support-hover.ts +1 -1
  282. package/src/use-on-screen-keyboard-data.tsx +66 -0
  283. package/src/workflow-progress-icon.tsx +17 -23
  284. package/src/number-input/text-area-with-autosize.tsx +0 -91
  285. package/src/number-input/text-inline-input-with-autosize.tsx +0 -60
  286. package/src/popup/index.tsx +0 -382
  287. package/src/use-is-phone.ts +0 -5
  288. /package/src/{popup → popover}/popup-stack-context.tsx +0 -0
  289. /package/src/{popup → popover}/reference.ts +0 -0
  290. /package/src/{popup → popover}/styles.ts +0 -0
  291. /package/src/{popup → popover}/use-click-outside.ts +0 -0
  292. /package/src/select/{custom-select-partials → components}/clear-indicator.tsx +0 -0
  293. /package/src/select/{custom-select-partials → components}/drop-down-indicator.tsx +0 -0
  294. /package/src/select/{custom-select-partials → components}/group-heading.tsx +0 -0
  295. /package/src/select/{custom-select-partials → components}/menu-list-virtualized.tsx +0 -0
  296. /package/src/select/{custom-select-partials → components}/option.tsx +0 -0
@@ -0,0 +1,687 @@
1
+ /* eslint-disable max-lines */
2
+ import {stopPropagation} from "@fibery/react/src/stop-propagation";
3
+ import {useControllableState} from "@fibery/react/src/use-controllable-state";
4
+ import {usePointerDownOutside} from "@fibery/react/src/use-pointer-down-outside";
5
+ import {css} from "@linaria/core";
6
+ import {createPopper, Modifier, Placement, State} from "@popperjs/core";
7
+ import {Options as FlipOptions} from "@popperjs/core/lib/modifiers/flip";
8
+ import {Options as PreventOverflowOptions} from "@popperjs/core/lib/modifiers/preventOverflow";
9
+ import cx from "classnames";
10
+ import _ from "lodash";
11
+ import {
12
+ cloneElement,
13
+ CSSProperties,
14
+ forwardRef,
15
+ isValidElement,
16
+ MouseEvent,
17
+ ReactNode,
18
+ useEffect,
19
+ useId,
20
+ useImperativeHandle,
21
+ useMemo,
22
+ } from "react";
23
+ import {
24
+ safeAreaInsetBottomVar,
25
+ safeAreaInsetLeftVar,
26
+ safeAreaInsetRightVar,
27
+ safeAreaInsetTopVar,
28
+ } from "../mobile-styles";
29
+ import {createPortal} from "react-dom";
30
+ import {usePopper} from "react-popper";
31
+ import {createInlineTheme, space, textStyles, themeVars, transition} from "../design-system";
32
+ import {IconButton} from "../button/icon-button";
33
+ import CloseIcon from "../icons/react/Close";
34
+ import ArrowLeftIcon from "../icons/react/ArrowLeft";
35
+ import {useTheme} from "../theme-provider";
36
+ import {$TSFixMe} from "../tsfixme";
37
+ import {useIsPhone} from "../use-is-phone";
38
+ import {usePopupModifiers} from "./popup-modifiers-context";
39
+ import {getMaxSize, observeWidth} from "./modifiers";
40
+ import {isElementReference, Reference} from "./reference";
41
+ import {basicPopupClassName, basicPopupContainerClassName, hidePopupWhenTriggerIsHiddenClassName} from "./styles";
42
+ import {MobilePopupProvider, useMobilePopup} from "./mobile-popup-context";
43
+ import {usePopupStackContext} from "./popup-stack-context";
44
+ import {useComposedRefs} from "@fibery/react/src/use-composed-refs";
45
+ import {createContext} from "@fibery/react/src/create-context";
46
+ import {Wrap} from "@fibery/react/src/wrap";
47
+
48
+ const Backdrop = ({className}: {className?: string}) => {
49
+ return (
50
+ <div
51
+ id={"popup-backdrop"}
52
+ data-testid={"popup-backdrop"}
53
+ className={cx(
54
+ className,
55
+ css`
56
+ position: absolute;
57
+ inset: 0;
58
+ z-index: 1000;
59
+ `
60
+ )}
61
+ />
62
+ );
63
+ };
64
+
65
+ const mobileAnimationClassName = css`
66
+ transition: transform ${transition}, opacity ${transition};
67
+ transform: translateY(0);
68
+ opacity: 1;
69
+
70
+ @media (prefers-reduced-motion: no-preference) {
71
+ @starting-style {
72
+ transform: translateY(20%);
73
+ opacity: 0;
74
+ }
75
+ }
76
+ `;
77
+
78
+ const popupContentClassName = css`
79
+ display: grid;
80
+ background: ${themeVars.actionMenuBg};
81
+ grid-template-rows: 60px 1fr;
82
+ `;
83
+
84
+ export function MobilePopup({
85
+ content,
86
+ mobilePopupStyle,
87
+ mobilePopupContentClassName,
88
+ title,
89
+ onClose,
90
+ height,
91
+ onNavigateBack,
92
+ }: {
93
+ content: ReactNode;
94
+ mobilePopupStyle?: CSSProperties;
95
+ mobilePopupContentClassName?: string;
96
+ title: string;
97
+ height?: string;
98
+ onClose?: () => void;
99
+ onNavigateBack?: () => void;
100
+ }) {
101
+ const theme = useTheme();
102
+
103
+ const {title: contextTitle, headerSlot} = useMobilePopup();
104
+
105
+ const headerContent = headerSlot ?? (
106
+ <>
107
+ {onNavigateBack && (
108
+ <IconButton onClick={onNavigateBack} size="xLarge">
109
+ <ArrowLeftIcon />
110
+ </IconButton>
111
+ )}
112
+ <div
113
+ className={css`
114
+ ${textStyles.heading3}
115
+ flex: 1;
116
+ min-width: 0;
117
+ overflow: hidden;
118
+ text-overflow: ellipsis;
119
+ white-space: nowrap;
120
+ `}
121
+ >
122
+ {contextTitle ?? title}
123
+ </div>
124
+ <IconButton onClick={onClose} size="xLarge">
125
+ <CloseIcon />
126
+ </IconButton>
127
+ </>
128
+ );
129
+
130
+ const popupContent = (
131
+ <>
132
+ <div
133
+ className={cx(
134
+ css`
135
+ display: flex;
136
+ align-items: center;
137
+ gap: ${space.s8}px;
138
+ padding: ${space.s8}px ${space.s8}px ${space.s8}px ${space.s20}px;
139
+ min-width: 0;
140
+ `,
141
+ onNavigateBack &&
142
+ css`
143
+ padding-left: ${space.s4}px;
144
+ `
145
+ )}
146
+ >
147
+ {headerContent}
148
+ </div>
149
+ <div
150
+ className={cx(
151
+ css`
152
+ border-top: ${themeVars.separators.opacity5};
153
+ min-width: 0;
154
+ `,
155
+ mobilePopupContentClassName
156
+ )}
157
+ >
158
+ {content}
159
+ </div>
160
+ </>
161
+ );
162
+
163
+ return (
164
+ <div
165
+ className={cx(
166
+ css`
167
+ position: absolute;
168
+ left: var(${safeAreaInsetLeftVar});
169
+ right: var(${safeAreaInsetRightVar});
170
+ top: var(${safeAreaInsetTopVar});
171
+ bottom: var(${safeAreaInsetBottomVar});
172
+ z-index: 1002;
173
+ `,
174
+ !height && mobileAnimationClassName,
175
+ !height && popupContentClassName,
176
+ height &&
177
+ css`
178
+ display: grid;
179
+ `
180
+ )}
181
+ style={{
182
+ ...createInlineTheme(theme),
183
+ ...mobilePopupStyle,
184
+ ...(height ? {gridTemplateRows: `1fr ${height}`} : {}),
185
+ }}
186
+ onClick={stopPropagation}
187
+ >
188
+ {height ? (
189
+ <>
190
+ <div
191
+ onClick={onClose}
192
+ className={css`
193
+ background-color: ${themeVars.modalBg};
194
+ `}
195
+ />
196
+ <div
197
+ className={cx(
198
+ css`
199
+ display: grid;
200
+ grid-template-rows: 60px 1fr;
201
+ `,
202
+ mobileAnimationClassName,
203
+ popupContentClassName
204
+ )}
205
+ >
206
+ {popupContent}
207
+ </div>
208
+ </>
209
+ ) : (
210
+ popupContent
211
+ )}
212
+ </div>
213
+ );
214
+ }
215
+
216
+ type PortalProps = {
217
+ getPopupContainer?: () => HTMLElement;
218
+ children: ReactNode;
219
+ };
220
+
221
+ const Portal = ({getPopupContainer: popupContainerOverride, children}: PortalProps) => {
222
+ const popupModifiers = usePopupModifiers();
223
+ const getPopupContainer = popupContainerOverride ?? popupModifiers.getPopupContainer;
224
+ return createPortal(children, getPopupContainer());
225
+ };
226
+
227
+ type TriggerProps = {
228
+ asChild?: boolean;
229
+ // Support case when popup trigger is virtual reference
230
+ reference?: Reference;
231
+ children?: ReactNode;
232
+ };
233
+
234
+ const Trigger = ({asChild = false, reference, children}: TriggerProps) => {
235
+ const {setTriggerElement, disabled, open, onOpen, onClose} = useContext();
236
+
237
+ const onMouseDown: React.MouseEventHandler<HTMLElement> = (e) => {
238
+ if (disabled) {
239
+ return;
240
+ }
241
+ if (e.button === 0) {
242
+ open ? onClose?.(e as unknown as Event) : onOpen?.();
243
+ }
244
+ };
245
+
246
+ useEffect(() => {
247
+ if (reference) {
248
+ setTriggerElement(reference);
249
+ }
250
+ }, [reference, setTriggerElement]);
251
+
252
+ if (reference) {
253
+ return null;
254
+ }
255
+
256
+ if (asChild && isValidElement(children)) {
257
+ return cloneElement(children, {
258
+ // @ts-expect-error: React.cloneElement typing does not include `ref`, but it's valid at runtime
259
+ ref: setTriggerElement,
260
+ "aria-expanded": open,
261
+ "aria-haspopup": true,
262
+ onMouseDown,
263
+ });
264
+ }
265
+
266
+ return (
267
+ <div ref={setTriggerElement} onMouseDown={onMouseDown}>
268
+ {children}
269
+ </div>
270
+ );
271
+ };
272
+
273
+ type ContentProps = {
274
+ children: ReactNode;
275
+ placement?: Placement;
276
+ popupStyle?: $TSFixMe;
277
+ popupClassName?: string;
278
+ popupContainerClassName?: string;
279
+ offset?: [number, number];
280
+ onMouseLeave?: (e: MouseEvent<HTMLDivElement>) => void;
281
+ renderBackdrop?: boolean;
282
+ backdropClassName?: string;
283
+ preventOverflowOptions?: Partial<PreventOverflowOptions>;
284
+ flipOptions?: Partial<FlipOptions>;
285
+ additionalModifiers?: Array<Partial<Modifier<$TSFixMe, $TSFixMe>>>;
286
+ hidePopupWhenTriggerIsHidden?: boolean;
287
+ shrinkable?: boolean;
288
+ supportMobile?: boolean;
289
+ title?: string;
290
+ mobilePopupStyle?: CSSProperties;
291
+ mobilePopupContentClassName?: string;
292
+ transform?: boolean;
293
+ /**
294
+ * @deprecated
295
+ * Because of openning animation popup creates new stacking context https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context
296
+ * this might break any components that use position: fixed inside the popup (i.e. formula editor -> monaco editor)
297
+ * Pass true to restore stacking context after animation is finished.
298
+ *
299
+ * Marked as deprecated because it's better to avoid this prop
300
+ */
301
+ fixStackingContextAfterOpen?: boolean;
302
+ /**
303
+ * ran after Popup positioned the element the first time
304
+ */
305
+ onFirstUpdate?: () => void;
306
+ };
307
+
308
+ export type ContentRef = {update: (() => Promise<unknown>) | null};
309
+
310
+ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInner(
311
+ {
312
+ children,
313
+ placement,
314
+ popupStyle,
315
+ popupContainerClassName,
316
+ popupClassName,
317
+ offset = [0, 0],
318
+ onMouseLeave,
319
+ renderBackdrop,
320
+ backdropClassName,
321
+ preventOverflowOptions = {},
322
+ flipOptions = {},
323
+ additionalModifiers = [],
324
+ hidePopupWhenTriggerIsHidden = true,
325
+ shrinkable,
326
+ supportMobile = false,
327
+ title = "",
328
+ mobilePopupStyle,
329
+ mobilePopupContentClassName,
330
+ fixStackingContextAfterOpen = false,
331
+ onFirstUpdate,
332
+ transform,
333
+ },
334
+ ref
335
+ ) {
336
+ const {open, onClose, triggerElement, popupElement, setPopupElement} = useContext();
337
+
338
+ const popupModifiers = usePopupModifiers();
339
+ const theme = useTheme();
340
+ const isPhone = useIsPhone();
341
+
342
+ // We get infinite popup renders if this is not in a separate memo
343
+ const shrinkableModifiers = useMemo(() => {
344
+ return shrinkable ? getMaxSize(preventOverflowOptions) : [];
345
+ }, [preventOverflowOptions, shrinkable]);
346
+
347
+ const modifiers = useMemo(() => {
348
+ return _.compact([
349
+ {name: "flip", options: {...popupModifiers.flipOptions, ...flipOptions}},
350
+ {
351
+ name: "preventOverflow",
352
+ options: {
353
+ tether: false,
354
+ padding: space.s6,
355
+ ...popupModifiers.preventOverflowOptions,
356
+ ...preventOverflowOptions,
357
+ },
358
+ },
359
+ {name: "offset", options: {offset: offset}},
360
+ !transform && {name: "computeStyles", options: {gpuAcceleration: false}},
361
+ observeWidth,
362
+ ...shrinkableModifiers,
363
+ ...additionalModifiers,
364
+ ...(popupModifiers.additionalModifiers ?? []),
365
+ ]);
366
+ }, [
367
+ additionalModifiers,
368
+ flipOptions,
369
+ offset,
370
+ popupModifiers,
371
+ preventOverflowOptions,
372
+ shrinkableModifiers,
373
+ transform,
374
+ ]);
375
+
376
+ // migrate to floating-ui https://floating-ui.com/docs/migration
377
+ const {styles, attributes, update} = usePopper(triggerElement, popupElement, {placement, onFirstUpdate, modifiers});
378
+
379
+ useImperativeHandle(ref, () => ({update}), [update]);
380
+
381
+ const {onPointerDownCapture} = usePointerDownOutside((event) => {
382
+ if (!open || (isPhone && supportMobile)) {
383
+ return;
384
+ }
385
+ const target = event.target;
386
+ if (target instanceof Node) {
387
+ const ignoreElements = _.compact([isElementReference(triggerElement) && triggerElement]);
388
+ if (ignoreElements.some((element) => element.contains(target))) {
389
+ return;
390
+ }
391
+ }
392
+ onClose?.(event);
393
+ });
394
+
395
+ const onAnimationEnd: React.AnimationEventHandler = (e) => {
396
+ if (fixStackingContextAfterOpen) {
397
+ (e.target as HTMLElement).setAttribute("data-fix-stacking-context", "");
398
+ }
399
+ };
400
+
401
+ const contentStyles = useMemo(() => {
402
+ return {...styles.popper, ...createInlineTheme(theme), ...popupStyle};
403
+ }, [popupStyle, styles.popper, theme]);
404
+
405
+ if (!open) {
406
+ return null;
407
+ }
408
+
409
+ // When transform is disabled there is a noticeable blink on popup open (it's not positioned correctly)
410
+ if (!transform && !triggerElement) {
411
+ return null;
412
+ }
413
+
414
+ if (isPhone && supportMobile) {
415
+ return (
416
+ <MobilePopupProvider>
417
+ <MobilePopup
418
+ onClose={onClose as $TSFixMe}
419
+ title={title}
420
+ mobilePopupContentClassName={mobilePopupContentClassName}
421
+ mobilePopupStyle={mobilePopupStyle}
422
+ content={children}
423
+ />
424
+ </MobilePopupProvider>
425
+ );
426
+ }
427
+ return (
428
+ <>
429
+ {renderBackdrop && <Backdrop className={backdropClassName} />}
430
+ <div
431
+ ref={setPopupElement}
432
+ className={cx(
433
+ basicPopupContainerClassName,
434
+ hidePopupWhenTriggerIsHidden && hidePopupWhenTriggerIsHiddenClassName,
435
+ popupContainerClassName
436
+ )}
437
+ onMouseLeave={onMouseLeave}
438
+ onClick={stopPropagation}
439
+ {...attributes.popper}
440
+ onPointerDownCapture={onPointerDownCapture}
441
+ style={contentStyles}
442
+ >
443
+ <div className={cx(basicPopupClassName, popupClassName)} style={popupStyle} onAnimationEnd={onAnimationEnd}>
444
+ {children}
445
+ </div>
446
+ </div>
447
+ </>
448
+ );
449
+ });
450
+
451
+ type ContextValue = {
452
+ open: boolean;
453
+ disabled?: boolean;
454
+ onOpen?: () => unknown;
455
+ onClose?: (event: Event) => unknown;
456
+ triggerElement: Reference;
457
+ setTriggerElement: (value: Reference) => void;
458
+ popupElement: HTMLDivElement | null;
459
+ setPopupElement: (value: HTMLDivElement) => void;
460
+ };
461
+
462
+ const [Provider, useContext] = createContext<ContextValue>("PopoverContext");
463
+
464
+ type RootProps = {
465
+ children: ReactNode;
466
+ open: boolean;
467
+ disabled?: boolean;
468
+ closeOnEscape?: boolean;
469
+ onOpen?: () => unknown;
470
+ onClose?: (event: Event) => unknown;
471
+ // For legacy popup component support
472
+ triggerElement?: Reference;
473
+ setTriggerElement?: (value: Reference) => void;
474
+ popupElement?: HTMLDivElement | null;
475
+ setPopupElement?: (value: HTMLDivElement | null) => void;
476
+ };
477
+
478
+ const Root = ({children, open, disabled = false, closeOnEscape = true, onOpen, onClose, ...rest}: RootProps) => {
479
+ const id = useId();
480
+ const {registerEscapeCallback, registerPopup} = usePopupStackContext();
481
+
482
+ const [triggerElement, setTriggerElement] = useControllableState<Reference>({
483
+ value: rest.triggerElement,
484
+ onChange: rest.setTriggerElement,
485
+ });
486
+ const [popupElement, setPopupElement] = useControllableState<HTMLDivElement | null>({
487
+ value: rest.popupElement,
488
+ onChange: rest.setPopupElement,
489
+ });
490
+
491
+ const composedSetTriggerElement = useComposedRefs(setTriggerElement, rest.setTriggerElement);
492
+ const composedSetPopupElement = useComposedRefs(setPopupElement, rest.setPopupElement);
493
+
494
+ useEffect(() => {
495
+ let unregister: () => void;
496
+ if (registerPopup && open) {
497
+ unregister = registerPopup(id);
498
+ }
499
+ return () => {
500
+ unregister?.();
501
+ };
502
+ }, [registerPopup, id, open]);
503
+
504
+ useEffect(() => {
505
+ if (triggerElement && closeOnEscape && open) {
506
+ const onKeyDown = (evt: KeyboardEvent) => {
507
+ if (evt.defaultPrevented) {
508
+ return;
509
+ }
510
+ if (evt.key === "Escape") {
511
+ onClose?.(evt);
512
+ evt.preventDefault();
513
+ evt.stopPropagation();
514
+ }
515
+ };
516
+
517
+ if (registerEscapeCallback) {
518
+ registerEscapeCallback(id, onKeyDown);
519
+ } else {
520
+ document.addEventListener("keydown", onKeyDown);
521
+ }
522
+
523
+ // if popup was opened but nothing was focused inside it we should catch 'Escape' key from trigger as well
524
+ // otherwise it can be caught by navigation panel
525
+ "addEventListener" in triggerElement && triggerElement.addEventListener("keydown", onKeyDown);
526
+
527
+ return () => {
528
+ if (!registerEscapeCallback) {
529
+ document.removeEventListener("keydown", onKeyDown);
530
+ }
531
+
532
+ "removeEventListener" in triggerElement && triggerElement.removeEventListener("keydown", onKeyDown);
533
+ };
534
+ }
535
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
536
+ return () => {};
537
+ }, [closeOnEscape, onClose, open, triggerElement, registerEscapeCallback, id]);
538
+
539
+ const contextValue = useMemo(() => {
540
+ return {
541
+ disabled,
542
+ triggerElement,
543
+ setTriggerElement: composedSetTriggerElement,
544
+ popupElement,
545
+ setPopupElement: composedSetPopupElement,
546
+ open,
547
+ onClose,
548
+ onOpen,
549
+ };
550
+ }, [
551
+ disabled,
552
+ triggerElement,
553
+ composedSetTriggerElement,
554
+ popupElement,
555
+ composedSetPopupElement,
556
+ open,
557
+ onClose,
558
+ onOpen,
559
+ ]);
560
+
561
+ return <Provider value={contextValue}>{children}</Provider>;
562
+ };
563
+
564
+ export const Popover = {
565
+ Root,
566
+ Trigger,
567
+ Portal,
568
+ Content,
569
+ };
570
+
571
+ export {createPopper};
572
+
573
+ export type {Placement, State, Reference, RootProps, TriggerProps, ContentProps};
574
+
575
+ export type PopoverOldProps = Pick<
576
+ RootProps,
577
+ "children" | "open" | "onOpen" | "onClose" | "closeOnEscape" | "disabled"
578
+ > &
579
+ ContentProps &
580
+ Pick<PortalProps, "getPopupContainer"> & {
581
+ trigger?: ReactNode;
582
+ triggerDomElement?: Reference;
583
+ renderInPortal?: boolean;
584
+ };
585
+
586
+ /**
587
+ * @deprecated Use the new `Popover` compound component instead.
588
+ *
589
+ * The `PopoverOld` component is deprecated and will be removed in a future version.
590
+ * Please migrate to the new `Popover` API which provides better composition and flexibility:
591
+ *
592
+ * @example
593
+ * ```tsx
594
+ * // Instead of PopoverOld:
595
+ * <PopoverOld
596
+ * trigger={<button>Click me</button>}
597
+ * open={isOpen}
598
+ * onOpen={() => setIsOpen(true)}
599
+ * onClose={() => setIsOpen(false)}
600
+ * >
601
+ * <div>Popover content</div>
602
+ * </PopoverOld>
603
+ *
604
+ * // Use Popover compound components:
605
+ * <Popover.Root
606
+ * open={isOpen}
607
+ * onOpen={() => setIsOpen(true)}
608
+ * onClose={() => setIsOpen(false)}
609
+ * >
610
+ * <Popover.Trigger>
611
+ * <button>Click me</button>
612
+ * </Popover.Trigger>
613
+ * <Popover.Portal>
614
+ * <Popover.Content>
615
+ * <div>Popover content</div>
616
+ * </Popover.Content>
617
+ * </Popover.Portal>
618
+ * </Popover.Root>
619
+ * ```
620
+ */
621
+ export const PopoverOld = ({
622
+ trigger,
623
+ triggerDomElement,
624
+ children,
625
+ placement,
626
+ open,
627
+ popupStyle,
628
+ popupContainerClassName,
629
+ popupClassName,
630
+ renderInPortal,
631
+ offset = [0, 0],
632
+ disabled = false,
633
+ onMouseLeave,
634
+ renderBackdrop,
635
+ backdropClassName,
636
+ closeOnEscape = true,
637
+ preventOverflowOptions = {},
638
+ flipOptions = {},
639
+ additionalModifiers = [],
640
+ hidePopupWhenTriggerIsHidden = true,
641
+ shrinkable,
642
+ onOpen = _.noop,
643
+ onClose = _.noop,
644
+ supportMobile = false,
645
+ title = "",
646
+ mobilePopupStyle,
647
+ mobilePopupContentClassName,
648
+ getPopupContainer,
649
+ fixStackingContextAfterOpen = false,
650
+ onFirstUpdate,
651
+ transform,
652
+ }: PopoverOldProps) => {
653
+ return (
654
+ <Popover.Root open={open} disabled={disabled} closeOnEscape={closeOnEscape} onOpen={onOpen} onClose={onClose}>
655
+ {triggerDomElement ? (
656
+ <Popover.Trigger reference={triggerDomElement}></Popover.Trigger>
657
+ ) : (
658
+ <Popover.Trigger>{trigger}</Popover.Trigger>
659
+ )}
660
+ <Wrap with={Popover.Portal} if={renderInPortal} getPopupContainer={getPopupContainer}>
661
+ <Popover.Content
662
+ children={children}
663
+ placement={placement}
664
+ popupStyle={popupStyle}
665
+ popupContainerClassName={popupContainerClassName}
666
+ popupClassName={popupClassName}
667
+ offset={offset}
668
+ onMouseLeave={onMouseLeave}
669
+ renderBackdrop={renderBackdrop}
670
+ backdropClassName={backdropClassName}
671
+ preventOverflowOptions={preventOverflowOptions}
672
+ flipOptions={flipOptions}
673
+ additionalModifiers={additionalModifiers}
674
+ hidePopupWhenTriggerIsHidden={hidePopupWhenTriggerIsHidden}
675
+ shrinkable={shrinkable}
676
+ supportMobile={supportMobile}
677
+ title={title}
678
+ mobilePopupStyle={mobilePopupStyle}
679
+ mobilePopupContentClassName={mobilePopupContentClassName}
680
+ fixStackingContextAfterOpen={fixStackingContextAfterOpen}
681
+ onFirstUpdate={onFirstUpdate}
682
+ transform={transform}
683
+ />
684
+ </Wrap>
685
+ </Popover.Root>
686
+ );
687
+ };
@@ -0,0 +1,29 @@
1
+ import _ from "lodash";
2
+ import {FC, ReactNode, useState, useMemo} from "react";
3
+ import {createContext} from "@fibery/react/src/create-context";
4
+
5
+ type MobilePopupContext = {
6
+ title?: string;
7
+ headerSlot?: ReactNode;
8
+ setTitle: (title: string) => void;
9
+ setHeader: (header?: ReactNode) => void;
10
+ };
11
+
12
+ const [Provider, useMobilePopup] = createContext<MobilePopupContext>("MobilePopupContext", {
13
+ setTitle: _.noop,
14
+ setHeader: _.noop,
15
+ });
16
+
17
+ export const MobilePopupProvider: FC<{children?: ReactNode}> = ({children}) => {
18
+ const [title, setTitle] = useState<string | undefined>();
19
+ const [headerSlot, setHeader] = useState<ReactNode | undefined>();
20
+
21
+ const contextValue = useMemo(
22
+ () => ({title, headerSlot, setTitle, setHeader}),
23
+ [title, headerSlot, setTitle, setHeader]
24
+ );
25
+
26
+ return <Provider value={contextValue}>{children}</Provider>;
27
+ };
28
+
29
+ export {useMobilePopup};