@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.
@@ -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
- });