@fibery/ui-kit 1.40.3 → 1.41.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.
- package/CHANGELOG.md +14 -0
- package/eslint.config.mjs +16 -0
- package/package.json +11 -13
- package/src/a11y-color.test.ts +162 -0
- package/src/a11y-color.ts +13 -20
- package/src/actions-menu/actions-menu-item.tsx +9 -12
- package/src/actions-menu/context-actions-menu.tsx +8 -3
- package/src/ai/model.tsx +0 -1
- package/src/ai/temperature.tsx +0 -1
- package/src/animated-height-container.tsx +3 -3
- package/src/antd/ant-modal.tsx +10 -5
- package/src/antd/index.tsx +3 -0
- package/src/antd/input.tsx +0 -1
- package/src/antd/styles.ts +15 -15
- package/src/app-icon-with-fallback.tsx +6 -6
- package/src/app-icon-wrapper.tsx +2 -3
- package/src/app-icon.tsx +4 -4
- package/src/avatar.tsx +3 -3
- package/src/button/base-button.tsx +0 -1
- package/src/button/button.tsx +1 -0
- package/src/button/make-button-colors.ts +5 -13
- package/src/checkbox.tsx +2 -2
- package/src/collapsible-section.tsx +11 -1
- package/src/color-picker/ColorPickerOrLoader.js +2 -2
- package/src/color-utils.test.ts +307 -0
- package/src/color-utils.ts +215 -0
- package/src/command-menu/index.tsx +1 -0
- package/src/comment.tsx +3 -2
- package/src/context-menu/index.tsx +12 -7
- package/src/date-picker/contexts.ts +2 -2
- package/src/date-picker/date-range-picker.tsx +3 -4
- package/src/date-picker/single-date-picker.tsx +147 -119
- package/src/date-picker/types.ts +4 -3
- package/src/day-select/iso-week-day-select.tsx +2 -2
- package/src/day-select/week-day-select.tsx +2 -2
- package/src/delayed.tsx +0 -1
- package/src/design-system/alpha.test.ts +59 -0
- package/src/design-system/alpha.ts +41 -0
- package/src/design-system/animation.ts +10 -0
- package/src/design-system/colors-css.test.ts +39 -0
- package/src/design-system/colors-css.ts +25 -0
- package/src/design-system/colors-js.test.ts +232 -0
- package/src/design-system/colors-js.ts +107 -0
- package/src/design-system/colors.test.ts +74 -0
- package/src/design-system/colors.ts +759 -0
- package/src/design-system/date.ts +3 -0
- package/src/design-system/layout.ts +106 -0
- package/src/design-system/theme.test.ts +94 -0
- package/src/design-system/theme.ts +93 -0
- package/src/design-system/typography.ts +179 -0
- package/src/design-system/vars.test.ts +1679 -0
- package/src/design-system/vars.ts +52 -0
- package/src/design-system.test.ts +45 -22
- package/src/design-system.ts +25 -1339
- package/src/dropdown-menu/index.tsx +21 -16
- package/src/emoji-picker/app-icon-picker.tsx +5 -5
- package/src/emoji-picker/emoji-picker-content-with-color.tsx +4 -4
- package/src/emoji-picker/icon-emoji-picker.tsx +3 -3
- package/src/favorites-icon.tsx +1 -1
- package/src/fibermoji-placeholder.tsx +3 -3
- package/src/field-container.tsx +11 -2
- package/src/file-item/file-icon.tsx +169 -0
- package/src/file-item/file-menu-items.tsx +68 -0
- package/src/file-item/file-preview-actions.tsx +38 -0
- package/src/file-item/file-title.tsx +48 -0
- package/src/file-item/types.ts +27 -0
- package/src/file-item/use-register-in-image-gallery.tsx +70 -0
- package/src/file-item-2.tsx +32 -348
- package/src/file-item.tsx +6 -3
- package/src/hue-shift.test.ts +91 -0
- package/src/icons/ast/ChatBubble.ts +8 -0
- package/src/icons/ast/ChatFloat.ts +8 -0
- package/src/icons/ast/ChatSidebar.ts +8 -0
- package/src/icons/ast/FileCounter.ts +8 -0
- package/src/icons/ast/FileMultiple.ts +8 -0
- package/src/icons/ast/FileOther.ts +8 -0
- package/src/icons/ast/NoBorder.ts +8 -0
- package/src/icons/ast/NoFill.ts +8 -0
- package/src/icons/ast/ValueEdit.ts +8 -0
- package/src/icons/ast/index.tsx +9 -0
- package/src/icons/react/ChatBubble.tsx +13 -0
- package/src/icons/react/ChatFloat.tsx +13 -0
- package/src/icons/react/ChatSidebar.tsx +13 -0
- package/src/icons/react/FileCounter.tsx +13 -0
- package/src/icons/react/FileMultiple.tsx +13 -0
- package/src/icons/react/FileOther.tsx +13 -0
- package/src/icons/react/NoBorder.tsx +13 -0
- package/src/icons/react/NoFill.tsx +13 -0
- package/src/icons/react/ValueEdit.tsx +13 -0
- package/src/icons/react/index.tsx +9 -0
- package/src/icons/svg/chat-bubble.svg +4 -0
- package/src/icons/svg/chat-float.svg +4 -0
- package/src/icons/svg/chat-sidebar.svg +4 -0
- package/src/icons/svg/file-counter.svg +3 -0
- package/src/icons/svg/file-multiple.svg +3 -0
- package/src/icons/svg/file-other.svg +3 -0
- package/src/icons/svg/no-border.svg +17 -0
- package/src/icons/svg/no-fill.svg +4 -0
- package/src/icons/svg/value-edit.svg +3 -0
- package/src/images-gallery/images-gallery.tsx +8 -6
- package/src/images-gallery/slide-buttons.tsx +4 -11
- package/src/is-iOS.ts +0 -1
- package/src/is-in-popup.ts +2 -1
- package/src/lists/actions-menu-row-surface.tsx +7 -7
- package/src/loading-sausage.tsx +2 -2
- package/src/media-query-utils.ts +1 -2
- package/src/mobile-keyboard-aware-popup.tsx +6 -5
- package/src/modal-menu/modal-menu-content.tsx +2 -2
- package/src/number-input/decimal.js +9 -7
- package/src/number-input/{number-inline-input-with-autosize.tsx → number-input-inline-with-autosize.tsx} +20 -41
- package/src/number-input/number-input-inline.tsx +118 -0
- package/src/number-input/{index.test.js → number-input.test.tsx} +38 -43
- package/src/number-input/number-input.tsx +63 -121
- package/src/number-input/types.ts +19 -0
- package/src/number-input/utils.ts +61 -0
- package/src/online-users.tsx +2 -2
- package/src/palette-generator.test.ts +309 -0
- package/src/palette-generator.ts +160 -0
- package/src/palettes/_.ts +72 -0
- package/src/palettes/slate-arch.colors-snapshot.test.ts +1689 -0
- package/src/palettes/slate-arch.ts +188 -0
- package/src/palettes/slate-user.colors-snapshot.test.ts +1689 -0
- package/src/palettes/slate-user.ts +187 -0
- package/src/palettes/warm-arch.colors-snapshot.test.ts +1689 -0
- package/src/palettes/warm-arch.ts +222 -0
- package/src/palettes/warm-user.colors-snapshot.test.ts +1689 -0
- package/src/palettes/warm-user.ts +222 -0
- package/src/platform.ts +0 -3
- package/src/popover/get-element-ref.ts +28 -0
- package/src/popover/index.tsx +236 -339
- package/src/popover/{mobile-popup-context.tsx → mobile-popover-context.tsx} +7 -7
- package/src/popover/mobile-popover.tsx +169 -0
- package/src/popover/modifiers.tsx +2 -2
- package/src/popover/popup-stack-context.tsx +8 -9
- package/src/progress.tsx +2 -2
- package/src/reactions/reaction-button.tsx +12 -6
- package/src/root-theme-provider.test.tsx +411 -0
- package/src/scale-generator.ts +356 -0
- package/src/select/components/menu-list-virtualized.tsx +12 -25
- package/src/select/components/menu.tsx +12 -2
- package/src/select/index.tsx +6 -5
- package/src/select/select.tsx +38 -39
- package/src/select/styles.ts +0 -1
- package/src/select/util.ts +1 -1
- package/src/static-palettes.ts +356 -0
- package/src/thematic-color-picker.tsx +266 -0
- package/src/thematic-constants.tsx +27 -0
- package/src/thematic-controls.tsx +144 -0
- package/src/thematic-scales.tsx +122 -0
- package/src/thematic-state.ts +370 -0
- package/src/thematic.tsx +386 -0
- package/src/theme-provider.test.tsx +820 -0
- package/src/theme-provider.tsx +158 -92
- package/src/theme-settings.ts +67 -12
- package/src/theme-styles.ts +58 -6
- package/src/toast/toast-action.tsx +1 -1
- package/src/toast/toast.tsx +1 -2
- package/src/toggle-on-off.tsx +2 -2
- package/src/toggle.tsx +7 -8
- package/src/tooltip.tsx +14 -10
- package/src/type-badge.tsx +7 -14
- package/src/unit/styles.ts +2 -25
- package/src/unit/unit-with-tooltip.tsx +3 -2
- package/src/use-is-phone.tsx +7 -2
- package/src/use-long-press.tsx +2 -2
- package/src/use-on-screen-keyboard-data.tsx +2 -2
- package/src/with-data.tsx +4 -3
- package/src/workflow-progress-icon.tsx +2 -2
- package/.eslintignore +0 -3
- package/.eslintrc +0 -14
- package/src/__mocks__/createInlineTheme.js +0 -3
- package/src/create-inline-theme.ts +0 -66
- package/src/number-input/index.js +0 -191
- package/src/palette.ts +0 -237
package/src/popover/index.tsx
CHANGED
|
@@ -7,7 +7,8 @@ import {createPopper, Modifier, Placement, State} from "@popperjs/core";
|
|
|
7
7
|
import {Options as FlipOptions} from "@popperjs/core/lib/modifiers/flip";
|
|
8
8
|
import {Options as PreventOverflowOptions} from "@popperjs/core/lib/modifiers/preventOverflow";
|
|
9
9
|
import cx from "classnames";
|
|
10
|
-
import
|
|
10
|
+
import compact from "lodash/compact";
|
|
11
|
+
import noop from "lodash/noop";
|
|
11
12
|
import {
|
|
12
13
|
cloneElement,
|
|
13
14
|
CSSProperties,
|
|
@@ -19,34 +20,141 @@ import {
|
|
|
19
20
|
useId,
|
|
20
21
|
useImperativeHandle,
|
|
21
22
|
useMemo,
|
|
23
|
+
useRef,
|
|
22
24
|
} from "react";
|
|
23
|
-
import {
|
|
24
|
-
safeAreaInsetBottomVar,
|
|
25
|
-
safeAreaInsetLeftVar,
|
|
26
|
-
safeAreaInsetRightVar,
|
|
27
|
-
safeAreaInsetTopVar,
|
|
28
|
-
} from "../mobile-styles";
|
|
29
25
|
import {createPortal} from "react-dom";
|
|
30
26
|
import {usePopper} from "react-popper";
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import CloseIcon from "../icons/react/Close";
|
|
34
|
-
import ArrowLeftIcon from "../icons/react/ArrowLeft";
|
|
35
|
-
import {useTheme} from "../theme-provider";
|
|
27
|
+
import {space} from "../design-system";
|
|
28
|
+
import {ThemeProvider} from "../theme-provider";
|
|
36
29
|
import {$TSFixMe} from "../tsfixme";
|
|
37
30
|
import {useIsPhone} from "../use-is-phone";
|
|
38
31
|
import {usePopupModifiers} from "./popup-modifiers-context";
|
|
39
32
|
import {applyMaxSize, makeMaxSizeModifier, observeWidth} from "./modifiers";
|
|
40
33
|
import {isElementReference, Reference} from "./reference";
|
|
41
34
|
import {basicPopupClassName, basicPopupContainerClassName, hidePopupWhenTriggerIsHiddenClassName} from "./styles";
|
|
42
|
-
import {
|
|
35
|
+
import {MobilePopoverProvider} from "./mobile-popover-context";
|
|
43
36
|
import {usePopupStackContext} from "./popup-stack-context";
|
|
44
|
-
import {useComposedRefs} from "@fibery/react/src/use-composed-refs";
|
|
37
|
+
import {composeRefs, useComposedRefs} from "@fibery/react/src/use-composed-refs";
|
|
45
38
|
import {createContext} from "@fibery/react/src/create-context";
|
|
46
39
|
import {Wrap} from "@fibery/react/src/wrap";
|
|
47
40
|
import {composeEventHandlers} from "@fibery/react/src/compose-event-handlers";
|
|
41
|
+
import {getElementRef} from "./get-element-ref";
|
|
42
|
+
import {MobilePopover} from "./mobile-popover";
|
|
43
|
+
import {FocusScope, FocusScopeProps} from "@radix-ui/react-focus-scope";
|
|
44
|
+
|
|
45
|
+
type PopoverContextValue = {
|
|
46
|
+
open: boolean;
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
onOpen?: () => unknown;
|
|
49
|
+
onClose?: (event: Event) => unknown;
|
|
50
|
+
triggerElement: Reference;
|
|
51
|
+
setTriggerElement: (value: Reference) => void;
|
|
52
|
+
popupElement: HTMLDivElement | null;
|
|
53
|
+
setPopupElement: (value: HTMLDivElement) => void;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const [PopoverProvider, usePopoverContext] = createContext<PopoverContextValue>("PopoverContext");
|
|
57
|
+
|
|
58
|
+
type RootProps = {
|
|
59
|
+
children: ReactNode;
|
|
60
|
+
open: boolean;
|
|
61
|
+
disabled?: boolean;
|
|
62
|
+
onOpen?: () => unknown;
|
|
63
|
+
onClose?: (event: Event) => unknown;
|
|
64
|
+
// For legacy popup component support
|
|
65
|
+
triggerElement?: Reference;
|
|
66
|
+
setTriggerElement?: (value: Reference) => void;
|
|
67
|
+
popupElement?: HTMLDivElement | null;
|
|
68
|
+
setPopupElement?: (value: HTMLDivElement | null) => void;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const Root = ({children, open, disabled = false, onOpen, onClose, ...rest}: RootProps) => {
|
|
72
|
+
const id = useId();
|
|
73
|
+
const {registerEscapeCallback, registerPopup} = usePopupStackContext();
|
|
74
|
+
|
|
75
|
+
const [triggerElement, setTriggerElement] = useControllableState<Reference>({
|
|
76
|
+
value: rest.triggerElement,
|
|
77
|
+
onChange: rest.setTriggerElement,
|
|
78
|
+
});
|
|
79
|
+
const [popupElement, setPopupElement] = useControllableState<HTMLDivElement | null>({
|
|
80
|
+
value: rest.popupElement,
|
|
81
|
+
onChange: rest.setPopupElement,
|
|
82
|
+
});
|
|
48
83
|
|
|
49
|
-
|
|
84
|
+
const composedSetTriggerElement = useComposedRefs(setTriggerElement, rest.setTriggerElement);
|
|
85
|
+
const composedSetPopupElement = useComposedRefs(setPopupElement, rest.setPopupElement);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
let unregister: () => void;
|
|
89
|
+
if (registerPopup && open) {
|
|
90
|
+
unregister = registerPopup(id);
|
|
91
|
+
}
|
|
92
|
+
return () => {
|
|
93
|
+
unregister?.();
|
|
94
|
+
};
|
|
95
|
+
}, [registerPopup, id, open]);
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (triggerElement && open) {
|
|
99
|
+
// Note: We don't check defaultPrevented because react-select and similar
|
|
100
|
+
// components call preventDefault() on all keydown events. We also don't
|
|
101
|
+
// expose onEscapeKeyDown prop to let consumers prevent default close behavior.
|
|
102
|
+
const onKeyDown = (evt: KeyboardEvent) => {
|
|
103
|
+
if (evt.key === "Escape") {
|
|
104
|
+
onClose?.(evt);
|
|
105
|
+
if (isElementReference(triggerElement)) {
|
|
106
|
+
triggerElement.focus();
|
|
107
|
+
}
|
|
108
|
+
evt.preventDefault();
|
|
109
|
+
evt.stopPropagation();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (registerEscapeCallback) {
|
|
114
|
+
registerEscapeCallback(id, onKeyDown);
|
|
115
|
+
} else {
|
|
116
|
+
document.addEventListener("keydown", onKeyDown);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// if popup was opened but nothing was focused inside it we should catch 'Escape' key from trigger as well
|
|
120
|
+
// otherwise it can be caught by navigation panel
|
|
121
|
+
"addEventListener" in triggerElement && triggerElement.addEventListener("keydown", onKeyDown);
|
|
122
|
+
|
|
123
|
+
return () => {
|
|
124
|
+
if (!registerEscapeCallback) {
|
|
125
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
"removeEventListener" in triggerElement && triggerElement.removeEventListener("keydown", onKeyDown);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return () => {};
|
|
132
|
+
}, [onClose, open, triggerElement, registerEscapeCallback, id]);
|
|
133
|
+
|
|
134
|
+
const contextValue = useMemo(() => {
|
|
135
|
+
return {
|
|
136
|
+
disabled,
|
|
137
|
+
triggerElement,
|
|
138
|
+
setTriggerElement: composedSetTriggerElement,
|
|
139
|
+
popupElement,
|
|
140
|
+
setPopupElement: composedSetPopupElement,
|
|
141
|
+
open,
|
|
142
|
+
onClose,
|
|
143
|
+
onOpen,
|
|
144
|
+
};
|
|
145
|
+
}, [
|
|
146
|
+
disabled,
|
|
147
|
+
triggerElement,
|
|
148
|
+
composedSetTriggerElement,
|
|
149
|
+
popupElement,
|
|
150
|
+
composedSetPopupElement,
|
|
151
|
+
open,
|
|
152
|
+
onClose,
|
|
153
|
+
onOpen,
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
return <PopoverProvider value={contextValue}>{children}</PopoverProvider>;
|
|
157
|
+
};
|
|
50
158
|
|
|
51
159
|
const Backdrop = ({className}: {className?: string}) => {
|
|
52
160
|
return (
|
|
@@ -65,187 +173,55 @@ const Backdrop = ({className}: {className?: string}) => {
|
|
|
65
173
|
);
|
|
66
174
|
};
|
|
67
175
|
|
|
68
|
-
const mobileAnimationClassName = css`
|
|
69
|
-
transition: transform ${transition}, opacity ${transition};
|
|
70
|
-
transform: translateY(0);
|
|
71
|
-
opacity: 1;
|
|
72
|
-
|
|
73
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
74
|
-
@starting-style {
|
|
75
|
-
transform: translateY(20%);
|
|
76
|
-
opacity: 0;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
`;
|
|
80
|
-
|
|
81
|
-
const popupContentClassName = css`
|
|
82
|
-
display: grid;
|
|
83
|
-
background: ${themeVars.actionMenuBg};
|
|
84
|
-
grid-template-rows: 60px 1fr;
|
|
85
|
-
`;
|
|
86
|
-
|
|
87
|
-
export function MobilePopup({
|
|
88
|
-
content,
|
|
89
|
-
mobilePopupStyle,
|
|
90
|
-
mobilePopupContentClassName,
|
|
91
|
-
title,
|
|
92
|
-
onClose,
|
|
93
|
-
height,
|
|
94
|
-
onNavigateBack,
|
|
95
|
-
}: {
|
|
96
|
-
content: ReactNode;
|
|
97
|
-
mobilePopupStyle?: CSSProperties;
|
|
98
|
-
mobilePopupContentClassName?: string;
|
|
99
|
-
title: string;
|
|
100
|
-
height?: string;
|
|
101
|
-
onClose?: () => void;
|
|
102
|
-
onNavigateBack?: () => void;
|
|
103
|
-
}) {
|
|
104
|
-
const theme = useTheme();
|
|
105
|
-
|
|
106
|
-
const {title: contextTitle, headerSlot} = useMobilePopup();
|
|
107
|
-
|
|
108
|
-
const headerContent = headerSlot ?? (
|
|
109
|
-
<>
|
|
110
|
-
{onNavigateBack && (
|
|
111
|
-
<IconButton onClick={onNavigateBack} size="xLarge">
|
|
112
|
-
<ArrowLeftIcon />
|
|
113
|
-
</IconButton>
|
|
114
|
-
)}
|
|
115
|
-
<div
|
|
116
|
-
className={css`
|
|
117
|
-
${textStyles.heading3}
|
|
118
|
-
flex: 1;
|
|
119
|
-
min-width: 0;
|
|
120
|
-
overflow: hidden;
|
|
121
|
-
text-overflow: ellipsis;
|
|
122
|
-
white-space: nowrap;
|
|
123
|
-
`}
|
|
124
|
-
>
|
|
125
|
-
{contextTitle ?? title}
|
|
126
|
-
</div>
|
|
127
|
-
{onClose && (
|
|
128
|
-
<IconButton onClick={onClose} size="xLarge">
|
|
129
|
-
<CloseIcon />
|
|
130
|
-
</IconButton>
|
|
131
|
-
)}
|
|
132
|
-
</>
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const popupContent = (
|
|
136
|
-
<>
|
|
137
|
-
<div
|
|
138
|
-
className={cx(
|
|
139
|
-
css`
|
|
140
|
-
display: flex;
|
|
141
|
-
align-items: center;
|
|
142
|
-
gap: ${space.s8}px;
|
|
143
|
-
padding: ${space.s8}px ${space.s8}px ${space.s8}px ${space.s20}px;
|
|
144
|
-
min-width: 0;
|
|
145
|
-
`,
|
|
146
|
-
onNavigateBack &&
|
|
147
|
-
css`
|
|
148
|
-
padding-left: ${space.s4}px;
|
|
149
|
-
`
|
|
150
|
-
)}
|
|
151
|
-
>
|
|
152
|
-
{headerContent}
|
|
153
|
-
</div>
|
|
154
|
-
<div
|
|
155
|
-
className={cx(
|
|
156
|
-
css`
|
|
157
|
-
border-top: ${themeVars.separators.opacity5};
|
|
158
|
-
min-width: 0;
|
|
159
|
-
`,
|
|
160
|
-
mobilePopupContentClassName
|
|
161
|
-
)}
|
|
162
|
-
>
|
|
163
|
-
{content}
|
|
164
|
-
</div>
|
|
165
|
-
</>
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
return (
|
|
169
|
-
<div
|
|
170
|
-
className={cx(
|
|
171
|
-
css`
|
|
172
|
-
position: absolute;
|
|
173
|
-
left: var(${safeAreaInsetLeftVar});
|
|
174
|
-
right: var(${safeAreaInsetRightVar});
|
|
175
|
-
top: var(${safeAreaInsetTopVar});
|
|
176
|
-
bottom: var(${safeAreaInsetBottomVar});
|
|
177
|
-
z-index: ${mobileZIndex};
|
|
178
|
-
`,
|
|
179
|
-
!height && mobileAnimationClassName,
|
|
180
|
-
!height && popupContentClassName,
|
|
181
|
-
height &&
|
|
182
|
-
css`
|
|
183
|
-
display: grid;
|
|
184
|
-
`
|
|
185
|
-
)}
|
|
186
|
-
style={{
|
|
187
|
-
...createInlineTheme(theme),
|
|
188
|
-
...mobilePopupStyle,
|
|
189
|
-
...(height ? {gridTemplateRows: `1fr ${height}`} : {}),
|
|
190
|
-
}}
|
|
191
|
-
onClick={stopPropagation}
|
|
192
|
-
>
|
|
193
|
-
{height ? (
|
|
194
|
-
<>
|
|
195
|
-
<div
|
|
196
|
-
onClick={onClose}
|
|
197
|
-
className={css`
|
|
198
|
-
background-color: ${themeVars.modalBg};
|
|
199
|
-
`}
|
|
200
|
-
/>
|
|
201
|
-
<div
|
|
202
|
-
className={cx(
|
|
203
|
-
css`
|
|
204
|
-
display: grid;
|
|
205
|
-
grid-template-rows: 60px 1fr;
|
|
206
|
-
`,
|
|
207
|
-
mobileAnimationClassName,
|
|
208
|
-
popupContentClassName
|
|
209
|
-
)}
|
|
210
|
-
>
|
|
211
|
-
{popupContent}
|
|
212
|
-
</div>
|
|
213
|
-
</>
|
|
214
|
-
) : (
|
|
215
|
-
popupContent
|
|
216
|
-
)}
|
|
217
|
-
</div>
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
176
|
type PortalProps = {
|
|
222
177
|
getPopupContainer?: () => HTMLElement;
|
|
223
178
|
children: ReactNode;
|
|
224
179
|
};
|
|
225
180
|
|
|
226
|
-
const Portal = ({getPopupContainer
|
|
181
|
+
const Portal = ({getPopupContainer, children}: PortalProps) => {
|
|
227
182
|
const popupModifiers = usePopupModifiers();
|
|
228
|
-
const
|
|
229
|
-
return createPortal(children,
|
|
183
|
+
const getPopupContainerResult = getPopupContainer ?? popupModifiers.getPopupContainer;
|
|
184
|
+
return createPortal(children, getPopupContainerResult());
|
|
230
185
|
};
|
|
231
186
|
|
|
232
187
|
type TriggerProps = {
|
|
233
188
|
asChild?: boolean;
|
|
234
|
-
|
|
189
|
+
/** Custom reference element for popover positioning. Use when the trigger element differs from the anchor point (e.g., virtual/detached elements). */
|
|
235
190
|
reference?: Reference;
|
|
191
|
+
/** Event that opens the popover. "click" allows proper focus handling inside popover content. Default: "mousedown" */
|
|
192
|
+
triggerEvent?: "mousedown" | "click";
|
|
236
193
|
} & React.ComponentPropsWithoutRef<"div">;
|
|
237
194
|
|
|
238
|
-
const Trigger = ({
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
195
|
+
const Trigger = ({
|
|
196
|
+
asChild = false,
|
|
197
|
+
reference,
|
|
198
|
+
children,
|
|
199
|
+
triggerEvent = "mousedown",
|
|
200
|
+
onMouseDown,
|
|
201
|
+
onClick,
|
|
202
|
+
...props
|
|
203
|
+
}: TriggerProps) => {
|
|
204
|
+
const {setTriggerElement, disabled, open, onOpen, onClose} = usePopoverContext();
|
|
205
|
+
|
|
206
|
+
const onToggle = (e: React.MouseEvent<HTMLElement>) => {
|
|
242
207
|
if (disabled) {
|
|
243
208
|
return;
|
|
244
209
|
}
|
|
245
210
|
if (e.button === 0) {
|
|
246
211
|
open ? onClose?.(e as unknown as Event) : onOpen?.();
|
|
247
212
|
}
|
|
248
|
-
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const triggerEventHandlers =
|
|
216
|
+
triggerEvent === "click"
|
|
217
|
+
? {
|
|
218
|
+
onClick: composeEventHandlers(onClick, onToggle),
|
|
219
|
+
onMouseDown,
|
|
220
|
+
}
|
|
221
|
+
: {
|
|
222
|
+
onMouseDown: composeEventHandlers(onMouseDown, onToggle),
|
|
223
|
+
onClick,
|
|
224
|
+
};
|
|
249
225
|
|
|
250
226
|
useEffect(() => {
|
|
251
227
|
if (reference) {
|
|
@@ -259,17 +235,20 @@ const Trigger = ({asChild = false, reference, children, onMouseDown, ...props}:
|
|
|
259
235
|
}
|
|
260
236
|
|
|
261
237
|
if (asChild && isValidElement(children)) {
|
|
238
|
+
const childrenRef = getElementRef(children);
|
|
239
|
+
|
|
262
240
|
return cloneElement(children, {
|
|
263
|
-
ref: setTriggerElement,
|
|
264
241
|
"aria-expanded": open,
|
|
265
242
|
"aria-haspopup": true,
|
|
266
|
-
onMouseDown: onMouseDownCb,
|
|
267
243
|
...props,
|
|
244
|
+
ref: composeRefs(setTriggerElement, childrenRef),
|
|
245
|
+
className: cx(children.props.className, props.className),
|
|
246
|
+
...triggerEventHandlers,
|
|
268
247
|
});
|
|
269
248
|
}
|
|
270
249
|
|
|
271
250
|
return (
|
|
272
|
-
<div ref={setTriggerElement}
|
|
251
|
+
<div ref={setTriggerElement} {...triggerEventHandlers} {...props}>
|
|
273
252
|
{children}
|
|
274
253
|
</div>
|
|
275
254
|
);
|
|
@@ -282,7 +261,7 @@ type ContentProps = {
|
|
|
282
261
|
popupClassName?: string;
|
|
283
262
|
popupContainerClassName?: string;
|
|
284
263
|
offset?: [number, number];
|
|
285
|
-
|
|
264
|
+
padding?: number;
|
|
286
265
|
renderBackdrop?: boolean;
|
|
287
266
|
backdropClassName?: string;
|
|
288
267
|
preventOverflowOptions?: Partial<PreventOverflowOptions>;
|
|
@@ -304,17 +283,24 @@ type ContentProps = {
|
|
|
304
283
|
* Marked as deprecated because it's better to avoid this prop
|
|
305
284
|
*/
|
|
306
285
|
fixStackingContextAfterOpen?: boolean;
|
|
286
|
+
/** ran after Popup positioned the element the first time */
|
|
287
|
+
onFirstUpdate?: () => void;
|
|
288
|
+
onMouseLeave?: (e: MouseEvent<HTMLDivElement>) => void;
|
|
307
289
|
/**
|
|
308
|
-
*
|
|
290
|
+
* Event handler called when auto-focusing on open.
|
|
291
|
+
* Can be prevented.
|
|
309
292
|
*/
|
|
310
|
-
|
|
293
|
+
onOpenAutoFocus?: FocusScopeProps["onMountAutoFocus"];
|
|
294
|
+
/**
|
|
295
|
+
* Event handler called when auto-focusing on close.
|
|
296
|
+
* Can be prevented.
|
|
297
|
+
*/
|
|
298
|
+
onCloseAutoFocus?: FocusScopeProps["onUnmountAutoFocus"];
|
|
311
299
|
};
|
|
312
300
|
|
|
313
301
|
export type ContentRef = {update: (() => Promise<unknown>) | null};
|
|
314
302
|
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInner(
|
|
303
|
+
const Content = forwardRef<ContentRef, ContentProps>(function PopoverContent(
|
|
318
304
|
{
|
|
319
305
|
children,
|
|
320
306
|
placement,
|
|
@@ -322,7 +308,7 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
322
308
|
popupContainerClassName,
|
|
323
309
|
popupClassName,
|
|
324
310
|
offset = [0, 0],
|
|
325
|
-
|
|
311
|
+
padding = space.s8,
|
|
326
312
|
renderBackdrop,
|
|
327
313
|
backdropClassName,
|
|
328
314
|
preventOverflowOptions = {},
|
|
@@ -335,15 +321,17 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
335
321
|
mobilePopupStyle,
|
|
336
322
|
mobilePopupContentClassName,
|
|
337
323
|
fixStackingContextAfterOpen = false,
|
|
338
|
-
onFirstUpdate,
|
|
339
324
|
transform,
|
|
325
|
+
onFirstUpdate,
|
|
326
|
+
onMouseLeave,
|
|
327
|
+
onOpenAutoFocus,
|
|
328
|
+
onCloseAutoFocus,
|
|
340
329
|
},
|
|
341
330
|
ref
|
|
342
331
|
) {
|
|
343
|
-
const {open, onClose, triggerElement, popupElement, setPopupElement} =
|
|
332
|
+
const {open, onClose, triggerElement, popupElement, setPopupElement} = usePopoverContext();
|
|
344
333
|
|
|
345
334
|
const popupModifiers = usePopupModifiers();
|
|
346
|
-
const theme = useTheme();
|
|
347
335
|
const isPhone = useIsPhone();
|
|
348
336
|
|
|
349
337
|
// We get infinite popup renders if this is not in a separate memo
|
|
@@ -357,10 +345,10 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
357
345
|
applyMaxSize,
|
|
358
346
|
]
|
|
359
347
|
: [];
|
|
360
|
-
}, [preventOverflowOptions, shrinkable]);
|
|
348
|
+
}, [padding, preventOverflowOptions, shrinkable]);
|
|
361
349
|
|
|
362
350
|
const modifiers = useMemo(() => {
|
|
363
|
-
return
|
|
351
|
+
return compact([
|
|
364
352
|
{
|
|
365
353
|
name: "flip",
|
|
366
354
|
options: {
|
|
@@ -395,6 +383,7 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
395
383
|
additionalModifiers,
|
|
396
384
|
flipOptions,
|
|
397
385
|
offset,
|
|
386
|
+
padding,
|
|
398
387
|
popupModifiers,
|
|
399
388
|
preventOverflowOptions,
|
|
400
389
|
shrinkableModifiers,
|
|
@@ -406,17 +395,20 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
406
395
|
|
|
407
396
|
useImperativeHandle(ref, () => ({update}), [update]);
|
|
408
397
|
|
|
398
|
+
const hasInteractedOutsideRef = useRef(false);
|
|
399
|
+
|
|
409
400
|
const {onPointerDownCapture} = usePointerDownOutside((event) => {
|
|
410
401
|
if (!open || (isPhone && supportMobile)) {
|
|
411
402
|
return;
|
|
412
403
|
}
|
|
413
404
|
const target = event.target;
|
|
414
405
|
if (target instanceof Node) {
|
|
415
|
-
const ignoreElements =
|
|
406
|
+
const ignoreElements = compact([isElementReference(triggerElement) && triggerElement]);
|
|
416
407
|
if (ignoreElements.some((element) => element.contains(target))) {
|
|
417
408
|
return;
|
|
418
409
|
}
|
|
419
410
|
}
|
|
411
|
+
hasInteractedOutsideRef.current = true;
|
|
420
412
|
onClose?.(event);
|
|
421
413
|
});
|
|
422
414
|
|
|
@@ -427,8 +419,8 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
427
419
|
};
|
|
428
420
|
|
|
429
421
|
const contentStyles = useMemo(() => {
|
|
430
|
-
return {...styles.popper, ...
|
|
431
|
-
}, [popupStyle, styles.popper
|
|
422
|
+
return {...styles.popper, ...popupStyle};
|
|
423
|
+
}, [popupStyle, styles.popper]);
|
|
432
424
|
|
|
433
425
|
if (!open) {
|
|
434
426
|
return null;
|
|
@@ -441,153 +433,66 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
441
433
|
|
|
442
434
|
if (isPhone && supportMobile) {
|
|
443
435
|
return (
|
|
444
|
-
<
|
|
445
|
-
<
|
|
436
|
+
<MobilePopoverProvider>
|
|
437
|
+
<MobilePopover
|
|
446
438
|
onClose={onClose as $TSFixMe}
|
|
447
439
|
title={title}
|
|
448
440
|
mobilePopupContentClassName={mobilePopupContentClassName}
|
|
449
441
|
mobilePopupStyle={mobilePopupStyle}
|
|
450
442
|
content={children}
|
|
451
443
|
/>
|
|
452
|
-
</
|
|
444
|
+
</MobilePopoverProvider>
|
|
453
445
|
);
|
|
454
446
|
}
|
|
447
|
+
|
|
455
448
|
return (
|
|
456
|
-
|
|
449
|
+
<ThemeProvider portal>
|
|
457
450
|
{renderBackdrop && <Backdrop className={backdropClassName} />}
|
|
458
|
-
<
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
451
|
+
<FocusScope
|
|
452
|
+
asChild
|
|
453
|
+
loop
|
|
454
|
+
trapped={false}
|
|
455
|
+
onMountAutoFocus={onOpenAutoFocus}
|
|
456
|
+
onUnmountAutoFocus={(event) => {
|
|
457
|
+
onCloseAutoFocus?.(event);
|
|
458
|
+
|
|
459
|
+
if (!event.defaultPrevented) {
|
|
460
|
+
if (!hasInteractedOutsideRef.current) {
|
|
461
|
+
if (isElementReference(triggerElement)) {
|
|
462
|
+
triggerElement.focus();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Always prevent auto focus because we either focus manually or want user agent focus
|
|
466
|
+
event.preventDefault();
|
|
467
|
+
}
|
|
468
|
+
}}
|
|
470
469
|
>
|
|
471
|
-
<div
|
|
472
|
-
|
|
470
|
+
<div
|
|
471
|
+
role="dialog"
|
|
472
|
+
ref={setPopupElement}
|
|
473
|
+
className={cx(
|
|
474
|
+
basicPopupContainerClassName,
|
|
475
|
+
hidePopupWhenTriggerIsHidden && hidePopupWhenTriggerIsHiddenClassName,
|
|
476
|
+
popupContainerClassName
|
|
477
|
+
)}
|
|
478
|
+
onMouseLeave={onMouseLeave}
|
|
479
|
+
onClick={stopPropagation}
|
|
480
|
+
{...attributes.popper}
|
|
481
|
+
onPointerDownCapture={onPointerDownCapture}
|
|
482
|
+
style={contentStyles}
|
|
483
|
+
>
|
|
484
|
+
<div className={cx(basicPopupClassName, popupClassName)} style={popupStyle} onAnimationEnd={onAnimationEnd}>
|
|
485
|
+
{children}
|
|
486
|
+
</div>
|
|
473
487
|
</div>
|
|
474
|
-
</
|
|
475
|
-
|
|
488
|
+
</FocusScope>
|
|
489
|
+
</ThemeProvider>
|
|
476
490
|
);
|
|
477
491
|
});
|
|
478
492
|
|
|
479
|
-
|
|
480
|
-
open: boolean;
|
|
481
|
-
disabled?: boolean;
|
|
482
|
-
onOpen?: () => unknown;
|
|
483
|
-
onClose?: (event: Event) => unknown;
|
|
484
|
-
triggerElement: Reference;
|
|
485
|
-
setTriggerElement: (value: Reference) => void;
|
|
486
|
-
popupElement: HTMLDivElement | null;
|
|
487
|
-
setPopupElement: (value: HTMLDivElement) => void;
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
const [Provider, useContext] = createContext<ContextValue>("PopoverContext");
|
|
491
|
-
|
|
492
|
-
type RootProps = {
|
|
493
|
-
children: ReactNode;
|
|
494
|
-
open: boolean;
|
|
495
|
-
disabled?: boolean;
|
|
496
|
-
closeOnEscape?: boolean;
|
|
497
|
-
onOpen?: () => unknown;
|
|
498
|
-
onClose?: (event: Event) => unknown;
|
|
499
|
-
// For legacy popup component support
|
|
500
|
-
triggerElement?: Reference;
|
|
501
|
-
setTriggerElement?: (value: Reference) => void;
|
|
502
|
-
popupElement?: HTMLDivElement | null;
|
|
503
|
-
setPopupElement?: (value: HTMLDivElement | null) => void;
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
const Root = ({children, open, disabled = false, closeOnEscape = true, onOpen, onClose, ...rest}: RootProps) => {
|
|
507
|
-
const id = useId();
|
|
508
|
-
const {registerEscapeCallback, registerPopup} = usePopupStackContext();
|
|
509
|
-
|
|
510
|
-
const [triggerElement, setTriggerElement] = useControllableState<Reference>({
|
|
511
|
-
value: rest.triggerElement,
|
|
512
|
-
onChange: rest.setTriggerElement,
|
|
513
|
-
});
|
|
514
|
-
const [popupElement, setPopupElement] = useControllableState<HTMLDivElement | null>({
|
|
515
|
-
value: rest.popupElement,
|
|
516
|
-
onChange: rest.setPopupElement,
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
const composedSetTriggerElement = useComposedRefs(setTriggerElement, rest.setTriggerElement);
|
|
520
|
-
const composedSetPopupElement = useComposedRefs(setPopupElement, rest.setPopupElement);
|
|
521
|
-
|
|
522
|
-
useEffect(() => {
|
|
523
|
-
let unregister: () => void;
|
|
524
|
-
if (registerPopup && open) {
|
|
525
|
-
unregister = registerPopup(id);
|
|
526
|
-
}
|
|
527
|
-
return () => {
|
|
528
|
-
unregister?.();
|
|
529
|
-
};
|
|
530
|
-
}, [registerPopup, id, open]);
|
|
531
|
-
|
|
532
|
-
useEffect(() => {
|
|
533
|
-
if (triggerElement && closeOnEscape && open) {
|
|
534
|
-
const onKeyDown = (evt: KeyboardEvent) => {
|
|
535
|
-
if (evt.defaultPrevented) {
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
if (evt.key === "Escape") {
|
|
539
|
-
onClose?.(evt);
|
|
540
|
-
evt.preventDefault();
|
|
541
|
-
evt.stopPropagation();
|
|
542
|
-
}
|
|
543
|
-
};
|
|
544
|
-
|
|
545
|
-
if (registerEscapeCallback) {
|
|
546
|
-
registerEscapeCallback(id, onKeyDown);
|
|
547
|
-
} else {
|
|
548
|
-
document.addEventListener("keydown", onKeyDown);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// if popup was opened but nothing was focused inside it we should catch 'Escape' key from trigger as well
|
|
552
|
-
// otherwise it can be caught by navigation panel
|
|
553
|
-
"addEventListener" in triggerElement && triggerElement.addEventListener("keydown", onKeyDown);
|
|
554
|
-
|
|
555
|
-
return () => {
|
|
556
|
-
if (!registerEscapeCallback) {
|
|
557
|
-
document.removeEventListener("keydown", onKeyDown);
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
"removeEventListener" in triggerElement && triggerElement.removeEventListener("keydown", onKeyDown);
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
564
|
-
return () => {};
|
|
565
|
-
}, [closeOnEscape, onClose, open, triggerElement, registerEscapeCallback, id]);
|
|
566
|
-
|
|
567
|
-
const contextValue = useMemo(() => {
|
|
568
|
-
return {
|
|
569
|
-
disabled,
|
|
570
|
-
triggerElement,
|
|
571
|
-
setTriggerElement: composedSetTriggerElement,
|
|
572
|
-
popupElement,
|
|
573
|
-
setPopupElement: composedSetPopupElement,
|
|
574
|
-
open,
|
|
575
|
-
onClose,
|
|
576
|
-
onOpen,
|
|
577
|
-
};
|
|
578
|
-
}, [
|
|
579
|
-
disabled,
|
|
580
|
-
triggerElement,
|
|
581
|
-
composedSetTriggerElement,
|
|
582
|
-
popupElement,
|
|
583
|
-
composedSetPopupElement,
|
|
584
|
-
open,
|
|
585
|
-
onClose,
|
|
586
|
-
onOpen,
|
|
587
|
-
]);
|
|
493
|
+
export {createPopper};
|
|
588
494
|
|
|
589
|
-
|
|
590
|
-
};
|
|
495
|
+
export type {Placement, State, Reference, RootProps, TriggerProps, ContentProps};
|
|
591
496
|
|
|
592
497
|
export const Popover = {
|
|
593
498
|
Root,
|
|
@@ -596,14 +501,7 @@ export const Popover = {
|
|
|
596
501
|
Content,
|
|
597
502
|
};
|
|
598
503
|
|
|
599
|
-
export
|
|
600
|
-
|
|
601
|
-
export type {Placement, State, Reference, RootProps, TriggerProps, ContentProps};
|
|
602
|
-
|
|
603
|
-
export type PopoverOldProps = Pick<
|
|
604
|
-
RootProps,
|
|
605
|
-
"children" | "open" | "onOpen" | "onClose" | "closeOnEscape" | "disabled"
|
|
606
|
-
> &
|
|
504
|
+
export type PopoverOldProps = Pick<RootProps, "children" | "open" | "onOpen" | "onClose" | "disabled"> &
|
|
607
505
|
ContentProps &
|
|
608
506
|
Pick<PortalProps, "getPopupContainer"> & {
|
|
609
507
|
trigger?: ReactNode;
|
|
@@ -635,7 +533,7 @@ export type PopoverOldProps = Pick<
|
|
|
635
533
|
* onOpen={() => setIsOpen(true)}
|
|
636
534
|
* onClose={() => setIsOpen(false)}
|
|
637
535
|
* >
|
|
638
|
-
* <Popover.Trigger>
|
|
536
|
+
* <Popover.Trigger asChild triggerEvent="click">
|
|
639
537
|
* <button>Click me</button>
|
|
640
538
|
* </Popover.Trigger>
|
|
641
539
|
* <Popover.Portal>
|
|
@@ -661,14 +559,13 @@ export const PopoverOld = ({
|
|
|
661
559
|
onMouseLeave,
|
|
662
560
|
renderBackdrop,
|
|
663
561
|
backdropClassName,
|
|
664
|
-
closeOnEscape = true,
|
|
665
562
|
preventOverflowOptions = {},
|
|
666
563
|
flipOptions = {},
|
|
667
564
|
additionalModifiers = [],
|
|
668
565
|
hidePopupWhenTriggerIsHidden = true,
|
|
669
566
|
shrinkable,
|
|
670
|
-
onOpen =
|
|
671
|
-
onClose =
|
|
567
|
+
onOpen = noop,
|
|
568
|
+
onClose = noop,
|
|
672
569
|
supportMobile = false,
|
|
673
570
|
title = "",
|
|
674
571
|
mobilePopupStyle,
|
|
@@ -679,7 +576,7 @@ export const PopoverOld = ({
|
|
|
679
576
|
transform,
|
|
680
577
|
}: PopoverOldProps) => {
|
|
681
578
|
return (
|
|
682
|
-
<Popover.Root open={open} disabled={disabled}
|
|
579
|
+
<Popover.Root open={open} disabled={disabled} onOpen={onOpen} onClose={onClose}>
|
|
683
580
|
{triggerDomElement ? (
|
|
684
581
|
<Popover.Trigger reference={triggerDomElement}></Popover.Trigger>
|
|
685
582
|
) : (
|