@fibery/ui-kit 1.38.0 → 1.38.2

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 (111) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/config/jest/testFrameworkSetup.js +1 -1
  3. package/package.json +23 -22
  4. package/src/abbreviate-name.ts +42 -0
  5. package/src/actions-menu/actions-menu-item.tsx +14 -5
  6. package/src/actions-menu/actions-menu-props.tsx +1 -0
  7. package/src/actions-menu/actions-menu.tsx +4 -1
  8. package/src/actions-menu/context-actions-menu.tsx +22 -0
  9. package/src/animated-height-container.tsx +24 -5
  10. package/src/antd/ant-upload.tsx +1 -1
  11. package/src/antd/input.tsx +4 -0
  12. package/src/avatar.tsx +1 -27
  13. package/src/button/base-button.tsx +2 -0
  14. package/src/button/button.tsx +4 -4
  15. package/src/button/icon-button.tsx +3 -3
  16. package/src/card-container.tsx +3 -2
  17. package/src/checkbox.tsx +17 -0
  18. package/src/collapsible/index.tsx +4 -2
  19. package/src/collapsible-section.tsx +3 -1
  20. package/src/date-picker/autocomplete.tsx +11 -5
  21. package/src/date-picker/date-range-input.tsx +5 -1
  22. package/src/date-picker/date-range-picker-popup-content.tsx +31 -13
  23. package/src/date-picker/single-date-input.tsx +5 -1
  24. package/src/date-picker/styles.ts +20 -0
  25. package/src/design-system.ts +26 -1
  26. package/src/dropdown-menu/index.tsx +11 -1
  27. package/src/emoji-picker/emoji-picker-preview-footer.tsx +4 -0
  28. package/src/emoji-picker/emoji.tsx +4 -3
  29. package/src/emoji-picker/primitives/emoji.tsx +49 -51
  30. package/src/emoji-picker/primitives/footer.tsx +6 -0
  31. package/src/emoji-picker/primitives/search.tsx +8 -0
  32. package/src/emoji-picker/primitives/skin-tone.tsx +12 -2
  33. package/src/fibermoji-placeholder.tsx +2 -29
  34. package/src/file-item-2.tsx +566 -0
  35. package/src/file-item.tsx +196 -162
  36. package/src/header-layout.tsx +11 -0
  37. package/src/html-styles.ts +13 -4
  38. package/src/icons/ast/Back.ts +1 -1
  39. package/src/icons/ast/ExtensionFiles.ts +1 -1
  40. package/src/icons/ast/FileArchive.ts +8 -0
  41. package/src/icons/ast/FileBroken.ts +8 -0
  42. package/src/icons/ast/FileDocument.ts +8 -0
  43. package/src/icons/ast/FileImage.ts +8 -0
  44. package/src/icons/ast/FilePresentation.ts +8 -0
  45. package/src/icons/ast/FileTable.ts +8 -0
  46. package/src/icons/ast/FileUpload.ts +1 -1
  47. package/src/icons/ast/FileVideo.ts +8 -0
  48. package/src/icons/ast/SwitchToMention.ts +8 -0
  49. package/src/icons/ast/SwitchToText.ts +8 -0
  50. package/src/icons/ast/SwitchToWidget.ts +8 -0
  51. package/src/icons/ast/ViewGallery.ts +8 -0
  52. package/src/icons/ast/index.tsx +11 -0
  53. package/src/icons/react/FileArchive.tsx +13 -0
  54. package/src/icons/react/FileBroken.tsx +13 -0
  55. package/src/icons/react/FileDocument.tsx +13 -0
  56. package/src/icons/react/FileImage.tsx +13 -0
  57. package/src/icons/react/FilePresentation.tsx +13 -0
  58. package/src/icons/react/FileTable.tsx +13 -0
  59. package/src/icons/react/FileVideo.tsx +13 -0
  60. package/src/icons/react/SwitchToMention.tsx +13 -0
  61. package/src/icons/react/SwitchToText.tsx +13 -0
  62. package/src/icons/react/SwitchToWidget.tsx +13 -0
  63. package/src/icons/react/ViewGallery.tsx +13 -0
  64. package/src/icons/react/index.tsx +11 -0
  65. package/src/icons/svg/back.svg +3 -1
  66. package/src/icons/svg/extension/files.svg +3 -1
  67. package/src/icons/svg/file-archive.svg +3 -0
  68. package/src/icons/svg/file-broken.svg +3 -0
  69. package/src/icons/svg/file-document.svg +3 -0
  70. package/src/icons/svg/file-image.svg +3 -0
  71. package/src/icons/svg/file-presentation.svg +3 -0
  72. package/src/icons/svg/file-table.svg +3 -0
  73. package/src/icons/svg/file-upload.svg +3 -6
  74. package/src/icons/svg/file-video.svg +3 -0
  75. package/src/icons/svg/switch-to-mention.svg +3 -0
  76. package/src/icons/svg/switch-to-text.svg +4 -0
  77. package/src/icons/svg/switch-to-widget.svg +4 -0
  78. package/src/icons/svg/view-gallery.svg +4 -0
  79. package/src/images-gallery/images-gallery-fall-through-provider.tsx +11 -0
  80. package/src/images-gallery/images-gallery.tsx +13 -10
  81. package/src/images-gallery/zoom.tsx +13 -2
  82. package/src/images-gallery-2/images-gallery-2.tsx +522 -0
  83. package/src/images-gallery-2/slide-buttons.tsx +271 -0
  84. package/src/images-gallery-2/zoom.tsx +35 -0
  85. package/src/kbd.tsx +46 -0
  86. package/src/loading-sausage.tsx +2 -2
  87. package/src/modal-menu/modal-menu-item.tsx +10 -2
  88. package/src/modal-menu/modal-menu-trigger.tsx +19 -2
  89. package/src/notification-dot.tsx +16 -7
  90. package/src/platform-symbols.ts +9 -0
  91. package/src/platform.ts +2 -0
  92. package/src/popover/index.tsx +31 -8
  93. package/src/popover/modifiers.tsx +26 -19
  94. package/src/reactions/reaction-button.tsx +323 -101
  95. package/src/reactions/reaction-picker.tsx +14 -1
  96. package/src/reactions/reactions.tsx +11 -1
  97. package/src/reactions/tootltip.tsx +55 -0
  98. package/src/rich-text-embed-error.tsx +2 -1
  99. package/src/select/components/group-heading.tsx +8 -0
  100. package/src/select/components/menu-list-virtualized.tsx +6 -2
  101. package/src/select/components/option.tsx +4 -0
  102. package/src/select/select-control-settings-context.tsx +11 -2
  103. package/src/select/select-in-popover.tsx +10 -7
  104. package/src/select/styles.ts +21 -1
  105. package/src/tab-nav/tab-nav.tsx +1 -1
  106. package/src/toggle.tsx +8 -0
  107. package/src/tooltip.tsx +28 -19
  108. package/src/type-badge.tsx +5 -2
  109. package/src/unit/unit-with-tooltip.tsx +1 -1
  110. package/src/use-is-phone.tsx +34 -6
  111. package/src/shortcut-badge.tsx +0 -29
@@ -0,0 +1,271 @@
1
+ import {PhotoSwipe} from "photoswipe/lightbox";
2
+ import {useEffect, useMemo, useState} from "react";
3
+ import {css} from "@linaria/core";
4
+ import {IconButton} from "../button/icon-button";
5
+ import Back from "../icons/react/Back";
6
+ import ArrowForward from "../icons/react/ArrowForward";
7
+ import LoupeZoomIn from "../icons/react/LoupeZoomIn";
8
+ import LoupeZoomOut from "../icons/react/LoupeZoomOut";
9
+ import RicheditorOpenLink from "../icons/react/RicheditorOpenLink";
10
+ import Link from "../icons/react/Link";
11
+ import Close from "../icons/react/Close";
12
+ import {colors, createInlineTheme, getThemeColors, space} from "../design-system";
13
+ import {safeAreaInsetRightVar, safeAreaInsetTopVar} from "../mobile-styles";
14
+ import {Button} from "../button/button";
15
+ import {copyToClipboard} from "../copy-to-clipboard";
16
+ import {ToastProvider, useToast} from "../toast/toast-provider";
17
+ import {ToastQueue} from "../toast/toast-queue";
18
+ import {ActionsMenu} from "../actions-menu";
19
+ import {ActionsButtonCompact} from "../button/actions-button-compact";
20
+ import {ThemeProvider} from "../theme-provider";
21
+ import {ThemeMode} from "../theme-settings";
22
+
23
+ const barClassName = css`
24
+ position: absolute;
25
+ top: calc(${space.s20}px + var(${safeAreaInsetTopVar}));
26
+ right: calc(${space.s20}px + var(${safeAreaInsetRightVar}));
27
+
28
+ display: flex;
29
+ flex-direction: row;
30
+ align-items: center;
31
+ justify-content: flex-end; /* align content to the right */
32
+ gap: ${space.s8}px; /* space between children */
33
+ `;
34
+
35
+ const wrapper = css`
36
+ width: 100%;
37
+ height: 100%;
38
+ `;
39
+
40
+ type SlideState = {
41
+ currIndex: number;
42
+ numItems: number;
43
+ src: string;
44
+ actions?: React.ReactNode;
45
+ };
46
+
47
+ export function SlideButtons({
48
+ pswp,
49
+ toastQueue,
50
+ items,
51
+ uiHandlers,
52
+ }: {
53
+ pswp: PhotoSwipe;
54
+ toastQueue: ToastQueue;
55
+ themeMode: ThemeMode;
56
+ items: {
57
+ src: string;
58
+ actions?: React.ReactNode;
59
+ }[];
60
+ uiHandlers: {
61
+ showUI: () => void;
62
+ setForceShowUI: (value: boolean) => void;
63
+ };
64
+ }) {
65
+ const [slideState, setSliceState] = useState<SlideState>(getSlideData(pswp, items));
66
+ useEffect(() => {
67
+ const handler = () => {
68
+ setSliceState(getSlideData(pswp, items));
69
+ };
70
+ pswp.on("slideActivate", handler);
71
+
72
+ return () => {
73
+ pswp.off("slideActivate", handler);
74
+ };
75
+ }, [items, pswp]);
76
+
77
+ const {theme, styles} = useMemo(() => {
78
+ const theme = getThemeColors(colors.brandColors.blue, "dark");
79
+ return {theme, styles: createInlineTheme(theme, emptyArray)};
80
+ }, []);
81
+
82
+ return (
83
+ <ToastProvider toastQueue={toastQueue}>
84
+ <ThemeProvider theme={theme}>
85
+ <div style={styles} className={wrapper}>
86
+ <div className={barClassName}>
87
+ <CopyURLButton pswp={pswp} showUI={uiHandlers.showUI} />
88
+ <ZoomButton pswp={pswp} showUI={uiHandlers.showUI} />
89
+ {slideState.actions ? (
90
+ <SlideActionsMenu actions={slideState.actions} uiHandlers={uiHandlers} />
91
+ ) : (
92
+ <IconButton
93
+ size="medium"
94
+ color="neutral"
95
+ variant="soft"
96
+ onClick={() => {
97
+ window.open(slideState.src, "_blank");
98
+ uiHandlers.showUI();
99
+ }}
100
+ >
101
+ <RicheditorOpenLink />
102
+ </IconButton>
103
+ )}
104
+
105
+ <IconButton
106
+ onClick={() => {
107
+ pswp.close();
108
+ uiHandlers.showUI();
109
+ }}
110
+ size="medium"
111
+ color="neutral"
112
+ variant="soft"
113
+ >
114
+ <Close />
115
+ </IconButton>
116
+ </div>
117
+ <div className="pswp__button--arrow--prev pswp__button--arrow">
118
+ <IconButton
119
+ disabled={slideState.currIndex === 0}
120
+ onClick={() => {
121
+ pswp.prev();
122
+ uiHandlers.showUI();
123
+ }}
124
+ size="medium"
125
+ color="neutral"
126
+ variant="soft"
127
+ >
128
+ <Back />
129
+ </IconButton>
130
+ </div>
131
+ <div className="pswp__button--arrow--next pswp__button--arrow">
132
+ <IconButton
133
+ disabled={slideState.currIndex === slideState.numItems - 1}
134
+ onClick={() => {
135
+ pswp.next();
136
+ uiHandlers.showUI();
137
+ }}
138
+ size="medium"
139
+ color="neutral"
140
+ variant="soft"
141
+ >
142
+ <ArrowForward />
143
+ </IconButton>
144
+ </div>
145
+ </div>
146
+ </ThemeProvider>
147
+ </ToastProvider>
148
+ );
149
+ }
150
+
151
+ function SlideActionsMenu({
152
+ actions,
153
+ uiHandlers,
154
+ }: {
155
+ actions?: React.ReactNode;
156
+ uiHandlers: {showUI: () => void; setForceShowUI: (value: boolean) => void};
157
+ }) {
158
+ const [container, setContainer] = useState<HTMLDivElement | null>();
159
+
160
+ return (
161
+ <div>
162
+ <ActionsMenu
163
+ onOpenChange={(value) => {
164
+ uiHandlers.setForceShowUI(value);
165
+ }}
166
+ contentStyles={css`
167
+ margin-top: ${space.s8}px;
168
+ `}
169
+ container={container}
170
+ trigger={<ActionsButtonCompact size="medium" color="neutral" variant="soft" onClick={uiHandlers.showUI} />}
171
+ >
172
+ {actions}
173
+ </ActionsMenu>
174
+ <div ref={setContainer} />
175
+ </div>
176
+ );
177
+ }
178
+
179
+ function ZoomButton({pswp, showUI}: {pswp: PhotoSwipe; showUI: () => void}) {
180
+ const [zoomed, setZoomed] = useState(false);
181
+ const [isVisible, setIsVisible] = useState(false);
182
+
183
+ useEffect(() => {
184
+ if (!pswp) {
185
+ return undefined;
186
+ }
187
+
188
+ function handleZoomChange() {
189
+ const slide = pswp.currSlide;
190
+ if (!slide) {
191
+ return;
192
+ }
193
+
194
+ setIsVisible(slide.zoomLevels.fit !== 1);
195
+
196
+ const zoomed = slide.currZoomLevel > slide.zoomLevels.fit;
197
+ setZoomed(zoomed);
198
+ }
199
+
200
+ pswp.on("afterInit", handleZoomChange);
201
+ pswp.on("zoomPanUpdate", handleZoomChange);
202
+
203
+ return () => {
204
+ pswp.off("afterInit", handleZoomChange);
205
+ pswp.off("zoomPanUpdate", handleZoomChange);
206
+ };
207
+ }, [pswp]);
208
+
209
+ if (!isVisible) {
210
+ return null;
211
+ }
212
+
213
+ return (
214
+ <IconButton
215
+ onClick={() => {
216
+ setZoomed(!zoomed);
217
+ pswp.toggleZoom();
218
+ showUI();
219
+ }}
220
+ size="medium"
221
+ color="neutral"
222
+ variant="soft"
223
+ >
224
+ {zoomed ? <LoupeZoomOut /> : <LoupeZoomIn />}
225
+ </IconButton>
226
+ );
227
+ }
228
+
229
+ function CopyURLButton({pswp, showUI}: {pswp: PhotoSwipe; showUI: () => void}) {
230
+ const toast = useToast();
231
+ return (
232
+ <Button
233
+ onClick={() => {
234
+ showUI();
235
+ if (!pswp?.currSlide?.data.src) {
236
+ toast.error({title: "Unable to copy."});
237
+ return;
238
+ }
239
+
240
+ const src = new URL(pswp.currSlide.data.src, `${window.location}`).href;
241
+ if (src) {
242
+ copyToClipboard(src)
243
+ .then(() => {
244
+ toast.success({title: "Copied!"});
245
+ })
246
+ .catch(() => {
247
+ toast.error({title: "Unable to copy."});
248
+ });
249
+ }
250
+ }}
251
+ size="medium"
252
+ variant="soft"
253
+ color="neutral"
254
+ iconStart={<Link />}
255
+ type="button"
256
+ >
257
+ Copy file URL
258
+ </Button>
259
+ );
260
+ }
261
+
262
+ const emptyArray: string[] = [];
263
+
264
+ function getSlideData(pswp: PhotoSwipe, items: {src: string; actions?: React.ReactNode}[]) {
265
+ return {
266
+ currIndex: pswp.currIndex,
267
+ numItems: pswp.getNumItems(),
268
+ src: items[pswp.currIndex].src,
269
+ actions: items[pswp.currIndex].actions,
270
+ };
271
+ }
@@ -0,0 +1,35 @@
1
+ import React, {ReactNode, useCallback} from "react";
2
+ import "react-medium-image-zoom/dist/styles.css";
3
+
4
+ type Props = {
5
+ zoomed: boolean;
6
+ onZoomedChange: (value: boolean) => void;
7
+ children: ReactNode;
8
+ className?: string;
9
+ openOnClick?: boolean;
10
+ zoomImg?: {src: string; alt?: string};
11
+ actions?: ReactNode;
12
+ };
13
+
14
+ export const ZoomV2 = (props: Props) => {
15
+ const {zoomed, onZoomedChange, children, className, openOnClick} = props;
16
+ const onClick = useCallback(() => {
17
+ if (zoomed) {
18
+ onZoomedChange(false);
19
+ } else if (openOnClick) {
20
+ onZoomedChange(true);
21
+ }
22
+ }, [zoomed, onZoomedChange, openOnClick]);
23
+
24
+ const onDoubleClick = useCallback(() => {
25
+ if (!zoomed) {
26
+ onZoomedChange(true);
27
+ }
28
+ }, [zoomed, onZoomedChange]);
29
+
30
+ return (
31
+ <div className={className} onClick={onClick} onDoubleClick={openOnClick ? undefined : onDoubleClick}>
32
+ {children}
33
+ </div>
34
+ );
35
+ };
package/src/kbd.tsx ADDED
@@ -0,0 +1,46 @@
1
+ import {css, cx} from "@linaria/core";
2
+ import {border, lineHeight, space, textStyles, themeVars} from "./design-system";
3
+ import {useIsSupportHardwareKeyboard} from "./use-is-support-hardware-keyboard";
4
+
5
+ export const kbdCss = css`
6
+ ${{
7
+ ...textStyles.small,
8
+ ...textStyles.numeric.tabularOn,
9
+ }}
10
+ display: inline-flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ min-width: ${space.s18}px;
14
+ height: ${space.s18}px;
15
+ padding: ${space.s2}px;
16
+ border-radius: ${border.radius4}px;
17
+ border: ${themeVars.shortcutBorder};
18
+ color: ${themeVars.shortcutTextColor};
19
+ line-height: ${lineHeight.nowrap};
20
+ white-space: nowrap;
21
+ user-select: none;
22
+ `;
23
+
24
+ export const Kbd = ({className, ...props}: React.ComponentProps<"kbd">): JSX.Element | null => {
25
+ const isSupportHardwareKeyboard = useIsSupportHardwareKeyboard();
26
+ if (!isSupportHardwareKeyboard) {
27
+ return null;
28
+ }
29
+ return <kbd className={cx(kbdCss, className)} {...props} />;
30
+ };
31
+
32
+ export const KbdGroup = ({className, ...props}: React.ComponentProps<"kbd">) => {
33
+ return (
34
+ <kbd
35
+ className={cx(
36
+ className,
37
+ css`
38
+ display: flex;
39
+ align-items: center;
40
+ gap: ${space.s2}px;
41
+ `
42
+ )}
43
+ {...props}
44
+ />
45
+ );
46
+ };
@@ -2,7 +2,7 @@ import {css} from "@linaria/core";
2
2
  import {styled} from "@linaria/react";
3
3
  import cn from "classnames";
4
4
  import _ from "lodash";
5
- import {memo, useRef} from "react";
5
+ import {ComponentType, memo, ReactNode, useRef} from "react";
6
6
  import {space, themeVars} from "./design-system";
7
7
 
8
8
  const getRandomWithSeed = _.memoize((seed: number) => {
@@ -141,7 +141,7 @@ type SausageListProps = {
141
141
  amount?: number;
142
142
  minAmount?: number;
143
143
  maxAmount?: number;
144
- WrapperComponent?: ({children}: {children: JSX.Element}) => JSX.Element | null;
144
+ WrapperComponent?: ComponentType<{children: ReactNode}> | null;
145
145
  } & Omit<LoadingSausageProps, "index">;
146
146
  export const LoadingSausageList = memo<SausageListProps>(function LoadingSausageList({
147
147
  amount,
@@ -4,11 +4,12 @@ import {space, textStyles} from "../design-system";
4
4
  import {useModalMenu} from "./contexts/modal-menu-context";
5
5
 
6
6
  interface ModalMenuItemProps extends Omit<ComponentProps<"div">, "onSelect"> {
7
+ disabled?: boolean;
7
8
  onSelect?: (event: Event) => void;
8
9
  children?: ReactNode;
9
10
  }
10
11
 
11
- export const ModalMenuItem = ({children, onSelect, ...otherProps}: ModalMenuItemProps) => {
12
+ export const ModalMenuItem = ({children, disabled, onSelect, ...otherProps}: ModalMenuItemProps) => {
12
13
  const {onOpenChange} = useModalMenu();
13
14
 
14
15
  return (
@@ -20,10 +21,17 @@ export const ModalMenuItem = ({children, onSelect, ...otherProps}: ModalMenuItem
20
21
  css`
21
22
  ${textStyles.big}
22
23
  padding: ${space.s10}px ${space.s16}px ${space.s10}px ${space.s16}px;
23
- `
24
+ `,
25
+ disabled &&
26
+ css`
27
+ opacity: 0.4;
28
+ `
24
29
  )}
25
30
  onClick={(event) => {
26
31
  otherProps.onClick?.(event);
32
+ if (disabled) {
33
+ return;
34
+ }
27
35
  const selectionEvent = new CustomEvent("modal-menu-select", {bubbles: true, cancelable: true});
28
36
  onSelect?.(selectionEvent);
29
37
  if (!selectionEvent.defaultPrevented) {
@@ -4,11 +4,20 @@ import {useModalMenu} from "./contexts/modal-menu-context";
4
4
 
5
5
  interface ModalMenuTriggerProps extends ComponentProps<"div"> {
6
6
  asChild?: boolean;
7
+ triggerEvent?: "click" | "context-menu";
7
8
  children: ReactHTMLElement<HTMLDivElement> | ReactNode;
8
9
  }
9
10
 
10
- export const ModalMenuTrigger = ({asChild, children, onClick, ...otherProps}: ModalMenuTriggerProps) => {
11
+ export const ModalMenuTrigger = ({
12
+ asChild,
13
+ triggerEvent,
14
+ children,
15
+ onClick,
16
+ onContextMenu,
17
+ ...otherProps
18
+ }: ModalMenuTriggerProps) => {
11
19
  const {open, onOpenChange} = useModalMenu();
20
+
12
21
  if (asChild) {
13
22
  const className = (children as ReactHTMLElement<HTMLDivElement>)?.props?.className;
14
23
  return cloneElement(children as ReactHTMLElement<HTMLDivElement>, {
@@ -16,7 +25,15 @@ export const ModalMenuTrigger = ({asChild, children, onClick, ...otherProps}: Mo
16
25
  className: cx(className, otherProps.className),
17
26
  onClick: (event) => {
18
27
  onClick?.(event);
19
- onOpenChange(!open);
28
+ if (triggerEvent === "click") {
29
+ onOpenChange(!open);
30
+ }
31
+ },
32
+ onContextMenu: (event) => {
33
+ onContextMenu?.(event);
34
+ if (triggerEvent === "context-menu") {
35
+ onOpenChange(!open);
36
+ }
20
37
  },
21
38
  });
22
39
  }
@@ -10,14 +10,23 @@ const activityIndicatorStyle = css`
10
10
  }}
11
11
  `;
12
12
 
13
- export const NotificationDot = ({big = true, color = themeVars.active}: {big: boolean; color: string}) => {
13
+ type PropsType = {
14
+ big?: boolean;
15
+ color?: string;
16
+ style?: Record<string, string | number | null>;
17
+ };
18
+
19
+ export const NotificationDot = ({big = true, color = themeVars.active, style}: PropsType) => {
14
20
  const size = big ? space.s6 : space.s4;
15
21
  const marginTop = big ? -6 : -1;
16
22
  const marginRight = big ? -1 : 0;
17
- return (
18
- <div
19
- className={activityIndicatorStyle}
20
- style={{backgroundColor: color, width: size, height: size, marginTop, marginRight}}
21
- />
22
- );
23
+ const styleObject = {
24
+ backgroundColor: color,
25
+ width: size,
26
+ height: size,
27
+ marginTop,
28
+ marginRight,
29
+ ...style,
30
+ };
31
+ return <div className={activityIndicatorStyle} style={styleObject} />;
23
32
  };
@@ -0,0 +1,9 @@
1
+ import {isMac} from "./platform";
2
+
3
+ export const ctrlSymbol = isMac ? "⌘" : "Ctrl";
4
+ export const explicitCtrlSymbol = isMac ? "⌃" : "Ctrl";
5
+ export const winSymbol = isMac ? "⌘" : "Win";
6
+ export const shiftSymbol = isMac ? "⇧" : "⇧";
7
+ export const altSymbol = isMac ? "⌥" : "Alt";
8
+ export const deleteSymbol = isMac ? "⌫" : "Delete";
9
+ export const enterSymbol = isMac ? "↵" : "↵";
package/src/platform.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import {UAParser} from "ua-parser-js";
2
2
 
3
3
  export const isMac = navigator.platform.startsWith("Mac");
4
+ export const isWindows = navigator.platform.startsWith("Win");
5
+ export const isLinux = navigator.platform.startsWith("Linux");
4
6
  export const isMobile = new UAParser(navigator.userAgent).getDevice().type === "mobile";
@@ -36,7 +36,7 @@ import {useTheme} from "../theme-provider";
36
36
  import {$TSFixMe} from "../tsfixme";
37
37
  import {useIsPhone} from "../use-is-phone";
38
38
  import {usePopupModifiers} from "./popup-modifiers-context";
39
- import {getMaxSize, observeWidth} from "./modifiers";
39
+ import {applyMaxSize, makeMaxSizeModifier, observeWidth} from "./modifiers";
40
40
  import {isElementReference, Reference} from "./reference";
41
41
  import {basicPopupClassName, basicPopupContainerClassName, hidePopupWhenTriggerIsHiddenClassName} from "./styles";
42
42
  import {MobilePopupProvider, useMobilePopup} from "./mobile-popup-context";
@@ -249,13 +249,13 @@ const Trigger = ({asChild = false, reference, children}: TriggerProps) => {
249
249
  }
250
250
  }, [reference, setTriggerElement]);
251
251
 
252
- if (reference) {
252
+ // Reference is passed
253
+ if (reference !== undefined) {
253
254
  return null;
254
255
  }
255
256
 
256
257
  if (asChild && isValidElement(children)) {
257
258
  return cloneElement(children, {
258
- // @ts-expect-error: React.cloneElement typing does not include `ref`, but it's valid at runtime
259
259
  ref: setTriggerElement,
260
260
  "aria-expanded": open,
261
261
  "aria-haspopup": true,
@@ -307,6 +307,8 @@ type ContentProps = {
307
307
 
308
308
  export type ContentRef = {update: (() => Promise<unknown>) | null};
309
309
 
310
+ const padding = space.s8;
311
+
310
312
  const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInner(
311
313
  {
312
314
  children,
@@ -341,23 +343,44 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
341
343
 
342
344
  // We get infinite popup renders if this is not in a separate memo
343
345
  const shrinkableModifiers = useMemo(() => {
344
- return shrinkable ? getMaxSize(preventOverflowOptions) : [];
346
+ return shrinkable
347
+ ? [
348
+ makeMaxSizeModifier({
349
+ padding,
350
+ ...preventOverflowOptions,
351
+ }),
352
+ applyMaxSize,
353
+ ]
354
+ : [];
345
355
  }, [preventOverflowOptions, shrinkable]);
346
356
 
347
357
  const modifiers = useMemo(() => {
348
358
  return _.compact([
349
- {name: "flip", options: {...popupModifiers.flipOptions, ...flipOptions}},
359
+ {
360
+ name: "flip",
361
+ options: {
362
+ padding,
363
+ ...popupModifiers.flipOptions,
364
+ ...flipOptions,
365
+ },
366
+ },
350
367
  {
351
368
  name: "preventOverflow",
352
369
  options: {
353
370
  tether: false,
354
- padding: space.s6,
371
+ padding,
355
372
  ...popupModifiers.preventOverflowOptions,
356
373
  ...preventOverflowOptions,
357
374
  },
358
375
  },
359
- {name: "offset", options: {offset: offset}},
360
- !transform && {name: "computeStyles", options: {gpuAcceleration: false}},
376
+ {
377
+ name: "offset",
378
+ options: {offset},
379
+ },
380
+ !transform && {
381
+ name: "computeStyles",
382
+ options: {gpuAcceleration: false},
383
+ },
361
384
  observeWidth,
362
385
  ...shrinkableModifiers,
363
386
  ...additionalModifiers,
@@ -1,30 +1,37 @@
1
1
  import {Options as PreventOverflowOptions} from "@popperjs/core/lib/modifiers/preventOverflow";
2
- import {$TSFixMe} from "../tsfixme";
3
- import {Modifier, ModifierArguments} from "@popperjs/core";
2
+ import {Modifier, ModifierArguments, Placement, Boundary, RootBoundary, Padding, Context} from "@popperjs/core";
4
3
  import maxSize from "popper-max-size-modifier";
5
4
  import {getResizeListener} from "@fibery/helpers/utils/resize-listener";
6
5
  import {isElementReference} from "./reference";
7
6
  import _ from "lodash";
8
7
 
9
- type PopupModifier = Modifier<$TSFixMe, $TSFixMe>;
8
+ type MaxSizeOptions = {
9
+ placement: Placement;
10
+ boundary: Boundary;
11
+ rootBoundary: RootBoundary;
12
+ elementContext: Context;
13
+ altBoundary: boolean;
14
+ padding: Padding;
15
+ };
10
16
 
11
- export const getMaxSize = (overflowOptions: Partial<PreventOverflowOptions>): PopupModifier[] => [
12
- {
17
+ export const makeMaxSizeModifier = (options: Partial<PreventOverflowOptions>): Modifier<"maxSize", MaxSizeOptions> => {
18
+ return {
13
19
  ...maxSize,
14
- options: overflowOptions,
15
- },
16
- {
17
- name: "applyMaxSize",
18
- enabled: true,
19
- phase: "beforeWrite",
20
- requires: ["maxSize"],
21
- fn({state}) {
22
- const {width, height} = state.modifiersData.maxSize;
23
- state.styles.popper.maxWidth = `${width}px`;
24
- state.styles.popper.maxHeight = `${height}px`;
25
- },
20
+ options,
21
+ };
22
+ };
23
+
24
+ export const applyMaxSize: Modifier<"applyMaxSize", Record<string, never>> = {
25
+ name: "applyMaxSize",
26
+ enabled: true,
27
+ phase: "beforeWrite",
28
+ requires: ["maxSize"],
29
+ fn({state}) {
30
+ const {width, height} = state.modifiersData.maxSize;
31
+ state.styles.popper.maxWidth = `${width}px`;
32
+ state.styles.popper.maxHeight = `${height}px`;
26
33
  },
27
- ];
34
+ };
28
35
 
29
36
  const observeWidthEffect: (arg0: ModifierArguments<Record<string, unknown>>) => void = ({state, instance}) => {
30
37
  const {popper, reference} = state.elements;
@@ -54,7 +61,7 @@ const observeWidthEffect: (arg0: ModifierArguments<Record<string, unknown>>) =>
54
61
  };
55
62
  };
56
63
 
57
- export const observeWidth: PopupModifier = {
64
+ export const observeWidth: Modifier<"observeWidth", Record<string, never>> = {
58
65
  name: "observeWidth",
59
66
  enabled: true,
60
67
  phase: "main",