@fibery/ui-kit 1.37.0 → 1.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/config/jest/testFrameworkSetup.js +1 -1
  3. package/package.json +20 -16
  4. package/src/abbreviate-name.ts +42 -0
  5. package/src/actions-menu/actions-menu-item.tsx +32 -11
  6. package/src/actions-menu/actions-menu-props.tsx +5 -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 +28 -9
  10. package/src/actions-menu/context-actions-menu.tsx +22 -0
  11. package/src/actions-panel.tsx +12 -2
  12. package/src/animated-height-container.tsx +24 -5
  13. package/src/antd/ant-upload.tsx +1 -1
  14. package/src/antd/input.tsx +4 -0
  15. package/src/avatar.tsx +1 -27
  16. package/src/axis-header.tsx +136 -76
  17. package/src/box.tsx +1 -1
  18. package/src/breadcrumb.tsx +78 -0
  19. package/src/button/actions-button-compact.tsx +4 -1
  20. package/src/button/base-button.tsx +7 -3
  21. package/src/button/button-group.tsx +1 -1
  22. package/src/button/button.tsx +33 -1
  23. package/src/button/icon-button.tsx +10 -1
  24. package/src/button/select-button.tsx +4 -0
  25. package/src/card-container.tsx +10 -6
  26. package/src/checkbox.tsx +17 -0
  27. package/src/collapsible-section.tsx +3 -1
  28. package/src/color-picker/swatch.tsx +1 -0
  29. package/src/command-menu/index.tsx +3 -3
  30. package/src/context-menu/index.tsx +10 -1
  31. package/src/date-picker/autocomplete.tsx +11 -5
  32. package/src/date-picker/date-range-input.tsx +5 -1
  33. package/src/date-picker/date-range-picker-popup-content.tsx +31 -13
  34. package/src/date-picker/date-range-picker.tsx +6 -3
  35. package/src/date-picker/relative-date-picker.tsx +6 -2
  36. package/src/date-picker/single-date-input.tsx +5 -1
  37. package/src/date-picker/single-date-picker.tsx +8 -5
  38. package/src/date-picker/styles.ts +52 -26
  39. package/src/date-picker/types.ts +3 -1
  40. package/src/day-select/day-select.tsx +21 -75
  41. package/src/day-select/iso-week-day-select.tsx +1 -1
  42. package/src/day-select/mount-day-select.tsx +2 -2
  43. package/src/day-select/week-day-select.tsx +1 -5
  44. package/src/design-system.ts +45 -12
  45. package/src/dropdown-menu/index.tsx +19 -2
  46. package/src/emoji-picker/app-icon-picker.tsx +6 -4
  47. package/src/emoji-picker/emoji-picker-preview-footer.tsx +5 -1
  48. package/src/emoji-picker/emoji-picker.tsx +3 -0
  49. package/src/emoji-picker/emoji.tsx +5 -3
  50. package/src/emoji-picker/icon-emoji-picker.tsx +10 -1
  51. package/src/emoji-picker/primitives/emoji.tsx +49 -51
  52. package/src/emoji-picker/primitives/footer.tsx +5 -0
  53. package/src/emoji-picker/primitives/search.tsx +8 -0
  54. package/src/emoji-picker/primitives/skin-tone.tsx +12 -1
  55. package/src/emoji-picker/use-responsive-emoji-size.ts +11 -0
  56. package/src/emoji-picker/utils/emoji-set.ts +2 -1
  57. package/src/empty-state/empty-state.tsx +6 -0
  58. package/src/fibermoji-placeholder.tsx +1 -27
  59. package/src/field-icon.tsx +4 -1
  60. package/src/file-item-2.tsx +408 -0
  61. package/src/file-item.tsx +215 -47
  62. package/src/header-layout.tsx +11 -0
  63. package/src/html-styles.ts +21 -4
  64. package/src/icons/ast/ActivityFilled.ts +8 -0
  65. package/src/icons/ast/AiAssistantFilled.ts +8 -0
  66. package/src/icons/ast/Back.ts +1 -1
  67. package/src/icons/ast/CollapseAllHorizontal.ts +8 -0
  68. package/src/icons/ast/DateRange.ts +1 -1
  69. package/src/icons/ast/EntityCreateFilled.ts +8 -0
  70. package/src/icons/ast/ExpandAllHorizontal.ts +8 -0
  71. package/src/icons/ast/ExtensionFiles.ts +1 -1
  72. package/src/icons/ast/FileUpload.ts +1 -1
  73. package/src/icons/ast/GlobeUnpublish.ts +8 -0
  74. package/src/icons/ast/HomeFilled.ts +8 -0
  75. package/src/icons/ast/KeyboardHideDown.ts +8 -0
  76. package/src/icons/ast/LayoutMultiple.ts +8 -0
  77. package/src/icons/ast/RicheditorHistoryRedo.ts +1 -1
  78. package/src/icons/ast/RicheditorHistoryUndo.ts +1 -1
  79. package/src/icons/ast/SearchFilled.ts +8 -0
  80. package/src/icons/ast/SettingsFilled.ts +8 -0
  81. package/src/icons/ast/SlideMenuFilled.ts +8 -0
  82. package/src/icons/ast/SwitchToMention.ts +8 -0
  83. package/src/icons/ast/SwitchToText.ts +8 -0
  84. package/src/icons/ast/SwitchToWidget.ts +8 -0
  85. package/src/icons/ast/TypeDate.ts +1 -1
  86. package/src/icons/ast/ViewGallery.ts +8 -0
  87. package/src/icons/ast/index.tsx +16 -0
  88. package/src/icons/react/ActivityFilled.tsx +13 -0
  89. package/src/icons/react/AiAssistantFilled.tsx +13 -0
  90. package/src/icons/react/CollapseAllHorizontal.tsx +13 -0
  91. package/src/icons/react/EntityCreateFilled.tsx +13 -0
  92. package/src/icons/react/ExpandAllHorizontal.tsx +13 -0
  93. package/src/icons/react/GlobeUnpublish.tsx +13 -0
  94. package/src/icons/react/HomeFilled.tsx +13 -0
  95. package/src/icons/react/KeyboardHideDown.tsx +13 -0
  96. package/src/icons/react/LayoutMultiple.tsx +13 -0
  97. package/src/icons/react/SearchFilled.tsx +13 -0
  98. package/src/icons/react/SettingsFilled.tsx +13 -0
  99. package/src/icons/react/SlideMenuFilled.tsx +13 -0
  100. package/src/icons/react/SwitchToMention.tsx +13 -0
  101. package/src/icons/react/SwitchToText.tsx +13 -0
  102. package/src/icons/react/SwitchToWidget.tsx +13 -0
  103. package/src/icons/react/ViewGallery.tsx +13 -0
  104. package/src/icons/react/index.tsx +16 -0
  105. package/src/icons/svg/activity-filled.svg +4 -0
  106. package/src/icons/svg/ai-assistant-filled.svg +3 -0
  107. package/src/icons/svg/back.svg +3 -1
  108. package/src/icons/svg/collapse-all-horizontal.svg +3 -0
  109. package/src/icons/svg/date-range.svg +3 -3
  110. package/src/icons/svg/entity-create-filled.svg +4 -0
  111. package/src/icons/svg/expand-all-horizontal.svg +3 -0
  112. package/src/icons/svg/extension/files.svg +3 -1
  113. package/src/icons/svg/file-upload.svg +3 -6
  114. package/src/icons/svg/globe-unpublish.svg +4 -0
  115. package/src/icons/svg/home-filled.svg +3 -0
  116. package/src/icons/svg/keyboard-hide-down.svg +1 -0
  117. package/src/icons/svg/layout-multiple.svg +3 -0
  118. package/src/icons/svg/richeditor/history/redo.svg +3 -2
  119. package/src/icons/svg/richeditor/history/undo.svg +3 -2
  120. package/src/icons/svg/search-filled.svg +3 -0
  121. package/src/icons/svg/settings-filled.svg +3 -0
  122. package/src/icons/svg/slide-menu-filled.svg +1 -0
  123. package/src/icons/svg/switch-to-mention.svg +3 -0
  124. package/src/icons/svg/switch-to-text.svg +4 -0
  125. package/src/icons/svg/switch-to-widget.svg +4 -0
  126. package/src/icons/svg/type/date.svg +3 -2
  127. package/src/icons/svg/view-gallery.svg +4 -0
  128. package/src/images-gallery/images-gallery-fall-through-provider.tsx +11 -0
  129. package/src/images-gallery/images-gallery.tsx +257 -0
  130. package/src/images-gallery/zoom.tsx +123 -0
  131. package/src/images-gallery-2/images-gallery-2.tsx +442 -0
  132. package/src/images-gallery-2/slide-buttons.tsx +230 -0
  133. package/src/images-gallery-2/zoom.tsx +35 -0
  134. package/src/is-in-popup.ts +1 -1
  135. package/src/kbd.tsx +46 -0
  136. package/src/layout-styles.ts +21 -1
  137. package/src/lists/actions-menu-row-surface.tsx +3 -1
  138. package/src/lists/list-row-surface.tsx +7 -1
  139. package/src/mobile-keyboard-aware-popup.tsx +87 -0
  140. package/src/mobile-styles.ts +8 -0
  141. package/src/modal-menu/contexts/modal-menu-context.tsx +22 -0
  142. package/src/modal-menu/contexts/modal-submenu-context.tsx +16 -0
  143. package/src/modal-menu/index.tsx +14 -0
  144. package/src/modal-menu/modal-menu-content.tsx +68 -0
  145. package/src/modal-menu/modal-menu-group.tsx +10 -0
  146. package/src/modal-menu/modal-menu-item.tsx +45 -0
  147. package/src/modal-menu/modal-menu-label.tsx +24 -0
  148. package/src/modal-menu/modal-menu-root.tsx +57 -0
  149. package/src/modal-menu/modal-menu-separator.tsx +14 -0
  150. package/src/modal-menu/modal-menu-trigger.tsx +54 -0
  151. package/src/modal-menu/modal-submenu-content.tsx +49 -0
  152. package/src/modal-menu/modal-submenu-trigger.tsx +34 -0
  153. package/src/modal-menu/modal-submenu.tsx +17 -0
  154. package/src/notification-dot.tsx +16 -7
  155. package/src/online-users.tsx +3 -3
  156. package/src/palette.ts +1 -1
  157. package/src/platform-symbols.ts +9 -0
  158. package/src/platform.ts +2 -0
  159. package/src/popover/index.tsx +710 -0
  160. package/src/popover/mobile-popup-context.tsx +29 -0
  161. package/src/{popup → popover}/modifiers.tsx +25 -25
  162. package/src/{popup → popover}/popup-modifiers-context.ts +3 -3
  163. package/src/reactions/reaction-button.tsx +342 -135
  164. package/src/reactions/reaction-picker.tsx +51 -44
  165. package/src/reactions/reactions.tsx +11 -1
  166. package/src/reactions/tootltip.tsx +55 -0
  167. package/src/rich-text-embed-error.tsx +2 -1
  168. package/src/select/components/group-heading.tsx +8 -0
  169. package/src/select/components/menu-list-virtualized.tsx +6 -2
  170. package/src/select/components/option.tsx +4 -0
  171. package/src/select/index.tsx +8 -4
  172. package/src/select/select-control-settings-context.tsx +11 -2
  173. package/src/select/select-in-popover.tsx +30 -39
  174. package/src/select/styles.ts +21 -1
  175. package/src/tab-nav/tab-nav.tsx +36 -10
  176. package/src/toast/primitives.tsx +8 -1
  177. package/src/toggle-button/round-toggle-button.tsx +23 -0
  178. package/src/toggle-button/toggle-button-group.tsx +34 -0
  179. package/src/toggle-button/toggle-button.tsx +164 -0
  180. package/src/toggle.tsx +11 -3
  181. package/src/tooltip.tsx +29 -20
  182. package/src/type-badge.tsx +5 -2
  183. package/src/unit/styles.ts +2 -2
  184. package/src/unit/unit-with-tooltip.tsx +1 -1
  185. package/src/use-is-phone.tsx +50 -4
  186. package/src/use-is-support-hover.ts +1 -1
  187. package/src/use-on-screen-keyboard-data.tsx +66 -0
  188. package/src/workflow-progress-icon.tsx +16 -22
  189. package/src/popup/index.tsx +0 -383
  190. package/src/shortcut-badge.tsx +0 -29
  191. /package/src/{popup → popover}/popup-stack-context.tsx +0 -0
  192. /package/src/{popup → popover}/reference.ts +0 -0
  193. /package/src/{popup → popover}/styles.ts +0 -0
  194. /package/src/{popup → popover}/use-click-outside.ts +0 -0
@@ -0,0 +1,710 @@
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 {applyMaxSize, makeMaxSizeModifier, 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
+ // Reference is passed
253
+ if (reference !== undefined) {
254
+ return null;
255
+ }
256
+
257
+ if (asChild && isValidElement(children)) {
258
+ return cloneElement(children, {
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 padding = space.s8;
311
+
312
+ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInner(
313
+ {
314
+ children,
315
+ placement,
316
+ popupStyle,
317
+ popupContainerClassName,
318
+ popupClassName,
319
+ offset = [0, 0],
320
+ onMouseLeave,
321
+ renderBackdrop,
322
+ backdropClassName,
323
+ preventOverflowOptions = {},
324
+ flipOptions = {},
325
+ additionalModifiers = [],
326
+ hidePopupWhenTriggerIsHidden = true,
327
+ shrinkable,
328
+ supportMobile = false,
329
+ title = "",
330
+ mobilePopupStyle,
331
+ mobilePopupContentClassName,
332
+ fixStackingContextAfterOpen = false,
333
+ onFirstUpdate,
334
+ transform,
335
+ },
336
+ ref
337
+ ) {
338
+ const {open, onClose, triggerElement, popupElement, setPopupElement} = useContext();
339
+
340
+ const popupModifiers = usePopupModifiers();
341
+ const theme = useTheme();
342
+ const isPhone = useIsPhone();
343
+
344
+ // We get infinite popup renders if this is not in a separate memo
345
+ const shrinkableModifiers = useMemo(() => {
346
+ return shrinkable
347
+ ? [
348
+ makeMaxSizeModifier({
349
+ padding,
350
+ ...preventOverflowOptions,
351
+ }),
352
+ applyMaxSize,
353
+ ]
354
+ : [];
355
+ }, [preventOverflowOptions, shrinkable]);
356
+
357
+ const modifiers = useMemo(() => {
358
+ return _.compact([
359
+ {
360
+ name: "flip",
361
+ options: {
362
+ padding,
363
+ ...popupModifiers.flipOptions,
364
+ ...flipOptions,
365
+ },
366
+ },
367
+ {
368
+ name: "preventOverflow",
369
+ options: {
370
+ tether: false,
371
+ padding,
372
+ ...popupModifiers.preventOverflowOptions,
373
+ ...preventOverflowOptions,
374
+ },
375
+ },
376
+ {
377
+ name: "offset",
378
+ options: {offset},
379
+ },
380
+ !transform && {
381
+ name: "computeStyles",
382
+ options: {gpuAcceleration: false},
383
+ },
384
+ observeWidth,
385
+ ...shrinkableModifiers,
386
+ ...additionalModifiers,
387
+ ...(popupModifiers.additionalModifiers ?? []),
388
+ ]);
389
+ }, [
390
+ additionalModifiers,
391
+ flipOptions,
392
+ offset,
393
+ popupModifiers,
394
+ preventOverflowOptions,
395
+ shrinkableModifiers,
396
+ transform,
397
+ ]);
398
+
399
+ // migrate to floating-ui https://floating-ui.com/docs/migration
400
+ const {styles, attributes, update} = usePopper(triggerElement, popupElement, {placement, onFirstUpdate, modifiers});
401
+
402
+ useImperativeHandle(ref, () => ({update}), [update]);
403
+
404
+ const {onPointerDownCapture} = usePointerDownOutside((event) => {
405
+ if (!open || (isPhone && supportMobile)) {
406
+ return;
407
+ }
408
+ const target = event.target;
409
+ if (target instanceof Node) {
410
+ const ignoreElements = _.compact([isElementReference(triggerElement) && triggerElement]);
411
+ if (ignoreElements.some((element) => element.contains(target))) {
412
+ return;
413
+ }
414
+ }
415
+ onClose?.(event);
416
+ });
417
+
418
+ const onAnimationEnd: React.AnimationEventHandler = (e) => {
419
+ if (fixStackingContextAfterOpen) {
420
+ (e.target as HTMLElement).setAttribute("data-fix-stacking-context", "");
421
+ }
422
+ };
423
+
424
+ const contentStyles = useMemo(() => {
425
+ return {...styles.popper, ...createInlineTheme(theme), ...popupStyle};
426
+ }, [popupStyle, styles.popper, theme]);
427
+
428
+ if (!open) {
429
+ return null;
430
+ }
431
+
432
+ // When transform is disabled there is a noticeable blink on popup open (it's not positioned correctly)
433
+ if (!transform && !triggerElement) {
434
+ return null;
435
+ }
436
+
437
+ if (isPhone && supportMobile) {
438
+ return (
439
+ <MobilePopupProvider>
440
+ <MobilePopup
441
+ onClose={onClose as $TSFixMe}
442
+ title={title}
443
+ mobilePopupContentClassName={mobilePopupContentClassName}
444
+ mobilePopupStyle={mobilePopupStyle}
445
+ content={children}
446
+ />
447
+ </MobilePopupProvider>
448
+ );
449
+ }
450
+ return (
451
+ <>
452
+ {renderBackdrop && <Backdrop className={backdropClassName} />}
453
+ <div
454
+ ref={setPopupElement}
455
+ className={cx(
456
+ basicPopupContainerClassName,
457
+ hidePopupWhenTriggerIsHidden && hidePopupWhenTriggerIsHiddenClassName,
458
+ popupContainerClassName
459
+ )}
460
+ onMouseLeave={onMouseLeave}
461
+ onClick={stopPropagation}
462
+ {...attributes.popper}
463
+ onPointerDownCapture={onPointerDownCapture}
464
+ style={contentStyles}
465
+ >
466
+ <div className={cx(basicPopupClassName, popupClassName)} style={popupStyle} onAnimationEnd={onAnimationEnd}>
467
+ {children}
468
+ </div>
469
+ </div>
470
+ </>
471
+ );
472
+ });
473
+
474
+ type ContextValue = {
475
+ open: boolean;
476
+ disabled?: boolean;
477
+ onOpen?: () => unknown;
478
+ onClose?: (event: Event) => unknown;
479
+ triggerElement: Reference;
480
+ setTriggerElement: (value: Reference) => void;
481
+ popupElement: HTMLDivElement | null;
482
+ setPopupElement: (value: HTMLDivElement) => void;
483
+ };
484
+
485
+ const [Provider, useContext] = createContext<ContextValue>("PopoverContext");
486
+
487
+ type RootProps = {
488
+ children: ReactNode;
489
+ open: boolean;
490
+ disabled?: boolean;
491
+ closeOnEscape?: boolean;
492
+ onOpen?: () => unknown;
493
+ onClose?: (event: Event) => unknown;
494
+ // For legacy popup component support
495
+ triggerElement?: Reference;
496
+ setTriggerElement?: (value: Reference) => void;
497
+ popupElement?: HTMLDivElement | null;
498
+ setPopupElement?: (value: HTMLDivElement | null) => void;
499
+ };
500
+
501
+ const Root = ({children, open, disabled = false, closeOnEscape = true, onOpen, onClose, ...rest}: RootProps) => {
502
+ const id = useId();
503
+ const {registerEscapeCallback, registerPopup} = usePopupStackContext();
504
+
505
+ const [triggerElement, setTriggerElement] = useControllableState<Reference>({
506
+ value: rest.triggerElement,
507
+ onChange: rest.setTriggerElement,
508
+ });
509
+ const [popupElement, setPopupElement] = useControllableState<HTMLDivElement | null>({
510
+ value: rest.popupElement,
511
+ onChange: rest.setPopupElement,
512
+ });
513
+
514
+ const composedSetTriggerElement = useComposedRefs(setTriggerElement, rest.setTriggerElement);
515
+ const composedSetPopupElement = useComposedRefs(setPopupElement, rest.setPopupElement);
516
+
517
+ useEffect(() => {
518
+ let unregister: () => void;
519
+ if (registerPopup && open) {
520
+ unregister = registerPopup(id);
521
+ }
522
+ return () => {
523
+ unregister?.();
524
+ };
525
+ }, [registerPopup, id, open]);
526
+
527
+ useEffect(() => {
528
+ if (triggerElement && closeOnEscape && open) {
529
+ const onKeyDown = (evt: KeyboardEvent) => {
530
+ if (evt.defaultPrevented) {
531
+ return;
532
+ }
533
+ if (evt.key === "Escape") {
534
+ onClose?.(evt);
535
+ evt.preventDefault();
536
+ evt.stopPropagation();
537
+ }
538
+ };
539
+
540
+ if (registerEscapeCallback) {
541
+ registerEscapeCallback(id, onKeyDown);
542
+ } else {
543
+ document.addEventListener("keydown", onKeyDown);
544
+ }
545
+
546
+ // if popup was opened but nothing was focused inside it we should catch 'Escape' key from trigger as well
547
+ // otherwise it can be caught by navigation panel
548
+ "addEventListener" in triggerElement && triggerElement.addEventListener("keydown", onKeyDown);
549
+
550
+ return () => {
551
+ if (!registerEscapeCallback) {
552
+ document.removeEventListener("keydown", onKeyDown);
553
+ }
554
+
555
+ "removeEventListener" in triggerElement && triggerElement.removeEventListener("keydown", onKeyDown);
556
+ };
557
+ }
558
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
559
+ return () => {};
560
+ }, [closeOnEscape, onClose, open, triggerElement, registerEscapeCallback, id]);
561
+
562
+ const contextValue = useMemo(() => {
563
+ return {
564
+ disabled,
565
+ triggerElement,
566
+ setTriggerElement: composedSetTriggerElement,
567
+ popupElement,
568
+ setPopupElement: composedSetPopupElement,
569
+ open,
570
+ onClose,
571
+ onOpen,
572
+ };
573
+ }, [
574
+ disabled,
575
+ triggerElement,
576
+ composedSetTriggerElement,
577
+ popupElement,
578
+ composedSetPopupElement,
579
+ open,
580
+ onClose,
581
+ onOpen,
582
+ ]);
583
+
584
+ return <Provider value={contextValue}>{children}</Provider>;
585
+ };
586
+
587
+ export const Popover = {
588
+ Root,
589
+ Trigger,
590
+ Portal,
591
+ Content,
592
+ };
593
+
594
+ export {createPopper};
595
+
596
+ export type {Placement, State, Reference, RootProps, TriggerProps, ContentProps};
597
+
598
+ export type PopoverOldProps = Pick<
599
+ RootProps,
600
+ "children" | "open" | "onOpen" | "onClose" | "closeOnEscape" | "disabled"
601
+ > &
602
+ ContentProps &
603
+ Pick<PortalProps, "getPopupContainer"> & {
604
+ trigger?: ReactNode;
605
+ triggerDomElement?: Reference;
606
+ renderInPortal?: boolean;
607
+ };
608
+
609
+ /**
610
+ * @deprecated Use the new `Popover` compound component instead.
611
+ *
612
+ * The `PopoverOld` component is deprecated and will be removed in a future version.
613
+ * Please migrate to the new `Popover` API which provides better composition and flexibility:
614
+ *
615
+ * @example
616
+ * ```tsx
617
+ * // Instead of PopoverOld:
618
+ * <PopoverOld
619
+ * trigger={<button>Click me</button>}
620
+ * open={isOpen}
621
+ * onOpen={() => setIsOpen(true)}
622
+ * onClose={() => setIsOpen(false)}
623
+ * >
624
+ * <div>Popover content</div>
625
+ * </PopoverOld>
626
+ *
627
+ * // Use Popover compound components:
628
+ * <Popover.Root
629
+ * open={isOpen}
630
+ * onOpen={() => setIsOpen(true)}
631
+ * onClose={() => setIsOpen(false)}
632
+ * >
633
+ * <Popover.Trigger>
634
+ * <button>Click me</button>
635
+ * </Popover.Trigger>
636
+ * <Popover.Portal>
637
+ * <Popover.Content>
638
+ * <div>Popover content</div>
639
+ * </Popover.Content>
640
+ * </Popover.Portal>
641
+ * </Popover.Root>
642
+ * ```
643
+ */
644
+ export const PopoverOld = ({
645
+ trigger,
646
+ triggerDomElement,
647
+ children,
648
+ placement,
649
+ open,
650
+ popupStyle,
651
+ popupContainerClassName,
652
+ popupClassName,
653
+ renderInPortal,
654
+ offset = [0, 0],
655
+ disabled = false,
656
+ onMouseLeave,
657
+ renderBackdrop,
658
+ backdropClassName,
659
+ closeOnEscape = true,
660
+ preventOverflowOptions = {},
661
+ flipOptions = {},
662
+ additionalModifiers = [],
663
+ hidePopupWhenTriggerIsHidden = true,
664
+ shrinkable,
665
+ onOpen = _.noop,
666
+ onClose = _.noop,
667
+ supportMobile = false,
668
+ title = "",
669
+ mobilePopupStyle,
670
+ mobilePopupContentClassName,
671
+ getPopupContainer,
672
+ fixStackingContextAfterOpen = false,
673
+ onFirstUpdate,
674
+ transform,
675
+ }: PopoverOldProps) => {
676
+ return (
677
+ <Popover.Root open={open} disabled={disabled} closeOnEscape={closeOnEscape} onOpen={onOpen} onClose={onClose}>
678
+ {triggerDomElement ? (
679
+ <Popover.Trigger reference={triggerDomElement}></Popover.Trigger>
680
+ ) : (
681
+ <Popover.Trigger>{trigger}</Popover.Trigger>
682
+ )}
683
+ <Wrap with={Popover.Portal} if={renderInPortal} getPopupContainer={getPopupContainer}>
684
+ <Popover.Content
685
+ children={children}
686
+ placement={placement}
687
+ popupStyle={popupStyle}
688
+ popupContainerClassName={popupContainerClassName}
689
+ popupClassName={popupClassName}
690
+ offset={offset}
691
+ onMouseLeave={onMouseLeave}
692
+ renderBackdrop={renderBackdrop}
693
+ backdropClassName={backdropClassName}
694
+ preventOverflowOptions={preventOverflowOptions}
695
+ flipOptions={flipOptions}
696
+ additionalModifiers={additionalModifiers}
697
+ hidePopupWhenTriggerIsHidden={hidePopupWhenTriggerIsHidden}
698
+ shrinkable={shrinkable}
699
+ supportMobile={supportMobile}
700
+ title={title}
701
+ mobilePopupStyle={mobilePopupStyle}
702
+ mobilePopupContentClassName={mobilePopupContentClassName}
703
+ fixStackingContextAfterOpen={fixStackingContextAfterOpen}
704
+ onFirstUpdate={onFirstUpdate}
705
+ transform={transform}
706
+ />
707
+ </Wrap>
708
+ </Popover.Root>
709
+ );
710
+ };