@khanacademy/wonder-blocks-modal 7.1.25 → 8.0.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/.turbo/turbo-build$colon$css.log +5 -0
- package/CHANGELOG.md +13 -0
- package/dist/components/modal-backdrop.d.ts +36 -4
- package/dist/components/modal-dialog.d.ts +10 -0
- package/dist/components/modal-header.d.ts +2 -15
- package/dist/components/modal-panel.d.ts +1 -7
- package/dist/css/vars.css +13 -0
- package/dist/es/index.js +13 -16
- package/dist/index.js +10 -13
- package/dist/{themes → theme}/default.d.ts +9 -20
- package/dist/theme/index.d.ts +42 -0
- package/package.json +13 -5
- package/dist/themes/khanmigo.d.ts +0 -56
- package/dist/themes/themed-modal-dialog.d.ts +0 -68
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
|
|
2
|
+
> @khanacademy/wonder-blocks-modal@8.0.0 build:css /home/runner/work/wonder-blocks/wonder-blocks/packages/wonder-blocks-modal
|
|
3
|
+
> pnpm exec wonder-blocks-tokens .
|
|
4
|
+
|
|
5
|
+
CSS variables generated successfully in: /home/runner/work/wonder-blocks/wonder-blocks/packages/wonder-blocks-modal/dist/css
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-modal
|
|
2
2
|
|
|
3
|
+
## 8.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 9ef528a: Removes khanmigo theme from modal in favor of CSS vars. Uses `sizing` instead of `spacing`.
|
|
8
|
+
- 038f9a9: Removes `light` prop from Modal package.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- Updated dependencies [705ee01]
|
|
13
|
+
- @khanacademy/wonder-blocks-typography@4.2.5
|
|
14
|
+
- @khanacademy/wonder-blocks-icon-button@10.3.4
|
|
15
|
+
|
|
3
16
|
## 7.1.25
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { WithThemeProps } from "@khanacademy/wonder-blocks-theming";
|
|
3
2
|
import type { ModalElement } from "../util/types";
|
|
4
3
|
type Props = {
|
|
5
4
|
children: ModalElement;
|
|
@@ -14,6 +13,39 @@ type Props = {
|
|
|
14
13
|
* Test ID used for e2e testing.
|
|
15
14
|
*/
|
|
16
15
|
testId?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A private component used by ModalLauncher. This is the fixed-position
|
|
19
|
+
* container element that gets mounted outside the DOM. It overlays the modal
|
|
20
|
+
* content (provided as `children`) over the content, with a gray backdrop
|
|
21
|
+
* behind it.
|
|
22
|
+
*
|
|
23
|
+
* This component is also responsible for cloning the provided modal `children`,
|
|
24
|
+
* and adding an `onClose` prop that will call `onCloseModal`. If an
|
|
25
|
+
* `onClose` prop is already provided, the two are merged.
|
|
26
|
+
*/
|
|
27
|
+
export default class ModalBackdrop extends React.Component<Props> {
|
|
28
|
+
componentDidMount(): void;
|
|
29
|
+
_mousePressedOutside: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Returns an element specified by the user
|
|
32
|
+
*/
|
|
33
|
+
_getInitialFocusElement(node: HTMLElement): HTMLElement | null;
|
|
34
|
+
/**
|
|
35
|
+
* Returns the first focusable element found inside the Dialog
|
|
36
|
+
*/
|
|
37
|
+
_getFirstFocusableElement(node: HTMLElement): HTMLElement | null;
|
|
38
|
+
/**
|
|
39
|
+
* Returns the dialog element
|
|
40
|
+
*/
|
|
41
|
+
_getDialogElement(node: HTMLElement): HTMLElement;
|
|
42
|
+
/**
|
|
43
|
+
* When the user clicks on the gray backdrop area (i.e., the click came
|
|
44
|
+
* _directly_ from the positioner, not bubbled up from its children), close
|
|
45
|
+
* the modal.
|
|
46
|
+
*/
|
|
47
|
+
handleMouseDown: (e: React.SyntheticEvent) => void;
|
|
48
|
+
handleMouseUp: (e: React.SyntheticEvent) => void;
|
|
49
|
+
render(): React.ReactNode;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -41,5 +41,15 @@ type Props = {
|
|
|
41
41
|
*/
|
|
42
42
|
"aria-describedby"?: string;
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* `ModalDialog` is a component that contains these elements:
|
|
46
|
+
* - The visual dialog element itself (`<div role="dialog"/>`)
|
|
47
|
+
* - The custom contents below and/or above the Dialog itself (e.g. decorative graphics).
|
|
48
|
+
*
|
|
49
|
+
* **Accessibility notes:**
|
|
50
|
+
* - By default (e.g. using `OnePaneDialog`), `aria-labelledby` is populated automatically using the dialog title `id`.
|
|
51
|
+
* - If there is a custom Dialog implementation (e.g. `TwoPaneDialog`), the dialog element doesn’t have to have
|
|
52
|
+
* the `aria-labelledby` attribute however this is recommended. It should match the `id` of the dialog title.
|
|
53
|
+
*/
|
|
44
54
|
declare const ModalDialog: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLDivElement>>;
|
|
45
55
|
export default ModalDialog;
|
|
@@ -5,11 +5,6 @@ type Common = {
|
|
|
5
5
|
* The main title rendered in larger bold text.
|
|
6
6
|
*/
|
|
7
7
|
title: string;
|
|
8
|
-
/**
|
|
9
|
-
* Whether to display the "light" version of this component instead, for
|
|
10
|
-
* use when the item is used on a dark background.
|
|
11
|
-
*/
|
|
12
|
-
light: boolean;
|
|
13
8
|
/**
|
|
14
9
|
* An id to provide a selector for the title element.
|
|
15
10
|
*/
|
|
@@ -66,8 +61,6 @@ type Props = Common | WithSubtitle | WithBreadcrumbs;
|
|
|
66
61
|
* - Add a title (required).
|
|
67
62
|
* - Optionally add a subtitle or breadcrumbs.
|
|
68
63
|
* - We encourage you to add `titleId` (see Accessibility notes).
|
|
69
|
-
* - If the `ModalPanel` has a dark background, make sure to set `light` to
|
|
70
|
-
* `false`.
|
|
71
64
|
* - If you need to create e2e tests, make sure to pass a `testId` prop and
|
|
72
65
|
* add a sufix to scope the testId to this component: e.g.
|
|
73
66
|
* `some-random-id-ModalHeader`. This scope will also be passed to the title
|
|
@@ -80,14 +73,8 @@ type Props = Common | WithSubtitle | WithBreadcrumbs;
|
|
|
80
73
|
* title="Sidebar using ModalHeader"
|
|
81
74
|
* subtitle="subtitle"
|
|
82
75
|
* titleId="uniqueTitleId"
|
|
83
|
-
* light={false}
|
|
84
76
|
* />
|
|
85
77
|
* ```
|
|
86
78
|
*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
var defaultProps: {
|
|
90
|
-
light: boolean;
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
export default ModalHeader;
|
|
79
|
+
export default function ModalHeader(props: Props): React.JSX.Element;
|
|
80
|
+
export {};
|
|
@@ -27,11 +27,6 @@ type Props = {
|
|
|
27
27
|
* become too tall?
|
|
28
28
|
*/
|
|
29
29
|
scrollOverflow: boolean;
|
|
30
|
-
/**
|
|
31
|
-
* Whether to display the "light" version of this component instead, for
|
|
32
|
-
* use when the item is used on a dark background.
|
|
33
|
-
*/
|
|
34
|
-
light: boolean;
|
|
35
30
|
/**
|
|
36
31
|
* Any optional styling to apply to the panel.
|
|
37
32
|
*/
|
|
@@ -72,12 +67,11 @@ type Props = {
|
|
|
72
67
|
* </ModalDialog>
|
|
73
68
|
* ```
|
|
74
69
|
*/
|
|
75
|
-
declare function ModalPanel({ closeButtonVisible, scrollOverflow,
|
|
70
|
+
declare function ModalPanel({ closeButtonVisible, scrollOverflow, content, footer, header, onClose, style, testId, }: Props): React.JSX.Element;
|
|
76
71
|
declare namespace ModalPanel {
|
|
77
72
|
var defaultProps: {
|
|
78
73
|
closeButtonVisible: boolean;
|
|
79
74
|
scrollOverflow: boolean;
|
|
80
|
-
light: boolean;
|
|
81
75
|
};
|
|
82
76
|
}
|
|
83
77
|
export default ModalPanel;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
:root {--wb-c-modal-root-border-radius: var(--wb-border-radius-radius_040);
|
|
2
|
+
--wb-c-modal-dialog-spacing-padding: var(--wb-sizing-size_160);
|
|
3
|
+
--wb-c-modal-footer-color-border: var(--wb-semanticColor-core-border-neutral-subtle);
|
|
4
|
+
--wb-c-modal-header-color-border: var(--wb-semanticColor-core-border-neutral-subtle);
|
|
5
|
+
--wb-c-modal-header-color-secondary: var(--wb-semanticColor-text-secondary);
|
|
6
|
+
--wb-c-modal-header-spacing-paddingBlockMd: var(--wb-sizing-size_240);
|
|
7
|
+
--wb-c-modal-header-spacing-paddingInlineMd: var(--wb-sizing-size_320);
|
|
8
|
+
--wb-c-modal-header-spacing-paddingInlineSm: var(--wb-sizing-size_160);
|
|
9
|
+
--wb-c-modal-header-spacing-gap: var(--wb-sizing-size_080);
|
|
10
|
+
--wb-c-modal-header-spacing-titleGapMd: var(--wb-sizing-size_160);
|
|
11
|
+
--wb-c-modal-header-spacing-titleGapSm: var(--wb-sizing-size_320);
|
|
12
|
+
--wb-c-modal-panel-spacing-gap: var(--wb-sizing-size_320);
|
|
13
|
+
--wb-c-modal-closeButton-spacing-gap: var(--wb-sizing-size_080);}
|
package/dist/es/index.js
CHANGED
|
@@ -1,28 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { View, Id } from '@khanacademy/wonder-blocks-core';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { StyleSheet } from 'aphrodite';
|
|
5
|
+
import { border, sizing, semanticColor, mapValuesToCssVars } from '@khanacademy/wonder-blocks-tokens';
|
|
6
6
|
import { HeadingMedium, LabelSmall } from '@khanacademy/wonder-blocks-typography';
|
|
7
7
|
import * as ReactDOM from 'react-dom';
|
|
8
|
-
import { StyleSheet } from 'aphrodite';
|
|
9
8
|
import { withActionScheduler } from '@khanacademy/wonder-blocks-timing';
|
|
10
|
-
import {
|
|
9
|
+
import { focusStyles } from '@khanacademy/wonder-blocks-styles';
|
|
11
10
|
import xIcon from '@phosphor-icons/core/regular/x.svg';
|
|
12
11
|
import IconButton from '@khanacademy/wonder-blocks-icon-button';
|
|
13
12
|
import { MediaLayout } from '@khanacademy/wonder-blocks-layout';
|
|
14
13
|
|
|
15
|
-
const theme$1={root:{
|
|
16
|
-
|
|
17
|
-
const theme=mergeTheme(theme$1,{root:{color:{inverse:{background:semanticColor.khanmigo.primary}}}});
|
|
14
|
+
const theme$1={root:{border:{radius:border.radius.radius_040}},dialog:{spacing:{padding:sizing.size_160}},footer:{color:{border:semanticColor.core.border.neutral.subtle}},header:{color:{border:semanticColor.core.border.neutral.subtle,secondary:semanticColor.text.secondary},spacing:{paddingBlockMd:sizing.size_240,paddingInlineMd:sizing.size_320,paddingInlineSm:sizing.size_160,gap:sizing.size_080,titleGapMd:sizing.size_160,titleGapSm:sizing.size_320}},panel:{spacing:{gap:sizing.size_320}},closeButton:{spacing:{gap:sizing.size_080}}};
|
|
18
15
|
|
|
19
|
-
|
|
16
|
+
var theme = mapValuesToCssVars(theme$1,"--wb-c-modal-");
|
|
20
17
|
|
|
21
|
-
const
|
|
18
|
+
const ModalDialog=React.forwardRef(function ModalDialog(props,ref){const{above,below,role="dialog",style,children,testId,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy}=props;return jsxs(View,{style:[styles$6.wrapper,style],children:[below&&jsx(View,{style:styles$6.below,children:below}),jsx(View,{role:role,"aria-modal":"true","aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy,ref:ref,style:styles$6.dialog,testId:testId,children:children}),above&&jsx(View,{style:styles$6.above,children:above})]})});const small$2="@media (max-width: 767px)";const styles$6=StyleSheet.create({wrapper:{display:"flex",flexDirection:"row",alignItems:"stretch",width:"100%",height:"100%",position:"relative",[small$2]:{padding:theme.dialog.spacing.padding,flexDirection:"column"}},dialog:{width:"100%",height:"100%",borderRadius:theme.root.border.radius,overflow:"hidden"},above:{pointerEvents:"none",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:1},below:{pointerEvents:"none",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:-1}});ModalDialog.displayName="ModalDialog";
|
|
22
19
|
|
|
23
|
-
function ModalFooter({children}){
|
|
20
|
+
function ModalFooter({children}){return jsx(View,{style:styles$5.footer,children:children})}ModalFooter.__IS_MODAL_FOOTER__=true;ModalFooter.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_FOOTER__};const styles$5=StyleSheet.create({footer:{flex:"0 0 auto",boxSizing:"border-box",minHeight:sizing.size_640,paddingInline:sizing.size_160,paddingBlock:sizing.size_080,display:"flex",flexDirection:"row",alignItems:"center",justifyContent:"flex-end",boxShadow:`0px -1px 0px ${theme.footer.color.border}`}});
|
|
24
21
|
|
|
25
|
-
function ModalHeader(props){const{breadcrumbs=undefined,
|
|
22
|
+
function ModalHeader(props){const{breadcrumbs=undefined,subtitle=undefined,testId,title,titleId}=props;if(subtitle&&breadcrumbs){throw new Error("'subtitle' and 'breadcrumbs' can't be used together")}return jsxs(View,{style:[styles$4.header],testId:testId,children:[breadcrumbs&&jsx(View,{style:styles$4.breadcrumbs,children:breadcrumbs}),jsx(HeadingMedium,{tag:"h2",style:styles$4.title,id:titleId,testId:testId&&`${testId}-title`,children:title}),subtitle&&jsx(LabelSmall,{style:styles$4.subtitle,testId:testId&&`${testId}-subtitle`,children:subtitle})]})}const small$1="@media (max-width: 767px)";const styles$4=StyleSheet.create({header:{boxShadow:`0px 1px 0px ${theme.header.color.border}`,display:"flex",flexDirection:"column",minHeight:66,paddingBlock:theme.header.spacing.paddingBlockMd,paddingInline:theme.header.spacing.paddingInlineMd,position:"relative",width:"100%",[small$1]:{paddingInline:theme.header.spacing.paddingInlineSm}},breadcrumbs:{color:theme.header.color.secondary,marginBottom:theme.header.spacing.gap},title:{paddingRight:theme.header.spacing.titleGapMd,[small$1]:{paddingRight:theme.header.spacing.titleGapSm}},subtitle:{color:theme.header.color.secondary,marginTop:theme.header.spacing.gap}});
|
|
26
23
|
|
|
27
24
|
const FOCUSABLE_ELEMENTS$1='button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';class FocusTrap extends React.Component{tryToFocus(node){if(node instanceof HTMLElement){try{node.focus();}catch(e){}return document.activeElement===node}}focusElementIn(isLast){const modalRootAsHtmlEl=this.modalRoot;const focusableNodes=Array.from(modalRootAsHtmlEl.querySelectorAll(FOCUSABLE_ELEMENTS$1));const nodeIndex=!isLast?focusableNodes.length-1:0;const focusableNode=focusableNodes[nodeIndex];this.tryToFocus(focusableNode);}render(){const{style}=this.props;return jsxs(React.Fragment,{children:[jsx("div",{tabIndex:0,className:"modal-focus-trap-first",onFocus:this.handleFocusMoveToLast,style:{position:"fixed"}}),jsx(View,{style:style,ref:this.getModalRoot,children:this.props.children}),jsx("div",{tabIndex:0,className:"modal-focus-trap-last",onFocus:this.handleFocusMoveToFirst,style:{position:"fixed"}})]})}constructor(...args){super(...args),this.getModalRoot=node=>{if(!node){return}const modalRoot=ReactDOM.findDOMNode(node);if(!modalRoot){throw new Error("Assertion error: modal root should exist after mount")}this.modalRoot=modalRoot;},this.handleFocusMoveToLast=()=>{this.focusElementIn(false);},this.handleFocusMoveToFirst=()=>{this.focusElementIn(true);};}}
|
|
28
25
|
|
|
@@ -30,19 +27,19 @@ const ModalLauncherPortalAttributeName="data-modal-launcher-portal";
|
|
|
30
27
|
|
|
31
28
|
const FOCUSABLE_ELEMENTS="a[href], details, input, textarea, select, button";function findFocusableNodes(root){return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS))}
|
|
32
29
|
|
|
33
|
-
class ModalBackdrop extends React.Component{componentDidMount(){const node=ReactDOM.findDOMNode(this);if(!node){return}const firstFocusableElement=this._getInitialFocusElement(node)||this._getFirstFocusableElement(node)||this._getDialogElement(node);setTimeout(()=>{firstFocusableElement.focus();},0);}_getInitialFocusElement(node){const{initialFocusId}=this.props;if(!initialFocusId){return null}return ReactDOM.findDOMNode(node.querySelector(`#${initialFocusId}`))}_getFirstFocusableElement(node){const focusableElements=findFocusableNodes(node);if(!focusableElements){return null}return focusableElements[0]}_getDialogElement(node){const dialogElement=ReactDOM.findDOMNode(node.querySelector('[role="dialog"]'));dialogElement.tabIndex=-1;return dialogElement}render(){const{children,testId}=this.props;const backdropProps={[ModalLauncherPortalAttributeName]:true};return jsx(View,{style:
|
|
30
|
+
class ModalBackdrop extends React.Component{componentDidMount(){const node=ReactDOM.findDOMNode(this);if(!node){return}const firstFocusableElement=this._getInitialFocusElement(node)||this._getFirstFocusableElement(node)||this._getDialogElement(node);setTimeout(()=>{firstFocusableElement.focus();},0);}_getInitialFocusElement(node){const{initialFocusId}=this.props;if(!initialFocusId){return null}return ReactDOM.findDOMNode(node.querySelector(`#${initialFocusId}`))}_getFirstFocusableElement(node){const focusableElements=findFocusableNodes(node);if(!focusableElements){return null}return focusableElements[0]}_getDialogElement(node){const dialogElement=ReactDOM.findDOMNode(node.querySelector('[role="dialog"]'));dialogElement.tabIndex=-1;return dialogElement}render(){const{children,testId}=this.props;const backdropProps={[ModalLauncherPortalAttributeName]:true};return jsx(View,{style:styles$3.modalPositioner,onMouseDown:this.handleMouseDown,onMouseUp:this.handleMouseUp,testId:testId,...backdropProps,children:children})}constructor(...args){super(...args),this._mousePressedOutside=false,this.handleMouseDown=e=>{this._mousePressedOutside=e.target===e.currentTarget;},this.handleMouseUp=e=>{if(e.target===e.currentTarget&&this._mousePressedOutside){this.props.onCloseModal();}this._mousePressedOutside=false;};}}const styles$3=StyleSheet.create({modalPositioner:{position:"fixed",left:0,top:0,width:"100%",height:"100%",alignItems:"center",justifyContent:"center",overflow:"auto",background:semanticColor.surface.overlay}});
|
|
34
31
|
|
|
35
32
|
const needsHackyMobileSafariScrollDisabler=(()=>{if(typeof window==="undefined"){return false}const userAgent=window.navigator.userAgent;return userAgent.indexOf("iPad")>-1||userAgent.indexOf("iPhone")>-1})();class ScrollDisabler extends React.Component{componentDidMount(){if(ScrollDisabler.numModalsOpened===0){const body=document.body;if(!body){throw new Error("couldn't find document.body")}ScrollDisabler.oldOverflow=body.style.overflow;ScrollDisabler.oldScrollY=window.scrollY;if(needsHackyMobileSafariScrollDisabler){ScrollDisabler.oldPosition=body.style.position;ScrollDisabler.oldWidth=body.style.width;ScrollDisabler.oldTop=body.style.top;}body.style.overflow="hidden";if(needsHackyMobileSafariScrollDisabler){body.style.position="fixed";body.style.width="100%";body.style.top=`${-ScrollDisabler.oldScrollY}px`;}}ScrollDisabler.numModalsOpened++;}componentWillUnmount(){ScrollDisabler.numModalsOpened--;if(ScrollDisabler.numModalsOpened===0){const body=document.body;if(!body){throw new Error("couldn't find document.body")}body.style.overflow=ScrollDisabler.oldOverflow;if(needsHackyMobileSafariScrollDisabler){body.style.position=ScrollDisabler.oldPosition;body.style.width=ScrollDisabler.oldWidth;body.style.top=ScrollDisabler.oldTop;}if(typeof window!=="undefined"&&window.scrollTo){window.scrollTo(0,ScrollDisabler.oldScrollY);}}}render(){return null}}ScrollDisabler.numModalsOpened=0;
|
|
36
33
|
|
|
37
34
|
const defaultContext={closeModal:undefined};const ModalContext=React.createContext(defaultContext);ModalContext.displayName="ModalContext";
|
|
38
35
|
|
|
39
|
-
class ModalLauncher extends React.Component{static getDerivedStateFromProps(props,state){if(typeof props.opened==="boolean"&&props.children){console.warn("'children' and 'opened' can't be used together");}if(typeof props.opened==="boolean"&&!props.onClose){console.warn("'onClose' should be used with 'opened'");}if(typeof props.opened!=="boolean"&&!props.children){console.warn("either 'children' or 'opened' must be set");}return {opened:typeof props.opened==="boolean"?props.opened:state.opened}}componentDidUpdate(prevProps){if(!prevProps.opened&&this.props.opened){this._saveLastElementFocused();}}_renderModal(){if(typeof this.props.modal==="function"){return this.props.modal({closeModal:this.handleCloseModal})}else {return this.props.modal}}render(){const renderedChildren=this.props.children?this.props.children({openModal:this._openModal}):null;const{body}=document;if(!body){return null}return jsxs(ModalContext.Provider,{value:{closeModal:this.handleCloseModal},children:[renderedChildren,this.state.opened&&ReactDOM.createPortal(jsx(FocusTrap,{style:styles.container,children:jsx(ModalBackdrop
|
|
36
|
+
class ModalLauncher extends React.Component{static getDerivedStateFromProps(props,state){if(typeof props.opened==="boolean"&&props.children){console.warn("'children' and 'opened' can't be used together");}if(typeof props.opened==="boolean"&&!props.onClose){console.warn("'onClose' should be used with 'opened'");}if(typeof props.opened!=="boolean"&&!props.children){console.warn("either 'children' or 'opened' must be set");}return {opened:typeof props.opened==="boolean"?props.opened:state.opened}}componentDidUpdate(prevProps){if(!prevProps.opened&&this.props.opened){this._saveLastElementFocused();}}_renderModal(){if(typeof this.props.modal==="function"){return this.props.modal({closeModal:this.handleCloseModal})}else {return this.props.modal}}render(){const renderedChildren=this.props.children?this.props.children({openModal:this._openModal}):null;const{body}=document;if(!body){return null}return jsxs(ModalContext.Provider,{value:{closeModal:this.handleCloseModal},children:[renderedChildren,this.state.opened&&ReactDOM.createPortal(jsx(FocusTrap,{style:styles$2.container,children:jsx(ModalBackdrop,{initialFocusId:this.props.initialFocusId,testId:this.props.testId,onCloseModal:this.props.backdropDismissEnabled?this.handleCloseModal:()=>{},children:this._renderModal()})}),body),this.state.opened&&jsx(ModalLauncherKeypressListener,{onClose:this.handleCloseModal}),this.state.opened&&jsx(ScrollDisabler,{})]})}constructor(...args){super(...args),this.state={opened:false},this._saveLastElementFocused=()=>{this.lastElementFocusedOutsideModal=document.activeElement;},this._openModal=()=>{this._saveLastElementFocused();this.setState({opened:true});},this._returnFocus=()=>{const{closedFocusId,schedule}=this.props;const lastElement=this.lastElementFocusedOutsideModal;if(closedFocusId){const focusElement=ReactDOM.findDOMNode(document.getElementById(closedFocusId));if(focusElement){schedule.animationFrame(()=>{focusElement.focus();});return}}if(lastElement!=null){schedule.animationFrame(()=>{lastElement.focus();});}},this.handleCloseModal=()=>{this.setState({opened:false},()=>{const{onClose}=this.props;onClose?.();this._returnFocus();});};}}ModalLauncher.defaultProps={backdropDismissEnabled:true};class ModalLauncherKeypressListener extends React.Component{componentDidMount(){window.addEventListener("keyup",this._handleKeyup);}componentWillUnmount(){window.removeEventListener("keyup",this._handleKeyup);}render(){return null}constructor(...args){super(...args),this._handleKeyup=e=>{if(e.key==="Escape"){e.preventDefault();e.stopPropagation();this.props.onClose();}};}}const styles$2=StyleSheet.create({container:{zIndex:1080}});var modalLauncher = withActionScheduler(ModalLauncher);
|
|
40
37
|
|
|
41
|
-
function ModalContent(props){const{scrollOverflow,style,children}=props;
|
|
38
|
+
function ModalContent(props){const{scrollOverflow,style,children}=props;return jsx(View,{style:[styles$1.wrapper,scrollOverflow&&styles$1.scrollOverflow],children:jsx(View,{style:[styles$1.content,style],children:children})})}ModalContent.__IS_MODAL_CONTENT__=true;ModalContent.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_CONTENT__};const small="@media (max-width: 767px)";const styles$1=StyleSheet.create({wrapper:{flex:1,display:"block"},scrollOverflow:{overflow:"auto"},content:{flex:1,minHeight:"100%",padding:sizing.size_320,boxSizing:"border-box",[small]:{paddingInline:sizing.size_160}}});ModalContent.defaultProps={scrollOverflow:true};
|
|
42
39
|
|
|
43
40
|
class CloseButton extends React.Component{render(){const{onClick,style,testId}=this.props;return jsx(ModalContext.Consumer,{children:({closeModal})=>{if(closeModal&&onClick){throw new Error("You've specified 'onClose' on a modal when using ModalLauncher. Please specify 'onClose' on the ModalLauncher instead")}return jsx(IconButton,{icon:xIcon,"aria-label":"Close modal",onClick:onClick||closeModal,kind:"tertiary",actionType:"neutral",style:style,testId:testId})}})}}
|
|
44
41
|
|
|
45
|
-
function ModalPanel({closeButtonVisible=true,scrollOverflow=true,
|
|
42
|
+
function ModalPanel({closeButtonVisible=true,scrollOverflow=true,content,footer,header,onClose,style,testId}){const renderMainContent=React.useCallback(()=>{const mainContent=ModalContent.isComponentOf(content)?content:jsx(ModalContent,{children:content});if(!mainContent){return mainContent}return React.cloneElement(mainContent,{scrollOverflow,style:[!!footer&&styles.hasFooter,mainContent.props.style]})},[content,footer,scrollOverflow]);const mainContent=renderMainContent();return jsxs(View,{style:[styles.wrapper,style],testId:testId&&`${testId}-panel`,children:[closeButtonVisible&&jsx(CloseButton,{onClick:onClose,style:[styles.closeButton],testId:testId&&`${testId}-close`}),header,mainContent,!footer||ModalFooter.isComponentOf(footer)?footer:jsx(ModalFooter,{children:footer})]})}ModalPanel.defaultProps={closeButtonVisible:true,scrollOverflow:true};const styles=StyleSheet.create({wrapper:{flex:"1 1 auto",position:"relative",display:"flex",flexDirection:"column",background:"white",boxSizing:"border-box",overflow:"hidden",height:"100%",width:"100%"},closeButton:{position:"absolute",right:theme.closeButton.spacing.gap,top:theme.closeButton.spacing.gap,zIndex:1,":focus":focusStyles.focus[":focus-visible"]},hasFooter:{paddingBlockEnd:theme.panel.spacing.gap}});
|
|
46
43
|
|
|
47
44
|
class OnePaneDialog extends React.Component{renderHeader(uniqueId){const{title,breadcrumbs=undefined,subtitle=undefined,testId}=this.props;if(breadcrumbs){return jsx(ModalHeader,{title:title,breadcrumbs:breadcrumbs,titleId:uniqueId,testId:testId&&`${testId}-header`})}else if(subtitle){return jsx(ModalHeader,{title:title,subtitle:subtitle,titleId:uniqueId,testId:testId&&`${testId}-header`})}else {return jsx(ModalHeader,{title:title,titleId:uniqueId,testId:testId&&`${testId}-header`})}}render(){const{onClose,footer,content,above,below,style,closeButtonVisible,testId,titleId,role,"aria-describedby":ariaDescribedBy}=this.props;return jsx(MediaLayout,{styleSheets:styleSheets,children:({styles})=>jsx(Id,{id:titleId,children:uniqueId=>jsx(ModalDialog,{style:[styles.dialog,style],above:above,below:below,testId:testId,"aria-labelledby":uniqueId,"aria-describedby":ariaDescribedBy,role:role,children:jsx(ModalPanel,{onClose:onClose,header:this.renderHeader(uniqueId),content:content,footer:footer,closeButtonVisible:closeButtonVisible,testId:testId})})})})}}OnePaneDialog.defaultProps={closeButtonVisible:true};const styleSheets={small:StyleSheet.create({dialog:{width:"100%",height:"100%",overflow:"hidden"}}),mdOrLarger:StyleSheet.create({dialog:{width:"93.75%",maxWidth:576,height:"81.25%",maxHeight:624}})};
|
|
48
45
|
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,10 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
6
|
var React = require('react');
|
|
7
7
|
var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
|
|
8
|
-
var
|
|
8
|
+
var aphrodite = require('aphrodite');
|
|
9
9
|
var wonderBlocksTokens = require('@khanacademy/wonder-blocks-tokens');
|
|
10
10
|
var wonderBlocksTypography = require('@khanacademy/wonder-blocks-typography');
|
|
11
11
|
var ReactDOM = require('react-dom');
|
|
12
|
-
var aphrodite = require('aphrodite');
|
|
13
12
|
var wonderBlocksTiming = require('@khanacademy/wonder-blocks-timing');
|
|
14
13
|
var wonderBlocksStyles = require('@khanacademy/wonder-blocks-styles');
|
|
15
14
|
var xIcon = require('@phosphor-icons/core/regular/x.svg');
|
|
@@ -41,17 +40,15 @@ var ReactDOM__namespace = /*#__PURE__*/_interopNamespace(ReactDOM);
|
|
|
41
40
|
var xIcon__default = /*#__PURE__*/_interopDefaultLegacy(xIcon);
|
|
42
41
|
var IconButton__default = /*#__PURE__*/_interopDefaultLegacy(IconButton);
|
|
43
42
|
|
|
44
|
-
const theme$1={root:{
|
|
45
|
-
|
|
46
|
-
const theme=wonderBlocksTheming.mergeTheme(theme$1,{root:{color:{inverse:{background:wonderBlocksTokens.semanticColor.khanmigo.primary}}}});
|
|
43
|
+
const theme$1={root:{border:{radius:wonderBlocksTokens.border.radius.radius_040}},dialog:{spacing:{padding:wonderBlocksTokens.sizing.size_160}},footer:{color:{border:wonderBlocksTokens.semanticColor.core.border.neutral.subtle}},header:{color:{border:wonderBlocksTokens.semanticColor.core.border.neutral.subtle,secondary:wonderBlocksTokens.semanticColor.text.secondary},spacing:{paddingBlockMd:wonderBlocksTokens.sizing.size_240,paddingInlineMd:wonderBlocksTokens.sizing.size_320,paddingInlineSm:wonderBlocksTokens.sizing.size_160,gap:wonderBlocksTokens.sizing.size_080,titleGapMd:wonderBlocksTokens.sizing.size_160,titleGapSm:wonderBlocksTokens.sizing.size_320}},panel:{spacing:{gap:wonderBlocksTokens.sizing.size_320}},closeButton:{spacing:{gap:wonderBlocksTokens.sizing.size_080}}};
|
|
47
44
|
|
|
48
|
-
|
|
45
|
+
var theme = wonderBlocksTokens.mapValuesToCssVars(theme$1,"--wb-c-modal-");
|
|
49
46
|
|
|
50
|
-
const
|
|
47
|
+
const ModalDialog=React__namespace.forwardRef(function ModalDialog(props,ref){const{above,below,role="dialog",style,children,testId,"aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy}=props;return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[styles$6.wrapper,style],children:[below&&jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$6.below,children:below}),jsxRuntime.jsx(wonderBlocksCore.View,{role:role,"aria-modal":"true","aria-labelledby":ariaLabelledBy,"aria-describedby":ariaDescribedBy,ref:ref,style:styles$6.dialog,testId:testId,children:children}),above&&jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$6.above,children:above})]})});const small$2="@media (max-width: 767px)";const styles$6=aphrodite.StyleSheet.create({wrapper:{display:"flex",flexDirection:"row",alignItems:"stretch",width:"100%",height:"100%",position:"relative",[small$2]:{padding:theme.dialog.spacing.padding,flexDirection:"column"}},dialog:{width:"100%",height:"100%",borderRadius:theme.root.border.radius,overflow:"hidden"},above:{pointerEvents:"none",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:1},below:{pointerEvents:"none",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:-1}});ModalDialog.displayName="ModalDialog";
|
|
51
48
|
|
|
52
|
-
function ModalFooter({children}){
|
|
49
|
+
function ModalFooter({children}){return jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$5.footer,children:children})}ModalFooter.__IS_MODAL_FOOTER__=true;ModalFooter.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_FOOTER__};const styles$5=aphrodite.StyleSheet.create({footer:{flex:"0 0 auto",boxSizing:"border-box",minHeight:wonderBlocksTokens.sizing.size_640,paddingInline:wonderBlocksTokens.sizing.size_160,paddingBlock:wonderBlocksTokens.sizing.size_080,display:"flex",flexDirection:"row",alignItems:"center",justifyContent:"flex-end",boxShadow:`0px -1px 0px ${theme.footer.color.border}`}});
|
|
53
50
|
|
|
54
|
-
function ModalHeader(props){const{breadcrumbs=undefined,
|
|
51
|
+
function ModalHeader(props){const{breadcrumbs=undefined,subtitle=undefined,testId,title,titleId}=props;if(subtitle&&breadcrumbs){throw new Error("'subtitle' and 'breadcrumbs' can't be used together")}return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[styles$4.header],testId:testId,children:[breadcrumbs&&jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$4.breadcrumbs,children:breadcrumbs}),jsxRuntime.jsx(wonderBlocksTypography.HeadingMedium,{tag:"h2",style:styles$4.title,id:titleId,testId:testId&&`${testId}-title`,children:title}),subtitle&&jsxRuntime.jsx(wonderBlocksTypography.LabelSmall,{style:styles$4.subtitle,testId:testId&&`${testId}-subtitle`,children:subtitle})]})}const small$1="@media (max-width: 767px)";const styles$4=aphrodite.StyleSheet.create({header:{boxShadow:`0px 1px 0px ${theme.header.color.border}`,display:"flex",flexDirection:"column",minHeight:66,paddingBlock:theme.header.spacing.paddingBlockMd,paddingInline:theme.header.spacing.paddingInlineMd,position:"relative",width:"100%",[small$1]:{paddingInline:theme.header.spacing.paddingInlineSm}},breadcrumbs:{color:theme.header.color.secondary,marginBottom:theme.header.spacing.gap},title:{paddingRight:theme.header.spacing.titleGapMd,[small$1]:{paddingRight:theme.header.spacing.titleGapSm}},subtitle:{color:theme.header.color.secondary,marginTop:theme.header.spacing.gap}});
|
|
55
52
|
|
|
56
53
|
const FOCUSABLE_ELEMENTS$1='button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';class FocusTrap extends React__namespace.Component{tryToFocus(node){if(node instanceof HTMLElement){try{node.focus();}catch(e){}return document.activeElement===node}}focusElementIn(isLast){const modalRootAsHtmlEl=this.modalRoot;const focusableNodes=Array.from(modalRootAsHtmlEl.querySelectorAll(FOCUSABLE_ELEMENTS$1));const nodeIndex=!isLast?focusableNodes.length-1:0;const focusableNode=focusableNodes[nodeIndex];this.tryToFocus(focusableNode);}render(){const{style}=this.props;return jsxRuntime.jsxs(React__namespace.Fragment,{children:[jsxRuntime.jsx("div",{tabIndex:0,className:"modal-focus-trap-first",onFocus:this.handleFocusMoveToLast,style:{position:"fixed"}}),jsxRuntime.jsx(wonderBlocksCore.View,{style:style,ref:this.getModalRoot,children:this.props.children}),jsxRuntime.jsx("div",{tabIndex:0,className:"modal-focus-trap-last",onFocus:this.handleFocusMoveToFirst,style:{position:"fixed"}})]})}constructor(...args){super(...args),this.getModalRoot=node=>{if(!node){return}const modalRoot=ReactDOM__namespace.findDOMNode(node);if(!modalRoot){throw new Error("Assertion error: modal root should exist after mount")}this.modalRoot=modalRoot;},this.handleFocusMoveToLast=()=>{this.focusElementIn(false);},this.handleFocusMoveToFirst=()=>{this.focusElementIn(true);};}}
|
|
57
54
|
|
|
@@ -59,19 +56,19 @@ const ModalLauncherPortalAttributeName="data-modal-launcher-portal";
|
|
|
59
56
|
|
|
60
57
|
const FOCUSABLE_ELEMENTS="a[href], details, input, textarea, select, button";function findFocusableNodes(root){return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS))}
|
|
61
58
|
|
|
62
|
-
class ModalBackdrop extends React__namespace.Component{componentDidMount(){const node=ReactDOM__namespace.findDOMNode(this);if(!node){return}const firstFocusableElement=this._getInitialFocusElement(node)||this._getFirstFocusableElement(node)||this._getDialogElement(node);setTimeout(()=>{firstFocusableElement.focus();},0);}_getInitialFocusElement(node){const{initialFocusId}=this.props;if(!initialFocusId){return null}return ReactDOM__namespace.findDOMNode(node.querySelector(`#${initialFocusId}`))}_getFirstFocusableElement(node){const focusableElements=findFocusableNodes(node);if(!focusableElements){return null}return focusableElements[0]}_getDialogElement(node){const dialogElement=ReactDOM__namespace.findDOMNode(node.querySelector('[role="dialog"]'));dialogElement.tabIndex=-1;return dialogElement}render(){const{children,testId}=this.props;const backdropProps={[ModalLauncherPortalAttributeName]:true};return jsxRuntime.jsx(wonderBlocksCore.View,{style:
|
|
59
|
+
class ModalBackdrop extends React__namespace.Component{componentDidMount(){const node=ReactDOM__namespace.findDOMNode(this);if(!node){return}const firstFocusableElement=this._getInitialFocusElement(node)||this._getFirstFocusableElement(node)||this._getDialogElement(node);setTimeout(()=>{firstFocusableElement.focus();},0);}_getInitialFocusElement(node){const{initialFocusId}=this.props;if(!initialFocusId){return null}return ReactDOM__namespace.findDOMNode(node.querySelector(`#${initialFocusId}`))}_getFirstFocusableElement(node){const focusableElements=findFocusableNodes(node);if(!focusableElements){return null}return focusableElements[0]}_getDialogElement(node){const dialogElement=ReactDOM__namespace.findDOMNode(node.querySelector('[role="dialog"]'));dialogElement.tabIndex=-1;return dialogElement}render(){const{children,testId}=this.props;const backdropProps={[ModalLauncherPortalAttributeName]:true};return jsxRuntime.jsx(wonderBlocksCore.View,{style:styles$3.modalPositioner,onMouseDown:this.handleMouseDown,onMouseUp:this.handleMouseUp,testId:testId,...backdropProps,children:children})}constructor(...args){super(...args),this._mousePressedOutside=false,this.handleMouseDown=e=>{this._mousePressedOutside=e.target===e.currentTarget;},this.handleMouseUp=e=>{if(e.target===e.currentTarget&&this._mousePressedOutside){this.props.onCloseModal();}this._mousePressedOutside=false;};}}const styles$3=aphrodite.StyleSheet.create({modalPositioner:{position:"fixed",left:0,top:0,width:"100%",height:"100%",alignItems:"center",justifyContent:"center",overflow:"auto",background:wonderBlocksTokens.semanticColor.surface.overlay}});
|
|
63
60
|
|
|
64
61
|
const needsHackyMobileSafariScrollDisabler=(()=>{if(typeof window==="undefined"){return false}const userAgent=window.navigator.userAgent;return userAgent.indexOf("iPad")>-1||userAgent.indexOf("iPhone")>-1})();class ScrollDisabler extends React__namespace.Component{componentDidMount(){if(ScrollDisabler.numModalsOpened===0){const body=document.body;if(!body){throw new Error("couldn't find document.body")}ScrollDisabler.oldOverflow=body.style.overflow;ScrollDisabler.oldScrollY=window.scrollY;if(needsHackyMobileSafariScrollDisabler){ScrollDisabler.oldPosition=body.style.position;ScrollDisabler.oldWidth=body.style.width;ScrollDisabler.oldTop=body.style.top;}body.style.overflow="hidden";if(needsHackyMobileSafariScrollDisabler){body.style.position="fixed";body.style.width="100%";body.style.top=`${-ScrollDisabler.oldScrollY}px`;}}ScrollDisabler.numModalsOpened++;}componentWillUnmount(){ScrollDisabler.numModalsOpened--;if(ScrollDisabler.numModalsOpened===0){const body=document.body;if(!body){throw new Error("couldn't find document.body")}body.style.overflow=ScrollDisabler.oldOverflow;if(needsHackyMobileSafariScrollDisabler){body.style.position=ScrollDisabler.oldPosition;body.style.width=ScrollDisabler.oldWidth;body.style.top=ScrollDisabler.oldTop;}if(typeof window!=="undefined"&&window.scrollTo){window.scrollTo(0,ScrollDisabler.oldScrollY);}}}render(){return null}}ScrollDisabler.numModalsOpened=0;
|
|
65
62
|
|
|
66
63
|
const defaultContext={closeModal:undefined};const ModalContext=React__namespace.createContext(defaultContext);ModalContext.displayName="ModalContext";
|
|
67
64
|
|
|
68
|
-
class ModalLauncher extends React__namespace.Component{static getDerivedStateFromProps(props,state){if(typeof props.opened==="boolean"&&props.children){console.warn("'children' and 'opened' can't be used together");}if(typeof props.opened==="boolean"&&!props.onClose){console.warn("'onClose' should be used with 'opened'");}if(typeof props.opened!=="boolean"&&!props.children){console.warn("either 'children' or 'opened' must be set");}return {opened:typeof props.opened==="boolean"?props.opened:state.opened}}componentDidUpdate(prevProps){if(!prevProps.opened&&this.props.opened){this._saveLastElementFocused();}}_renderModal(){if(typeof this.props.modal==="function"){return this.props.modal({closeModal:this.handleCloseModal})}else {return this.props.modal}}render(){const renderedChildren=this.props.children?this.props.children({openModal:this._openModal}):null;const{body}=document;if(!body){return null}return jsxRuntime.jsxs(ModalContext.Provider,{value:{closeModal:this.handleCloseModal},children:[renderedChildren,this.state.opened&&ReactDOM__namespace.createPortal(jsxRuntime.jsx(FocusTrap,{style:styles.container,children:jsxRuntime.jsx(ModalBackdrop
|
|
65
|
+
class ModalLauncher extends React__namespace.Component{static getDerivedStateFromProps(props,state){if(typeof props.opened==="boolean"&&props.children){console.warn("'children' and 'opened' can't be used together");}if(typeof props.opened==="boolean"&&!props.onClose){console.warn("'onClose' should be used with 'opened'");}if(typeof props.opened!=="boolean"&&!props.children){console.warn("either 'children' or 'opened' must be set");}return {opened:typeof props.opened==="boolean"?props.opened:state.opened}}componentDidUpdate(prevProps){if(!prevProps.opened&&this.props.opened){this._saveLastElementFocused();}}_renderModal(){if(typeof this.props.modal==="function"){return this.props.modal({closeModal:this.handleCloseModal})}else {return this.props.modal}}render(){const renderedChildren=this.props.children?this.props.children({openModal:this._openModal}):null;const{body}=document;if(!body){return null}return jsxRuntime.jsxs(ModalContext.Provider,{value:{closeModal:this.handleCloseModal},children:[renderedChildren,this.state.opened&&ReactDOM__namespace.createPortal(jsxRuntime.jsx(FocusTrap,{style:styles$2.container,children:jsxRuntime.jsx(ModalBackdrop,{initialFocusId:this.props.initialFocusId,testId:this.props.testId,onCloseModal:this.props.backdropDismissEnabled?this.handleCloseModal:()=>{},children:this._renderModal()})}),body),this.state.opened&&jsxRuntime.jsx(ModalLauncherKeypressListener,{onClose:this.handleCloseModal}),this.state.opened&&jsxRuntime.jsx(ScrollDisabler,{})]})}constructor(...args){super(...args),this.state={opened:false},this._saveLastElementFocused=()=>{this.lastElementFocusedOutsideModal=document.activeElement;},this._openModal=()=>{this._saveLastElementFocused();this.setState({opened:true});},this._returnFocus=()=>{const{closedFocusId,schedule}=this.props;const lastElement=this.lastElementFocusedOutsideModal;if(closedFocusId){const focusElement=ReactDOM__namespace.findDOMNode(document.getElementById(closedFocusId));if(focusElement){schedule.animationFrame(()=>{focusElement.focus();});return}}if(lastElement!=null){schedule.animationFrame(()=>{lastElement.focus();});}},this.handleCloseModal=()=>{this.setState({opened:false},()=>{const{onClose}=this.props;onClose?.();this._returnFocus();});};}}ModalLauncher.defaultProps={backdropDismissEnabled:true};class ModalLauncherKeypressListener extends React__namespace.Component{componentDidMount(){window.addEventListener("keyup",this._handleKeyup);}componentWillUnmount(){window.removeEventListener("keyup",this._handleKeyup);}render(){return null}constructor(...args){super(...args),this._handleKeyup=e=>{if(e.key==="Escape"){e.preventDefault();e.stopPropagation();this.props.onClose();}};}}const styles$2=aphrodite.StyleSheet.create({container:{zIndex:1080}});var modalLauncher = wonderBlocksTiming.withActionScheduler(ModalLauncher);
|
|
69
66
|
|
|
70
|
-
function ModalContent(props){const{scrollOverflow,style,children}=props;
|
|
67
|
+
function ModalContent(props){const{scrollOverflow,style,children}=props;return jsxRuntime.jsx(wonderBlocksCore.View,{style:[styles$1.wrapper,scrollOverflow&&styles$1.scrollOverflow],children:jsxRuntime.jsx(wonderBlocksCore.View,{style:[styles$1.content,style],children:children})})}ModalContent.__IS_MODAL_CONTENT__=true;ModalContent.isComponentOf=instance=>{return instance&&instance.type&&instance.type.__IS_MODAL_CONTENT__};const small="@media (max-width: 767px)";const styles$1=aphrodite.StyleSheet.create({wrapper:{flex:1,display:"block"},scrollOverflow:{overflow:"auto"},content:{flex:1,minHeight:"100%",padding:wonderBlocksTokens.sizing.size_320,boxSizing:"border-box",[small]:{paddingInline:wonderBlocksTokens.sizing.size_160}}});ModalContent.defaultProps={scrollOverflow:true};
|
|
71
68
|
|
|
72
69
|
class CloseButton extends React__namespace.Component{render(){const{onClick,style,testId}=this.props;return jsxRuntime.jsx(ModalContext.Consumer,{children:({closeModal})=>{if(closeModal&&onClick){throw new Error("You've specified 'onClose' on a modal when using ModalLauncher. Please specify 'onClose' on the ModalLauncher instead")}return jsxRuntime.jsx(IconButton__default["default"],{icon:xIcon__default["default"],"aria-label":"Close modal",onClick:onClick||closeModal,kind:"tertiary",actionType:"neutral",style:style,testId:testId})}})}}
|
|
73
70
|
|
|
74
|
-
function ModalPanel({closeButtonVisible=true,scrollOverflow=true,
|
|
71
|
+
function ModalPanel({closeButtonVisible=true,scrollOverflow=true,content,footer,header,onClose,style,testId}){const renderMainContent=React__namespace.useCallback(()=>{const mainContent=ModalContent.isComponentOf(content)?content:jsxRuntime.jsx(ModalContent,{children:content});if(!mainContent){return mainContent}return React__namespace.cloneElement(mainContent,{scrollOverflow,style:[!!footer&&styles.hasFooter,mainContent.props.style]})},[content,footer,scrollOverflow]);const mainContent=renderMainContent();return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[styles.wrapper,style],testId:testId&&`${testId}-panel`,children:[closeButtonVisible&&jsxRuntime.jsx(CloseButton,{onClick:onClose,style:[styles.closeButton],testId:testId&&`${testId}-close`}),header,mainContent,!footer||ModalFooter.isComponentOf(footer)?footer:jsxRuntime.jsx(ModalFooter,{children:footer})]})}ModalPanel.defaultProps={closeButtonVisible:true,scrollOverflow:true};const styles=aphrodite.StyleSheet.create({wrapper:{flex:"1 1 auto",position:"relative",display:"flex",flexDirection:"column",background:"white",boxSizing:"border-box",overflow:"hidden",height:"100%",width:"100%"},closeButton:{position:"absolute",right:theme.closeButton.spacing.gap,top:theme.closeButton.spacing.gap,zIndex:1,":focus":wonderBlocksStyles.focusStyles.focus[":focus-visible"]},hasFooter:{paddingBlockEnd:theme.panel.spacing.gap}});
|
|
75
72
|
|
|
76
73
|
class OnePaneDialog extends React__namespace.Component{renderHeader(uniqueId){const{title,breadcrumbs=undefined,subtitle=undefined,testId}=this.props;if(breadcrumbs){return jsxRuntime.jsx(ModalHeader,{title:title,breadcrumbs:breadcrumbs,titleId:uniqueId,testId:testId&&`${testId}-header`})}else if(subtitle){return jsxRuntime.jsx(ModalHeader,{title:title,subtitle:subtitle,titleId:uniqueId,testId:testId&&`${testId}-header`})}else {return jsxRuntime.jsx(ModalHeader,{title:title,titleId:uniqueId,testId:testId&&`${testId}-header`})}}render(){const{onClose,footer,content,above,below,style,closeButtonVisible,testId,titleId,role,"aria-describedby":ariaDescribedBy}=this.props;return jsxRuntime.jsx(wonderBlocksLayout.MediaLayout,{styleSheets:styleSheets,children:({styles})=>jsxRuntime.jsx(wonderBlocksCore.Id,{id:titleId,children:uniqueId=>jsxRuntime.jsx(ModalDialog,{style:[styles.dialog,style],above:above,below:below,testId:testId,"aria-labelledby":uniqueId,"aria-describedby":ariaDescribedBy,role:role,children:jsxRuntime.jsx(ModalPanel,{onClose:onClose,header:this.renderHeader(uniqueId),content:content,footer:footer,closeButtonVisible:closeButtonVisible,testId:testId})})})})}}OnePaneDialog.defaultProps={closeButtonVisible:true};const styleSheets={small:aphrodite.StyleSheet.create({dialog:{width:"100%",height:"100%",overflow:"hidden"}}),mdOrLarger:aphrodite.StyleSheet.create({dialog:{width:"93.75%",maxWidth:576,height:"81.25%",maxHeight:624}})};
|
|
77
74
|
|
|
@@ -3,12 +3,6 @@ declare const theme: {
|
|
|
3
3
|
* Shared tokens
|
|
4
4
|
*/
|
|
5
5
|
root: {
|
|
6
|
-
color: {
|
|
7
|
-
inverse: {
|
|
8
|
-
background: string;
|
|
9
|
-
foreground: string;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
12
6
|
border: {
|
|
13
7
|
radius: string;
|
|
14
8
|
};
|
|
@@ -16,14 +10,9 @@ declare const theme: {
|
|
|
16
10
|
/**
|
|
17
11
|
* Building blocks
|
|
18
12
|
*/
|
|
19
|
-
backdrop: {
|
|
20
|
-
color: {
|
|
21
|
-
background: string;
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
13
|
dialog: {
|
|
25
14
|
spacing: {
|
|
26
|
-
padding:
|
|
15
|
+
padding: string;
|
|
27
16
|
};
|
|
28
17
|
};
|
|
29
18
|
footer: {
|
|
@@ -37,22 +26,22 @@ declare const theme: {
|
|
|
37
26
|
secondary: string;
|
|
38
27
|
};
|
|
39
28
|
spacing: {
|
|
40
|
-
paddingBlockMd:
|
|
41
|
-
paddingInlineMd:
|
|
42
|
-
paddingInlineSm:
|
|
43
|
-
gap:
|
|
44
|
-
titleGapMd:
|
|
45
|
-
titleGapSm:
|
|
29
|
+
paddingBlockMd: string;
|
|
30
|
+
paddingInlineMd: string;
|
|
31
|
+
paddingInlineSm: string;
|
|
32
|
+
gap: string;
|
|
33
|
+
titleGapMd: string;
|
|
34
|
+
titleGapSm: string;
|
|
46
35
|
};
|
|
47
36
|
};
|
|
48
37
|
panel: {
|
|
49
38
|
spacing: {
|
|
50
|
-
gap:
|
|
39
|
+
gap: string;
|
|
51
40
|
};
|
|
52
41
|
};
|
|
53
42
|
closeButton: {
|
|
54
43
|
spacing: {
|
|
55
|
-
gap:
|
|
44
|
+
gap: string;
|
|
56
45
|
};
|
|
57
46
|
};
|
|
58
47
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
root: {
|
|
3
|
+
border: {
|
|
4
|
+
radius: string;
|
|
5
|
+
};
|
|
6
|
+
};
|
|
7
|
+
dialog: {
|
|
8
|
+
spacing: {
|
|
9
|
+
padding: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
footer: {
|
|
13
|
+
color: {
|
|
14
|
+
border: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
header: {
|
|
18
|
+
color: {
|
|
19
|
+
border: string;
|
|
20
|
+
secondary: string;
|
|
21
|
+
};
|
|
22
|
+
spacing: {
|
|
23
|
+
paddingBlockMd: string;
|
|
24
|
+
paddingInlineMd: string;
|
|
25
|
+
paddingInlineSm: string;
|
|
26
|
+
gap: string;
|
|
27
|
+
titleGapMd: string;
|
|
28
|
+
titleGapSm: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
panel: {
|
|
32
|
+
spacing: {
|
|
33
|
+
gap: string;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
closeButton: {
|
|
37
|
+
spacing: {
|
|
38
|
+
gap: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export default _default;
|
package/package.json
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-modal",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"design": "v2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"description": "",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/es/index.js",
|
|
12
|
+
"require": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./styles.css": "./dist/css/vars.css"
|
|
16
|
+
},
|
|
9
17
|
"main": "dist/index.js",
|
|
10
18
|
"types": "dist/index.d.ts",
|
|
11
19
|
"module": "dist/es/index.js",
|
|
@@ -13,14 +21,13 @@
|
|
|
13
21
|
"license": "MIT",
|
|
14
22
|
"dependencies": {
|
|
15
23
|
"@khanacademy/wonder-blocks-breadcrumbs": "3.1.20",
|
|
16
|
-
"@khanacademy/wonder-blocks-
|
|
17
|
-
"@khanacademy/wonder-blocks-icon-button": "10.3.3",
|
|
24
|
+
"@khanacademy/wonder-blocks-icon-button": "10.3.4",
|
|
18
25
|
"@khanacademy/wonder-blocks-layout": "3.1.20",
|
|
19
26
|
"@khanacademy/wonder-blocks-styles": "0.2.16",
|
|
20
|
-
"@khanacademy/wonder-blocks-theming": "3.4.0",
|
|
21
27
|
"@khanacademy/wonder-blocks-timing": "7.0.2",
|
|
28
|
+
"@khanacademy/wonder-blocks-core": "12.3.0",
|
|
22
29
|
"@khanacademy/wonder-blocks-tokens": "11.1.0",
|
|
23
|
-
"@khanacademy/wonder-blocks-typography": "4.2.
|
|
30
|
+
"@khanacademy/wonder-blocks-typography": "4.2.5"
|
|
24
31
|
},
|
|
25
32
|
"peerDependencies": {
|
|
26
33
|
"@phosphor-icons/core": "^2.0.2",
|
|
@@ -33,6 +40,7 @@
|
|
|
33
40
|
"@khanacademy/wb-dev-build-settings": "3.2.0"
|
|
34
41
|
},
|
|
35
42
|
"scripts": {
|
|
43
|
+
"build:css": "pnpm exec wonder-blocks-tokens .",
|
|
36
44
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
37
45
|
}
|
|
38
46
|
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The overrides for the Khanmigo theme.
|
|
3
|
-
*/
|
|
4
|
-
declare const theme: {
|
|
5
|
-
root: {
|
|
6
|
-
color: {
|
|
7
|
-
inverse: {
|
|
8
|
-
background: string;
|
|
9
|
-
foreground: string;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
12
|
-
border: {
|
|
13
|
-
radius: string;
|
|
14
|
-
};
|
|
15
|
-
};
|
|
16
|
-
backdrop: {
|
|
17
|
-
color: {
|
|
18
|
-
background: string;
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
dialog: {
|
|
22
|
-
spacing: {
|
|
23
|
-
padding: 16;
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
footer: {
|
|
27
|
-
color: {
|
|
28
|
-
border: string;
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
header: {
|
|
32
|
-
color: {
|
|
33
|
-
border: string;
|
|
34
|
-
secondary: string;
|
|
35
|
-
};
|
|
36
|
-
spacing: {
|
|
37
|
-
paddingBlockMd: 24;
|
|
38
|
-
paddingInlineMd: 32;
|
|
39
|
-
paddingInlineSm: 16;
|
|
40
|
-
gap: 8;
|
|
41
|
-
titleGapMd: 16;
|
|
42
|
-
titleGapSm: 32;
|
|
43
|
-
};
|
|
44
|
-
};
|
|
45
|
-
panel: {
|
|
46
|
-
spacing: {
|
|
47
|
-
gap: 32;
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
closeButton: {
|
|
51
|
-
spacing: {
|
|
52
|
-
gap: 8;
|
|
53
|
-
};
|
|
54
|
-
};
|
|
55
|
-
};
|
|
56
|
-
export default theme;
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import defaultTheme from "./default";
|
|
3
|
-
type Props = {
|
|
4
|
-
children: React.ReactNode;
|
|
5
|
-
};
|
|
6
|
-
export type ModalDialogThemeContract = typeof defaultTheme;
|
|
7
|
-
/**
|
|
8
|
-
* The context that provides the theme to the ModalDialog component.
|
|
9
|
-
* This is generally consumed via the `useScopedTheme` hook.
|
|
10
|
-
*/
|
|
11
|
-
export declare const ModalDialogThemeContext: React.Context<{
|
|
12
|
-
root: {
|
|
13
|
-
color: {
|
|
14
|
-
inverse: {
|
|
15
|
-
background: string;
|
|
16
|
-
foreground: string;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
border: {
|
|
20
|
-
radius: string;
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
backdrop: {
|
|
24
|
-
color: {
|
|
25
|
-
background: string;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
dialog: {
|
|
29
|
-
spacing: {
|
|
30
|
-
padding: 16;
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
footer: {
|
|
34
|
-
color: {
|
|
35
|
-
border: string;
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
header: {
|
|
39
|
-
color: {
|
|
40
|
-
border: string;
|
|
41
|
-
secondary: string;
|
|
42
|
-
};
|
|
43
|
-
spacing: {
|
|
44
|
-
paddingBlockMd: 24;
|
|
45
|
-
paddingInlineMd: 32;
|
|
46
|
-
paddingInlineSm: 16;
|
|
47
|
-
gap: 8;
|
|
48
|
-
titleGapMd: 16;
|
|
49
|
-
titleGapSm: 32;
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
panel: {
|
|
53
|
-
spacing: {
|
|
54
|
-
gap: 32;
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
closeButton: {
|
|
58
|
-
spacing: {
|
|
59
|
-
gap: 8;
|
|
60
|
-
};
|
|
61
|
-
};
|
|
62
|
-
}>;
|
|
63
|
-
/**
|
|
64
|
-
* ThemeModalDialog is a component that provides a theme to the <ModalDialog/>
|
|
65
|
-
* component.
|
|
66
|
-
*/
|
|
67
|
-
export default function ThemeModalDialog(props: Props): React.JSX.Element;
|
|
68
|
-
export {};
|