@apify/ui-library 1.138.2 → 1.138.4-featpublictasks-2f3d3c.48
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/dist/src/components/box.d.ts +1 -0
- package/dist/src/components/box.d.ts.map +1 -1
- package/dist/src/components/box.js.map +1 -1
- package/dist/src/components/browser_window/browser_window.d.ts +20 -0
- package/dist/src/components/browser_window/browser_window.d.ts.map +1 -0
- package/dist/src/components/browser_window/browser_window.js +72 -0
- package/dist/src/components/browser_window/browser_window.js.map +1 -0
- package/dist/src/components/browser_window/index.d.ts +2 -0
- package/dist/src/components/browser_window/index.d.ts.map +1 -0
- package/dist/src/components/browser_window/index.js +2 -0
- package/dist/src/components/browser_window/index.js.map +1 -0
- package/dist/src/components/chip.d.ts +1 -0
- package/dist/src/components/chip.d.ts.map +1 -1
- package/dist/src/components/chip.js +25 -2
- package/dist/src/components/chip.js.map +1 -1
- package/dist/src/components/code/prism_highlighter.d.ts +2 -0
- package/dist/src/components/code/prism_highlighter.d.ts.map +1 -1
- package/dist/src/components/collapsible_card/collapsible_card.d.ts +3 -2
- package/dist/src/components/collapsible_card/collapsible_card.d.ts.map +1 -1
- package/dist/src/components/collapsible_card/collapsible_card.js +2 -2
- package/dist/src/components/collapsible_card/collapsible_card.js.map +1 -1
- package/dist/src/components/floating/floating_component_base.d.ts +72 -1
- package/dist/src/components/floating/floating_component_base.d.ts.map +1 -1
- package/dist/src/components/floating/floating_component_base.js +66 -36
- package/dist/src/components/floating/floating_component_base.js.map +1 -1
- package/dist/src/components/floating/tooltip.d.ts +10 -3
- package/dist/src/components/floating/tooltip.d.ts.map +1 -1
- package/dist/src/components/floating/tooltip.js +24 -20
- package/dist/src/components/floating/tooltip.js.map +1 -1
- package/dist/src/components/icon_button.d.ts +1 -0
- package/dist/src/components/icon_button.d.ts.map +1 -1
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/components/index.d.ts.map +1 -1
- package/dist/src/components/index.js +1 -0
- package/dist/src/components/index.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/components/box.tsx +1 -0
- package/src/components/browser_window/browser_window.tsx +106 -0
- package/src/components/browser_window/index.ts +1 -0
- package/src/components/chip.tsx +27 -1
- package/src/components/collapsible_card/collapsible_card.tsx +6 -4
- package/src/components/floating/floating_component_base.tsx +89 -47
- package/src/components/floating/tooltip.tsx +53 -25
- package/src/components/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apify/ui-library",
|
|
3
|
-
"version": "1.138.
|
|
3
|
+
"version": "1.138.4-featpublictasks-2f3d3c.48+ff8f29a938a",
|
|
4
4
|
"description": "React UI library used by apify.com",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"It's not nice, but helps us to get around the problem of multiple react instances."
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@apify/ui-icons": "^1.38.
|
|
30
|
+
"@apify/ui-icons": "^1.38.2-featpublictasks-2f3d3c.73+ff8f29a938a",
|
|
31
31
|
"@floating-ui/react": "^0.27.19",
|
|
32
32
|
"@floating-ui/react-dom": "^2.1.8",
|
|
33
33
|
"@radix-ui/react-checkbox": "^1.3.3",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"styled-components": "^6.1.19"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@apify-packages/types": "^3.353.
|
|
61
|
+
"@apify-packages/types": "^3.353.4-featpublictasks-2f3d3c.48+ff8f29a938a",
|
|
62
62
|
"@storybook/react-vite": "^10.3.5",
|
|
63
63
|
"@types/hast": "^3.0.4",
|
|
64
64
|
"@types/lodash": "^4.14.200",
|
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
"src",
|
|
73
73
|
"style"
|
|
74
74
|
],
|
|
75
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "ff8f29a938ac47851ce5b052bf96594609db7041"
|
|
76
76
|
}
|
package/src/components/box.tsx
CHANGED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
|
|
5
|
+
import { theme } from '../../design_system/theme.js';
|
|
6
|
+
import { Badge } from '../badge.js';
|
|
7
|
+
|
|
8
|
+
const classNames = {
|
|
9
|
+
ROOT: 'browser-window',
|
|
10
|
+
HEADER: 'browser-window__header',
|
|
11
|
+
DOTS: 'browser-window__dots',
|
|
12
|
+
DOT: 'browser-window__dot',
|
|
13
|
+
URL: 'browser-window__url',
|
|
14
|
+
TITLE: 'browser-window__title',
|
|
15
|
+
CONTAINER: 'browser-window__container',
|
|
16
|
+
CONTENT: 'browser-window__content',
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
export { classNames as browserWindowClassNames };
|
|
20
|
+
|
|
21
|
+
const BrowserWindowWrapper = styled.div`
|
|
22
|
+
border: 1px solid ${theme.color.neutral.separatorSubtle};
|
|
23
|
+
border-radius: ${theme.radius.radius12};
|
|
24
|
+
background-color: ${theme.color.neutral.background};
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
|
|
27
|
+
.${classNames.HEADER} {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
gap: ${theme.space.space8};
|
|
31
|
+
padding: ${theme.space.space6} ${theme.space.space8};
|
|
32
|
+
border-bottom: 1px solid ${theme.color.neutral.separatorSubtle};
|
|
33
|
+
background-color: ${theme.color.neutral.backgroundMuted};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.${classNames.DOTS} {
|
|
37
|
+
display: flex;
|
|
38
|
+
gap: 0.3rem;
|
|
39
|
+
flex-shrink: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.${classNames.DOT} {
|
|
43
|
+
width: 0.6rem;
|
|
44
|
+
height: 0.6rem;
|
|
45
|
+
border-radius: 50%;
|
|
46
|
+
background-color: ${theme.color.neutral.separatorSubtle};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.${classNames.URL} {
|
|
50
|
+
width: 100%;
|
|
51
|
+
min-width: 0;
|
|
52
|
+
justify-content: flex-start;
|
|
53
|
+
color: ${theme.color.neutral.textSubtle};
|
|
54
|
+
|
|
55
|
+
span {
|
|
56
|
+
min-width: 0;
|
|
57
|
+
text-overflow: ellipsis;
|
|
58
|
+
white-space: nowrap;
|
|
59
|
+
overflow: hidden;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.${classNames.TITLE} {
|
|
64
|
+
margin-left: auto;
|
|
65
|
+
flex-shrink: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.${classNames.CONTAINER} {
|
|
69
|
+
min-width: 0;
|
|
70
|
+
overflow-x: auto;
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
type Props = {
|
|
75
|
+
title: string;
|
|
76
|
+
url?: ReactNode;
|
|
77
|
+
children: ReactNode;
|
|
78
|
+
className?: string;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const BrowserWindow = ({ title, url, children, className }: Props) => {
|
|
82
|
+
return (
|
|
83
|
+
<BrowserWindowWrapper className={clsx(classNames.ROOT, className)}>
|
|
84
|
+
<div className={classNames.HEADER}>
|
|
85
|
+
{url && (
|
|
86
|
+
<>
|
|
87
|
+
<div className={classNames.DOTS}>
|
|
88
|
+
<span className={classNames.DOT} />
|
|
89
|
+
<span className={classNames.DOT} />
|
|
90
|
+
<span className={classNames.DOT} />
|
|
91
|
+
</div>
|
|
92
|
+
<Badge size="extra_small" variant="neutral_subtle" className={classNames.URL}>
|
|
93
|
+
{url}
|
|
94
|
+
</Badge>
|
|
95
|
+
</>
|
|
96
|
+
)}
|
|
97
|
+
<Badge size="extra_small" className={classNames.TITLE}>
|
|
98
|
+
{title}
|
|
99
|
+
</Badge>
|
|
100
|
+
</div>
|
|
101
|
+
<div className={classNames.CONTAINER}>
|
|
102
|
+
<div className={classNames.CONTENT}>{children}</div>
|
|
103
|
+
</div>
|
|
104
|
+
</BrowserWindowWrapper>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './browser_window.js';
|
package/src/components/chip.tsx
CHANGED
|
@@ -80,9 +80,34 @@ const chipTypeStyle = {
|
|
|
80
80
|
`,
|
|
81
81
|
} satisfies Record<CHIP_TYPES, unknown>;
|
|
82
82
|
|
|
83
|
+
const chipTypeHoverStyle = {
|
|
84
|
+
[CHIP_TYPES.DEFAULT]: css`
|
|
85
|
+
background: ${theme.color.neutral.chipBackgroundHover};
|
|
86
|
+
`,
|
|
87
|
+
[CHIP_TYPES.PRIMARY]: css`
|
|
88
|
+
background: ${theme.color.primary.chipBackgroundHover};
|
|
89
|
+
`,
|
|
90
|
+
[CHIP_TYPES.SUCCESS]: css`
|
|
91
|
+
background: ${theme.color.success.chipBackgroundHover};
|
|
92
|
+
`,
|
|
93
|
+
[CHIP_TYPES.WARNING]: css`
|
|
94
|
+
background: ${theme.color.warning.chipBackgroundHover};
|
|
95
|
+
`,
|
|
96
|
+
[CHIP_TYPES.DANGER]: css`
|
|
97
|
+
background: ${theme.color.danger.chipBackgroundHover};
|
|
98
|
+
`,
|
|
99
|
+
} satisfies Record<CHIP_TYPES, unknown>;
|
|
100
|
+
|
|
83
101
|
const StyledChip = styled.span<{ size: CHIP_SIZES; type: CHIP_TYPES; clickable: boolean }>`
|
|
84
102
|
${({ size }) => chipSizeStyle[size]};
|
|
85
103
|
${({ type }) => chipTypeStyle[type]};
|
|
104
|
+
${({ type, clickable }) =>
|
|
105
|
+
clickable &&
|
|
106
|
+
css`
|
|
107
|
+
&:hover {
|
|
108
|
+
${chipTypeHoverStyle[type]};
|
|
109
|
+
}
|
|
110
|
+
`};
|
|
86
111
|
/* Static styles */
|
|
87
112
|
width: fit-content;
|
|
88
113
|
display: flex;
|
|
@@ -111,6 +136,7 @@ export const Chip = forwardRef(
|
|
|
111
136
|
{
|
|
112
137
|
type = CHIP_TYPES.DEFAULT,
|
|
113
138
|
size = CHIP_SIZES.MEDIUM,
|
|
139
|
+
clickable = false,
|
|
114
140
|
icon,
|
|
115
141
|
children,
|
|
116
142
|
className,
|
|
@@ -118,7 +144,7 @@ export const Chip = forwardRef(
|
|
|
118
144
|
}: ChipProps,
|
|
119
145
|
ref,
|
|
120
146
|
) => {
|
|
121
|
-
const otherProps = { ...passThroughProps, type, size, className: clsx(className, classNames.BODY) };
|
|
147
|
+
const otherProps = { ...passThroughProps, type, size, clickable, className: clsx(className, classNames.BODY) };
|
|
122
148
|
|
|
123
149
|
return (
|
|
124
150
|
<StyledChip ref={ref} {...otherProps}>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Collapsible from '@radix-ui/react-collapsible';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
|
-
import { type
|
|
3
|
+
import { type ReactNode, useState } from 'react';
|
|
4
4
|
import styled, { css } from 'styled-components';
|
|
5
5
|
|
|
6
6
|
import { ChevronDownIcon } from '@apify/ui-icons';
|
|
@@ -27,6 +27,7 @@ export type CollapsibleCardProps = {
|
|
|
27
27
|
header: ReactNode;
|
|
28
28
|
children: ReactNode;
|
|
29
29
|
isExpanded?: boolean;
|
|
30
|
+
isExpandedDefault?: boolean;
|
|
30
31
|
onIsExpandedChanged?: (expanded: boolean) => void;
|
|
31
32
|
noChevron?: boolean;
|
|
32
33
|
noDivider?: boolean;
|
|
@@ -110,10 +111,11 @@ const CollapsibleContent = styled(Collapsible.Content)<{ $noDivider?: boolean }>
|
|
|
110
111
|
${({ $noDivider }) => ($noDivider ? '' : `border-top: 1px solid ${theme.color.neutral.border};`)}
|
|
111
112
|
`;
|
|
112
113
|
|
|
113
|
-
export const CollapsibleCard
|
|
114
|
+
export const CollapsibleCard = ({
|
|
114
115
|
header,
|
|
115
116
|
children,
|
|
116
117
|
isExpanded,
|
|
118
|
+
isExpandedDefault = false,
|
|
117
119
|
onIsExpandedChanged,
|
|
118
120
|
noChevron,
|
|
119
121
|
noDivider,
|
|
@@ -124,9 +126,9 @@ export const CollapsibleCard: FC<CollapsibleCardProps> = ({
|
|
|
124
126
|
isHeaderGreyOnHover,
|
|
125
127
|
as: Element,
|
|
126
128
|
...rest
|
|
127
|
-
}) => {
|
|
129
|
+
}: CollapsibleCardProps) => {
|
|
128
130
|
const isUncontrolled = isExpanded === undefined;
|
|
129
|
-
const [isOpen, setOpen] = useState(
|
|
131
|
+
const [isOpen, setOpen] = useState(isExpandedDefault);
|
|
130
132
|
|
|
131
133
|
let onHeaderClick;
|
|
132
134
|
if (isUncontrolled) onHeaderClick = () => setOpen((prevIsOpen) => !prevIsOpen);
|
|
@@ -44,6 +44,81 @@ export const FLOATING_PLACEMENT = {
|
|
|
44
44
|
|
|
45
45
|
export type FloatingPlacement = (typeof FLOATING_PLACEMENT)[keyof typeof FLOATING_PLACEMENT];
|
|
46
46
|
|
|
47
|
+
type UseFloatingPopupOptions = {
|
|
48
|
+
open: boolean;
|
|
49
|
+
onOpenChange?: (open: boolean) => void;
|
|
50
|
+
placement?: FloatingPlacement;
|
|
51
|
+
strategy?: Strategy;
|
|
52
|
+
autoPlacements?: FloatingPlacement[];
|
|
53
|
+
offsetPx?: number;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Shared `useFloating` + `useTransitionStyles` setup for popup-style components (`Tooltip`,
|
|
58
|
+
* `Popover`, the inner positioning of `FloatingComponentBase`). Owns the middleware stack
|
|
59
|
+
* (`offset` / `flip` | `autoPlacement` / `shift` / `hide`) and the side-aware in/out animation,
|
|
60
|
+
* so individual consumers don't duplicate these.
|
|
61
|
+
*
|
|
62
|
+
* Returns the full `useFloating` result plus `isMounted` and `transitionStyles` from
|
|
63
|
+
* `useTransitionStyles` — destructure what each consumer needs.
|
|
64
|
+
*/
|
|
65
|
+
export const useFloatingPopup = ({
|
|
66
|
+
open,
|
|
67
|
+
onOpenChange,
|
|
68
|
+
placement = FLOATING_PLACEMENT.TOP,
|
|
69
|
+
strategy = 'fixed',
|
|
70
|
+
autoPlacements,
|
|
71
|
+
offsetPx = 10,
|
|
72
|
+
}: UseFloatingPopupOptions) => {
|
|
73
|
+
const floating = useFloating({
|
|
74
|
+
open,
|
|
75
|
+
onOpenChange,
|
|
76
|
+
placement,
|
|
77
|
+
strategy,
|
|
78
|
+
whileElementsMounted: autoUpdate,
|
|
79
|
+
middleware: [
|
|
80
|
+
offset(offsetPx),
|
|
81
|
+
autoPlacements?.length ? autoPlacement({ allowedPlacements: autoPlacements }) : flip(),
|
|
82
|
+
shift({ padding: 5 }),
|
|
83
|
+
hide({ strategy: 'referenceHidden' }),
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const { isMounted, styles: transitionStyles } = useTransitionStyles(floating.context, {
|
|
88
|
+
initial: ({ side }) => {
|
|
89
|
+
switch (side) {
|
|
90
|
+
case 'top':
|
|
91
|
+
return { opacity: 0, scale: '0.9', transform: 'translateY(10px)' };
|
|
92
|
+
case 'bottom':
|
|
93
|
+
return { opacity: 0, scale: '0.9', transform: 'translateY(-10px)' };
|
|
94
|
+
case 'left':
|
|
95
|
+
return { opacity: 0, scale: '0.9', transform: 'translateX(10px)' };
|
|
96
|
+
case 'right':
|
|
97
|
+
return { opacity: 0, scale: '0.9', transform: 'translateX(-10px)' };
|
|
98
|
+
default:
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return { ...floating, isMounted, transitionStyles };
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Builds the inline `style` object floating-ui consumers spread onto their popup wrapper:
|
|
109
|
+
* positioning (`position`/`top`/`left`), `width: 'max-content'`, the `referenceHidden`
|
|
110
|
+
* visibility toggle, and the in/out transition styles. Centralized so Tooltip / Popover /
|
|
111
|
+
* `FloatingComponentBase` don't redeclare it.
|
|
112
|
+
*/
|
|
113
|
+
export const getFloatingPopupStyle = (floating: ReturnType<typeof useFloatingPopup>): CSSProperties => ({
|
|
114
|
+
position: floating.strategy,
|
|
115
|
+
top: floating.y ?? 0,
|
|
116
|
+
left: floating.x ?? 0,
|
|
117
|
+
width: 'max-content',
|
|
118
|
+
visibility: floating.middlewareData.hide?.referenceHidden ? 'hidden' : 'visible',
|
|
119
|
+
...floating.transitionStyles,
|
|
120
|
+
});
|
|
121
|
+
|
|
47
122
|
interface FloatingComponentWrapProps {
|
|
48
123
|
showInPortal?: boolean;
|
|
49
124
|
className?: string;
|
|
@@ -67,7 +142,12 @@ export interface FloatingComponentBaseProps {
|
|
|
67
142
|
showInPortal?: boolean;
|
|
68
143
|
}
|
|
69
144
|
|
|
70
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Base styling for any popup that renders via floating-ui (Tooltip, Popover, …). Tooltip extends
|
|
147
|
+
* this via `styled(...)` to layer on its dark theme + tighter padding without re-declaring the
|
|
148
|
+
* shared rules.
|
|
149
|
+
*/
|
|
150
|
+
export const FloatingComponentWrapper = styled.span`
|
|
71
151
|
padding: ${theme.space.space16};
|
|
72
152
|
${theme.typography.shared.mobile.bodyM};
|
|
73
153
|
border-radius: 0.8rem;
|
|
@@ -100,7 +180,7 @@ const StyledPopoverBox = styled.div`
|
|
|
100
180
|
|
|
101
181
|
const FloatingComponentWrap = forwardRef<HTMLSpanElement, FloatingComponentWrapProps>((props, ref) => {
|
|
102
182
|
const { showInPortal, ...rest } = props;
|
|
103
|
-
const component = <
|
|
183
|
+
const component = <FloatingComponentWrapper {...rest} ref={ref} />;
|
|
104
184
|
if (showInPortal) {
|
|
105
185
|
return <FloatingPortal>{component}</FloatingPortal>;
|
|
106
186
|
}
|
|
@@ -127,43 +207,12 @@ export const FloatingComponentBase = ({
|
|
|
127
207
|
CloseButtonComponent,
|
|
128
208
|
showInPortal = false,
|
|
129
209
|
}: FloatingComponentBaseProps) => {
|
|
130
|
-
const {
|
|
131
|
-
x,
|
|
132
|
-
y,
|
|
133
|
-
refs: { setReference, setFloating },
|
|
134
|
-
strategy: effectiveStrategy,
|
|
135
|
-
middlewareData: { hide: refHidden },
|
|
136
|
-
context,
|
|
137
|
-
} = useFloating({
|
|
210
|
+
const floating = useFloatingPopup({
|
|
138
211
|
open: isOpen,
|
|
139
212
|
placement,
|
|
140
213
|
strategy,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
offset(offsetPx),
|
|
144
|
-
autoPlacements?.length ? autoPlacement({ allowedPlacements: autoPlacements }) : flip(),
|
|
145
|
-
shift({ padding: 5 }),
|
|
146
|
-
hide({
|
|
147
|
-
strategy: 'referenceHidden',
|
|
148
|
-
}),
|
|
149
|
-
],
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
const { isMounted, styles } = useTransitionStyles(context, {
|
|
153
|
-
initial: ({ side }) => {
|
|
154
|
-
switch (side) {
|
|
155
|
-
case 'top':
|
|
156
|
-
return { opacity: 0, scale: '0.9', transform: 'translateY(10px)' };
|
|
157
|
-
case 'bottom':
|
|
158
|
-
return { opacity: 0, scale: '0.9', transform: 'translateY(-10px)' };
|
|
159
|
-
case 'left':
|
|
160
|
-
return { opacity: 0, scale: '0.9', transform: 'translateX(10px)' };
|
|
161
|
-
case 'right':
|
|
162
|
-
return { opacity: 0, scale: '0.9', transform: 'translateX(-10px)' };
|
|
163
|
-
default:
|
|
164
|
-
return {};
|
|
165
|
-
}
|
|
166
|
-
},
|
|
214
|
+
autoPlacements,
|
|
215
|
+
offsetPx,
|
|
167
216
|
});
|
|
168
217
|
|
|
169
218
|
if (!content) return <span>{children}</span>;
|
|
@@ -171,21 +220,14 @@ export const FloatingComponentBase = ({
|
|
|
171
220
|
return (
|
|
172
221
|
<>
|
|
173
222
|
{/* Adding className to children for easier identifying in DevTools */}
|
|
174
|
-
<ChildrenWrap className={clsx(classNames.CHILDREN, contentWrapClassName)} ref={setReference}>
|
|
223
|
+
<ChildrenWrap className={clsx(classNames.CHILDREN, contentWrapClassName)} ref={floating.refs.setReference}>
|
|
175
224
|
{children}
|
|
176
225
|
</ChildrenWrap>
|
|
177
|
-
{isMounted && (
|
|
226
|
+
{floating.isMounted && (
|
|
178
227
|
<FloatingComponentWrap
|
|
179
228
|
className={className}
|
|
180
|
-
ref={setFloating}
|
|
181
|
-
style={
|
|
182
|
-
position: effectiveStrategy,
|
|
183
|
-
top: y ?? 0,
|
|
184
|
-
left: x ?? 0,
|
|
185
|
-
width: 'max-content',
|
|
186
|
-
visibility: refHidden?.referenceHidden ? 'hidden' : 'visible',
|
|
187
|
-
...styles,
|
|
188
|
-
}}
|
|
229
|
+
ref={floating.refs.setFloating}
|
|
230
|
+
style={getFloatingPopupStyle(floating)}
|
|
189
231
|
onClick={(e) => e.stopPropagation()}
|
|
190
232
|
showInPortal={showInPortal}
|
|
191
233
|
>
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FloatingPortal, useHover, useInteractions } from '@floating-ui/react';
|
|
2
2
|
import { type ComponentType, type ElementType, forwardRef, type ReactNode, useState } from 'react';
|
|
3
3
|
import styled, { css } from 'styled-components';
|
|
4
4
|
|
|
5
5
|
import { theme } from '../../design_system/theme.js';
|
|
6
6
|
import { useSharedUiDependencies } from '../../ui_dependency_provider.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
type FloatingComponentBaseProps,
|
|
9
|
+
FloatingComponentWrapper,
|
|
10
|
+
getFloatingPopupStyle,
|
|
11
|
+
useFloatingPopup,
|
|
12
|
+
} from './floating_component_base.js';
|
|
8
13
|
import { TooltipContent } from './tooltip_content.js';
|
|
9
14
|
|
|
10
15
|
export const TOOLTIP_TEXT_ALIGNS = {
|
|
@@ -24,7 +29,10 @@ export const TOOLTIP_SIZES = {
|
|
|
24
29
|
|
|
25
30
|
export type TooltipSize = (typeof TOOLTIP_SIZES)[keyof typeof TOOLTIP_SIZES];
|
|
26
31
|
|
|
27
|
-
export interface TooltipProps extends Omit<
|
|
32
|
+
export interface TooltipProps extends Omit<
|
|
33
|
+
FloatingComponentBaseProps,
|
|
34
|
+
'isOpen' | 'size' | 'triggerRef' | 'CloseButtonComponent'
|
|
35
|
+
> {
|
|
28
36
|
as?: ElementType;
|
|
29
37
|
className?: string;
|
|
30
38
|
delayShow?: number;
|
|
@@ -43,7 +51,7 @@ interface WithTooltipProps {
|
|
|
43
51
|
// Using a styled component to get access to the `as` prop
|
|
44
52
|
const TooltipFocusArea = styled.span``;
|
|
45
53
|
|
|
46
|
-
const
|
|
54
|
+
const TooltipPopup = styled(FloatingComponentWrapper)<{ $isDarkTheme?: boolean }>`
|
|
47
55
|
color: ${theme.colorPalette.dark.neutral0};
|
|
48
56
|
background-color: ${theme.colorPalette.dark.neutral900};
|
|
49
57
|
padding: ${theme.space.space8};
|
|
@@ -65,7 +73,14 @@ const StyledFloatingComponentBase = styled(FloatingComponentBase)<{ $isDarkTheme
|
|
|
65
73
|
`;
|
|
66
74
|
|
|
67
75
|
/**
|
|
68
|
-
* Tooltip appears on hover, for onclick use Popover
|
|
76
|
+
* Tooltip appears on hover, for onclick use Popover.
|
|
77
|
+
*
|
|
78
|
+
* Reference (the trigger wrapper) and floating (the popup) are rendered as separate DOM subtrees
|
|
79
|
+
* — the popup is portaled (or rendered as a sibling fragment when `showInPortal={false}`), never
|
|
80
|
+
* nested inside the trigger. floating-ui's `useHover` resolves open/close state by tracking the
|
|
81
|
+
* cursor across distinct reference and floating elements; nesting them causes their bounding
|
|
82
|
+
* rects to overlap and the state machine flips unpredictably (see floating-ui docs on the
|
|
83
|
+
* reference/floating element separation requirement).
|
|
69
84
|
*/
|
|
70
85
|
export const Tooltip = ({
|
|
71
86
|
as,
|
|
@@ -77,48 +92,61 @@ export const Tooltip = ({
|
|
|
77
92
|
subtleText,
|
|
78
93
|
size = TOOLTIP_SIZES.SMALL,
|
|
79
94
|
textAlign = TOOLTIP_TEXT_ALIGNS.LEFT,
|
|
80
|
-
|
|
95
|
+
placement,
|
|
96
|
+
autoPlacements,
|
|
97
|
+
strategy,
|
|
98
|
+
offsetPx,
|
|
99
|
+
contentWrapClassName,
|
|
100
|
+
showInPortal = false,
|
|
101
|
+
content,
|
|
102
|
+
children,
|
|
81
103
|
}: TooltipProps): ReactNode => {
|
|
82
104
|
const { uiTheme, tooltipSafeHtml } = useSharedUiDependencies();
|
|
83
105
|
const [open, setOpen] = useState(false);
|
|
84
106
|
|
|
85
|
-
const
|
|
107
|
+
const floating = useFloatingPopup({
|
|
86
108
|
open,
|
|
87
109
|
onOpenChange: setOpen,
|
|
110
|
+
placement,
|
|
111
|
+
strategy,
|
|
112
|
+
autoPlacements,
|
|
113
|
+
offsetPx,
|
|
88
114
|
});
|
|
89
115
|
|
|
90
|
-
const hover = useHover(context, {
|
|
91
|
-
delay: {
|
|
92
|
-
open: delayShow,
|
|
93
|
-
close: delayHide,
|
|
94
|
-
},
|
|
116
|
+
const hover = useHover(floating.context, {
|
|
117
|
+
delay: { open: delayShow, close: delayHide },
|
|
95
118
|
});
|
|
96
119
|
|
|
97
120
|
const { getReferenceProps, getFloatingProps } = useInteractions([hover]);
|
|
98
121
|
|
|
99
|
-
if (!
|
|
122
|
+
if (!content && !shortcuts?.length && !imageUrl && !subtleText) return children;
|
|
100
123
|
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
124
|
+
const popup = (
|
|
125
|
+
<TooltipPopup
|
|
126
|
+
ref={floating.refs.setFloating}
|
|
127
|
+
className={contentWrapClassName}
|
|
128
|
+
$isDarkTheme={uiTheme === 'DARK'}
|
|
129
|
+
style={getFloatingPopupStyle(floating)}
|
|
130
|
+
{...getFloatingProps()}
|
|
131
|
+
>
|
|
105
132
|
<TooltipContent
|
|
106
|
-
content={tooltipSafeHtml(
|
|
133
|
+
content={tooltipSafeHtml(content)}
|
|
107
134
|
shortcuts={shortcuts}
|
|
108
135
|
imageUrl={imageUrl}
|
|
109
136
|
subtleText={subtleText}
|
|
110
137
|
size={size}
|
|
111
138
|
textAlign={textAlign}
|
|
112
139
|
/>
|
|
113
|
-
|
|
114
|
-
|
|
140
|
+
</TooltipPopup>
|
|
141
|
+
);
|
|
115
142
|
|
|
116
143
|
return (
|
|
117
|
-
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
</
|
|
121
|
-
|
|
144
|
+
<>
|
|
145
|
+
<TooltipFocusArea as={as} className={className} ref={floating.refs.setReference} {...getReferenceProps()}>
|
|
146
|
+
{children}
|
|
147
|
+
</TooltipFocusArea>
|
|
148
|
+
{floating.isMounted && (showInPortal ? <FloatingPortal>{popup}</FloatingPortal> : popup)}
|
|
149
|
+
</>
|
|
122
150
|
);
|
|
123
151
|
};
|
|
124
152
|
|
package/src/components/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export * from './icon_button.js';
|
|
|
27
27
|
export * from './spinner.js';
|
|
28
28
|
export * from './store/index.js';
|
|
29
29
|
export * from './checkbox/index.js';
|
|
30
|
+
export * from './browser_window/index.js';
|
|
30
31
|
export * from './collapsible_card/index.js';
|
|
31
32
|
export * from './select/index.js';
|
|
32
33
|
export * from './switch/index.js';
|