diff --git a/PopupContainer/PopupContainer.js b/PopupContainer/PopupContainer.js new file mode 100644 index 0000000000..a869a3335f --- /dev/null +++ b/PopupContainer/PopupContainer.js @@ -0,0 +1,205 @@ +/** + * Modal component available with free positioning. + * + * @example + * + * + * {* App Contents *} + * + * + * + * @module sandstone/PopupContainer + * @exports PopupContainer + * @exports PopupContainerBase + */ + +import {forward, forwardCustom} from '@enact/core/handle'; +import kind from '@enact/core/kind'; +// import {I18nContextDecorator} from '@enact/i18n/I18nDecorator'; +import FloatingLayer from '@enact/ui/FloatingLayer'; +import PropTypes from 'prop-types'; +import {Component} from 'react'; + +import css from './PopupContainer.module.less'; + +/** + * + * @class PopupContainerBase + * @memberof sandstone/PopupContainer + * @ui + * @private + */ +const PopupContainerBase = kind({ + name: 'PopupContainerBase', + + propTypes: /** @lends sandstone/PopupContainer.PopupContainerBase.prototype */ { + /** + * Force direction like ltr, rtl. + * + * @type {String} + * @public + */ + forceDirection: PropTypes.string, + + /** + * Position of the PopupContainer on the screen. + * + * @type {Object} + * @public + */ + position: PropTypes.object, + + /** + * Whether rtl locale. + * + * @type {Boolean} + * @private + */ + rtl: PropTypes.bool + }, + + defaultProps: { + position: {left: 0, top: 0} + }, + + styles: { + css, + className: 'container' + }, + + computed: { + style: ({position, rtl, forceDirection}) => { + if (forceDirection === 'rtl') { + return {transform: `translate(${position.left * (-1)}px,${position.top}px`}; + } + if (forceDirection === 'ltr') { + return {transform: `translate(${position.left}px,${position.top}px`}; + } + if (rtl) { + return {transform: `translate(${position.left * (-1)}px,${position.top}px`}; + } + if (!rtl) { + return {transform: `translate(${position.left}px,${position.top}px`}; + } + }, + directionStyle: ({forceDirection, rtl}) => { + if (forceDirection) return {direction : forceDirection}; + if (!forceDirection && rtl) return {direction: 'rtl'}; + if (!forceDirection && !rtl) return {direction: 'ltr'}; + } + }, + + render: ({children, directionStyle, ...rest}) => { + delete rest.forceDirection; + delete rest.position; + delete rest.rtl; + + return ( +
+
+
{children}
+
+
+ ); + } +}); + +// const I18nPopupContainer = I18nContextDecorator( +// {rtlProp: 'rtl'}, +// PopupContainerBase +// ); + +/** + * + * @class PopupContainer + * @memberof sandstone/PopupContainer + * @extends sandstone/PopupContainerBase + * @ui + * @private + */ +class PopupContainer extends Component { + + static propTypes = /** @lends sandstone/PopupContainer.PopupContainer.prototype */ { + /** + * Force direction like rtl, ltr. + * + * @type {String} + * @public + */ + forceDirection: PropTypes.string, + + /** + * Called when the user has attempted to close the popup. + * + * @type {Function} + * @public + */ + onClose: PropTypes.func, + + /** + * Called when the popup is opened. + * + * @type {Function} + * @public + */ + onOpen: PropTypes.func, + + /** + * Controls the visibility of the PopupContainer. + * + * By default, the PopupContainer and its contents are not rendered until `open`. + * + * @type {Boolean} + * @default false + * @public + */ + open: PropTypes.bool, + + /** + * Position of the PopupContainer on the screen. + * + * @type {Object} + * @public + */ + position: PropTypes.object + }; + + static defaultProps = { + open: false, + position: {left: 0, top: 0} + }; + + constructor (props) { + super(props); + } + + handleClose = (ev) => { + forwardCustom('onClose')(ev, this.props); + }; + + handleOpen = (ev) => { + forward('onOpen', ev, this.props); + }; + + render () { + const {open, children, ...rest} = this.props; + delete rest.onClose; + delete rest.onOpen; + + return ( + + + {children} + + + ); + } +} + +export default PopupContainer; +export {PopupContainer, PopupContainerBase}; diff --git a/PopupContainer/PopupContainer.module.less b/PopupContainer/PopupContainer.module.less new file mode 100644 index 0000000000..79ed12ad23 --- /dev/null +++ b/PopupContainer/PopupContainer.module.less @@ -0,0 +1,7 @@ +// PopupContainer.module.less + +.PopupContainer { + .container { + position: absolute; + } +} diff --git a/PopupContainer/package.json b/PopupContainer/package.json new file mode 100644 index 0000000000..81d4e61cc6 --- /dev/null +++ b/PopupContainer/package.json @@ -0,0 +1,3 @@ +{ + "main": "PopupContainer.js" +} diff --git a/PopupContainer/tests/PopupContainer-specs.js b/PopupContainer/tests/PopupContainer-specs.js new file mode 100644 index 0000000000..d7ff3e706a --- /dev/null +++ b/PopupContainer/tests/PopupContainer-specs.js @@ -0,0 +1,35 @@ +import {FloatingLayerDecorator} from '@enact/ui/FloatingLayer'; + +import '@testing-library/jest-dom'; +import {render, screen} from '@testing-library/react'; + +import {PopupContainer} from '../PopupContainer'; + +const FloatingLayerController = FloatingLayerDecorator('div'); + +describe('PopupContainer specs', () => { + test('should be rendered opened if open is set to true', () => { + render( + +
PopupContainer
+
+ ); + + const popupContainer = screen.getByText('PopupContainer'); + + expect(popupContainer).toBeInTheDocument(); + }); + + test('should not be rendered if open is set to false', () => { + render( + +
PopupContainer
+
+ ); + + const popupContainer = screen.queryByText('PopupContainer'); + + expect(popupContainer).toBeNull(); + }); +}); + diff --git a/samples/sampler/stories/default/PopupContainer.js b/samples/sampler/stories/default/PopupContainer.js new file mode 100644 index 0000000000..6f16583442 --- /dev/null +++ b/samples/sampler/stories/default/PopupContainer.js @@ -0,0 +1,73 @@ +import {I18nContextDecorator} from '@enact/i18n/I18nDecorator'; +import BodyText from '@enact/sandstone/BodyText'; +import PopupContainer from '@enact/sandstone/PopupContainer'; +import {mergeComponentMetadata} from '@enact/storybook-utils'; +import {action} from '@enact/storybook-utils/addons/actions'; +import {boolean, object} from '@enact/storybook-utils/addons/controls'; + +import {svgGenerator} from '../helper/svg'; +import PropTypes from 'prop-types'; + +const Config = mergeComponentMetadata('PopupContainer', PopupContainer); + +export default { + title: 'Sandstone/PopupContainer', + component: 'PopupContainer' +}; + +const AppImageContents = () => guide; +const AppTextContents = () =>
This is text for popup contents
; + +const contentStyle = { + display: 'flex', + alignItems: 'center', + border: 'solid 1px', + borderRadius: '20px' +}; + +const PopupContents = () => ( +
+ + +
+); + +const PopupContainerBaseSample = ({args}) => { + return ( +
+ + + + Use CONTROLS to interact with PopupContainer. +
+ ); +}; + +PopupContainerBaseSample.propTypes = { + args: PropTypes.object, + rtl: PropTypes.bool +}; + +const PopupContainerSamples = I18nContextDecorator( + {rtlProp: 'rtl'}, + PopupContainerBaseSample +); + +export const _PopupContainer = (args) => ; + +boolean('open', _PopupContainer, Config); +object('position', _PopupContainer, Config, {'left': 300, 'top': 300}); +// select('forceDirection', _PopupContainer,[null, 'ltr', 'rtl'], Config, null); + +_PopupContainer.storyName = 'PopupContainer'; +_PopupContainer.parameters = { + info: { + text: 'Basic usage of PopupContainer' + } +};