diff --git a/packages/ui-overlays/package.json b/packages/ui-overlays/package.json index 923c140e54..1d6fc42a45 100644 --- a/packages/ui-overlays/package.json +++ b/packages/ui-overlays/package.json @@ -80,11 +80,11 @@ "default": "./es/exports/a.js" }, "./v11_7": { - "src": "./src/exports/a.ts", - "types": "./types/exports/a.d.ts", - "import": "./es/exports/a.js", - "require": "./lib/exports/a.js", - "default": "./es/exports/a.js" + "src": "./src/exports/b.ts", + "types": "./types/exports/b.d.ts", + "import": "./es/exports/b.js", + "require": "./lib/exports/b.js", + "default": "./es/exports/b.js" }, "./latest": { "src": "./src/exports/a.ts", diff --git a/packages/ui-overlays/src/Mask/v2/MaskCounter.ts b/packages/ui-overlays/src/Mask/v2/MaskCounter.ts new file mode 100644 index 0000000000..5ea1525349 --- /dev/null +++ b/packages/ui-overlays/src/Mask/v2/MaskCounter.ts @@ -0,0 +1,47 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +const MaskCounter = (() => { + let counter = 0 + const getCounter = () => counter + const setCounter = (value: number) => { + counter = value + } + const incrementCounter = () => { + setCounter(getCounter() + 1) + } + + const decrementCounter = () => { + setCounter(getCounter() - 1) + } + + return { + getCounter, + setCounter, + incrementCounter, + decrementCounter + } +})() + +export default MaskCounter diff --git a/packages/ui-overlays/src/Mask/v2/README.md b/packages/ui-overlays/src/Mask/v2/README.md new file mode 100644 index 0000000000..8b1d4fb9f5 --- /dev/null +++ b/packages/ui-overlays/src/Mask/v2/README.md @@ -0,0 +1,56 @@ +--- +describes: Mask +--- + +A Mask component covers its closest positioned parent (either absolute or relative). + +```js +--- +type: example +--- + + Some content that is masked + + +``` + +The Mask component can be configured to cover the full screen if it is rendered inside a [Portal](Portal). + +```js +--- +type: example +--- +const Example = () => { + const [open, setOpen] = useState(false) + + const handleButtonClick = () => { + setOpen(!open) + } + + return ( +
+ + + { + setOpen(false) + }} + > + Click anywhere around this text to close the Mask + + +
+ ) +} + +render() +``` diff --git a/packages/ui-overlays/src/Mask/v1/__tests__/Mask.test.tsx b/packages/ui-overlays/src/Mask/v2/__tests__/Mask.test.tsx similarity index 100% rename from packages/ui-overlays/src/Mask/v1/__tests__/Mask.test.tsx rename to packages/ui-overlays/src/Mask/v2/__tests__/Mask.test.tsx diff --git a/packages/ui-overlays/src/Mask/v2/index.tsx b/packages/ui-overlays/src/Mask/v2/index.tsx new file mode 100644 index 0000000000..6b0039295f --- /dev/null +++ b/packages/ui-overlays/src/Mask/v2/index.tsx @@ -0,0 +1,119 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { Component } from 'react' +import noScroll from 'no-scroll' + +import { withStyle } from '@instructure/emotion' +import type { ComponentStyle } from '@instructure/emotion' +import { ensureSingleChild, omitProps } from '@instructure/ui-react-utils' + +import generateStyle from './styles' + +import type { MaskProps } from './props' +import { allowedProps } from './props' +import MaskCounter from './MaskCounter' + +/** +--- +category: components/utilities +--- +**/ +@withStyle(generateStyle) +class Mask extends Component { + static readonly componentId = 'Mask' + + static allowedProps = allowedProps + + static defaultProps = { + placement: 'center', + fullscreen: false + } + + componentDidMount() { + this.props.makeStyles?.() + + if (this.props.fullscreen) { + noScroll.on() + MaskCounter.incrementCounter() + } + } + + componentDidUpdate() { + this.props.makeStyles?.() + } + + componentWillUnmount() { + if (this.props.fullscreen) { + MaskCounter.decrementCounter() + if (MaskCounter.getCounter() <= 0) { + noScroll.off() + } + } + } + + ref: Element | null = null + + handleElementRef = (el: Element | null) => { + const { elementRef } = this.props + + this.ref = el + + if (typeof elementRef === 'function') { + elementRef(el) + } + } + + // It can be a ref for any type of child + _content: any + + contentRef: React.LegacyRef = (el) => { + this._content = el + } + + render() { + const content = ensureSingleChild(this.props.children, { + ref: this.contentRef + }) + + const props: React.ClassAttributes & + React.HTMLAttributes & { + css?: ComponentStyle<'mask'>['mask'] + } = { + ...omitProps(this.props, Mask.allowedProps), + css: this.props.styles?.mask, + ref: this.handleElementRef + } + + if (typeof this.props.onClick === 'function') { + props.onClick = this.props.onClick + props.tabIndex = -1 + } + + return {content} + } +} + +export default Mask +export { Mask } diff --git a/packages/ui-overlays/src/Mask/v2/props.ts b/packages/ui-overlays/src/Mask/v2/props.ts new file mode 100644 index 0000000000..e4830fccca --- /dev/null +++ b/packages/ui-overlays/src/Mask/v2/props.ts @@ -0,0 +1,58 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import type { MaskTheme, OtherHTMLAttributes } from '@instructure/shared-types' +import type { WithStyleProps, ComponentStyle } from '@instructure/emotion' + +type MaskOwnProps = { + children?: React.ReactNode + placement?: 'top' | 'center' | 'bottom' | 'stretch' + fullscreen?: boolean + onClick?: (event: React.MouseEvent) => void + /** + * provides a reference to the underlying html root element + */ + elementRef?: (element: Element | null) => void +} + +type PropKeys = keyof MaskOwnProps + +type AllowedPropKeys = Readonly> + +type MaskProps = MaskOwnProps & + WithStyleProps & + OtherHTMLAttributes + +type MaskStyle = ComponentStyle<'mask'> +const allowedProps: AllowedPropKeys = [ + 'placement', + 'fullscreen', + 'children', + 'onClick', + 'elementRef' +] + +export type { MaskProps, MaskStyle } +export { allowedProps } diff --git a/packages/ui-overlays/src/Mask/v2/styles.ts b/packages/ui-overlays/src/Mask/v2/styles.ts new file mode 100644 index 0000000000..3af10ebd1e --- /dev/null +++ b/packages/ui-overlays/src/Mask/v2/styles.ts @@ -0,0 +1,76 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import type { NewComponentTypes } from '@instructure/ui-themes' +import type { MaskProps, MaskStyle } from './props' + +/** + * --- + * private: true + * --- + * Generates the style object from the theme and provided additional information + * @param {Object} componentTheme The theme variable object. + * @param {Object} props the props of the component, the style is applied to + * @param {Object} sharedTokens Shared token object that stores common values for the theme. + * @param {Object} state the state of the component, the style is applied to + * @return {Object} The final style object, which will be used in the component + */ +const generateStyle = ( + componentTheme: NewComponentTypes['Mask'], + props: MaskProps +): MaskStyle => { + const { placement, fullscreen } = props + + const positionStyles = fullscreen + ? { position: 'fixed' } + : { position: 'absolute' } + + const placementStyles = { + top: { alignItems: 'flex-start' }, + center: { alignItems: 'center' }, + bottom: { alignItems: 'flex-end' }, + stretch: { alignItems: 'stretch' } + } + + return { + mask: { + label: 'mask', + boxSizing: 'border-box', + background: componentTheme.backgroundColor, + top: 0, + left: 0, + right: 0, + bottom: 0, + overflow: 'auto', + display: 'flex', + justifyContent: 'center', + outline: 'none', + zIndex: 9999, + ...positionStyles, + ...placementStyles[placement!] + } + } +} + +export default generateStyle diff --git a/packages/ui-overlays/src/exports/b.ts b/packages/ui-overlays/src/exports/b.ts new file mode 100644 index 0000000000..ae3b00c193 --- /dev/null +++ b/packages/ui-overlays/src/exports/b.ts @@ -0,0 +1,28 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +export { Mask } from '../Mask/v2' +export { Overlay } from '../Overlay/v1' + +export type { MaskProps } from '../Mask/v2/props' +export type { OverlayProps } from '../Overlay/v1/props'