@khanacademy/wonder-blocks-popover 3.2.15 → 3.3.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 +26 -0
- package/dist/components/popover.d.ts +6 -0
- package/dist/es/index.js +4 -2
- package/dist/index.js +4 -2
- package/package.json +7 -7
- package/src/components/__tests__/focus-manager.test.tsx +0 -180
- package/src/components/__tests__/initial-focus.test.tsx +0 -73
- package/src/components/__tests__/popover-anchor.test.tsx +0 -61
- package/src/components/__tests__/popover-content.test.tsx +0 -76
- package/src/components/__tests__/popover-content.typestest.tsx +0 -38
- package/src/components/__tests__/popover-dialog.test.tsx +0 -98
- package/src/components/__tests__/popover-event-listener.test.tsx +0 -98
- package/src/components/__tests__/popover.test.tsx +0 -932
- package/src/components/close-button.tsx +0 -61
- package/src/components/focus-manager.tsx +0 -344
- package/src/components/initial-focus.ts +0 -87
- package/src/components/popover-anchor.ts +0 -93
- package/src/components/popover-content-core.tsx +0 -143
- package/src/components/popover-content.tsx +0 -319
- package/src/components/popover-context.ts +0 -40
- package/src/components/popover-dialog.tsx +0 -150
- package/src/components/popover-event-listener.ts +0 -96
- package/src/components/popover.tsx +0 -410
- package/src/index.ts +0 -5
- package/src/util/__tests__/util.test.tsx +0 -38
- package/src/util/util.ts +0 -20
- package/tsconfig-build.json +0 -17
- package/tsconfig-build.tsbuildinfo +0 -1
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {StyleSheet} from "aphrodite";
|
|
3
|
-
|
|
4
|
-
import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
|
|
5
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
-
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
|
|
7
|
-
|
|
8
|
-
import CloseButton from "./close-button";
|
|
9
|
-
|
|
10
|
-
type Props = AriaProps & {
|
|
11
|
-
/**
|
|
12
|
-
* The content to render inside the popover.
|
|
13
|
-
*/
|
|
14
|
-
children: React.ReactNode;
|
|
15
|
-
/**
|
|
16
|
-
* Close button color
|
|
17
|
-
*/
|
|
18
|
-
closeButtonLight?: boolean;
|
|
19
|
-
/**
|
|
20
|
-
* Close button label for use in screen readers
|
|
21
|
-
*/
|
|
22
|
-
closeButtonLabel?: string;
|
|
23
|
-
/**
|
|
24
|
-
* When true, the close button is shown; otherwise, the close button is not shown.
|
|
25
|
-
*/
|
|
26
|
-
closeButtonVisible?: boolean;
|
|
27
|
-
/**
|
|
28
|
-
* Whether we should use the default light color scheme or switch to a
|
|
29
|
-
* different color scheme.
|
|
30
|
-
*/
|
|
31
|
-
color: "blue" | "darkBlue" | "white";
|
|
32
|
-
/**
|
|
33
|
-
* Custom styles applied to the content container
|
|
34
|
-
*/
|
|
35
|
-
style?: StyleType;
|
|
36
|
-
/**
|
|
37
|
-
* Test ID used for e2e testing.
|
|
38
|
-
*/
|
|
39
|
-
testId?: string;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
type DefaultProps = {
|
|
43
|
-
color: Props["color"];
|
|
44
|
-
closeButtonLight: Props["closeButtonLight"];
|
|
45
|
-
closeButtonVisible: Props["closeButtonVisible"];
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* This is the base popover container. It’s used internally by all the variants.
|
|
50
|
-
* Also, it can be used to create flexible popovers.
|
|
51
|
-
*
|
|
52
|
-
* ### Usage
|
|
53
|
-
*
|
|
54
|
-
* ```jsx
|
|
55
|
-
* import {PopoverContentCore} from "@khanacademy/wonder-blocks-popover";
|
|
56
|
-
*
|
|
57
|
-
* <PopoverContentCore>
|
|
58
|
-
* <>
|
|
59
|
-
* Some custom layout
|
|
60
|
-
* </>
|
|
61
|
-
* </PopoverContentCore>
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
|
-
export default class PopoverContentCore extends React.Component<Props> {
|
|
65
|
-
static defaultProps: DefaultProps = {
|
|
66
|
-
color: "white",
|
|
67
|
-
closeButtonLight: false,
|
|
68
|
-
closeButtonVisible: false,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
render(): React.ReactNode {
|
|
72
|
-
const {
|
|
73
|
-
"aria-label": ariaLabel,
|
|
74
|
-
children,
|
|
75
|
-
closeButtonLight,
|
|
76
|
-
closeButtonLabel,
|
|
77
|
-
closeButtonVisible,
|
|
78
|
-
color,
|
|
79
|
-
style,
|
|
80
|
-
testId,
|
|
81
|
-
} = this.props;
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<View
|
|
85
|
-
testId={testId}
|
|
86
|
-
style={[
|
|
87
|
-
styles.content,
|
|
88
|
-
color !== "white" && styles[color],
|
|
89
|
-
style,
|
|
90
|
-
]}
|
|
91
|
-
aria-label={ariaLabel}
|
|
92
|
-
>
|
|
93
|
-
{closeButtonVisible && (
|
|
94
|
-
<CloseButton
|
|
95
|
-
aria-label={closeButtonLabel}
|
|
96
|
-
light={closeButtonLight || color !== "white"}
|
|
97
|
-
style={styles.closeButton}
|
|
98
|
-
testId={`${testId || "popover"}-close-btn`}
|
|
99
|
-
/>
|
|
100
|
-
)}
|
|
101
|
-
{children}
|
|
102
|
-
</View>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const styles = StyleSheet.create({
|
|
108
|
-
content: {
|
|
109
|
-
borderRadius: spacing.xxxSmall_4,
|
|
110
|
-
border: `solid 1px ${color.offBlack16}`,
|
|
111
|
-
backgroundColor: color.white,
|
|
112
|
-
boxShadow: `0 ${spacing.xSmall_8}px ${spacing.xSmall_8}px 0 ${color.offBlack8}`,
|
|
113
|
-
margin: 0,
|
|
114
|
-
maxWidth: spacing.medium_16 * 18, // 288px
|
|
115
|
-
padding: spacing.large_24,
|
|
116
|
-
overflow: "hidden",
|
|
117
|
-
justifyContent: "center",
|
|
118
|
-
},
|
|
119
|
-
/**
|
|
120
|
-
* Theming
|
|
121
|
-
*/
|
|
122
|
-
blue: {
|
|
123
|
-
backgroundColor: color.blue,
|
|
124
|
-
color: color.white,
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
darkBlue: {
|
|
128
|
-
backgroundColor: color.darkBlue,
|
|
129
|
-
color: color.white,
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* elements
|
|
134
|
-
*/
|
|
135
|
-
closeButton: {
|
|
136
|
-
margin: 0,
|
|
137
|
-
position: "absolute",
|
|
138
|
-
right: spacing.xxxSmall_4,
|
|
139
|
-
top: spacing.xxxSmall_4,
|
|
140
|
-
// Allows the button to be above the title and/or custom content
|
|
141
|
-
zIndex: 1,
|
|
142
|
-
},
|
|
143
|
-
});
|
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {StyleSheet} from "aphrodite";
|
|
3
|
-
|
|
4
|
-
import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
|
|
5
|
-
import {addStyle, View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
-
import {spacing} from "@khanacademy/wonder-blocks-tokens";
|
|
7
|
-
import {Body, HeadingSmall} from "@khanacademy/wonder-blocks-typography";
|
|
8
|
-
|
|
9
|
-
import type {PopoverContextType} from "./popover-context";
|
|
10
|
-
|
|
11
|
-
import PopoverContentCore from "./popover-content-core";
|
|
12
|
-
import PopoverContext from "./popover-context";
|
|
13
|
-
|
|
14
|
-
type CommonProps = AriaProps & {
|
|
15
|
-
/**
|
|
16
|
-
* The content to render inside the popover.
|
|
17
|
-
*/
|
|
18
|
-
content: string;
|
|
19
|
-
/**
|
|
20
|
-
* The popover title
|
|
21
|
-
*/
|
|
22
|
-
title: string;
|
|
23
|
-
/**
|
|
24
|
-
* User-defined actions.
|
|
25
|
-
*
|
|
26
|
-
* It can be either a Node or a function using the children-as-function
|
|
27
|
-
* pattern to pass a close function for use anywhere within the actions.
|
|
28
|
-
* This provides a lot of flexibility in terms of what actions may trigger
|
|
29
|
-
* the Popover to close the popover dialog.
|
|
30
|
-
*/
|
|
31
|
-
actions?:
|
|
32
|
-
| React.ReactNode
|
|
33
|
-
| ((arg1: {close: () => unknown}) => React.ReactElement);
|
|
34
|
-
/**
|
|
35
|
-
* Close button label for use in screen readers
|
|
36
|
-
*/
|
|
37
|
-
closeButtonLabel?: string;
|
|
38
|
-
/**
|
|
39
|
-
* When true, the close button is shown; otherwise, the close button is not shown.
|
|
40
|
-
*/
|
|
41
|
-
closeButtonVisible?: boolean;
|
|
42
|
-
/**
|
|
43
|
-
* Custom styles to be injected to the popover content container
|
|
44
|
-
*/
|
|
45
|
-
style?: StyleType;
|
|
46
|
-
/**
|
|
47
|
-
* Test ID used for e2e testing.
|
|
48
|
-
*/
|
|
49
|
-
testId?: string;
|
|
50
|
-
/**
|
|
51
|
-
* Unique ID for the popover. This is used as a prefix to the IDs of the
|
|
52
|
-
* popover's elements.
|
|
53
|
-
* @ignore
|
|
54
|
-
*/
|
|
55
|
-
uniqueId?: string;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
type Props =
|
|
59
|
-
| (CommonProps & {
|
|
60
|
-
/**
|
|
61
|
-
* Decorate the popover with an illustrated icon. It cannot be used at the
|
|
62
|
-
* same time with image.
|
|
63
|
-
*/
|
|
64
|
-
icon?:
|
|
65
|
-
| string
|
|
66
|
-
| React.ReactElement<React.ComponentProps<"img">>
|
|
67
|
-
| React.ReactElement<React.ComponentProps<"svg">>;
|
|
68
|
-
/**
|
|
69
|
-
* Decorate the popover with a full-bleed illustration. It cannot be used at
|
|
70
|
-
* the same time with icon.
|
|
71
|
-
*/
|
|
72
|
-
image?:
|
|
73
|
-
| React.ReactElement<React.ComponentProps<"img">>
|
|
74
|
-
| React.ReactElement<React.ComponentProps<"svg">>;
|
|
75
|
-
|
|
76
|
-
emphasized?: never;
|
|
77
|
-
})
|
|
78
|
-
| (CommonProps & {
|
|
79
|
-
/**
|
|
80
|
-
* When true, changes the popover dialog background to blue; otherwise, the
|
|
81
|
-
* popover dialog background is not modified. It can be used only with
|
|
82
|
-
* Text-only popovers. It cannot be used with icon or image.
|
|
83
|
-
*/
|
|
84
|
-
emphasized?: boolean;
|
|
85
|
-
|
|
86
|
-
icon?: never;
|
|
87
|
-
image?: never;
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
type DefaultProps = {
|
|
91
|
-
closeButtonVisible: Props["closeButtonVisible"];
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// Created to add custom styles to the icon or image elements
|
|
95
|
-
const StyledImage = addStyle("img");
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* This is the container that is consumed by all the predefined variations. Its
|
|
99
|
-
* main responsibility is populate the contents depending on the variation used.
|
|
100
|
-
*
|
|
101
|
-
* ### Usage
|
|
102
|
-
*
|
|
103
|
-
* ```jsx
|
|
104
|
-
* import {PopoverContent} from "@khanacademy/wonder-blocks-popover";
|
|
105
|
-
*
|
|
106
|
-
* <PopoverContent
|
|
107
|
-
* closeButtonVisible
|
|
108
|
-
* content="Some content for the popover"
|
|
109
|
-
* title="Popover with text only"
|
|
110
|
-
* />
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
export default class PopoverContent extends React.Component<Props> {
|
|
114
|
-
static defaultProps: DefaultProps = {
|
|
115
|
-
closeButtonVisible: false,
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
componentDidMount() {
|
|
119
|
-
const {icon, image} = this.props;
|
|
120
|
-
|
|
121
|
-
// this runtime check is added to support <svg> and <img> elements
|
|
122
|
-
// inside the image prop
|
|
123
|
-
if (image && icon) {
|
|
124
|
-
throw new Error(
|
|
125
|
-
"'image' and 'icon' cannot be used at the same time. You can fix this by either removing 'image' or 'icon' from your instance.",
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Runtime validation in case we try to use an invalid shape
|
|
132
|
-
*/
|
|
133
|
-
validateProps({placement}: PopoverContextType) {
|
|
134
|
-
// illustration popover can't be placed horizontally
|
|
135
|
-
if (
|
|
136
|
-
this.props.image &&
|
|
137
|
-
(placement === "left" || placement === "right")
|
|
138
|
-
) {
|
|
139
|
-
throw new Error(
|
|
140
|
-
"'image' can only be vertically placed. You can fix this by either changing `placement` to `top` or `bottom` or removing the `image` prop inside `content`.",
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// @ts-expect-error [FEI-5019] - TS2322 - Type '({ placement, }: PopoverContextType) => Element | null' is not assignable to type '(context: PopoverContextType) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
146
|
-
maybeRenderImage: (context: PopoverContextType) => React.ReactElement = ({
|
|
147
|
-
placement,
|
|
148
|
-
}) => {
|
|
149
|
-
const {image} = this.props;
|
|
150
|
-
|
|
151
|
-
if (!image) {
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return (
|
|
156
|
-
<View
|
|
157
|
-
style={[
|
|
158
|
-
styles.image,
|
|
159
|
-
placement === "bottom" && styles.imageToBottom,
|
|
160
|
-
]}
|
|
161
|
-
>
|
|
162
|
-
{image}
|
|
163
|
-
</View>
|
|
164
|
-
);
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
// @ts-expect-error [FEI-5019] - TS2322 - Type '() => JSX.Element | null' is not assignable to type '() => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
168
|
-
maybeRenderIcon: () => React.ReactElement = () => {
|
|
169
|
-
const {icon} = this.props;
|
|
170
|
-
|
|
171
|
-
if (!icon) {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return (
|
|
176
|
-
<View style={styles.iconContainer}>
|
|
177
|
-
{typeof icon !== "string" ? (
|
|
178
|
-
icon
|
|
179
|
-
) : (
|
|
180
|
-
<StyledImage src={icon} style={styles.icon} />
|
|
181
|
-
)}
|
|
182
|
-
</View>
|
|
183
|
-
);
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
// @ts-expect-error [FEI-5019] - TS2322 - Type '(close: () => unknown) => Element | null' is not assignable to type '(close: () => unknown) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
187
|
-
maybeRenderActions: (close: () => unknown) => React.ReactElement = (
|
|
188
|
-
close,
|
|
189
|
-
) => {
|
|
190
|
-
const {actions} = this.props;
|
|
191
|
-
|
|
192
|
-
if (!actions) {
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return (
|
|
197
|
-
<View style={styles.actions}>
|
|
198
|
-
{typeof actions === "function"
|
|
199
|
-
? actions({
|
|
200
|
-
close: close,
|
|
201
|
-
})
|
|
202
|
-
: actions}
|
|
203
|
-
</View>
|
|
204
|
-
);
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
render(): React.ReactNode {
|
|
208
|
-
const {
|
|
209
|
-
closeButtonLabel,
|
|
210
|
-
closeButtonVisible,
|
|
211
|
-
content,
|
|
212
|
-
emphasized = undefined,
|
|
213
|
-
icon,
|
|
214
|
-
image,
|
|
215
|
-
style,
|
|
216
|
-
title,
|
|
217
|
-
testId,
|
|
218
|
-
uniqueId,
|
|
219
|
-
} = this.props;
|
|
220
|
-
|
|
221
|
-
return (
|
|
222
|
-
<PopoverContext.Consumer>
|
|
223
|
-
{({close, placement}) => {
|
|
224
|
-
// verify if the props are correct
|
|
225
|
-
this.validateProps({close, placement});
|
|
226
|
-
|
|
227
|
-
return (
|
|
228
|
-
<PopoverContentCore
|
|
229
|
-
color={emphasized ? "blue" : "white"}
|
|
230
|
-
closeButtonLight={image && placement === "top"}
|
|
231
|
-
closeButtonLabel={closeButtonLabel}
|
|
232
|
-
closeButtonVisible={closeButtonVisible}
|
|
233
|
-
style={style}
|
|
234
|
-
testId={testId}
|
|
235
|
-
>
|
|
236
|
-
<View style={!!icon && styles.withIcon}>
|
|
237
|
-
{this.maybeRenderImage({placement})}
|
|
238
|
-
|
|
239
|
-
{this.maybeRenderIcon()}
|
|
240
|
-
|
|
241
|
-
<View style={styles.text}>
|
|
242
|
-
<HeadingSmall
|
|
243
|
-
id={`${uniqueId}-title`}
|
|
244
|
-
style={styles.title}
|
|
245
|
-
>
|
|
246
|
-
{title}
|
|
247
|
-
</HeadingSmall>
|
|
248
|
-
<Body id={`${uniqueId}-content`}>
|
|
249
|
-
{content}
|
|
250
|
-
</Body>
|
|
251
|
-
</View>
|
|
252
|
-
</View>
|
|
253
|
-
|
|
254
|
-
{this.maybeRenderActions(close as any)}
|
|
255
|
-
</PopoverContentCore>
|
|
256
|
-
);
|
|
257
|
-
}}
|
|
258
|
-
</PopoverContext.Consumer>
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const styles = StyleSheet.create({
|
|
264
|
-
/**
|
|
265
|
-
* Shared styles
|
|
266
|
-
*/
|
|
267
|
-
actions: {
|
|
268
|
-
marginTop: spacing.large_24,
|
|
269
|
-
flexDirection: "row",
|
|
270
|
-
alignItems: "center",
|
|
271
|
-
justifyContent: "flex-end",
|
|
272
|
-
},
|
|
273
|
-
|
|
274
|
-
text: {
|
|
275
|
-
justifyContent: "center",
|
|
276
|
-
},
|
|
277
|
-
|
|
278
|
-
title: {
|
|
279
|
-
marginBottom: spacing.xSmall_8,
|
|
280
|
-
},
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Icon styles
|
|
284
|
-
*/
|
|
285
|
-
iconContainer: {
|
|
286
|
-
alignItems: "center",
|
|
287
|
-
justifyContent: "center",
|
|
288
|
-
height: spacing.xxxLarge_64,
|
|
289
|
-
width: spacing.xxxLarge_64,
|
|
290
|
-
minWidth: spacing.xxxLarge_64,
|
|
291
|
-
marginRight: spacing.medium_16,
|
|
292
|
-
overflow: "hidden",
|
|
293
|
-
},
|
|
294
|
-
|
|
295
|
-
icon: {
|
|
296
|
-
width: "100%",
|
|
297
|
-
},
|
|
298
|
-
|
|
299
|
-
withIcon: {
|
|
300
|
-
flexDirection: "row",
|
|
301
|
-
},
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Illustration styles
|
|
305
|
-
*/
|
|
306
|
-
image: {
|
|
307
|
-
marginBottom: spacing.large_24,
|
|
308
|
-
marginLeft: -spacing.large_24,
|
|
309
|
-
marginRight: -spacing.large_24,
|
|
310
|
-
marginTop: -spacing.large_24,
|
|
311
|
-
width: `calc(100% + ${spacing.large_24 * 2}px)`,
|
|
312
|
-
},
|
|
313
|
-
|
|
314
|
-
imageToBottom: {
|
|
315
|
-
marginBottom: -spacing.large_24,
|
|
316
|
-
marginTop: spacing.large_24,
|
|
317
|
-
order: 1,
|
|
318
|
-
},
|
|
319
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import type {Placement} from "@khanacademy/wonder-blocks-tooltip";
|
|
4
|
-
|
|
5
|
-
export type PopoverContextType = {
|
|
6
|
-
/**
|
|
7
|
-
* Facilitates passing the `onClose` handler from the Popover down to its
|
|
8
|
-
* children.
|
|
9
|
-
*/
|
|
10
|
-
close?: () => unknown;
|
|
11
|
-
/**
|
|
12
|
-
* Facilitates passing this value from Popover (via TooltipPopper) down to
|
|
13
|
-
* PopoverContent. This is needed here to reposition the illustration to the
|
|
14
|
-
* start or the end of the content, in case the popper changes its
|
|
15
|
-
* placement.
|
|
16
|
-
*/
|
|
17
|
-
placement?: Placement;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const defaultContext: PopoverContextType = {
|
|
21
|
-
close: undefined,
|
|
22
|
-
placement: "top",
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* This context is being used for two reasons:
|
|
27
|
-
*
|
|
28
|
-
* 1. Pass down the `close` method from the `Popover` component to its children
|
|
29
|
-
* (`PopoverContent` and `CloseButton`). This way, these components can use
|
|
30
|
-
* this handler internally.
|
|
31
|
-
*
|
|
32
|
-
* 2. Keeps a reference of the TooltipPopper's `placement` value. It can be one
|
|
33
|
-
* of the following values: "top", "bottom", "left" or "right".
|
|
34
|
-
*/
|
|
35
|
-
const PopoverContext = React.createContext<PopoverContextType>(
|
|
36
|
-
defaultContext,
|
|
37
|
-
) as React.Context<PopoverContextType>;
|
|
38
|
-
PopoverContext.displayName = "PopoverContext";
|
|
39
|
-
|
|
40
|
-
export default PopoverContext;
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {StyleSheet} from "aphrodite";
|
|
3
|
-
|
|
4
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
5
|
-
import {TooltipTail} from "@khanacademy/wonder-blocks-tooltip";
|
|
6
|
-
import * as tokens from "@khanacademy/wonder-blocks-tokens";
|
|
7
|
-
|
|
8
|
-
import type {AriaProps} from "@khanacademy/wonder-blocks-core";
|
|
9
|
-
import type {
|
|
10
|
-
Placement,
|
|
11
|
-
PopperElementProps,
|
|
12
|
-
} from "@khanacademy/wonder-blocks-tooltip";
|
|
13
|
-
|
|
14
|
-
import PopoverContent from "./popover-content";
|
|
15
|
-
import PopoverContentCore from "./popover-content-core";
|
|
16
|
-
|
|
17
|
-
type Props = AriaProps &
|
|
18
|
-
/**
|
|
19
|
-
* Required to correctly position the elements inside the dialog
|
|
20
|
-
* @ignore
|
|
21
|
-
*/ PopperElementProps & {
|
|
22
|
-
/**
|
|
23
|
-
* The content to render inside the dialog.
|
|
24
|
-
*/
|
|
25
|
-
children:
|
|
26
|
-
| React.ReactElement<React.ComponentProps<typeof PopoverContent>>
|
|
27
|
-
| React.ReactElement<
|
|
28
|
-
React.ComponentProps<typeof PopoverContentCore>
|
|
29
|
-
>;
|
|
30
|
-
/**
|
|
31
|
-
* The unique identifier to give to the popover content.
|
|
32
|
-
*/
|
|
33
|
-
id?: string;
|
|
34
|
-
/**
|
|
35
|
-
* Called when popper changes its placement
|
|
36
|
-
*/
|
|
37
|
-
onUpdate: (placement: Placement) => unknown;
|
|
38
|
-
/**
|
|
39
|
-
* Whether to show the popover tail or not.
|
|
40
|
-
*/
|
|
41
|
-
showTail: boolean;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* This is an internal component that we use to render the stuff that appears
|
|
46
|
-
* when a popover shows. It's composed by two elements: The popover content,
|
|
47
|
-
* that can be of type [PopoverContent](#PopoverContent) or
|
|
48
|
-
* [PopoverContentCore](#PopoverContentCore), and the
|
|
49
|
-
* [TooltipTail](#TooltipTail).
|
|
50
|
-
*
|
|
51
|
-
* The main difference with [TooltipBubble](#TooltipBubble) is that bubble
|
|
52
|
-
* handles hover states and PopoverDialog doesn't need to handle any states at
|
|
53
|
-
* all (for now). Also, PopoverDialog needs to coordinate different background
|
|
54
|
-
* colors for the content and tail components.
|
|
55
|
-
*
|
|
56
|
-
* Note that without explicit positioning, the tail will not be centered.
|
|
57
|
-
*/
|
|
58
|
-
export default class PopoverDialog extends React.Component<Props> {
|
|
59
|
-
componentDidUpdate(prevProps: Props) {
|
|
60
|
-
// if the placement has changed, then we need to notify this to the
|
|
61
|
-
// parent component (`Popover`). This way, the context will update its
|
|
62
|
-
// `placement` value.
|
|
63
|
-
if (prevProps.placement !== this.props.placement) {
|
|
64
|
-
this.props.onUpdate(this.props.placement);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
render(): React.ReactNode {
|
|
69
|
-
const {
|
|
70
|
-
placement,
|
|
71
|
-
children,
|
|
72
|
-
id,
|
|
73
|
-
isReferenceHidden,
|
|
74
|
-
updateBubbleRef,
|
|
75
|
-
updateTailRef,
|
|
76
|
-
tailOffset,
|
|
77
|
-
style,
|
|
78
|
-
showTail,
|
|
79
|
-
"aria-describedby": ariaDescribedby,
|
|
80
|
-
"aria-labelledby": ariaLabelledBy,
|
|
81
|
-
"aria-label": ariaLabel,
|
|
82
|
-
} = this.props;
|
|
83
|
-
|
|
84
|
-
const contentProps = children.props as any;
|
|
85
|
-
|
|
86
|
-
// extract the background color from the popover content
|
|
87
|
-
const color: keyof typeof tokens.color = contentProps.emphasized
|
|
88
|
-
? "blue"
|
|
89
|
-
: contentProps.color;
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<React.Fragment>
|
|
93
|
-
<View
|
|
94
|
-
aria-label={ariaLabel}
|
|
95
|
-
aria-describedby={ariaDescribedby}
|
|
96
|
-
aria-labelledby={ariaLabelledBy}
|
|
97
|
-
id={id}
|
|
98
|
-
role="dialog"
|
|
99
|
-
ref={updateBubbleRef}
|
|
100
|
-
data-placement={placement}
|
|
101
|
-
style={[
|
|
102
|
-
isReferenceHidden && styles.hide,
|
|
103
|
-
styles[`content-${placement}`],
|
|
104
|
-
style,
|
|
105
|
-
]}
|
|
106
|
-
>
|
|
107
|
-
{children}
|
|
108
|
-
<TooltipTail
|
|
109
|
-
show={showTail}
|
|
110
|
-
color={color}
|
|
111
|
-
updateRef={updateTailRef}
|
|
112
|
-
placement={placement}
|
|
113
|
-
offset={tailOffset}
|
|
114
|
-
/>
|
|
115
|
-
</View>
|
|
116
|
-
</React.Fragment>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const styles = StyleSheet.create({
|
|
122
|
-
/**
|
|
123
|
-
* The hide style ensures that the bounds of the popover stay unchanged.
|
|
124
|
-
* This is because popper.js calculates the bubble position based off its
|
|
125
|
-
* bounds and if we stopped rendering it entirely, it wouldn't know where to
|
|
126
|
-
* place it when it reappeared.
|
|
127
|
-
*/
|
|
128
|
-
hide: {
|
|
129
|
-
pointerEvents: "none",
|
|
130
|
-
opacity: 0,
|
|
131
|
-
backgroundColor: "transparent",
|
|
132
|
-
color: "transparent",
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Ensure the content and tail are properly arranged.
|
|
137
|
-
*/
|
|
138
|
-
"content-top": {
|
|
139
|
-
flexDirection: "column",
|
|
140
|
-
},
|
|
141
|
-
"content-right": {
|
|
142
|
-
flexDirection: "row-reverse",
|
|
143
|
-
},
|
|
144
|
-
"content-bottom": {
|
|
145
|
-
flexDirection: "column-reverse",
|
|
146
|
-
},
|
|
147
|
-
"content-left": {
|
|
148
|
-
flexDirection: "row",
|
|
149
|
-
},
|
|
150
|
-
});
|