@khanacademy/wonder-blocks-modal 4.0.39 → 4.2.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/dist/es/index.js CHANGED
@@ -1,114 +1,174 @@
1
1
  import * as React from 'react';
2
- import { StyleSheet } from 'aphrodite';
3
- import { MediaLayoutContext, MediaLayout, MEDIA_MODAL_SPEC } from '@khanacademy/wonder-blocks-layout';
4
2
  import { View, IDProvider } from '@khanacademy/wonder-blocks-core';
5
- import Spacing from '@khanacademy/wonder-blocks-spacing';
3
+ import { tokens, mergeTheme, createThemeContext, ThemeSwitcherContext, useScopedTheme, useStyles } from '@khanacademy/wonder-blocks-theming';
4
+ import { StyleSheet } from 'aphrodite';
6
5
  import Color from '@khanacademy/wonder-blocks-color';
6
+ import Spacing from '@khanacademy/wonder-blocks-spacing';
7
7
  import { HeadingMedium, LabelSmall } from '@khanacademy/wonder-blocks-typography';
8
8
  import * as ReactDOM from 'react-dom';
9
9
  import { withActionScheduler } from '@khanacademy/wonder-blocks-timing';
10
10
  import xIcon from '@phosphor-icons/core/regular/x.svg';
11
11
  import IconButton from '@khanacademy/wonder-blocks-icon-button';
12
+ import { MediaLayout } from '@khanacademy/wonder-blocks-layout';
12
13
 
13
- class ModalDialog extends React.Component {
14
- render() {
15
- const {
16
- above,
17
- below,
18
- role,
19
- style,
20
- children,
21
- testId,
22
- "aria-labelledby": ariaLabelledBy,
23
- "aria-describedby": ariaDescribedBy
24
- } = this.props;
25
- const contextValue = {
26
- ssrSize: "large",
27
- mediaSpec: MEDIA_MODAL_SPEC
28
- };
29
- return React.createElement(MediaLayoutContext.Provider, {
30
- value: contextValue
31
- }, React.createElement(MediaLayout, {
32
- styleSheets: styleSheets$3
33
- }, ({
34
- styles
35
- }) => React.createElement(View, {
36
- style: [styles.wrapper, style]
37
- }, below && React.createElement(View, {
38
- style: styles.below
39
- }, below), React.createElement(View, {
40
- role: role,
41
- "aria-modal": "true",
42
- "aria-labelledby": ariaLabelledBy,
43
- "aria-describedby": ariaDescribedBy,
44
- style: styles.dialog,
45
- testId: testId
46
- }, children), above && React.createElement(View, {
47
- style: styles.above
48
- }, above))));
49
- }
14
+ function _extends() {
15
+ _extends = Object.assign ? Object.assign.bind() : function (target) {
16
+ for (var i = 1; i < arguments.length; i++) {
17
+ var source = arguments[i];
18
+ for (var key in source) {
19
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
20
+ target[key] = source[key];
21
+ }
22
+ }
23
+ }
24
+ return target;
25
+ };
26
+ return _extends.apply(this, arguments);
50
27
  }
51
- ModalDialog.defaultProps = {
52
- role: "dialog"
53
- };
54
- const styleSheets$3 = {
55
- all: StyleSheet.create({
56
- wrapper: {
57
- display: "flex",
58
- flexDirection: "row",
59
- alignItems: "stretch",
60
- width: "100%",
61
- height: "100%",
62
- position: "relative"
28
+
29
+ const theme$1 = {
30
+ color: {
31
+ bg: {
32
+ inverse: tokens.color.darkBlue
33
+ },
34
+ text: {
35
+ inverse: tokens.color.white,
36
+ secondary: tokens.color.offBlack64
63
37
  },
38
+ shadow: {
39
+ default: tokens.color.offBlack16
40
+ }
41
+ },
42
+ border: {
43
+ radius: tokens.border.radius.medium_4
44
+ },
45
+ spacing: {
64
46
  dialog: {
65
- width: "100%",
66
- height: "100%",
67
- borderRadius: 4,
68
- overflow: "hidden"
47
+ small: tokens.spacing.medium_16
69
48
  },
70
- above: {
71
- pointerEvents: "none",
72
- position: "absolute",
73
- top: 0,
74
- left: 0,
75
- bottom: 0,
76
- right: 0,
77
- zIndex: 1
49
+ panel: {
50
+ closeButton: tokens.spacing.medium_16,
51
+ footer: tokens.spacing.xLarge_32
78
52
  },
79
- below: {
80
- pointerEvents: "none",
81
- position: "absolute",
82
- top: 0,
83
- left: 0,
84
- bottom: 0,
85
- right: 0,
86
- zIndex: -1
87
- }
88
- }),
89
- small: StyleSheet.create({
90
- wrapper: {
91
- padding: Spacing.medium_16,
92
- flexDirection: "column"
53
+ header: {
54
+ xsmall: tokens.spacing.xSmall_8,
55
+ small: tokens.spacing.medium_16,
56
+ medium: tokens.spacing.large_24,
57
+ large: tokens.spacing.xLarge_32
93
58
  }
94
- })
59
+ }
95
60
  };
96
61
 
97
- class ModalFooter extends React.Component {
98
- static isClassOf(instance) {
99
- return instance && instance.type && instance.type.__IS_MODAL_FOOTER__;
62
+ const theme = mergeTheme(theme$1, {
63
+ color: {
64
+ bg: {
65
+ inverse: tokens.color.eggplant
66
+ }
100
67
  }
101
- render() {
102
- const {
103
- children
104
- } = this.props;
105
- return React.createElement(View, {
106
- style: styles$3.footer
107
- }, children);
68
+ });
69
+
70
+ const themes = {
71
+ default: theme$1,
72
+ khanmigo: theme
73
+ };
74
+ const ModalDialogThemeContext = createThemeContext(theme$1);
75
+ function ThemeModalDialog(props) {
76
+ const currentTheme = React.useContext(ThemeSwitcherContext);
77
+ const theme = themes[currentTheme] || theme$1;
78
+ return React.createElement(ModalDialogThemeContext.Provider, {
79
+ value: theme
80
+ }, props.children);
81
+ }
82
+
83
+ const ModalDialogCore = React.forwardRef(function ModalDialogCore(props, ref) {
84
+ const {
85
+ above,
86
+ below,
87
+ role = "dialog",
88
+ style,
89
+ children,
90
+ testId,
91
+ "aria-labelledby": ariaLabelledBy,
92
+ "aria-describedby": ariaDescribedBy
93
+ } = props;
94
+ const {
95
+ theme
96
+ } = useScopedTheme(ModalDialogThemeContext);
97
+ const styles = useStyles(themedStylesFn$3, theme);
98
+ return React.createElement(View, {
99
+ style: [styles.wrapper, style]
100
+ }, below && React.createElement(View, {
101
+ style: styles.below
102
+ }, below), React.createElement(View, {
103
+ role: role,
104
+ "aria-modal": "true",
105
+ "aria-labelledby": ariaLabelledBy,
106
+ "aria-describedby": ariaDescribedBy,
107
+ ref: ref,
108
+ style: styles.dialog,
109
+ testId: testId
110
+ }, children), above && React.createElement(View, {
111
+ style: styles.above
112
+ }, above));
113
+ });
114
+ const ModalDialog = React.forwardRef(function ModalDialog(props, ref) {
115
+ return React.createElement(ThemeModalDialog, null, React.createElement(ModalDialogCore, _extends({}, props, {
116
+ ref: ref
117
+ })));
118
+ });
119
+ const small$2 = "@media (max-width: 767px)";
120
+ const themedStylesFn$3 = theme => ({
121
+ wrapper: {
122
+ display: "flex",
123
+ flexDirection: "row",
124
+ alignItems: "stretch",
125
+ width: "100%",
126
+ height: "100%",
127
+ position: "relative",
128
+ [small$2]: {
129
+ padding: theme.spacing.dialog.small,
130
+ flexDirection: "column"
131
+ }
132
+ },
133
+ dialog: {
134
+ width: "100%",
135
+ height: "100%",
136
+ borderRadius: theme.border.radius,
137
+ overflow: "hidden"
138
+ },
139
+ above: {
140
+ pointerEvents: "none",
141
+ position: "absolute",
142
+ top: 0,
143
+ left: 0,
144
+ bottom: 0,
145
+ right: 0,
146
+ zIndex: 1
147
+ },
148
+ below: {
149
+ pointerEvents: "none",
150
+ position: "absolute",
151
+ top: 0,
152
+ left: 0,
153
+ bottom: 0,
154
+ right: 0,
155
+ zIndex: -1
108
156
  }
157
+ });
158
+ ModalDialog.displayName = "ModalDialog";
159
+
160
+ function ModalFooter({
161
+ children
162
+ }) {
163
+ return React.createElement(View, {
164
+ style: styles$2.footer
165
+ }, children);
109
166
  }
110
167
  ModalFooter.__IS_MODAL_FOOTER__ = true;
111
- const styles$3 = StyleSheet.create({
168
+ ModalFooter.isComponentOf = instance => {
169
+ return instance && instance.type && instance.type.__IS_MODAL_FOOTER__;
170
+ };
171
+ const styles$2 = StyleSheet.create({
112
172
  footer: {
113
173
  flex: "0 0 auto",
114
174
  boxSizing: "border-box",
@@ -125,78 +185,73 @@ const styles$3 = StyleSheet.create({
125
185
  }
126
186
  });
127
187
 
128
- class ModalHeader extends React.Component {
129
- render() {
130
- const {
131
- breadcrumbs = undefined,
132
- light,
133
- subtitle = undefined,
134
- testId,
135
- title,
136
- titleId
137
- } = this.props;
138
- if (subtitle && breadcrumbs) {
139
- throw new Error("'subtitle' and 'breadcrumbs' can't be used together");
188
+ function ModalHeader(props) {
189
+ const {
190
+ breadcrumbs = undefined,
191
+ light,
192
+ subtitle = undefined,
193
+ testId,
194
+ title,
195
+ titleId
196
+ } = props;
197
+ if (subtitle && breadcrumbs) {
198
+ throw new Error("'subtitle' and 'breadcrumbs' can't be used together");
199
+ }
200
+ const {
201
+ theme
202
+ } = useScopedTheme(ModalDialogThemeContext);
203
+ const styles = useStyles(themedStylesFn$2, theme);
204
+ return React.createElement(View, {
205
+ style: [styles.header, !light && styles.dark],
206
+ testId: testId
207
+ }, breadcrumbs && React.createElement(View, {
208
+ style: styles.breadcrumbs
209
+ }, breadcrumbs), React.createElement(HeadingMedium, {
210
+ style: styles.title,
211
+ id: titleId,
212
+ testId: testId && `${testId}-title`
213
+ }, title), subtitle && React.createElement(LabelSmall, {
214
+ style: light && styles.subtitle,
215
+ testId: testId && `${testId}-subtitle`
216
+ }, subtitle));
217
+ }
218
+ const small$1 = "@media (max-width: 767px)";
219
+ const themedStylesFn$2 = theme => ({
220
+ header: {
221
+ boxShadow: `0px 1px 0px ${theme.color.shadow.default}`,
222
+ display: "flex",
223
+ flexDirection: "column",
224
+ minHeight: 66,
225
+ padding: `${theme.spacing.header.medium}px ${theme.spacing.header.large}px`,
226
+ position: "relative",
227
+ width: "100%",
228
+ [small$1]: {
229
+ paddingLeft: theme.spacing.header.small,
230
+ paddingRight: theme.spacing.header.small
140
231
  }
141
- return React.createElement(MediaLayout, {
142
- styleSheets: styleSheets$2
143
- }, ({
144
- styles
145
- }) => React.createElement(View, {
146
- style: [styles.header, !light && styles.dark],
147
- testId: testId
148
- }, breadcrumbs && React.createElement(View, {
149
- style: styles.breadcrumbs
150
- }, breadcrumbs), React.createElement(HeadingMedium, {
151
- style: styles.title,
152
- id: titleId,
153
- testId: testId && `${testId}-title`
154
- }, title), subtitle && React.createElement(LabelSmall, {
155
- style: light && styles.subtitle,
156
- testId: testId && `${testId}-subtitle`
157
- }, subtitle)));
232
+ },
233
+ dark: {
234
+ background: theme.color.bg.inverse,
235
+ color: theme.color.text.inverse
236
+ },
237
+ breadcrumbs: {
238
+ color: theme.color.text.secondary,
239
+ marginBottom: theme.spacing.header.xsmall
240
+ },
241
+ title: {
242
+ paddingRight: theme.spacing.header.small,
243
+ [small$1]: {
244
+ paddingRight: theme.spacing.header.large
245
+ }
246
+ },
247
+ subtitle: {
248
+ color: theme.color.text.secondary,
249
+ marginTop: theme.spacing.header.xsmall
158
250
  }
159
- }
251
+ });
160
252
  ModalHeader.defaultProps = {
161
253
  light: true
162
254
  };
163
- const styleSheets$2 = {
164
- all: StyleSheet.create({
165
- header: {
166
- boxShadow: `0px 1px 0px ${Color.offBlack16}`,
167
- display: "flex",
168
- flexDirection: "column",
169
- minHeight: 66,
170
- padding: `${Spacing.large_24}px ${Spacing.xLarge_32}px`,
171
- position: "relative",
172
- width: "100%"
173
- },
174
- dark: {
175
- background: Color.darkBlue,
176
- color: Color.white
177
- },
178
- breadcrumbs: {
179
- color: Color.offBlack64,
180
- marginBottom: Spacing.xSmall_8
181
- },
182
- title: {
183
- paddingRight: Spacing.medium_16
184
- },
185
- subtitle: {
186
- color: Color.offBlack64,
187
- marginTop: Spacing.xSmall_8
188
- }
189
- }),
190
- small: StyleSheet.create({
191
- header: {
192
- paddingLeft: Spacing.medium_16,
193
- paddingRight: Spacing.medium_16
194
- },
195
- title: {
196
- paddingRight: Spacing.xLarge_32
197
- }
198
- })
199
- };
200
255
 
201
256
  const FOCUSABLE_ELEMENTS$1 = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
202
257
  class FocusTrap extends React.Component {
@@ -260,21 +315,6 @@ class FocusTrap extends React.Component {
260
315
  }
261
316
  }
262
317
 
263
- function _extends() {
264
- _extends = Object.assign ? Object.assign.bind() : function (target) {
265
- for (var i = 1; i < arguments.length; i++) {
266
- var source = arguments[i];
267
- for (var key in source) {
268
- if (Object.prototype.hasOwnProperty.call(source, key)) {
269
- target[key] = source[key];
270
- }
271
- }
272
- }
273
- return target;
274
- };
275
- return _extends.apply(this, arguments);
276
- }
277
-
278
318
  const ModalLauncherPortalAttributeName = "data-modal-launcher-portal";
279
319
 
280
320
  const FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
@@ -336,14 +376,14 @@ class ModalBackdrop extends React.Component {
336
376
  [ModalLauncherPortalAttributeName]: true
337
377
  };
338
378
  return React.createElement(View, _extends({
339
- style: styles$2.modalPositioner,
379
+ style: styles$1.modalPositioner,
340
380
  onMouseDown: this.handleMouseDown,
341
381
  onMouseUp: this.handleMouseUp,
342
382
  testId: testId
343
383
  }, backdropProps), children);
344
384
  }
345
385
  }
346
- const styles$2 = StyleSheet.create({
386
+ const styles$1 = StyleSheet.create({
347
387
  modalPositioner: {
348
388
  position: "fixed",
349
389
  left: 0,
@@ -514,7 +554,7 @@ class ModalLauncher extends React.Component {
514
554
  closeModal: this.handleCloseModal
515
555
  }
516
556
  }, renderedChildren, this.state.opened && ReactDOM.createPortal(React.createElement(FocusTrap, {
517
- style: styles$1.container
557
+ style: styles.container
518
558
  }, React.createElement(ModalBackdrop, {
519
559
  initialFocusId: this.props.initialFocusId,
520
560
  testId: this.props.testId,
@@ -548,59 +588,54 @@ class ModalLauncherKeypressListener extends React.Component {
548
588
  return null;
549
589
  }
550
590
  }
551
- const styles$1 = StyleSheet.create({
591
+ const styles = StyleSheet.create({
552
592
  container: {
553
593
  zIndex: 1080
554
594
  }
555
595
  });
556
596
  var modalLauncher = withActionScheduler(ModalLauncher);
557
597
 
558
- class ModalContent extends React.Component {
559
- static isClassOf(instance) {
560
- return instance && instance.type && instance.type.__IS_MODAL_CONTENT__;
561
- }
562
- render() {
563
- const {
564
- scrollOverflow,
565
- style,
566
- children
567
- } = this.props;
568
- return React.createElement(MediaLayout, {
569
- styleSheets: styleSheets$1
570
- }, ({
571
- styles
572
- }) => React.createElement(View, {
573
- style: [styles.wrapper, scrollOverflow && styles.scrollOverflow]
574
- }, React.createElement(View, {
575
- style: [styles.content, style]
576
- }, children)));
577
- }
598
+ function ModalContent(props) {
599
+ const {
600
+ scrollOverflow,
601
+ style,
602
+ children
603
+ } = props;
604
+ const {
605
+ theme
606
+ } = useScopedTheme(ModalDialogThemeContext);
607
+ const styles = useStyles(themedStylesFn$1, theme);
608
+ return React.createElement(View, {
609
+ style: [styles.wrapper, scrollOverflow && styles.scrollOverflow]
610
+ }, React.createElement(View, {
611
+ style: [styles.content, style]
612
+ }, children));
578
613
  }
579
- ModalContent.defaultProps = {
580
- scrollOverflow: true
581
- };
582
614
  ModalContent.__IS_MODAL_CONTENT__ = true;
583
- const styleSheets$1 = {
584
- all: StyleSheet.create({
585
- wrapper: {
586
- flex: 1,
587
- display: "block"
588
- },
589
- scrollOverflow: {
590
- overflow: "auto"
591
- },
592
- content: {
593
- flex: 1,
594
- minHeight: "100%",
595
- padding: Spacing.xLarge_32,
596
- boxSizing: "border-box"
597
- }
598
- }),
599
- small: StyleSheet.create({
600
- content: {
615
+ ModalContent.isComponentOf = instance => {
616
+ return instance && instance.type && instance.type.__IS_MODAL_CONTENT__;
617
+ };
618
+ const small = "@media (max-width: 767px)";
619
+ const themedStylesFn$1 = theme => ({
620
+ wrapper: {
621
+ flex: 1,
622
+ display: "block"
623
+ },
624
+ scrollOverflow: {
625
+ overflow: "auto"
626
+ },
627
+ content: {
628
+ flex: 1,
629
+ minHeight: "100%",
630
+ padding: Spacing.xLarge_32,
631
+ boxSizing: "border-box",
632
+ [small]: {
601
633
  padding: `${Spacing.xLarge_32}px ${Spacing.medium_16}px`
602
634
  }
603
- })
635
+ }
636
+ });
637
+ ModalContent.defaultProps = {
638
+ scrollOverflow: true
604
639
  };
605
640
 
606
641
  class CloseButton extends React.Component {
@@ -630,14 +665,23 @@ class CloseButton extends React.Component {
630
665
  }
631
666
  }
632
667
 
633
- class ModalPanel extends React.Component {
634
- renderMainContent() {
635
- const {
636
- content,
637
- footer,
638
- scrollOverflow
639
- } = this.props;
640
- const mainContent = ModalContent.isClassOf(content) ? content : React.createElement(ModalContent, null, content);
668
+ function ModalPanel({
669
+ closeButtonVisible = true,
670
+ scrollOverflow = true,
671
+ light = true,
672
+ content,
673
+ footer,
674
+ header,
675
+ onClose,
676
+ style,
677
+ testId
678
+ }) {
679
+ const {
680
+ theme
681
+ } = useScopedTheme(ModalDialogThemeContext);
682
+ const styles = useStyles(themedStylesFn, theme);
683
+ const renderMainContent = React.useCallback(() => {
684
+ const mainContent = ModalContent.isComponentOf(content) ? content : React.createElement(ModalContent, null, content);
641
685
  if (!mainContent) {
642
686
  return mainContent;
643
687
  }
@@ -645,35 +689,24 @@ class ModalPanel extends React.Component {
645
689
  scrollOverflow,
646
690
  style: [!!footer && styles.hasFooter, mainContent.props.style]
647
691
  });
648
- }
649
- render() {
650
- const {
651
- closeButtonVisible,
652
- footer,
653
- header,
654
- light,
655
- onClose,
656
- style,
657
- testId
658
- } = this.props;
659
- const mainContent = this.renderMainContent();
660
- return React.createElement(View, {
661
- style: [styles.wrapper, !light && styles.dark, style],
662
- testId: testId && `${testId}-panel`
663
- }, closeButtonVisible && React.createElement(CloseButton, {
664
- light: !light,
665
- onClick: onClose,
666
- style: styles.closeButton,
667
- testId: testId && `${testId}-close`
668
- }), header, mainContent, !footer || ModalFooter.isClassOf(footer) ? footer : React.createElement(ModalFooter, null, footer));
669
- }
692
+ }, [content, footer, scrollOverflow, styles.hasFooter]);
693
+ const mainContent = renderMainContent();
694
+ return React.createElement(View, {
695
+ style: [styles.wrapper, !light && styles.dark, style],
696
+ testId: testId && `${testId}-panel`
697
+ }, closeButtonVisible && React.createElement(CloseButton, {
698
+ light: !light,
699
+ onClick: onClose,
700
+ style: styles.closeButton,
701
+ testId: testId && `${testId}-close`
702
+ }), header, mainContent, !footer || ModalFooter.isComponentOf(footer) ? footer : React.createElement(ModalFooter, null, footer));
670
703
  }
671
704
  ModalPanel.defaultProps = {
672
705
  closeButtonVisible: true,
673
706
  scrollOverflow: true,
674
707
  light: true
675
708
  };
676
- const styles = StyleSheet.create({
709
+ const themedStylesFn = theme => ({
677
710
  wrapper: {
678
711
  flex: "1 1 auto",
679
712
  position: "relative",
@@ -687,16 +720,16 @@ const styles = StyleSheet.create({
687
720
  },
688
721
  closeButton: {
689
722
  position: "absolute",
690
- right: Spacing.medium_16,
691
- top: Spacing.medium_16,
723
+ right: theme.spacing.panel.closeButton,
724
+ top: theme.spacing.panel.closeButton,
692
725
  zIndex: 1
693
726
  },
694
727
  dark: {
695
- background: Color.darkBlue,
696
- color: Color.white
728
+ background: theme.color.bg.inverse,
729
+ color: theme.color.text.inverse
697
730
  },
698
731
  hasFooter: {
699
- paddingBottom: Spacing.xLarge_32
732
+ paddingBottom: theme.spacing.panel.footer
700
733
  }
701
734
  });
702
735