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 ? : null}
+ |
+
+
+ {children}
+
+ |
+
+ {isNextButtonVisible ? : null}
+ |
+
+
+ {steps}
+ |
+
+
+ );
+ }
+});
+
+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'
+ }
+};