diff --git a/PageViews/Page.js b/PageViews/Page.js new file mode 100644 index 0000000000..4601c91f5c --- /dev/null +++ b/PageViews/Page.js @@ -0,0 +1,26 @@ +import {useContext, useEffect} from 'react'; + +import {WizardPanelsContext} from '../WizardPanels/WizardPanels'; + +function Page ({ + children +}) { + const set = useContext(WizardPanelsContext); + + useEffect(() => { + if (set) { + set({ + children + }); + } + }, [ + children, + set + ]); + return null; +} + +export default Page; +export { + Page +}; diff --git a/PageViews/PageViews.js b/PageViews/PageViews.js new file mode 100644 index 0000000000..7748dd0bbc --- /dev/null +++ b/PageViews/PageViews.js @@ -0,0 +1,149 @@ +import handle, {forwardCustomWithPrevent} from '@enact/core/handle'; +import kind from '@enact/core/kind'; +import EnactPropTypes from '@enact/core/internal/prop-types'; +import SpotlightContainerDecorator, {spotlightDefaultClass} from '@enact/spotlight/SpotlightContainerDecorator'; +import {Row, Column, Cell} from '@enact/ui/Layout'; +import ViewManager from '@enact/ui/ViewManager'; +import Button from '../Button'; +import PropTypes from 'prop-types'; +import compose from 'ramda/src/compose'; +import Changeable from '@enact/ui/Changeable'; +import {I18nContextDecorator} from '@enact/i18n/I18nDecorator'; + +import {BasicArranger} from '../internal/Panels'; +import Skinnable from '../Skinnable'; +import Steps from '../Steps'; +import {WizardPanelsRouter} from '../WizardPanels/utils'; + +import css from './PageViews.module.less'; + +const PageViewsBase = kind({ + name: 'PageViews', + propTypes: { + componentRef: EnactPropTypes.ref, + current: PropTypes.number, + index: PropTypes.number, + onTransition: PropTypes.func, + onWillTransition: PropTypes.func, + reverseTransition: PropTypes.bool, + total: PropTypes.number, + totalPanels: PropTypes.number + }, + styles: { + css, + className: 'pageViews', + publicClassNames: true + }, + handlers: { + onNextClick: handle( + forwardCustomWithPrevent('onNextClick'), + (ev, {index, onChange, totalPanels}) => { + if (onChange && index !== totalPanels) { + const nextIndex = index < (totalPanels - 1) ? (index + 1) : index; + onChange({type: 'onChange', index: nextIndex}); + } + } + ), + onPrevClick: handle( + forwardCustomWithPrevent('onPrevClick'), + (ev, {index, onChange}) => { + if (onChange && index !== 0) { + const prevIndex = index > 0 ? (index - 1) : index; + onChange({type: 'onChange', index: prevIndex}); + } + } + ), + onTransition: (ev, {index, onTransition}) => { + if (onTransition) { + onTransition({type: 'onTransition', index}); + } + }, + onWillTransition: (ev, {index, onWillTransition}) => { + if (onWillTransition) { + onWillTransition({type: 'onWillTransition', index}); + } + } + }, + computed: { + steps: ({current, index, total, totalPanels}) => { + const currentStep = (typeof current === 'number' && current > 0) ? current : (index + 1); + const totalSteps = (typeof total === 'number' && total > 0) ? total : totalPanels; + + return ( + + ); + } + }, + render: ({ + children, + index, + onNextClick, + onPrevClick, + onTransition, + onWillTransition, + reverseTransition, + steps, + totalPanels, + ...rest + }) => { + const isPrevButtonVisible = index !== 0; + const isNextButtonVisible = index < totalPanels - 1; + delete rest.componentRef; + + return ( +
+ + + + {isPrevButtonVisible ?
+ ); + } +}); + +const PageViewsDecorator = compose( + Changeable({prop: 'index'}), + SpotlightContainerDecorator({ + continue5WayHold: true, + defaultElement: [`.${spotlightDefaultClass}`, '#NavButton'], + enterTo: 'last-focused' + }), + I18nContextDecorator({rtlProp: 'rtl'}), + WizardPanelsRouter, + Skinnable +); + +const PageViews = PageViewsDecorator(PageViewsBase); + +export default PageViews; +export { + PageViews +}; diff --git a/PageViews/PageViews.module.less b/PageViews/PageViews.module.less new file mode 100644 index 0000000000..3e8ecbf27e --- /dev/null +++ b/PageViews/PageViews.module.less @@ -0,0 +1,23 @@ +@import "../styles/variables.less"; + +.pageViews { + height: 100%; + width: 100%; + display: inline-flex; + flex-direction: column; + + .page { + background-color: #4c5059 + } + + .navButton { + text-align: center; + display: flex; + align-items: center; + width: 2*@sand-button-small-height; + } + + .steps { + text-align: center; + } +} diff --git a/PageViews/index.js b/PageViews/index.js new file mode 100644 index 0000000000..c90e69a079 --- /dev/null +++ b/PageViews/index.js @@ -0,0 +1,10 @@ +import {PageViews} from './PageViews'; +import Page from './Page'; + +PageViews.Page = Page; + +export default PageViews; +export { + Page, + PageViews +}; diff --git a/WizardPanels/WizardPanels.js b/WizardPanels/WizardPanels.js index 77ab78e7a7..f4751d36a4 100644 --- a/WizardPanels/WizardPanels.js +++ b/WizardPanels/WizardPanels.js @@ -1,7 +1,6 @@ import handle, {forProp, forwardCustom, forwardCustomWithPrevent, not} from '@enact/core/handle'; import kind from '@enact/core/kind'; import EnactPropTypes from '@enact/core/internal/prop-types'; -import useChainRefs from '@enact/core/useChainRefs'; import {I18nContextDecorator} from '@enact/i18n/I18nDecorator'; import Spotlight from '@enact/spotlight'; import SpotlightContainerDecorator, {spotlightDefaultClass} from '@enact/spotlight/SpotlightContainerDecorator'; @@ -12,18 +11,17 @@ import ViewManager from '@enact/ui/ViewManager'; import IString from 'ilib/lib/IString'; import PropTypes from 'prop-types'; import compose from 'ramda/src/compose'; -import {createContext, useRef, useState, useCallback, Children} from 'react'; +import {createContext} from 'react'; import $L from '../internal/$L'; import Button from '../Button'; import {Header} from '../Panels'; import {PanelBase} from '../Panels/Panel'; -import {BasicArranger, CrossFadeArranger, CancelDecorator, FloatingLayerIdProvider, NavigationButton, useAutoFocus} from '../internal/Panels'; +import {BasicArranger, CrossFadeArranger, CancelDecorator, FloatingLayerIdProvider, NavigationButton} from '../internal/Panels'; import Skinnable from '../Skinnable'; import Steps from '../Steps'; -import useFocusOnTransition from './useFocusOnTransition'; -import useToggleRole from './useToggleRole'; +import {WizardPanelsRouter} from './utils'; import css from './WizardPanels.module.less'; @@ -562,189 +560,6 @@ const WizardPanelsBase = kind({ } }); -// single-index ViewManagers need some help knowing when the transition direction needs to change -// because the index is always 0 from its perspective. -function useReverseTransition (index = -1, rtl) { - const prevIndex = useRef(index); - const reverse = useRef(rtl); - // If the index was changed, the panel transition is occured on the next cycle by `Panel` - const prev = {reverseTransition: reverse.current, prevIndex: prevIndex.current}; - - if (prevIndex.current !== index) { - reverse.current = rtl ? (index > prevIndex.current) : (index < prevIndex.current); - prevIndex.current = index; - } - - return prev; -} - -/** - * WizardPanelsRouter passes the children, footer, subtitle, and title from - * {@link sandstone/WizardPanels.Panel|WizardPanel} to - * {@link sandstone/WizardPanels.WizardPanelsBase|WizardPanelsBase}. - * - * @class WizardPanelsRouter - * @memberof sandstone/WizardPanels - * @private - */ -const WizardPanelsRouter = (Wrapped) => { - const WizardPanelsProvider = ({ - children, - componentRef, - 'data-spotlight-id': spotlightId, - index, - onTransition, - onWillTransition, - subtitle, - title, - rtl, - ...rest - }) => { - const [panel, setPanel] = useState(null); - const {ref: a11yRef, onWillTransition: a11yOnWillTransition} = useToggleRole(); - const autoFocus = useAutoFocus({autoFocus: 'default-element', hideChildren: panel == null}); - const ref = useChainRefs(autoFocus, a11yRef, componentRef); - const {reverseTransition, prevIndex} = useReverseTransition(index, rtl); - const { - onWillTransition: focusOnWillTransition, - ...transition - } = useFocusOnTransition({onTransition, onWillTransition, spotlightId}); - - const handleWillTransition = useCallback((ev) => { - focusOnWillTransition(ev); - a11yOnWillTransition(ev); - }, [a11yOnWillTransition, focusOnWillTransition]); - - const totalPanels = panel ? Children.count(children) : 0; - const currentTitle = panel && panel.title ? panel.title : title; - const currentSubTitle = panel && panel.subtitle ? panel.subtitle : subtitle; - // eslint-disable-next-line enact/prop-types - delete rest.onBack; - - return ( - - {Children.toArray(children)[index]} - - {panel && panel.children ? ( -
- {panel.children} -
- ) : null} -
-
- ); - }; - - WizardPanelsProvider.propTypes = /** @lends sandstone/WizardPanels.WizardPanelsRouter.prototype */ { - /** - * Obtains a reference to the root node. - * - * @type {Function|Object} - * @private - */ - componentRef: EnactPropTypes.ref, - - /** - * The spotlight id for the panel - * - * @type {String} - * @private - */ - 'data-spotlight-id': PropTypes.string, - - /** - * The currently selected step. - * - * @type {Number} - * @default 0 - * @private - */ - index: PropTypes.number, - - /** - * Disables panel transitions. - * - * @type {Boolean} - * @public - */ - noAnimation: PropTypes.bool, - - /** - * Called when a transition completes - * - * @type {Function} - * @private - */ - onTransition: PropTypes.func, - - /** - * Called when a transition begins - * - * @type {Function} - * @private - */ - onWillTransition: PropTypes.func, - - /** - * Used to determine the transition direction - * - * @type {Boolean} - * @private - */ - rtl: PropTypes.bool, - - /** - * The "default" subtitle for WizardPanels if subtitle isn't explicitly set in - * {@link sandstone/WizardPanels.Panel|Panel}. - * @example - * - * - * lorem ipsum ... - * - * - * - * @type {String} - * @private - */ - subtitle: PropTypes.string, - - /** - * The "default" title for WizardPanels if title isn't explicitly set in - * {@link sandstone/WizardPanels.Panel|Panel}. - * @example - * - * - * lorem ipsum ... - * - * - * - * @type {String} - * @private - */ - title: PropTypes.string - }; - - WizardPanelsProvider.defaultProps = { - index: 0, - subtitle: '', - title: '' - }; - - return WizardPanelsProvider; -}; - const WizardPanelsDecorator = compose( ForwardRef({prop: 'componentRef'}), Changeable({prop: 'index'}), diff --git a/WizardPanels/utils.js b/WizardPanels/utils.js new file mode 100644 index 0000000000..fdf5b75331 --- /dev/null +++ b/WizardPanels/utils.js @@ -0,0 +1,198 @@ +import {useRef, useState, useCallback, Children} from 'react'; +import EnactPropTypes from '@enact/core/internal/prop-types'; +import useChainRefs from '@enact/core/useChainRefs'; +import PropTypes from 'prop-types'; + +import {useAutoFocus} from '../internal/Panels'; + +import useFocusOnTransition from './useFocusOnTransition'; +import useToggleRole from './useToggleRole'; +import {WizardPanelsContext} from './WizardPanels'; + +// single-index ViewManagers need some help knowing when the transition direction needs to change +// because the index is always 0 from its perspective. +function useReverseTransition (index = -1, rtl) { + const prevIndex = useRef(index); + const reverse = useRef(rtl); + // If the index was changed, the panel transition is occured on the next cycle by `Panel` + const prev = {reverseTransition: reverse.current, prevIndex: prevIndex.current}; + + if (prevIndex.current !== index) { + reverse.current = rtl ? (index > prevIndex.current) : (index < prevIndex.current); + prevIndex.current = index; + } + + return prev; +} + +/** + * WizardPanelsRouter passes the children, footer, subtitle, and title from + * {@link sandstone/WizardPanels.Panel|WizardPanel} to + * {@link sandstone/WizardPanels.WizardPanelsBase|WizardPanelsBase}. + * + * @class WizardPanelsRouter + * @memberof sandstone/WizardPanels + * @private + */ +function WizardPanelsRouter (Wrapped) { + const WizardPanelsProvider = ({ + children, + componentRef, + 'data-spotlight-id': spotlightId, + index, + onTransition, + onWillTransition, + subtitle, + title, + rtl, + ...rest + }) => { + const [panel, setPanel] = useState(null); + const {ref: a11yRef, onWillTransition: a11yOnWillTransition} = useToggleRole(); + const autoFocus = useAutoFocus({autoFocus: 'default-element', hideChildren: panel == null}); + const ref = useChainRefs(autoFocus, a11yRef, componentRef); + const {reverseTransition, prevIndex} = useReverseTransition(index, rtl); + const { + onWillTransition: focusOnWillTransition, + ...transition + } = useFocusOnTransition({onTransition, onWillTransition, spotlightId}); + + const handleWillTransition = useCallback((ev) => { + focusOnWillTransition(ev); + a11yOnWillTransition(ev); + }, [a11yOnWillTransition, focusOnWillTransition]); + + const totalPanels = panel ? Children.count(children) : 0; + const currentTitle = panel && panel.title ? panel.title : title; + const currentSubTitle = panel && panel.subtitle ? panel.subtitle : subtitle; + // eslint-disable-next-line enact/prop-types + delete rest.onBack; + + return ( + + {Children.toArray(children)[index]} + + {panel && panel.children ? ( +
+ {panel.children} +
+ ) : null} +
+
+ ); + }; + + WizardPanelsProvider.propTypes = /** @lends sandstone/WizardPanels.WizardPanelsRouter.prototype */ { + /** + * Obtains a reference to the root node. + * + * @type {Function|Object} + * @private + */ + componentRef: EnactPropTypes.ref, + + /** + * The spotlight id for the panel + * + * @type {String} + * @private + */ + 'data-spotlight-id': PropTypes.string, + + /** + * The currently selected step. + * + * @type {Number} + * @default 0 + * @private + */ + index: PropTypes.number, + + /** + * Disables panel transitions. + * + * @type {Boolean} + * @public + */ + noAnimation: PropTypes.bool, + + /** + * Called when a transition completes + * + * @type {Function} + * @private + */ + onTransition: PropTypes.func, + + /** + * Called when a transition begins + * + * @type {Function} + * @private + */ + onWillTransition: PropTypes.func, + + /** + * Used to determine the transition direction + * + * @type {Boolean} + * @private + */ + rtl: PropTypes.bool, + + /** + * The "default" subtitle for WizardPanels if subtitle isn't explicitly set in + * {@link sandstone/WizardPanels.Panel|Panel}. + * @example + * + * + * lorem ipsum ... + * + * + * + * @type {String} + * @private + */ + subtitle: PropTypes.string, + + /** + * The "default" title for WizardPanels if title isn't explicitly set in + * {@link sandstone/WizardPanels.Panel|Panel}. + * @example + * + * + * lorem ipsum ... + * + * + * + * @type {String} + * @private + */ + title: PropTypes.string + }; + + WizardPanelsProvider.defaultProps = { + index: 0, + subtitle: '', + title: '' + }; + + return WizardPanelsProvider; +} + +export default WizardPanelsRouter; +export { + WizardPanelsRouter +}; diff --git a/samples/sampler/stories/default/PageViews.js b/samples/sampler/stories/default/PageViews.js new file mode 100644 index 0000000000..e0b2b8c18f --- /dev/null +++ b/samples/sampler/stories/default/PageViews.js @@ -0,0 +1,64 @@ +import {PageViews} from '@enact/sandstone/PageViews'; +import Item from '@enact/sandstone/Item'; +import {Cell, Row, Column} from '@enact/ui/Layout'; + +PageViews.displayName = 'PageViews'; + +export default { + title: 'Sandstone/PageViews', + component: 'PageViews' +}; + +export const _PageViews = () => ( + + +
+ Item 1 + Item 2 +
+
+ + + + Country + City + Team + Rank + + + Korea + Seoul + Team A + 1 + + + USA + NewYork + Team B + 2 + + + France + Paris + Team C + 3 + + + + +
+ This is page 3 +
+
+ + This is page 4 + +
+); + +_PageViews.storyName = 'PageViews'; +_PageViews.parameters = { + info: { + text: 'The basic PageViews' + } +};