@fibery/ui-kit 1.40.4 → 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 +6 -0
- package/eslint.config.mjs +16 -0
- package/package.json +8 -7
- package/src/a11y-color.test.ts +5 -24
- package/src/actions-menu/actions-menu-item.tsx +6 -9
- 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/index.tsx +3 -0
- package/src/antd/input.tsx +0 -1
- package/src/antd/styles.ts +9 -9
- package/src/app-icon-with-fallback.tsx +5 -5
- package/src/app-icon-wrapper.tsx +2 -3
- package/src/app-icon.tsx +3 -3
- package/src/avatar.tsx +3 -3
- package/src/button/base-button.tsx +0 -1
- package/src/button/make-button-colors.ts +2 -2
- package/src/checkbox.tsx +1 -1
- package/src/collapsible-section.tsx +11 -1
- package/src/color-utils.test.ts +15 -25
- package/src/color-utils.ts +43 -8
- package/src/command-menu/index.tsx +1 -0
- package/src/context-menu/index.tsx +5 -5
- 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 → design-system/colors.ts} +133 -134
- 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 +46 -303
- package/src/design-system.ts +25 -545
- package/src/dropdown-menu/index.tsx +5 -5
- package/src/emoji-picker/icon-emoji-picker.tsx +1 -1
- package/src/fibermoji-placeholder.tsx +3 -3
- package/src/field-container.tsx +11 -2
- package/src/file-item/file-icon.tsx +3 -3
- package/src/file-item-2.tsx +4 -10
- package/src/file-item.tsx +0 -1
- 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/FileOther.ts +8 -0
- package/src/icons/ast/NoBorder.ts +8 -0
- package/src/icons/ast/NoFill.ts +8 -0
- package/src/icons/ast/index.tsx +6 -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/FileOther.tsx +13 -0
- package/src/icons/react/NoBorder.tsx +13 -0
- package/src/icons/react/NoFill.tsx +13 -0
- package/src/icons/react/index.tsx +6 -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-other.svg +3 -0
- package/src/icons/svg/no-border.svg +17 -0
- package/src/icons/svg/no-fill.svg +4 -0
- package/src/images-gallery/images-gallery.tsx +8 -6
- package/src/images-gallery/slide-buttons.tsx +2 -2
- package/src/is-iOS.ts +0 -1
- package/src/is-in-popup.ts +2 -1
- package/src/lists/actions-menu-row-surface.tsx +4 -4
- package/src/loading-sausage.tsx +2 -2
- package/src/media-query-utils.ts +1 -2
- package/src/mobile-keyboard-aware-popup.tsx +2 -2
- 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 +24 -281
- package/src/palette-generator.ts +39 -45
- 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/popover/get-element-ref.ts +28 -0
- package/src/popover/index.tsx +232 -333
- 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/root-theme-provider.test.tsx +114 -19
- package/src/scale-generator.ts +31 -22
- package/src/select/components/menu-list-virtualized.tsx +5 -3
- package/src/select/index.tsx +6 -5
- package/src/select/select.tsx +36 -38
- package/src/select/util.ts +1 -1
- package/src/static-palettes.ts +221 -11
- package/src/thematic-controls.tsx +6 -6
- package/src/thematic-scales.tsx +15 -15
- package/src/thematic-state.ts +57 -20
- package/src/thematic.tsx +36 -32
- package/src/theme-provider.test.tsx +31 -19
- package/src/theme-provider.tsx +41 -38
- package/src/theme-settings.ts +66 -11
- package/src/theme-styles.ts +53 -8
- package/src/toast/toast.tsx +1 -2
- package/src/toggle.tsx +2 -2
- package/src/tooltip.tsx +4 -3
- package/src/type-badge.tsx +4 -11
- package/src/unit/styles.ts +0 -23
- package/src/use-is-phone.tsx +7 -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/__snapshots__/design-system.test.ts.snap +0 -7265
- package/src/create-inline-theme.ts +0 -67
- package/src/number-input/index.js +0 -191
- package/src/palette.ts +0 -253
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,19 +20,11 @@ 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 {space
|
|
32
|
-
import {IconButton} from "../button/icon-button";
|
|
33
|
-
import CloseIcon from "../icons/react/Close";
|
|
34
|
-
import ArrowLeftIcon from "../icons/react/ArrowLeft";
|
|
27
|
+
import {space} from "../design-system";
|
|
35
28
|
import {ThemeProvider} from "../theme-provider";
|
|
36
29
|
import {$TSFixMe} from "../tsfixme";
|
|
37
30
|
import {useIsPhone} from "../use-is-phone";
|
|
@@ -39,14 +32,129 @@ 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
|
+
});
|
|
83
|
+
|
|
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
|
+
}
|
|
48
118
|
|
|
49
|
-
|
|
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,185 +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 {title: contextTitle, headerSlot} = useMobilePopup();
|
|
105
|
-
|
|
106
|
-
const headerContent = headerSlot ?? (
|
|
107
|
-
<>
|
|
108
|
-
{onNavigateBack && (
|
|
109
|
-
<IconButton onClick={onNavigateBack} size="xLarge">
|
|
110
|
-
<ArrowLeftIcon />
|
|
111
|
-
</IconButton>
|
|
112
|
-
)}
|
|
113
|
-
<div
|
|
114
|
-
className={css`
|
|
115
|
-
${textStyles.heading3}
|
|
116
|
-
flex: 1;
|
|
117
|
-
min-width: 0;
|
|
118
|
-
overflow: hidden;
|
|
119
|
-
text-overflow: ellipsis;
|
|
120
|
-
white-space: nowrap;
|
|
121
|
-
`}
|
|
122
|
-
>
|
|
123
|
-
{contextTitle ?? title}
|
|
124
|
-
</div>
|
|
125
|
-
{onClose && (
|
|
126
|
-
<IconButton onClick={onClose} size="xLarge">
|
|
127
|
-
<CloseIcon />
|
|
128
|
-
</IconButton>
|
|
129
|
-
)}
|
|
130
|
-
</>
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
const popupContent = (
|
|
134
|
-
<>
|
|
135
|
-
<div
|
|
136
|
-
className={cx(
|
|
137
|
-
css`
|
|
138
|
-
display: flex;
|
|
139
|
-
align-items: center;
|
|
140
|
-
gap: ${space.s8}px;
|
|
141
|
-
padding: ${space.s8}px ${space.s8}px ${space.s8}px ${space.s20}px;
|
|
142
|
-
min-width: 0;
|
|
143
|
-
`,
|
|
144
|
-
onNavigateBack &&
|
|
145
|
-
css`
|
|
146
|
-
padding-left: ${space.s4}px;
|
|
147
|
-
`
|
|
148
|
-
)}
|
|
149
|
-
>
|
|
150
|
-
{headerContent}
|
|
151
|
-
</div>
|
|
152
|
-
<div
|
|
153
|
-
className={cx(
|
|
154
|
-
css`
|
|
155
|
-
border-top: ${themeVars.separators.opacity5};
|
|
156
|
-
min-width: 0;
|
|
157
|
-
`,
|
|
158
|
-
mobilePopupContentClassName
|
|
159
|
-
)}
|
|
160
|
-
>
|
|
161
|
-
{content}
|
|
162
|
-
</div>
|
|
163
|
-
</>
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
const res = (
|
|
167
|
-
<div
|
|
168
|
-
className={cx(
|
|
169
|
-
css`
|
|
170
|
-
position: absolute;
|
|
171
|
-
left: var(${safeAreaInsetLeftVar});
|
|
172
|
-
right: var(${safeAreaInsetRightVar});
|
|
173
|
-
top: var(${safeAreaInsetTopVar});
|
|
174
|
-
bottom: var(${safeAreaInsetBottomVar});
|
|
175
|
-
z-index: ${mobileZIndex};
|
|
176
|
-
`,
|
|
177
|
-
!height && mobileAnimationClassName,
|
|
178
|
-
!height && popupContentClassName,
|
|
179
|
-
height &&
|
|
180
|
-
css`
|
|
181
|
-
display: grid;
|
|
182
|
-
`
|
|
183
|
-
)}
|
|
184
|
-
style={{
|
|
185
|
-
...mobilePopupStyle,
|
|
186
|
-
...(height ? {gridTemplateRows: `1fr ${height}`} : {}),
|
|
187
|
-
}}
|
|
188
|
-
onClick={stopPropagation}
|
|
189
|
-
>
|
|
190
|
-
{height ? (
|
|
191
|
-
<>
|
|
192
|
-
<div
|
|
193
|
-
onClick={onClose}
|
|
194
|
-
className={css`
|
|
195
|
-
background-color: ${themeVars.modalBg};
|
|
196
|
-
`}
|
|
197
|
-
/>
|
|
198
|
-
<div
|
|
199
|
-
className={cx(
|
|
200
|
-
css`
|
|
201
|
-
display: grid;
|
|
202
|
-
grid-template-rows: 60px 1fr;
|
|
203
|
-
`,
|
|
204
|
-
mobileAnimationClassName,
|
|
205
|
-
popupContentClassName
|
|
206
|
-
)}
|
|
207
|
-
>
|
|
208
|
-
{popupContent}
|
|
209
|
-
</div>
|
|
210
|
-
</>
|
|
211
|
-
) : (
|
|
212
|
-
popupContent
|
|
213
|
-
)}
|
|
214
|
-
</div>
|
|
215
|
-
);
|
|
216
|
-
return <ThemeProvider portal>{res}</ThemeProvider>;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
176
|
type PortalProps = {
|
|
220
177
|
getPopupContainer?: () => HTMLElement;
|
|
221
178
|
children: ReactNode;
|
|
222
179
|
};
|
|
223
180
|
|
|
224
|
-
const Portal = ({getPopupContainer
|
|
181
|
+
const Portal = ({getPopupContainer, children}: PortalProps) => {
|
|
225
182
|
const popupModifiers = usePopupModifiers();
|
|
226
|
-
const
|
|
227
|
-
return createPortal(children,
|
|
183
|
+
const getPopupContainerResult = getPopupContainer ?? popupModifiers.getPopupContainer;
|
|
184
|
+
return createPortal(children, getPopupContainerResult());
|
|
228
185
|
};
|
|
229
186
|
|
|
230
187
|
type TriggerProps = {
|
|
231
188
|
asChild?: boolean;
|
|
232
|
-
|
|
189
|
+
/** Custom reference element for popover positioning. Use when the trigger element differs from the anchor point (e.g., virtual/detached elements). */
|
|
233
190
|
reference?: Reference;
|
|
191
|
+
/** Event that opens the popover. "click" allows proper focus handling inside popover content. Default: "mousedown" */
|
|
192
|
+
triggerEvent?: "mousedown" | "click";
|
|
234
193
|
} & React.ComponentPropsWithoutRef<"div">;
|
|
235
194
|
|
|
236
|
-
const Trigger = ({
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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>) => {
|
|
240
207
|
if (disabled) {
|
|
241
208
|
return;
|
|
242
209
|
}
|
|
243
210
|
if (e.button === 0) {
|
|
244
211
|
open ? onClose?.(e as unknown as Event) : onOpen?.();
|
|
245
212
|
}
|
|
246
|
-
}
|
|
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
|
+
};
|
|
247
225
|
|
|
248
226
|
useEffect(() => {
|
|
249
227
|
if (reference) {
|
|
@@ -256,19 +234,21 @@ const Trigger = ({asChild = false, reference, children, onMouseDown, ...props}:
|
|
|
256
234
|
return null;
|
|
257
235
|
}
|
|
258
236
|
|
|
259
|
-
if (asChild && isValidElement
|
|
260
|
-
|
|
237
|
+
if (asChild && isValidElement(children)) {
|
|
238
|
+
const childrenRef = getElementRef(children);
|
|
239
|
+
|
|
240
|
+
return cloneElement(children, {
|
|
261
241
|
"aria-expanded": open,
|
|
262
242
|
"aria-haspopup": true,
|
|
263
243
|
...props,
|
|
264
|
-
ref: setTriggerElement,
|
|
244
|
+
ref: composeRefs(setTriggerElement, childrenRef),
|
|
265
245
|
className: cx(children.props.className, props.className),
|
|
266
|
-
|
|
246
|
+
...triggerEventHandlers,
|
|
267
247
|
});
|
|
268
248
|
}
|
|
269
249
|
|
|
270
250
|
return (
|
|
271
|
-
<div ref={setTriggerElement}
|
|
251
|
+
<div ref={setTriggerElement} {...triggerEventHandlers} {...props}>
|
|
272
252
|
{children}
|
|
273
253
|
</div>
|
|
274
254
|
);
|
|
@@ -281,7 +261,7 @@ type ContentProps = {
|
|
|
281
261
|
popupClassName?: string;
|
|
282
262
|
popupContainerClassName?: string;
|
|
283
263
|
offset?: [number, number];
|
|
284
|
-
|
|
264
|
+
padding?: number;
|
|
285
265
|
renderBackdrop?: boolean;
|
|
286
266
|
backdropClassName?: string;
|
|
287
267
|
preventOverflowOptions?: Partial<PreventOverflowOptions>;
|
|
@@ -303,17 +283,24 @@ type ContentProps = {
|
|
|
303
283
|
* Marked as deprecated because it's better to avoid this prop
|
|
304
284
|
*/
|
|
305
285
|
fixStackingContextAfterOpen?: boolean;
|
|
286
|
+
/** ran after Popup positioned the element the first time */
|
|
287
|
+
onFirstUpdate?: () => void;
|
|
288
|
+
onMouseLeave?: (e: MouseEvent<HTMLDivElement>) => void;
|
|
306
289
|
/**
|
|
307
|
-
*
|
|
290
|
+
* Event handler called when auto-focusing on open.
|
|
291
|
+
* Can be prevented.
|
|
308
292
|
*/
|
|
309
|
-
|
|
293
|
+
onOpenAutoFocus?: FocusScopeProps["onMountAutoFocus"];
|
|
294
|
+
/**
|
|
295
|
+
* Event handler called when auto-focusing on close.
|
|
296
|
+
* Can be prevented.
|
|
297
|
+
*/
|
|
298
|
+
onCloseAutoFocus?: FocusScopeProps["onUnmountAutoFocus"];
|
|
310
299
|
};
|
|
311
300
|
|
|
312
301
|
export type ContentRef = {update: (() => Promise<unknown>) | null};
|
|
313
302
|
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInner(
|
|
303
|
+
const Content = forwardRef<ContentRef, ContentProps>(function PopoverContent(
|
|
317
304
|
{
|
|
318
305
|
children,
|
|
319
306
|
placement,
|
|
@@ -321,7 +308,7 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
321
308
|
popupContainerClassName,
|
|
322
309
|
popupClassName,
|
|
323
310
|
offset = [0, 0],
|
|
324
|
-
|
|
311
|
+
padding = space.s8,
|
|
325
312
|
renderBackdrop,
|
|
326
313
|
backdropClassName,
|
|
327
314
|
preventOverflowOptions = {},
|
|
@@ -334,12 +321,15 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
334
321
|
mobilePopupStyle,
|
|
335
322
|
mobilePopupContentClassName,
|
|
336
323
|
fixStackingContextAfterOpen = false,
|
|
337
|
-
onFirstUpdate,
|
|
338
324
|
transform,
|
|
325
|
+
onFirstUpdate,
|
|
326
|
+
onMouseLeave,
|
|
327
|
+
onOpenAutoFocus,
|
|
328
|
+
onCloseAutoFocus,
|
|
339
329
|
},
|
|
340
330
|
ref
|
|
341
331
|
) {
|
|
342
|
-
const {open, onClose, triggerElement, popupElement, setPopupElement} =
|
|
332
|
+
const {open, onClose, triggerElement, popupElement, setPopupElement} = usePopoverContext();
|
|
343
333
|
|
|
344
334
|
const popupModifiers = usePopupModifiers();
|
|
345
335
|
const isPhone = useIsPhone();
|
|
@@ -355,10 +345,10 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
355
345
|
applyMaxSize,
|
|
356
346
|
]
|
|
357
347
|
: [];
|
|
358
|
-
}, [preventOverflowOptions, shrinkable]);
|
|
348
|
+
}, [padding, preventOverflowOptions, shrinkable]);
|
|
359
349
|
|
|
360
350
|
const modifiers = useMemo(() => {
|
|
361
|
-
return
|
|
351
|
+
return compact([
|
|
362
352
|
{
|
|
363
353
|
name: "flip",
|
|
364
354
|
options: {
|
|
@@ -393,6 +383,7 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
393
383
|
additionalModifiers,
|
|
394
384
|
flipOptions,
|
|
395
385
|
offset,
|
|
386
|
+
padding,
|
|
396
387
|
popupModifiers,
|
|
397
388
|
preventOverflowOptions,
|
|
398
389
|
shrinkableModifiers,
|
|
@@ -404,17 +395,20 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
404
395
|
|
|
405
396
|
useImperativeHandle(ref, () => ({update}), [update]);
|
|
406
397
|
|
|
398
|
+
const hasInteractedOutsideRef = useRef(false);
|
|
399
|
+
|
|
407
400
|
const {onPointerDownCapture} = usePointerDownOutside((event) => {
|
|
408
401
|
if (!open || (isPhone && supportMobile)) {
|
|
409
402
|
return;
|
|
410
403
|
}
|
|
411
404
|
const target = event.target;
|
|
412
405
|
if (target instanceof Node) {
|
|
413
|
-
const ignoreElements =
|
|
406
|
+
const ignoreElements = compact([isElementReference(triggerElement) && triggerElement]);
|
|
414
407
|
if (ignoreElements.some((element) => element.contains(target))) {
|
|
415
408
|
return;
|
|
416
409
|
}
|
|
417
410
|
}
|
|
411
|
+
hasInteractedOutsideRef.current = true;
|
|
418
412
|
onClose?.(event);
|
|
419
413
|
});
|
|
420
414
|
|
|
@@ -439,153 +433,66 @@ const Content = forwardRef<ContentRef, ContentProps>(function PopoverContentInne
|
|
|
439
433
|
|
|
440
434
|
if (isPhone && supportMobile) {
|
|
441
435
|
return (
|
|
442
|
-
<
|
|
443
|
-
<
|
|
436
|
+
<MobilePopoverProvider>
|
|
437
|
+
<MobilePopover
|
|
444
438
|
onClose={onClose as $TSFixMe}
|
|
445
439
|
title={title}
|
|
446
440
|
mobilePopupContentClassName={mobilePopupContentClassName}
|
|
447
441
|
mobilePopupStyle={mobilePopupStyle}
|
|
448
442
|
content={children}
|
|
449
443
|
/>
|
|
450
|
-
</
|
|
444
|
+
</MobilePopoverProvider>
|
|
451
445
|
);
|
|
452
446
|
}
|
|
447
|
+
|
|
453
448
|
return (
|
|
454
449
|
<ThemeProvider portal>
|
|
455
450
|
{renderBackdrop && <Backdrop className={backdropClassName} />}
|
|
456
|
-
<
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
+
}}
|
|
468
469
|
>
|
|
469
|
-
<div
|
|
470
|
-
|
|
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>
|
|
471
487
|
</div>
|
|
472
|
-
</
|
|
488
|
+
</FocusScope>
|
|
473
489
|
</ThemeProvider>
|
|
474
490
|
);
|
|
475
491
|
});
|
|
476
492
|
|
|
477
|
-
|
|
478
|
-
open: boolean;
|
|
479
|
-
disabled?: boolean;
|
|
480
|
-
onOpen?: () => unknown;
|
|
481
|
-
onClose?: (event: Event) => unknown;
|
|
482
|
-
triggerElement: Reference;
|
|
483
|
-
setTriggerElement: (value: Reference) => void;
|
|
484
|
-
popupElement: HTMLDivElement | null;
|
|
485
|
-
setPopupElement: (value: HTMLDivElement) => void;
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
const [Provider, useContext] = createContext<ContextValue>("PopoverContext");
|
|
489
|
-
|
|
490
|
-
type RootProps = {
|
|
491
|
-
children: ReactNode;
|
|
492
|
-
open: boolean;
|
|
493
|
-
disabled?: boolean;
|
|
494
|
-
closeOnEscape?: boolean;
|
|
495
|
-
onOpen?: () => unknown;
|
|
496
|
-
onClose?: (event: Event) => unknown;
|
|
497
|
-
// For legacy popup component support
|
|
498
|
-
triggerElement?: Reference;
|
|
499
|
-
setTriggerElement?: (value: Reference) => void;
|
|
500
|
-
popupElement?: HTMLDivElement | null;
|
|
501
|
-
setPopupElement?: (value: HTMLDivElement | null) => void;
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
const Root = ({children, open, disabled = false, closeOnEscape = true, onOpen, onClose, ...rest}: RootProps) => {
|
|
505
|
-
const id = useId();
|
|
506
|
-
const {registerEscapeCallback, registerPopup} = usePopupStackContext();
|
|
507
|
-
|
|
508
|
-
const [triggerElement, setTriggerElement] = useControllableState<Reference>({
|
|
509
|
-
value: rest.triggerElement,
|
|
510
|
-
onChange: rest.setTriggerElement,
|
|
511
|
-
});
|
|
512
|
-
const [popupElement, setPopupElement] = useControllableState<HTMLDivElement | null>({
|
|
513
|
-
value: rest.popupElement,
|
|
514
|
-
onChange: rest.setPopupElement,
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
const composedSetTriggerElement = useComposedRefs(setTriggerElement, rest.setTriggerElement);
|
|
518
|
-
const composedSetPopupElement = useComposedRefs(setPopupElement, rest.setPopupElement);
|
|
519
|
-
|
|
520
|
-
useEffect(() => {
|
|
521
|
-
let unregister: () => void;
|
|
522
|
-
if (registerPopup && open) {
|
|
523
|
-
unregister = registerPopup(id);
|
|
524
|
-
}
|
|
525
|
-
return () => {
|
|
526
|
-
unregister?.();
|
|
527
|
-
};
|
|
528
|
-
}, [registerPopup, id, open]);
|
|
529
|
-
|
|
530
|
-
useEffect(() => {
|
|
531
|
-
if (triggerElement && closeOnEscape && open) {
|
|
532
|
-
const onKeyDown = (evt: KeyboardEvent) => {
|
|
533
|
-
if (evt.defaultPrevented) {
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
if (evt.key === "Escape") {
|
|
537
|
-
onClose?.(evt);
|
|
538
|
-
evt.preventDefault();
|
|
539
|
-
evt.stopPropagation();
|
|
540
|
-
}
|
|
541
|
-
};
|
|
542
|
-
|
|
543
|
-
if (registerEscapeCallback) {
|
|
544
|
-
registerEscapeCallback(id, onKeyDown);
|
|
545
|
-
} else {
|
|
546
|
-
document.addEventListener("keydown", onKeyDown);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// if popup was opened but nothing was focused inside it we should catch 'Escape' key from trigger as well
|
|
550
|
-
// otherwise it can be caught by navigation panel
|
|
551
|
-
"addEventListener" in triggerElement && triggerElement.addEventListener("keydown", onKeyDown);
|
|
552
|
-
|
|
553
|
-
return () => {
|
|
554
|
-
if (!registerEscapeCallback) {
|
|
555
|
-
document.removeEventListener("keydown", onKeyDown);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
"removeEventListener" in triggerElement && triggerElement.removeEventListener("keydown", onKeyDown);
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
562
|
-
return () => {};
|
|
563
|
-
}, [closeOnEscape, onClose, open, triggerElement, registerEscapeCallback, id]);
|
|
564
|
-
|
|
565
|
-
const contextValue = useMemo(() => {
|
|
566
|
-
return {
|
|
567
|
-
disabled,
|
|
568
|
-
triggerElement,
|
|
569
|
-
setTriggerElement: composedSetTriggerElement,
|
|
570
|
-
popupElement,
|
|
571
|
-
setPopupElement: composedSetPopupElement,
|
|
572
|
-
open,
|
|
573
|
-
onClose,
|
|
574
|
-
onOpen,
|
|
575
|
-
};
|
|
576
|
-
}, [
|
|
577
|
-
disabled,
|
|
578
|
-
triggerElement,
|
|
579
|
-
composedSetTriggerElement,
|
|
580
|
-
popupElement,
|
|
581
|
-
composedSetPopupElement,
|
|
582
|
-
open,
|
|
583
|
-
onClose,
|
|
584
|
-
onOpen,
|
|
585
|
-
]);
|
|
493
|
+
export {createPopper};
|
|
586
494
|
|
|
587
|
-
|
|
588
|
-
};
|
|
495
|
+
export type {Placement, State, Reference, RootProps, TriggerProps, ContentProps};
|
|
589
496
|
|
|
590
497
|
export const Popover = {
|
|
591
498
|
Root,
|
|
@@ -594,14 +501,7 @@ export const Popover = {
|
|
|
594
501
|
Content,
|
|
595
502
|
};
|
|
596
503
|
|
|
597
|
-
export
|
|
598
|
-
|
|
599
|
-
export type {Placement, State, Reference, RootProps, TriggerProps, ContentProps};
|
|
600
|
-
|
|
601
|
-
export type PopoverOldProps = Pick<
|
|
602
|
-
RootProps,
|
|
603
|
-
"children" | "open" | "onOpen" | "onClose" | "closeOnEscape" | "disabled"
|
|
604
|
-
> &
|
|
504
|
+
export type PopoverOldProps = Pick<RootProps, "children" | "open" | "onOpen" | "onClose" | "disabled"> &
|
|
605
505
|
ContentProps &
|
|
606
506
|
Pick<PortalProps, "getPopupContainer"> & {
|
|
607
507
|
trigger?: ReactNode;
|
|
@@ -633,7 +533,7 @@ export type PopoverOldProps = Pick<
|
|
|
633
533
|
* onOpen={() => setIsOpen(true)}
|
|
634
534
|
* onClose={() => setIsOpen(false)}
|
|
635
535
|
* >
|
|
636
|
-
* <Popover.Trigger>
|
|
536
|
+
* <Popover.Trigger asChild triggerEvent="click">
|
|
637
537
|
* <button>Click me</button>
|
|
638
538
|
* </Popover.Trigger>
|
|
639
539
|
* <Popover.Portal>
|
|
@@ -659,14 +559,13 @@ export const PopoverOld = ({
|
|
|
659
559
|
onMouseLeave,
|
|
660
560
|
renderBackdrop,
|
|
661
561
|
backdropClassName,
|
|
662
|
-
closeOnEscape = true,
|
|
663
562
|
preventOverflowOptions = {},
|
|
664
563
|
flipOptions = {},
|
|
665
564
|
additionalModifiers = [],
|
|
666
565
|
hidePopupWhenTriggerIsHidden = true,
|
|
667
566
|
shrinkable,
|
|
668
|
-
onOpen =
|
|
669
|
-
onClose =
|
|
567
|
+
onOpen = noop,
|
|
568
|
+
onClose = noop,
|
|
670
569
|
supportMobile = false,
|
|
671
570
|
title = "",
|
|
672
571
|
mobilePopupStyle,
|
|
@@ -677,7 +576,7 @@ export const PopoverOld = ({
|
|
|
677
576
|
transform,
|
|
678
577
|
}: PopoverOldProps) => {
|
|
679
578
|
return (
|
|
680
|
-
<Popover.Root open={open} disabled={disabled}
|
|
579
|
+
<Popover.Root open={open} disabled={disabled} onOpen={onOpen} onClose={onClose}>
|
|
681
580
|
{triggerDomElement ? (
|
|
682
581
|
<Popover.Trigger reference={triggerDomElement}></Popover.Trigger>
|
|
683
582
|
) : (
|