diff --git a/.travis.yml b/.travis.yml
index fd77a8bdc..225a4744c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,7 +13,6 @@ install:
- npm install
script:
- echo -e "\x1b\x5b35;1m*** Starting eslint...\x1b\x5b0m"
- - rm tutorial-typescript -rf # Skip the test until CLI fixed the related issue(WRO-521)
- npm run lint
- echo -e "\x1b\x5b35;1m*** eslint complete\x1b\x5b0m"
- echo -e "\x1b\x5b35;1m*** Starting agate/all-samples build...\x1b\x5b0m"
@@ -22,6 +21,12 @@ script:
- npm run pack
- cd ../../
- echo -e "\x1b\x5b35;1m*** agate/all-samples build complete\x1b\x5b0m"
+ - echo -e "\x1b\x5b35;1m*** Starting limestone/all-samples build...\x1b\x5b0m"
+ - cd limestone/all-samples
+ - npm install
+ - npm run pack
+ - cd ../../
+ - echo -e "\x1b\x5b35;1m*** limestone/all-samples build complete\x1b\x5b0m"
- echo -e "\x1b\x5b35;1m*** Starting moonstone/all-samples build...\x1b\x5b0m"
- cd moonstone/all-samples
- npm install
diff --git a/limestone/all-samples/.gitignore b/limestone/all-samples/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/all-samples/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/all-samples/LICENSE b/limestone/all-samples/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/all-samples/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/all-samples/README.md b/limestone/all-samples/README.md
new file mode 100644
index 000000000..35b62b59a
--- /dev/null
+++ b/limestone/all-samples/README.md
@@ -0,0 +1,3 @@
+### all-samples
+
+This application is a wrapper application that links to all of the other sample applications for limestone.
diff --git a/limestone/all-samples/assets/images/butterfly.jpg b/limestone/all-samples/assets/images/butterfly.jpg
new file mode 100644
index 000000000..be4a1e0d2
Binary files /dev/null and b/limestone/all-samples/assets/images/butterfly.jpg differ
diff --git a/limestone/all-samples/assets/images/car.jpeg b/limestone/all-samples/assets/images/car.jpeg
new file mode 100644
index 000000000..a8fa4a973
Binary files /dev/null and b/limestone/all-samples/assets/images/car.jpeg differ
diff --git a/limestone/all-samples/assets/images/city.jpeg b/limestone/all-samples/assets/images/city.jpeg
new file mode 100644
index 000000000..22191b606
Binary files /dev/null and b/limestone/all-samples/assets/images/city.jpeg differ
diff --git a/limestone/all-samples/assets/images/details.jpg b/limestone/all-samples/assets/images/details.jpg
new file mode 100644
index 000000000..7a5b70df9
Binary files /dev/null and b/limestone/all-samples/assets/images/details.jpg differ
diff --git a/limestone/all-samples/assets/images/favorites-list.jpg b/limestone/all-samples/assets/images/favorites-list.jpg
new file mode 100644
index 000000000..6bfd409fc
Binary files /dev/null and b/limestone/all-samples/assets/images/favorites-list.jpg differ
diff --git a/limestone/all-samples/assets/images/frozenwaterfall.jpg b/limestone/all-samples/assets/images/frozenwaterfall.jpg
new file mode 100644
index 000000000..5ad17a364
Binary files /dev/null and b/limestone/all-samples/assets/images/frozenwaterfall.jpg differ
diff --git a/limestone/all-samples/assets/images/jellyfish.jpg b/limestone/all-samples/assets/images/jellyfish.jpg
new file mode 100644
index 000000000..7d05343d6
Binary files /dev/null and b/limestone/all-samples/assets/images/jellyfish.jpg differ
diff --git a/limestone/all-samples/assets/images/macaw.jpg b/limestone/all-samples/assets/images/macaw.jpg
new file mode 100644
index 000000000..b5ca62ca4
Binary files /dev/null and b/limestone/all-samples/assets/images/macaw.jpg differ
diff --git a/limestone/all-samples/assets/images/mural.jpeg b/limestone/all-samples/assets/images/mural.jpeg
new file mode 100644
index 000000000..205b74231
Binary files /dev/null and b/limestone/all-samples/assets/images/mural.jpeg differ
diff --git a/limestone/all-samples/assets/images/ornaments.jpg b/limestone/all-samples/assets/images/ornaments.jpg
new file mode 100644
index 000000000..475541785
Binary files /dev/null and b/limestone/all-samples/assets/images/ornaments.jpg differ
diff --git a/limestone/all-samples/assets/images/rainbow.jpg b/limestone/all-samples/assets/images/rainbow.jpg
new file mode 100644
index 000000000..71accaf92
Binary files /dev/null and b/limestone/all-samples/assets/images/rainbow.jpg differ
diff --git a/limestone/all-samples/assets/images/space-shuttle.jpg b/limestone/all-samples/assets/images/space-shuttle.jpg
new file mode 100644
index 000000000..e0cc3f172
Binary files /dev/null and b/limestone/all-samples/assets/images/space-shuttle.jpg differ
diff --git a/limestone/all-samples/assets/images/violin.jpeg b/limestone/all-samples/assets/images/violin.jpeg
new file mode 100644
index 000000000..0f2c9b1d6
Binary files /dev/null and b/limestone/all-samples/assets/images/violin.jpeg differ
diff --git a/limestone/all-samples/package.json b/limestone/all-samples/package.json
new file mode 100644
index 000000000..146c5fab2
--- /dev/null
+++ b/limestone/all-samples/package.json
@@ -0,0 +1,54 @@
+{
+ "name": "all-samples",
+ "version": "1.0.0",
+ "description": "A collection of sample applications for limestone.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "Limestone QA Samples",
+ "isomorphic": true,
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "@enact/webos": "^5.0.0-alpha.4",
+ "@reduxjs/toolkit": "^2.3.0",
+ "classnames": "^2.5.1",
+ "hls.js": "^1.5.17",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "query-string": "^9.1.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-redux": "^9.1.2",
+ "react-router-dom": "^6.28.0",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/all-samples/resources/ilibmanifest.json b/limestone/all-samples/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/all-samples/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/all-samples/src/App/App.js b/limestone/all-samples/src/App/App.js
new file mode 100644
index 000000000..27597cb5c
--- /dev/null
+++ b/limestone/all-samples/src/App/App.js
@@ -0,0 +1,111 @@
+import kind from '@enact/core/kind';
+import Scroller from '@enact/limestone/Scroller';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+import PropTypes from 'prop-types';
+import {HashRouter, Route, Routes, useNavigate} from 'react-router-dom';
+import {StaticRouter} from 'react-router-dom/server';
+
+import SampleItem from '../components/SampleItem';
+import ButtonToSamples from '../components/ButtonToSamples';
+import {AppBase as PatternDynamicPanel} from '../../../pattern-dynamic-panel/src/App/App';
+import {AppBase as PatternAccountIcon} from '../../../pattern-account-icon/src/App/App';
+import {AppBase as PatternVideoPlayerCustom} from '../../../pattern-video-player-custom/src/App/App';
+import {AppBase as PatternLayout} from '../../../pattern-layout/src/App/App';
+import {appElementBase as PatternLocaleSwitching} from '../../../pattern-locale-switching/src/main';
+import {App as PatternReact18New} from '../../../pattern-react18-new/src/App';
+import {appElementBase as PatternRoutablePanels} from '../../../pattern-routable-panels/src/main';
+import {AppBase as PatternSinglePanel} from '../../../pattern-single-panel/src/App/App';
+import {appElementBase as PatternSinglePanelRedux} from '../../../pattern-single-panel-redux/src/main';
+import {AppBase as PatternVideoPlayer} from '../../../pattern-video-player/src/App/App';
+import {appElementBase as PatternVirtualgridlistApi} from '../../../pattern-virtualgridlist-api/src/main';
+import {App as PatternVirtualgridlistIncrementalLoad} from '../../../pattern-virtualgridlist-incremental-load/src/App/App';
+import {appElementBase as PatternVirtuallistPreservingFocus} from '../../../pattern-virtuallist-preserving-focus/src/main';
+import {AppBase as TutorialHelloEnact} from '../../../tutorial-hello-enact/src/App/App';
+import {AppBase as TutorialKittenBrowser} from '../../../tutorial-kitten-browser/src/App/App';
+
+import css from './App.module.less';
+
+const NavigationMenu = kind({
+ name: 'NavigationMenu',
+
+ functional: true,
+
+ propTypes: {
+ location: PropTypes.any,
+ match: PropTypes.any,
+ staticContext: PropTypes.any
+ },
+
+ render: ({...props}) => {
+ delete props.match;
+ delete props.location;
+ delete props.staticContext;
+
+ const navigate = useNavigate(); //eslint-disable-line
+
+ return (
+
+
+ );
+ }
+});
+
+const App = ThemeDecorator(AppBase);
+export default App;
diff --git a/limestone/all-samples/src/App/App.module.less b/limestone/all-samples/src/App/App.module.less
new file mode 100644
index 000000000..23782defd
--- /dev/null
+++ b/limestone/all-samples/src/App/App.module.less
@@ -0,0 +1,3 @@
+.app {
+ // styles can be put here
+}
diff --git a/limestone/all-samples/src/App/package.json b/limestone/all-samples/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/all-samples/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/all-samples/src/components/ButtonToSamples/ButtonToSamples.js b/limestone/all-samples/src/components/ButtonToSamples/ButtonToSamples.js
new file mode 100644
index 000000000..8bda47fbd
--- /dev/null
+++ b/limestone/all-samples/src/components/ButtonToSamples/ButtonToSamples.js
@@ -0,0 +1,19 @@
+import Button from '@enact/limestone/Button';
+import {Link} from 'react-router-dom';
+
+import css from './ButtonToSamples.module.less';
+
+const ButtonToSamples = () => (
+
+
+
+
+
+);
+
+export default ButtonToSamples;
diff --git a/limestone/all-samples/src/components/ButtonToSamples/ButtonToSamples.module.less b/limestone/all-samples/src/components/ButtonToSamples/ButtonToSamples.module.less
new file mode 100644
index 000000000..a563aca30
--- /dev/null
+++ b/limestone/all-samples/src/components/ButtonToSamples/ButtonToSamples.module.less
@@ -0,0 +1,22 @@
+.backLink {
+ z-index: 1;
+ margin-right: 5%;
+
+ .backButton {
+ z-index: 2;
+ transform: translate(0px, -100px);
+ transition: all .5s ease-in-out;
+ }
+
+ &:hover {
+ .backButton {
+ transform: translate(0px, 0px);
+ }
+ }
+}
+
+.buttonContainer {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+}
diff --git a/limestone/all-samples/src/components/ButtonToSamples/package.json b/limestone/all-samples/src/components/ButtonToSamples/package.json
new file mode 100644
index 000000000..0aa02ee6e
--- /dev/null
+++ b/limestone/all-samples/src/components/ButtonToSamples/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "ButtonToSamples.js"
+}
diff --git a/limestone/all-samples/src/components/README.md b/limestone/all-samples/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/all-samples/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/all-samples/src/components/SampleItem/SampleItem.js b/limestone/all-samples/src/components/SampleItem/SampleItem.js
new file mode 100644
index 000000000..e68e3c93f
--- /dev/null
+++ b/limestone/all-samples/src/components/SampleItem/SampleItem.js
@@ -0,0 +1,22 @@
+import Item from '@enact/limestone/Item';
+import PropTypes from 'prop-types';
+import {useCallback} from 'react';
+
+const SampleItem = ({children, navigate, path, ...rest}) => {
+ const itemSelect = useCallback(() => {
+ navigate({pathname: path});
+ }, [navigate, path]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+SampleItem.propTypes = {
+ navigate: PropTypes.func,
+ path: PropTypes.any
+};
+
+export default SampleItem;
diff --git a/limestone/all-samples/src/components/SampleItem/package.json b/limestone/all-samples/src/components/SampleItem/package.json
new file mode 100644
index 000000000..974620350
--- /dev/null
+++ b/limestone/all-samples/src/components/SampleItem/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "SampleItem.js"
+}
diff --git a/limestone/all-samples/src/index.js b/limestone/all-samples/src/index.js
new file mode 100644
index 000000000..083b00238
--- /dev/null
+++ b/limestone/all-samples/src/index.js
@@ -0,0 +1,29 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './App';
+
+// Router causes an error with our samples, but we don't want our samples to know about router.
+// To avoid this for now we're just suppressing the error.
+/* eslint-disable no-console */
+const originalConsoleError = console.error;
+
+console.error = (...args) => {
+ return (args[0].includes('React does not recognize the `%s` prop on a DOM element.') && args[1] === 'staticContext') || args[0].includes('Unknown props `match`, `location`, `history`, `staticContext`') || args[0].includes('Warning: Hash history cannot PUSH the same path') ? null : originalConsoleError(...args);
+};
+/* eslint-enable no-console */
+
+const appElement = ();
+
+// In a browser environment, render the app to the document.
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/all-samples/src/views/README.md b/limestone/all-samples/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/all-samples/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/all-samples/webos-meta/appinfo.json b/limestone/all-samples/webos-meta/appinfo.json
new file mode 100644
index 000000000..4377fa8e3
--- /dev/null
+++ b/limestone/all-samples/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.limestoneqa",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Limestone QA Samples",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/all-samples/webos-meta/icon-large.png b/limestone/all-samples/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/all-samples/webos-meta/icon-large.png differ
diff --git a/limestone/all-samples/webos-meta/icon-mini.png b/limestone/all-samples/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/all-samples/webos-meta/icon-mini.png differ
diff --git a/limestone/all-samples/webos-meta/icon.png b/limestone/all-samples/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/all-samples/webos-meta/icon.png differ
diff --git a/limestone/feature-custom-skin-generator/.gitignore b/limestone/feature-custom-skin-generator/.gitignore
new file mode 100644
index 000000000..f94ea516b
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/feature-custom-skin-generator/README-devs.md b/limestone/feature-custom-skin-generator/README-devs.md
new file mode 100644
index 000000000..30c9671f6
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/README-devs.md
@@ -0,0 +1,33 @@
+## How to add/remove/change colors for a custom skin application:
+
+In order to change or add color to the app, you need 3 different things:
+1) The name you wish to display inside the app
+2) Its value in hex
+3) The name of the CSS property
+
+With these three things, you must next go to the `MainPanel.js` file and create a pair of [`colorName`, `setColorName`]
+using the hook `useState` with the default value in hex code mentioned above. You must add
+`colorName` to the `colors` array and the `setColorName` to `setColors` array. You must also add
+the display name into `propNames` array and the CSS property name into `varNames` array.
+
+Example: For `TextColorRGB`, we created [`TextSubColor`, `setTextSubColor`] = `useState(`'#ABAEB3'`)`
+and added them to `colors` and `setColors`. We chose 'Text Sub Color' as the display name and
+added it to `propNames`. The CSS property we added to `varNames` is `--sand-text-sub-color`.
+
+Please add RGB at the end display name if the color must be represented as a comma-separated RGB value (ex: `Component Text Color RGB`).
+
+When we add all those variables to their arrays, please add them at the same index as this is the method
+by which we do most of the operations (ex: if `TextSubColor` has index 2 in `colors` array `setTextSubColor` should have the same index in
+the `setColors` array and so on).
+
+Next, please go to the `util.js` file for generating colors including a new color. Here, `generateColors` function returns an array
+of colors that contains all the colors we use for auto mode. Please insert a new value in that array at
+the index you inserted the color in all those arrays back in the `MainPanel.js` file. The value is determined as described below:
+
+If the default hex value for our property is equal to another's, we copy that property's return element for
+the new one (ex: `Text Sub Color` has the same default value as `Component Text Sub Color RGB` so they
+both return `textColors[0].toUpperCase()`). This is the case if they are both in the same category (both are
+background colors or both are text colors). If this is the first time this hex value appears, just add one
+to the limit of `generateBGColors` (or `generateTextColors` for text colors) and use the new generated color.
+After you add, remove, or edit a variable, please check the app to see if any unintended changes are applied
+to the non-live preview components.
diff --git a/limestone/feature-custom-skin-generator/README.md b/limestone/feature-custom-skin-generator/README.md
new file mode 100644
index 000000000..2d526e5ca
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/README.md
@@ -0,0 +1,21 @@
+## Custom Skin
+
+A sample Enact application that uses custom-skin feature to style the components.
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+This app will help you to generate a stylesheet that customizes a Limestone UI component for your application. On the left side of the app, you can see all the customizable color fields, while on the right side is the `Live Preview` area. Any value changes you make inside the color fields will be reflected `Live Preview`.
+
+#### Customize Stylesheet
+
+The dropdown on the top of the page allows you to choose some presets including Limestone default skin. There is also an option to import your own CSS file.
+
+Turning on the `Generate color automatically` switch will generate a skin configuration automatically based on the background and text color you choose.
+
+You can interact with color fields by changing their value inside the input field, or by clicking the colored square which will open the basic color picker.
+
+#### Save Stylesheet
+
+At the bottom of the page, you can check out the generated custom CSS. `Show Output` will open a popup containing the contents of the generated stylesheet. The `Copy` button will copy that content to the clipboard. `Download` will generate a customized CSS file. The output shows only the modified value of CSS variables from the Limestone default skin but you can see all the CSS variable list by turning on the `Save full set of variables`.
+
+The `Reset` button will revert all the changes to the active selected preset.
diff --git a/limestone/feature-custom-skin-generator/package.json b/limestone/feature-custom-skin-generator/package.json
new file mode 100644
index 000000000..2a22dac61
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "custom-skin-generator",
+ "version": "1.0.0",
+ "description": "A general template for an Enact Limestone application.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "UNLICENSED",
+ "private": true,
+ "repository": "",
+ "enact": {
+ "theme": "limestone"
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "eslintIgnore": [
+ "node_modules/*",
+ "build/*",
+ "dist/*"
+ ],
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "classnames": "^2.5.1",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/feature-custom-skin-generator/resources/ilibmanifest.json b/limestone/feature-custom-skin-generator/resources/ilibmanifest.json
new file mode 100644
index 000000000..5916671d2
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
\ No newline at end of file
diff --git a/limestone/feature-custom-skin-generator/screenTypes.json b/limestone/feature-custom-skin-generator/screenTypes.json
new file mode 100644
index 000000000..0671927ab
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/screenTypes.json
@@ -0,0 +1,9 @@
+[
+ {"name": "pal", "pxPerRem": 12, "width": 1024, "height": 576, "aspectRatioName": "hdtv"},
+ {"name": "hd", "pxPerRem": 16, "width": 1280, "height": 720, "aspectRatioName": "hdtv"},
+ {"name": "fhd", "pxPerRem": 24, "width": 1920, "height": 1080, "aspectRatioName": "hdtv"},
+ {"name": "uw-uxga", "pxPerRem": 24, "width": 2560, "height": 1080, "aspectRatioName": "cinema"},
+ {"name": "qhd", "pxPerRem": 36, "width": 2560, "height": 1440, "aspectRatioName": "hdtv"},
+ {"name": "uhd", "pxPerRem": 48, "width": 3840, "height": 2160, "aspectRatioName": "hdtv", "base": true},
+ {"name": "uhd2", "pxPerRem": 96, "width": 7680, "height": 4320, "aspectRatioName": "hdtv"}
+]
diff --git a/limestone/feature-custom-skin-generator/src/App/App.js b/limestone/feature-custom-skin-generator/src/App/App.js
new file mode 100644
index 000000000..a36e4ec6a
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/App/App.js
@@ -0,0 +1,27 @@
+import kind from '@enact/core/kind';
+import Panels from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+
+import MainPanel from '../views/MainPanel';
+import screenTypes from '../../screenTypes.json';
+
+import css from './App.module.less';
+
+const App = kind({
+ name: 'App',
+
+ styles: {
+ css,
+ className: 'app'
+ },
+
+ render: (props) => (
+
+
+
+
+
+ )
+});
+
+export default ThemeDecorator({ri: {screenTypes}}, App);
diff --git a/limestone/feature-custom-skin-generator/src/App/App.module.less b/limestone/feature-custom-skin-generator/src/App/App.module.less
new file mode 100644
index 000000000..f0eab41ad
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/App/App.module.less
@@ -0,0 +1,10 @@
+.app {
+ .panels {
+ background-color: #1F3044;
+ font-size: 48px;
+ }
+
+ p {
+ font-size: 48px;
+ }
+}
diff --git a/limestone/feature-custom-skin-generator/src/App/package.json b/limestone/feature-custom-skin-generator/src/App/package.json
new file mode 100644
index 000000000..add0ba2a3
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
\ No newline at end of file
diff --git a/limestone/feature-custom-skin-generator/src/common/styles.module.less b/limestone/feature-custom-skin-generator/src/common/styles.module.less
new file mode 100644
index 000000000..9696030bc
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/common/styles.module.less
@@ -0,0 +1,51 @@
+// General styles
+
+.scrollerColors {
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-color-rgb: 230, 230, 230;
+ --sand-shadow-color-rgb: none;
+}
+
+.inputField {
+ --sand-shadow-color-rgb: none;
+ align-items: center;
+ padding: 12px 0;
+
+ .labelField {
+ color: #e6e6e6;
+ margin: 0 36px;
+ }
+}
+
+.colorBlock[type=color] {
+ height: 132px;
+ width: 132px;
+ min-width: 132px;
+ border-color: transparent;
+ border-radius: 12px;
+
+ &::-webkit-color-swatch-wrapper {
+ padding: 0;
+ }
+
+ &::-webkit-color-swatch {
+ border-color: transparent;
+ }
+}
+
+.customAlert {
+ --sand-alert-overlay-bg-color-rgb: 76, 89, 106;
+ --sand-component-bg-color: #7D848C;
+ --sand-component-focus-text-color-rgb: 76, 80, 89;
+ --sand-component-text-color-rgb: 230, 230, 230;
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ --sand-overlay-bg-color-rgb: 76, 89, 106;
+ --sand-shadow-color-rgb: none;
+ --sand-text-color-rgb: 230, 230, 230;
+ text-align-last: center;
+
+ .customAlertMsg {
+ --sand-text-color-rgb: 230, 230, 230;
+ }
+}
diff --git a/limestone/feature-custom-skin-generator/src/components/AutoPopup/AutoPopup.js b/limestone/feature-custom-skin-generator/src/components/AutoPopup/AutoPopup.js
new file mode 100644
index 000000000..c509003d3
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/components/AutoPopup/AutoPopup.js
@@ -0,0 +1,93 @@
+import kind from '@enact/core/kind';
+import BodyText from '@enact/limestone/BodyText';
+import Button from '@enact/limestone/Button';
+import Popup from '@enact/limestone/Popup';
+import PropTypes from 'prop-types';
+
+import commonCss from '../../common/styles.module.less';
+
+/**
+ * A popup component containing the alert for `switch to auto` warning
+ */
+const AutoPopup = kind({
+ name: 'AutoPopup',
+
+ propTypes:{
+ /**
+ * Indicates the auto mode
+ *
+ * @type {Boolean}
+ * @required
+ * @public
+ */
+ auto: PropTypes.bool.isRequired,
+
+ /**
+ * Opens the popup if certain conditions are met
+ *
+ * @type {Boolean}
+ * @required
+ * @public
+ */
+ openWarning: PropTypes.bool.isRequired,
+
+ /**
+ * Setter function that interacts with prop `auto`
+ *
+ * @type {Function}
+ * @required
+ * @public
+ */
+ setAuto: PropTypes.func.isRequired,
+
+ /**
+ * Setter function that resets the number of changes since `auto` was turned off
+ *
+ * @type {Function}
+ * @required
+ * @public
+ */
+ setChanges: PropTypes.func.isRequired,
+
+ /**
+ * Setter function that interacts with prop `openWarning`
+ *
+ * @type {Function}
+ * @required
+ * @public
+ */
+ setOpenWarning: PropTypes.func.isRequired
+ },
+
+ handlers:{
+ // Handler for the 'Yes' button of popup
+ // It sets the `auto` to it's opposite value, changes to 0 and `openWarning` to false.
+ onClickOk:(event, {auto, setAuto, setChanges, setOpenWarning}) => {
+ setAuto(!auto);
+ setChanges(0);
+ setOpenWarning(false);
+ },
+ // Handler for the 'No' button of popup
+ // It sets `openWarning` to false but it does not affect the other variables.
+ onClickCancel:(event, {setOpenWarning}) => {
+ setOpenWarning(false);
+ }
+ },
+
+ render:({onClickCancel, onClickOk, openWarning, ...rest}) => {
+ delete rest.auto;
+ delete rest.setAuto;
+ delete rest.setChanges;
+ delete rest.setOpenWarning;
+
+ return (
+
+ Do you want to switch from manual to auto?
+
+
+
+ );
+ }
+});
+
+export default AutoPopup;
diff --git a/limestone/feature-custom-skin-generator/src/components/ColorField/ColorField.js b/limestone/feature-custom-skin-generator/src/components/ColorField/ColorField.js
new file mode 100644
index 000000000..b3807973f
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/components/ColorField/ColorField.js
@@ -0,0 +1,72 @@
+import kind from '@enact/core/kind';
+import PropTypes from 'prop-types';
+import SingleField from '../SingleField/SingleField';
+import TripleField from '../TripleField/TripleField';
+
+/**
+ * A component that contains either SingleField or TripleField component
+ * based on the `propName` prop
+ */
+const ColorField = kind({
+ name: 'ColorField',
+
+ propTypes: {
+ /**
+ * Indicates the color displayed in this component
+ *
+ * @type {String}
+ * @required
+ * @public
+ */
+ color: PropTypes.string.isRequired,
+
+ /**
+ * Indicates if the component is disabled
+ *
+ * @type {Boolean}
+ * @required
+ * @public
+ */
+ disabled: PropTypes.bool.isRequired,
+
+ /**
+ * Indicates this components's position inside a larger list of components
+ * @type {Number}
+ * @required
+ * @public
+ */
+ index: PropTypes.number.isRequired,
+
+ /**
+ * Setter function that interacts with prop `color`
+ *
+ * @type {Function}
+ * @required
+ * @public
+ */
+ onChangeInput: PropTypes.func.isRequired,
+
+ /**
+ * Holds the name of the ColorFields
+ *
+ * @type {String}
+ * @required
+ * @public
+ */
+ propName: PropTypes.string.isRequired
+ },
+
+ render: ({color, disabled, index, onChangeInput, propName, ...rest}) => {
+ // As per requirement some colors must be displayed as RGB while other are hex.
+ // Our logic is not affected by this as we only use hex, but to display them we decided that if they hold RGB in name we will use
+ // TripleField instead of single field.
+ const tripleField = propName.includes('RGB');
+ return (
+ tripleField ?
+ :
+
+ );
+ }
+});
+
+export default ColorField;
diff --git a/limestone/feature-custom-skin-generator/src/components/ColorFields/ColorFields.js b/limestone/feature-custom-skin-generator/src/components/ColorFields/ColorFields.js
new file mode 100644
index 000000000..99213b86f
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/components/ColorFields/ColorFields.js
@@ -0,0 +1,86 @@
+import kind from '@enact/core/kind';
+import PropTypes from 'prop-types';
+
+import ColorField from '../ColorField/ColorField';
+import NameField from '../NameField/NameField';
+
+/**
+ * A component that contains all the input fields of the custom-skin application
+ * It contains a NameField for the skin and one ColorField for each color that can be customized.
+ */
+const ColorFields = kind({
+ name: 'ColorFields',
+
+ propTypes:{
+ /**
+ * Indicates the auto mode
+ *
+ * @type {Boolean}
+ * @required
+ * @public
+ */
+ auto: PropTypes.bool.isRequired,
+
+ /**
+ * An array containing all the colors for the variables we support customization for
+ *
+ * @type {Array}
+ * @required
+ * @public
+ */
+ colors: PropTypes.array.isRequired,
+
+ /**
+ * Indicates the name of the Skin
+ *
+ * @type {String}
+ * @required
+ * @public
+ */
+ name: PropTypes.string.isRequired,
+
+ /**
+ * Setter function that interacts with props `colors` and `name`
+ *
+ * @type {Function}
+ * @required
+ * @public
+ */
+ onChangeInput: PropTypes.func.isRequired,
+
+ /**
+ * An array containing all of the names for the variables we support customization for
+ *
+ * @type {Array}
+ * @required
+ * @public
+ */
+ propNames: PropTypes.array.isRequired
+ },
+
+ render:({auto, colors, name, onChangeInput, propNames, ...rest}) => {
+ return (
+
+ );
+ }
+});
+
+export default ImportSkin;
diff --git a/limestone/feature-custom-skin-generator/src/components/ImportSkin/ImportSkin.module.less b/limestone/feature-custom-skin-generator/src/components/ImportSkin/ImportSkin.module.less
new file mode 100644
index 000000000..0ebdcd801
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/components/ImportSkin/ImportSkin.module.less
@@ -0,0 +1,11 @@
+.inputFile {
+ .presetDropdown {
+ --sand-component-bg-color: #7D848C;
+ --sand-component-focus-text-color-rgb: 76, 80, 89;
+ --sand-component-text-color-rgb: 230, 230, 230;
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ --sand-selected-bg-color: #3E454D;
+ --sand-shadow-color-rgb: none;
+ margin: 0;
+ }
+}
diff --git a/limestone/feature-custom-skin-generator/src/components/NameField/NameField.js b/limestone/feature-custom-skin-generator/src/components/NameField/NameField.js
new file mode 100644
index 000000000..802158770
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/components/NameField/NameField.js
@@ -0,0 +1,103 @@
+import kind from '@enact/core/kind';
+import BodyText from '@enact/limestone/BodyText';
+import {InputField} from '@enact/limestone/Input';
+import {Cell, Layout} from '@enact/ui/Layout';
+import classnames from 'classnames';
+import PropTypes from 'prop-types';
+
+import commonCss from '../../common/styles.module.less';
+import componentCss from './NameField.module.less';
+
+/**
+ * A component that contains a label and an input field, used to denote the name of the skin
+ */
+const NameField = kind({
+ name: 'NameField',
+
+ propTypes: {
+ /**
+ * Setter function that interacts with prop `name`
+ *
+ * @type {Function}
+ * @required
+ * @public
+ */
+ onChangeInput: PropTypes.func.isRequired,
+
+ /**
+ * The name displayed next to the input field
+ *
+ * @type {String}
+ * @required
+ * @public
+ */
+ propName: PropTypes.string.isRequired,
+
+ /**
+ * Customizes the component by mapping the supplied collection of CSS class names to the
+ * corresponding internal elements and states of this component.
+ *
+ * @type {Object}
+ * @public
+ */
+ css: PropTypes.object,
+
+ /**
+ * The value attributed to input
+ *
+ * @type {String}
+ * @default ''
+ * @public
+ */
+ name: PropTypes.string,
+
+ /**
+ * The value attributed to input as a placeholder
+ *
+ * @type {String}
+ * @default 'Custom Skin'
+ * @public
+ */
+ placeholder: PropTypes.string
+ },
+
+ defaultProps: {
+ name : '',
+ placeholder : 'Custom Skin'
+ },
+
+ styles:{
+ css: componentCss,
+ className:'nameField'
+ },
+
+ handlers: {
+ // Handler that sends back to Main Panel the event captured and the name of the field it comes from via the
+ // onChangeInput function
+ onChangeInputField: (event, {onChangeInput, propName}) => {
+ onChangeInput({event, name: propName});
+ }
+ },
+
+ render: ({css, name, onChangeInputField, placeholder, propName, ...rest}) => {
+ delete rest.onChangeInput;
+
+ return (
+
+
+ {propName}
+
+
+
+ );
+ }
+});
+
+export default NameField;
diff --git a/limestone/feature-custom-skin-generator/src/components/NameField/NameField.module.less b/limestone/feature-custom-skin-generator/src/components/NameField/NameField.module.less
new file mode 100644
index 000000000..9f1376869
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/components/NameField/NameField.module.less
@@ -0,0 +1,20 @@
+.nameField {
+ --sand-component-focus-text-color-rgb: 0, 0, 0;
+ --sand-component-text-color-rgb: 230, 230, 230;
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ --sand-selection-bg-color: #3399FF;
+ --sand-selection-color: #4C5059;
+ --sand-shadow-color-rgb: none;
+ color: #e6e6e6;
+
+ .inputField{
+ color: #e6e6e6;
+ margin: 0;
+ max-width: 65%;
+
+ .input {
+ font-size: 48px;
+ }
+ }
+
+}
diff --git a/limestone/feature-custom-skin-generator/src/components/OutputField/OutputField.js b/limestone/feature-custom-skin-generator/src/components/OutputField/OutputField.js
new file mode 100644
index 000000000..74076cf4c
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/components/OutputField/OutputField.js
@@ -0,0 +1,202 @@
+/* eslint-disable react/jsx-no-bind */
+
+import kind from '@enact/core/kind';
+import platform from '@enact/core/platform';
+import BodyText from '@enact/limestone/BodyText';
+import Button from '@enact/limestone/Button';
+import Popup from '@enact/limestone/Popup';
+import Scroller from '@enact/limestone/Scroller';
+import Switch from '@enact/limestone/Switch';
+import Toggleable from '@enact/ui/Toggleable';
+import TooltipDecorator from '@enact/limestone/TooltipDecorator';
+import {Cell, Column, Row} from '@enact/ui/Layout';
+import PropTypes from 'prop-types';
+
+import css from './OutputField.module.less';
+
+import {generateCSS, generateCSSFile, getPresetDifferences} from '../../utils';
+
+const TooltipButton = TooltipDecorator({tooltipDestinationProp: 'decoration'}, Button);
+
+/**
+ * A component that contains the footer of the application
+ */
+const OutputField = kind({
+ name: 'OutputField',
+
+ propTypes:{
+ /**
+ * An array containing all of the colors for the variables we support customization for
+ *
+ * @type {Array}
+ * @required
+ * @public
+ */
+ colors: PropTypes.array.isRequired,
+
+ /**
+ * Opens the popup if certain conditions are met
+ *
+ * @type {Boolean}
+ * @required
+ * @public
+ */
+ fullCSS: PropTypes.bool.isRequired,
+
+ /**
+ * Setter function that interacts with prop `fullCSS`
+ *
+ * @type {Function}
+ * @required
+ * @public
+ */
+ handleFullCSS: PropTypes.func.isRequired,
+
+ /**
+ * Setter function that interacts with prop `popupOpen`
+ *
+ * @type {Function}
+ * @required
+ * @public
+ */
+ onToggleOpen: PropTypes.func.isRequired,
+
+ /**
+ * Opens the popup if certain conditions are met
+ *
+ * @type {Boolean}
+ * @required
+ * @public
+ */
+ popupOpen: PropTypes.bool.isRequired,
+
+ /**
+ * Object that contains a preset
+ *
+ * @type {Object}
+ * @required
+ * @public
+ */
+ presetColors: PropTypes.object.isRequired,
+
+ /**
+ * Function that resets all the values of the current skin to the selected preset
+ *
+ * @type {Function}
+ * @required
+ * @public
+ */
+ setDefaultState: PropTypes.func.isRequired,
+
+ /**
+ * The current value from the NameField
+ *
+ * @type {String}
+ * @required
+ * @public
+ */
+ skinName: PropTypes.string.isRequired,
+
+ /**
+ * An array containing all of the css properties we support customization for
+ *
+ * @type {Array}
+ * @required
+ * @public
+ */
+ varNames: PropTypes.array.isRequired
+ },
+
+ handlers:{
+ // Handler function that generates a css file that hold our current customization
+ generateFile: (event, {colors, fullCSS, presetColors, skinName, varNames}) => {
+ if (fullCSS) {
+ return generateCSSFile(skinName, generateCSS(colors, skinName, varNames));
+ } else {
+ return generateCSSFile(skinName, generateCSS(getPresetDifferences(colors, presetColors), skinName, varNames));
+ }
+ },
+ // Removes some css styles included by the handleOpen and handleFocus handlers.
+ handleClose: () => {
+ if (typeof document !== 'undefined') {
+ document.querySelector('#temporaryStylesheet')?.remove();
+ }
+ },
+ // Appends some styles via javascript. The styles must be appended for the
+ // non live demo components to have the basic limestone appearance.
+ handleFocus: () => {
+ if (typeof document !== 'undefined') {
+ const sheet = document.createElement('style');
+ sheet.id = 'temporaryStylesheet';
+ sheet.innerHTML = `.limestone-theme {
+ --sand-component-focus-text-color-rgb: 76, 80, 89;
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ --sand-shadow-color-rgb: none;
+ }`;
+ document.body?.appendChild(sheet);
+ }
+ },
+ // Opens the popup that contains the css and appends some styles via javascript. The styles must be appended
+ // for the non live demo components to have the basic limestone appearance.
+ handleOpen: (ev, {onToggleOpen}) => {
+ if (typeof document !== 'undefined') {
+ const sheet = document.createElement('style');
+ sheet.id = 'temporaryStylesheet';
+ sheet.innerHTML = `.limestone-theme {
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-color-rgb: 230, 230, 230;
+ --sand-overlay-bg-color-rgb: 87, 94, 102;
+ --sand-shadow-color-rgb: none;
+ }`;
+ document.body?.appendChild(sheet);
+ onToggleOpen();
+ }
+ }
+ },
+
+ computed: {
+ text: ({colors, fullCSS, presetColors, skinName, varNames}) => {
+ if (fullCSS) {
+ return generateCSS(colors, skinName, varNames);
+ } else {
+ return generateCSS(getPresetDifferences(colors, presetColors), skinName, varNames);
+ }
+ }
+ },
+
+ render: ({fullCSS, generateFile, handleClose, handleFocus, handleFullCSS, handleOpen, onToggleOpen, popupOpen, setDefaultState, text}) => {
+ // Function that copies the content of the custom-skin css file into clipboard
+ function copyToClipboard () {
+ return navigator.clipboard?.writeText(text);
+ }
+
+ return (
+
+
+
+
+ {text}
+
+
+
+
+
+
+ Save full set of variables
+
+
+
+ {!platform.webos ? Show output : ''}
+ {!platform.webos ? Copy : ''}
+ {!platform.webos ? Download : ''}
+
+
+ Reset
+
+
+
+ );
+ }});
+
+export default Toggleable({prop: 'popupOpen', toggle: 'onToggleOpen'}, OutputField);
diff --git a/limestone/feature-custom-skin-generator/src/components/OutputField/OutputField.module.less b/limestone/feature-custom-skin-generator/src/components/OutputField/OutputField.module.less
new file mode 100644
index 000000000..53dc30b87
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/components/OutputField/OutputField.module.less
@@ -0,0 +1,48 @@
+.outputField {
+ --sand-component-bg-color: #7D848C;
+ --sand-component-focus-text-color-rgb: 76, 80, 89;
+ --sand-component-text-color-rgb: 230, 230, 230;
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ --sand-shadow-color-rgb: none;
+
+ .outputBtnContainer {
+ display: flex;
+ flex-wrap: wrap;
+
+ .outputBtn {
+ --sand-focus-bg-color: #fff;
+ color: #e6e6e6;
+ margin: 18px 36px;
+
+ .icon {
+ font-size: 51px;
+ }
+ }
+
+ .switchContainer{
+ padding-top: 24px;
+
+ .switchLabel {
+ display: inline-block;
+ color: #e6e6e6;
+ }
+
+ .switchControl {
+ --sand-focus-bg-color-rgb: 125, 132, 140;
+ --sand-selected-color-rgb: 0, 0, 0;
+ --sand-shadow-color-rgb: none;
+ --sand-toggle-on-color: #505050;
+ --sand-toggle-on-bg-color: #e6e6e6;
+ --sand-toggle-off-color: #4c596a;
+ --sand-toggle-off-bg-color: #abaeb3;
+ margin: 18px;
+ }
+ }
+ }
+}
+
+.outputData {
+ font-size: 33px;
+ color: #e6e6e6;
+ max-height: 1200px;
+}
diff --git a/limestone/feature-custom-skin-generator/src/components/SingleField/SingleField.js b/limestone/feature-custom-skin-generator/src/components/SingleField/SingleField.js
new file mode 100644
index 000000000..a53521864
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/components/SingleField/SingleField.js
@@ -0,0 +1,109 @@
+import kind from '@enact/core/kind';
+import platform from '@enact/core/platform';
+import BodyText from '@enact/limestone/BodyText';
+import {InputField} from '@enact/limestone/Input';
+import {Cell, Layout} from '@enact/ui/Layout';
+import PropTypes from 'prop-types';
+
+import ColorPicker from '../ColorPicker/ColorPicker';
+
+import commonCss from '../../common/styles.module.less';
+import componentCss from './SingleField.module.less';
+
+/**
+ * A component that contains a label and an input field, used to change the value of a css attribute
+ */
+const SingleField = kind({
+ name: 'SingleField',
+
+ propTypes: {
+ /**
+ * Indicates this component's position inside a larger list of components
+ * @type {Number}
+ * @required
+ * @public
+ */
+ index: PropTypes.number.isRequired,
+
+ /**
+ * Setter function that interacts with prop `color`
+ *
+ * @type {Function}
+ * @required
+ * @public
+ */
+ onChangeInput: PropTypes.func.isRequired,
+
+ /**
+ * Holds the name displayed next to the input
+ *
+ * @type {String}
+ * @required
+ * @public
+ */
+ propName: PropTypes.string.isRequired,
+
+ /**
+ * Indicates the color displayed in the input field
+ *
+ * @type {String}
+ * @default #FB9039
+ * @public
+ */
+ color: PropTypes.string,
+
+ /**
+ * Customizes the component by mapping the supplied collection of CSS class names to the
+ * corresponding internal elements and states of this component.
+ *
+ * @type {Object}
+ * @public
+ */
+ css: PropTypes.object
+ },
+
+ defaultProps: {
+ color : '#FB9039'
+ },
+
+ styles:{
+ css: componentCss,
+ className:'singleField'
+ },
+
+ handlers: {
+ // Handler that sends back to Main Panel the event captured by the input field and the name of the field
+ // it comes from via the onChangeInput function and the index
+ onChangeInputField: (event, {index, onChangeInput, propName}) => {
+ onChangeInput({event, name: propName, index: index});
+ },
+
+ // Handler that sends back to Main Panel the event captured by the color picker and the name of the field
+ // it comes from via the onChangeInput function and the index
+ onChangeInput: (event, {index, onChangeInput, propName}) => {
+ onChangeInput({event: event.target, name: propName, index: index});
+ }
+ },
+
+ render: ({color, css, onChangeInput, onChangeInputField, propName, ...rest}) => {
+ delete rest.index;
+ delete rest.onChangeInput;
+
+ return (
+
+
+ {propName}
+
+
+ {platform.webos ?
+ :
+
+ }
+
+
+
+ );
+ }
+});
+
+export default SingleField;
diff --git a/limestone/feature-custom-skin-generator/src/components/SingleField/SingleField.module.less b/limestone/feature-custom-skin-generator/src/components/SingleField/SingleField.module.less
new file mode 100644
index 000000000..fb79b6021
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/components/SingleField/SingleField.module.less
@@ -0,0 +1,17 @@
+.singleField {
+ --sand-shadow-color-rgb: none;
+ display: flex;
+
+ .singleInput {
+ --sand-component-focus-text-color-rgb: 0, 0, 0;
+ --sand-focus-bg-color-rgb: 255, 255, 255;
+ --sand-selection-bg-color: #3399FF;
+ --sand-selection-color: #4C5059;
+ color: #e6e6e6;
+ max-width: 336px;
+
+ .input {
+ font-size: 48px;
+ }
+ }
+}
diff --git a/limestone/feature-custom-skin-generator/src/components/TripleField/TripleField.js b/limestone/feature-custom-skin-generator/src/components/TripleField/TripleField.js
new file mode 100644
index 000000000..8e2d31bee
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/components/TripleField/TripleField.js
@@ -0,0 +1,141 @@
+import kind from '@enact/core/kind';
+import platform from '@enact/core/platform';
+import BodyText from '@enact/limestone/BodyText';
+import {InputField} from '@enact/limestone/Input';
+import {Cell, Layout} from '@enact/ui/Layout';
+import PropTypes from 'prop-types';
+
+import ColorPicker from '../ColorPicker/ColorPicker';
+
+import {convertHexToRGB, convertRGBToHex} from '../../utils';
+
+import commonCss from '../../common/styles.module.less';
+import componentCss from './TripleField.module.less';
+
+/**
+ * A component that contains a label and three input fields, used to change the value of a css attribute
+ */
+const TripleField = kind({
+ name: 'TripleField',
+
+ propTypes: {
+ /**
+ * Indicates this component's position inside a larger list of components
+ * @type {Number}
+ * @required
+ * @public
+ */
+ index: PropTypes.number.isRequired,
+
+ /**
+ * Setter function that interacts with prop `color`
+ *
+ * @type {Function}
+ * @required
+ * @public
+ */
+ onChangeInput: PropTypes.func.isRequired,
+
+ /**
+ * Holds the name displayed next to the input
+ *
+ * @type {String}
+ * @required
+ * @public
+ */
+ propName: PropTypes.string.isRequired,
+
+ /**
+ * Indicates the color displayed in the input fields (converted from hex to RGB)
+ *
+ * @type {String}
+ * @default #FFFFFF
+ * @public
+ */
+ color: PropTypes.string,
+
+ /**
+ * Customizes the component by mapping the supplied collection of CSS class names to the
+ * corresponding internal elements and states of this component.
+ *
+ * @type {Object}
+ * @public
+ */
+ css: PropTypes.object
+ },
+
+ defaultProps: {
+ color: '#FFFFFF'
+ },
+
+ styles:{
+ css: componentCss,
+ className:'tripleField'
+ },
+
+ handlers: {
+ // Handler that sends back to Main Panel the event captured by the color picker and the name of the field
+ // it comes from via the onChangeInput function and the index
+ onChangeInput: (event, {index, onChangeInput, propName}) => {
+ onChangeInput({event: event.target, name: propName, index: index});
+ },
+ // Handler that sends back to Main Panel the new color value and the name of the field
+ // it comes from via the onChangeInput function and the index
+ onChangeInputB: (event, {color, index, onChangeInput, propName}) => {
+ const colors = convertHexToRGB(color);
+ const newColor = event.value ? parseInt(event.value) : 0;
+ onChangeInput({event: {value: convertRGBToHex([colors[0], colors[1], newColor])}, name: propName, index: index});
+ },
+ // Handler that sends back to Main Panel the new color value and the name of the field
+ // it comes from via the onChangeInput function and the index
+ onChangeInputG: (event, {color, index, onChangeInput, propName}) => {
+ const colors = convertHexToRGB(color);
+ const newColor = event.value ? parseInt(event.value) : 0;
+ onChangeInput({event: {value: convertRGBToHex([colors[0], newColor, colors[2]])}, name: propName, index: index});
+ },
+ // Handler that sends back to Main Panel the new color value and the name of the field
+ // it comes from via the onChangeInput function and the index
+ onChangeInputR: (event, {color, index, onChangeInput, propName}) => {
+ const colors = convertHexToRGB(color);
+ const newColor = event.value ? parseInt(event.value) : 0;
+ onChangeInput({event: {value: convertRGBToHex([newColor, colors[1], colors[2]])}, name: propName, index: index});
+ }
+ },
+
+ render: ({color, css, onChangeInput, onChangeInputB, onChangeInputG, onChangeInputR, propName, ...rest}) => {
+ delete rest.index;
+ delete rest.onChangeInput;
+
+ const colors = convertHexToRGB(color);
+
+ return (
+
+
+ {propName}
+
+
+ {platform.webos ?
+ :
+
+ }
+
+
+
+ R:
+
+
+
+ G:
+
+
+
+ B:
+
+
+
+
+ );
+ }
+});
+
+export default TripleField;
diff --git a/limestone/feature-custom-skin-generator/src/components/TripleField/TripleField.module.less b/limestone/feature-custom-skin-generator/src/components/TripleField/TripleField.module.less
new file mode 100644
index 000000000..60a1a9a35
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/components/TripleField/TripleField.module.less
@@ -0,0 +1,32 @@
+.tripleField {
+ --sand-component-focus-text-color-rgb: 0, 0, 0;
+ --sand-shadow-color-rgb: none;
+ display: flex;
+ align-items: center;
+ margin-left: 36px;
+ flex-wrap: wrap;
+
+ .tripleText {
+ color: #e6e6e6;
+ }
+
+ .redInput,
+ .greenInput,
+ .blueInput {
+ --sand-focus-bg-color-rgb: 255, 255, 255;
+ --sand-selection-bg-color: #3399FF;
+ --sand-selection-color: #4C5059;
+ color: #e6e6e6;
+ max-width: 171px;
+ margin: 3px 15px;
+
+ .input {
+ font-size: 48px;
+ }
+ }
+
+ .redInput,
+ .blueInput {
+ margin-left: 21px;
+ }
+}
diff --git a/limestone/feature-custom-skin-generator/src/constants.js b/limestone/feature-custom-skin-generator/src/constants.js
new file mode 100644
index 000000000..06b5cb5b9
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/constants.js
@@ -0,0 +1,504 @@
+export const presets = {
+ defaultTheme: {
+ '--sand-bg-color': '#000000',
+ '--sand-text-color-rgb': '#E6E6E6',
+ '--sand-text-sub-color': '#ABAEB3',
+ '--sand-shadow-color-rgb': '#000000',
+ '--sand-component-text-color-rgb': '#E6E6E6',
+ '--sand-component-text-sub-color-rgb': '#ABAEB3',
+ '--sand-component-bg-color': '#7D848C',
+ '--sand-component-active-indicator-bg-color': '#E6E6E6',
+ '--sand-component-inactive-indicator-bg-color': '#9DA2A7',
+ '--sand-focus-text-color': '#FFFFFF',
+ '--sand-focus-bg-color-rgb': '#E6E6E6',
+ '--sand-component-focus-text-color-rgb': '#4C5059',
+ '--sand-component-focus-active-indicator-bg-color': '#4C5059',
+ '--sand-component-focus-inactive-indicator-bg-color': '#B8B9BB',
+ '--sand-selected-color-rgb': '#E6E6E6',
+ '--sand-selected-text-color': '#E6E6E6',
+ '--sand-selected-bg-color': '#3E454D',
+ '--sand-disabled-focus-bg-color': '#ABAEB3',
+ '--sand-disabled-selected-color': '#4C5059',
+ '--sand-disabled-selected-bg-color': '#E6E6E6',
+ '--sand-disabled-selected-focus-color': '#E6E6E6',
+ '--sand-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-fullscreen-bg-color': '#000000',
+ '--sand-overlay-bg-color-rgb': '#575E66',
+ '--sand-selection-color': '#4C5059',
+ '--sand-selection-bg-color': '#3399FF',
+ '--sand-toggle-off-color': '#AEAEAE',
+ '--sand-toggle-off-bg-color': '#777777',
+ '--sand-toggle-on-color': '#E6E6E6',
+ '--sand-toggle-on-bg-color': '#30AD6B',
+ '--sand-progress-color-rgb': '#E6E6E6',
+ '--sand-progress-buffer-color': '#6B6D73',
+ '--sand-progress-bg-color-rgb': '#373A41',
+ '--sand-progress-highlighted-color': '#FFFFFF',
+ '--sand-progress-slider-color': '#8D9298',
+ '--sand-spinner-color-rgb': '#FFFFFF',
+ '--sand-checkbox-color': '#E6E6E6',
+ '--sand-item-disabled-focus-bg-color': '#E6E6E6',
+ '--sand-keyguide-bg-color-rgb': '#373A41',
+ '--sand-slider-disabled-knob-bg-color': '#666666',
+ '--sand-alert-overlay-bg-color-rgb': '#CACBCC',
+ '--sand-alert-overlay-text-color-rgb': '#2E3239',
+ '--sand-alert-overlay-text-sub-color': '#2E3239',
+ '--sand-alert-overlay-focus-text-color': '#575E66',
+ '--sand-alert-overlay-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-disabled-selected-bg-color': '#788688',
+ '--sand-alert-overlay-disabled-selected-focus-color': '#E6E6E6',
+ '--sand-alert-overlay-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-alert-overlay-progress-color-rgb': '#373A41',
+ '--sand-alert-overlay-progress-bg-color-rgb': '#A1A1A1',
+ '--sand-alert-overlay-checkbox-color': '#858B92',
+ '--sand-alert-overlay-checkbox-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-formcheckboxitem-focus-text-color': '#575E66',
+ '--sand-alert-overlay-item-disabled-focus-bg-color': '#989CA2'
+ },
+ blueColorSet1: {
+ '--sand-bg-color': '#272829',
+ '--sand-text-color-rgb': '#E6E6E6',
+ '--sand-text-sub-color': '#ABAEB3',
+ '--sand-shadow-color-rgb': '#000000',
+ '--sand-component-text-color-rgb': '#E6E6E6',
+ '--sand-component-text-sub-color-rgb': '#ABAEB3',
+ '--sand-component-bg-color': '#7D848C',
+ '--sand-component-active-indicator-bg-color': '#E6E6E6',
+ '--sand-component-inactive-indicator-bg-color': '#9DA2A7',
+ '--sand-focus-text-color': '#303030',
+ '--sand-focus-bg-color-rgb': '#E6E6E6',
+ '--sand-component-focus-text-color-rgb': '#333333',
+ '--sand-component-focus-active-indicator-bg-color': '#4C5059',
+ '--sand-component-focus-inactive-indicator-bg-color': '#B8B9BB',
+ '--sand-selected-color-rgb': '#E6E6E6',
+ '--sand-selected-text-color': '#E6E6E6',
+ '--sand-selected-bg-color': '#61688E',
+ '--sand-disabled-focus-bg-color': '#ABAEB3',
+ '--sand-disabled-selected-color': '#4C5059',
+ '--sand-disabled-selected-bg-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-fullscreen-bg-color': '#000000',
+ '--sand-overlay-bg-color-rgb': '#292929',
+ '--sand-selection-color': '#4C5059',
+ '--sand-selection-bg-color': '#3399FF',
+ '--sand-toggle-off-color': '#AEAEAE',
+ '--sand-toggle-off-bg-color': '#494949',
+ '--sand-toggle-on-color': '#E6E6E6',
+ '--sand-toggle-on-bg-color': '#61688E',
+ '--sand-progress-color-rgb': '#E6E6E6',
+ '--sand-progress-buffer-color': '#6B6D73',
+ '--sand-progress-bg-color-rgb': '#373A41',
+ '--sand-progress-highlighted-color': '#FFFFFF',
+ '--sand-progress-slider-color': '#8D9298',
+ '--sand-spinner-color-rgb': '#FFFFFF',
+ '--sand-checkbox-color': '#E6E6E6',
+ '--sand-item-disabled-focus-bg-color': '#E6E6E6',
+ '--sand-keyguide-bg-color-rgb': '#6B6D73',
+ '--sand-slider-disabled-knob-bg-color': '#666666',
+ '--sand-alert-overlay-bg-color-rgb': '#CACBCC',
+ '--sand-alert-overlay-text-color-rgb': '#2E3239',
+ '--sand-alert-overlay-text-sub-color': '#2E3239',
+ '--sand-alert-overlay-focus-text-color': '#575E66',
+ '--sand-alert-overlay-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-disabled-selected-bg-color': '#788688',
+ '--sand-alert-overlay-disabled-selected-focus-color': '#E6E6E6',
+ '--sand-alert-overlay-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-alert-overlay-progress-color-rgb': '#6B6D73',
+ '--sand-alert-overlay-progress-bg-color-rgb': '#A1A1A1',
+ '--sand-alert-overlay-checkbox-color': '#858B92',
+ '--sand-alert-overlay-checkbox-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-formcheckboxitem-focus-text-color': '#575E66',
+ '--sand-alert-overlay-item-disabled-focus-bg-color': '#989CA2'
+ },
+ greenColorSet1: {
+ '--sand-bg-color': '#272829',
+ '--sand-text-color-rgb': '#E6E6E6',
+ '--sand-text-sub-color': '#ABAEB3',
+ '--sand-shadow-color-rgb': '#000000',
+ '--sand-component-text-color-rgb': '#E6E6E6',
+ '--sand-component-text-sub-color-rgb': '#ABAEB3',
+ '--sand-component-bg-color': '#7D848C',
+ '--sand-component-active-indicator-bg-color': '#E6E6E6',
+ '--sand-component-inactive-indicator-bg-color': '#9DA2A7',
+ '--sand-focus-text-color': '#303030',
+ '--sand-focus-bg-color-rgb': '#E6E6E6',
+ '--sand-component-focus-text-color-rgb': '#333333',
+ '--sand-component-focus-active-indicator-bg-color': '#4C5059',
+ '--sand-component-focus-inactive-indicator-bg-color': '#B8B9BB',
+ '--sand-selected-color-rgb': '#E6E6E6',
+ '--sand-selected-text-color': '#E6E6E6',
+ '--sand-selected-bg-color': '#61828E',
+ '--sand-disabled-focus-bg-color': '#ABAEB3',
+ '--sand-disabled-selected-color': '#4C5059',
+ '--sand-disabled-selected-bg-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-fullscreen-bg-color': '#000000',
+ '--sand-overlay-bg-color-rgb': '#292929',
+ '--sand-selection-color': '#4C5059',
+ '--sand-selection-bg-color': '#3399FF',
+ '--sand-toggle-off-color': '#AEAEAE',
+ '--sand-toggle-off-bg-color': '#494949',
+ '--sand-toggle-on-color': '#E6E6E6',
+ '--sand-toggle-on-bg-color': '#61828E',
+ '--sand-progress-color-rgb': '#E6E6E6',
+ '--sand-progress-buffer-color': '#6B6D73',
+ '--sand-progress-bg-color-rgb': '#373A41',
+ '--sand-progress-highlighted-color': '#FFFFFF',
+ '--sand-progress-slider-color': '#8D9298',
+ '--sand-spinner-color-rgb': '#FFFFFF',
+ '--sand-checkbox-color': '#E6E6E6',
+ '--sand-item-disabled-focus-bg-color': '#E6E6E6',
+ '--sand-keyguide-bg-color-rgb': '#6B6D73',
+ '--sand-slider-disabled-knob-bg-color': '#666666',
+ '--sand-alert-overlay-bg-color-rgb': '#CACBCC',
+ '--sand-alert-overlay-text-color-rgb': '#2E3239',
+ '--sand-alert-overlay-text-sub-color': '#2E3239',
+ '--sand-alert-overlay-focus-text-color': '#575E66',
+ '--sand-alert-overlay-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-disabled-selected-bg-color': '#788688',
+ '--sand-alert-overlay-disabled-selected-focus-color': '#E6E6E6',
+ '--sand-alert-overlay-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-alert-overlay-progress-color-rgb': '#6B6D73',
+ '--sand-alert-overlay-progress-bg-color-rgb': '#A1A1A1',
+ '--sand-alert-overlay-checkbox-color': '#858B92',
+ '--sand-alert-overlay-checkbox-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-formcheckboxitem-focus-text-color': '#575E66',
+ '--sand-alert-overlay-item-disabled-focus-bg-color': '#989CA2'
+ },
+ purpleColorSet1: {
+ '--sand-bg-color': '#272829',
+ '--sand-text-color-rgb': '#E6E6E6',
+ '--sand-text-sub-color': '#ABAEB3',
+ '--sand-shadow-color-rgb': '#000000',
+ '--sand-component-text-color-rgb': '#E6E6E6',
+ '--sand-component-text-sub-color-rgb': '#ABAEB3',
+ '--sand-component-bg-color': '#7D848C',
+ '--sand-component-active-indicator-bg-color': '#E6E6E6',
+ '--sand-component-inactive-indicator-bg-color': '#9DA2A7',
+ '--sand-focus-text-color': '#303030',
+ '--sand-focus-bg-color-rgb': '#E6E6E6',
+ '--sand-component-focus-text-color-rgb': '#333333',
+ '--sand-component-focus-active-indicator-bg-color': '#4C5059',
+ '--sand-component-focus-inactive-indicator-bg-color': '#B8B9BB',
+ '--sand-selected-color-rgb': '#E6E6E6',
+ '--sand-selected-text-color': '#E6E6E6',
+ '--sand-selected-bg-color': '#75518E',
+ '--sand-disabled-focus-bg-color': '#ABAEB3',
+ '--sand-disabled-selected-color': '#4C5059',
+ '--sand-disabled-selected-bg-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-fullscreen-bg-color': '#000000',
+ '--sand-overlay-bg-color-rgb': '#292929',
+ '--sand-selection-color': '#4C5059',
+ '--sand-selection-bg-color': '#3399FF',
+ '--sand-toggle-off-color': '#AEAEAE',
+ '--sand-toggle-off-bg-color': '#494949',
+ '--sand-toggle-on-color': '#E6E6E6',
+ '--sand-toggle-on-bg-color': '#755183',
+ '--sand-progress-color-rgb': '#E6E6E6',
+ '--sand-progress-buffer-color': '#6B6D73',
+ '--sand-progress-bg-color-rgb': '#373A41',
+ '--sand-progress-highlighted-color': '#FFFFFF',
+ '--sand-progress-slider-color': '#8D9298',
+ '--sand-spinner-color-rgb': '#FFFFFF',
+ '--sand-checkbox-color': '#E6E6E6',
+ '--sand-item-disabled-focus-bg-color': '#E6E6E6',
+ '--sand-keyguide-bg-color-rgb': '#6B6D73',
+ '--sand-slider-disabled-knob-bg-color': '#666666',
+ '--sand-alert-overlay-bg-color-rgb': '#6B6D73',
+ '--sand-alert-overlay-text-color-rgb': '#2E3239',
+ '--sand-alert-overlay-text-sub-color': '#2E3239',
+ '--sand-alert-overlay-focus-text-color': '#575E66',
+ '--sand-alert-overlay-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-disabled-selected-bg-color': '#788688',
+ '--sand-alert-overlay-disabled-selected-focus-color': '#E6E6E6',
+ '--sand-alert-overlay-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-alert-overlay-progress-color-rgb': '#6B6D73',
+ '--sand-alert-overlay-progress-bg-color-rgb': '#A1A1A1',
+ '--sand-alert-overlay-checkbox-color': '#858B92',
+ '--sand-alert-overlay-checkbox-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-formcheckboxitem-focus-text-color': '#575E66',
+ '--sand-alert-overlay-item-disabled-focus-bg-color': '#989CA2'
+ },
+ redColorSet1: {
+ '--sand-bg-color': '#252424',
+ '--sand-text-color-rgb': '#E6E6E6',
+ '--sand-text-sub-color': '#ABAEB3',
+ '--sand-shadow-color-rgb': '#000000',
+ '--sand-component-text-color-rgb': '#E6E6E6',
+ '--sand-component-text-sub-color-rgb': '#ABAEB3',
+ '--sand-component-bg-color': '#7D848C',
+ '--sand-component-active-indicator-bg-color': '#E6E6E6',
+ '--sand-component-inactive-indicator-bg-color': '#9DA2A7',
+ '--sand-focus-text-color': '#303030',
+ '--sand-focus-bg-color-rgb': '#E6E6E6',
+ '--sand-component-focus-text-color-rgb': '#333333',
+ '--sand-component-focus-active-indicator-bg-color': '#4C5059',
+ '--sand-component-focus-inactive-indicator-bg-color': '#B8B9BB',
+ '--sand-selected-color-rgb': '#E6E6E6',
+ '--sand-selected-text-color': '#E6E6E6',
+ '--sand-selected-bg-color': '#8E6161',
+ '--sand-disabled-focus-bg-color': '#ABAEB3',
+ '--sand-disabled-selected-color': '#4C5059',
+ '--sand-disabled-selected-bg-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-fullscreen-bg-color': '#000000',
+ '--sand-overlay-bg-color-rgb': '#292929',
+ '--sand-selection-color': '#4C5059',
+ '--sand-selection-bg-color': '#3399FF',
+ '--sand-toggle-off-color': '#AEAEAE',
+ '--sand-toggle-off-bg-color': '#494949',
+ '--sand-toggle-on-color': '#E6E6E6',
+ '--sand-toggle-on-bg-color': '#8E6161',
+ '--sand-progress-color-rgb': '#E6E6E6',
+ '--sand-progress-buffer-color': '#6B6D73',
+ '--sand-progress-bg-color-rgb': '#373A41',
+ '--sand-progress-highlighted-color': '#FFFFFF',
+ '--sand-progress-slider-color': '#8D9298',
+ '--sand-spinner-color-rgb': '#FFFFFF',
+ '--sand-checkbox-color': '#E6E6E6',
+ '--sand-item-disabled-focus-bg-color': '#E6E6E6',
+ '--sand-keyguide-bg-color-rgb': '#6B6D73',
+ '--sand-slider-disabled-knob-bg-color': '#666666',
+ '--sand-alert-overlay-bg-color-rgb': '#CACBCC',
+ '--sand-alert-overlay-text-color-rgb': '#2E3239',
+ '--sand-alert-overlay-text-sub-color': '#2E3239',
+ '--sand-alert-overlay-focus-text-color': '#575E66',
+ '--sand-alert-overlay-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-disabled-selected-bg-color': '#788688',
+ '--sand-alert-overlay-disabled-selected-focus-color': '#E6E6E6',
+ '--sand-alert-overlay-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-alert-overlay-progress-color-rgb': '#6B6D73',
+ '--sand-alert-overlay-progress-bg-color-rgb': '#A1A1A1',
+ '--sand-alert-overlay-checkbox-color': '#858B92',
+ '--sand-alert-overlay-checkbox-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-formcheckboxitem-focus-text-color': '#575E66',
+ '--sand-alert-overlay-item-disabled-focus-bg-color': '#989CA2'
+ },
+ blueColorSet2: {
+ '--sand-bg-color': '#181E3D',
+ '--sand-text-color-rgb': '#E6E6E6',
+ '--sand-text-sub-color': '#ABAEB3',
+ '--sand-shadow-color-rgb': '#000000',
+ '--sand-component-text-color-rgb': '#E6E6E6',
+ '--sand-component-text-sub-color-rgb': '#ABAEB3',
+ '--sand-component-bg-color': '#7D848C',
+ '--sand-component-active-indicator-bg-color': '#E6E6E6',
+ '--sand-component-inactive-indicator-bg-color': '#9DA2A7',
+ '--sand-focus-text-color': '#152DAC',
+ '--sand-focus-bg-color-rgb': '#E6E6E6',
+ '--sand-component-focus-text-color-rgb': '#1C31AA',
+ '--sand-component-focus-active-indicator-bg-color': '#4C5059',
+ '--sand-component-focus-inactive-indicator-bg-color': '#B8B9BB',
+ '--sand-selected-color-rgb': '#E6E6E6',
+ '--sand-selected-text-color': '#E6E6E6',
+ '--sand-selected-bg-color': '#61688E',
+ '--sand-disabled-focus-bg-color': '#ABAEB3',
+ '--sand-disabled-selected-color': '#4C5059',
+ '--sand-disabled-selected-bg-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-fullscreen-bg-color': '#000000',
+ '--sand-overlay-bg-color-rgb': '#1E233F',
+ '--sand-selection-color': '#4C5059',
+ '--sand-selection-bg-color': '#3399FF',
+ '--sand-toggle-off-color': '#787C90',
+ '--sand-toggle-off-bg-color': '#444C73',
+ '--sand-toggle-on-color': '#E6E6E6',
+ '--sand-toggle-on-bg-color': '#7B84B2',
+ '--sand-progress-color-rgb': '#E6E6E6',
+ '--sand-progress-buffer-color': '#6B6D73',
+ '--sand-progress-bg-color-rgb': '#373A41',
+ '--sand-progress-highlighted-color': '#FFFFFF',
+ '--sand-progress-slider-color': '#8D9298',
+ '--sand-spinner-color-rgb': '#FFFFFF',
+ '--sand-checkbox-color': '#E6E6E6',
+ '--sand-item-disabled-focus-bg-color': '#E6E6E6',
+ '--sand-keyguide-bg-color-rgb': '#6B6D73',
+ '--sand-slider-disabled-knob-bg-color': '#666666',
+ '--sand-alert-overlay-bg-color-rgb': '#CACBCC',
+ '--sand-alert-overlay-text-color-rgb': '#2E3239',
+ '--sand-alert-overlay-text-sub-color': '#2E3239',
+ '--sand-alert-overlay-focus-text-color': '#575E66',
+ '--sand-alert-overlay-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-disabled-selected-bg-color': '#788688',
+ '--sand-alert-overlay-disabled-selected-focus-color': '#E6E6E6',
+ '--sand-alert-overlay-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-alert-overlay-progress-color-rgb': '#6B6D73',
+ '--sand-alert-overlay-progress-bg-color-rgb': '#A1A1A1',
+ '--sand-alert-overlay-checkbox-color': '#858B92',
+ '--sand-alert-overlay-checkbox-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-formcheckboxitem-focus-text-color': '#575E66',
+ '--sand-alert-overlay-item-disabled-focus-bg-color': '#989CA2'
+ },
+ greenColorSet2: {
+ '--sand-bg-color': '#102933',
+ '--sand-text-color-rgb': '#E6E6E6',
+ '--sand-text-sub-color': '#ABAEB3',
+ '--sand-shadow-color-rgb': '#000000',
+ '--sand-component-text-color-rgb': '#E6E6E6',
+ '--sand-component-text-sub-color-rgb': '#ABAEB3',
+ '--sand-component-bg-color': '#7D848C',
+ '--sand-component-active-indicator-bg-color': '#E6E6E6',
+ '--sand-component-inactive-indicator-bg-color': '#9DA2A7',
+ '--sand-focus-text-color': '#02435F',
+ '--sand-focus-bg-color-rgb': '#E6E6E6',
+ '--sand-component-focus-text-color-rgb': '#08455F',
+ '--sand-component-focus-active-indicator-bg-color': '#4C5059',
+ '--sand-component-focus-inactive-indicator-bg-color': '#B8B9BB',
+ '--sand-selected-color-rgb': '#E6E6E6',
+ '--sand-selected-text-color': '#E6E6E6',
+ '--sand-selected-bg-color': '#61828E',
+ '--sand-disabled-focus-bg-color': '#ABAEB3',
+ '--sand-disabled-selected-color': '#4C5059',
+ '--sand-disabled-selected-bg-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-fullscreen-bg-color': '#000000',
+ '--sand-overlay-bg-color-rgb': '#172D36',
+ '--sand-selection-color': '#4C5059',
+ '--sand-selection-bg-color': '#3399FF',
+ '--sand-toggle-off-color': '#6F7E84',
+ '--sand-toggle-off-bg-color': '#31505B',
+ '--sand-toggle-on-color': '#E6E6E6',
+ '--sand-toggle-on-bg-color': '#6B95A4',
+ '--sand-progress-color-rgb': '#E6E6E6',
+ '--sand-progress-buffer-color': '#6B6D73',
+ '--sand-progress-bg-color-rgb': '#373A41',
+ '--sand-progress-highlighted-color': '#FFFFFF',
+ '--sand-progress-slider-color': '#8D9298',
+ '--sand-spinner-color-rgb': '#FFFFFF',
+ '--sand-checkbox-color': '#E6E6E6',
+ '--sand-item-disabled-focus-bg-color': '#E6E6E6',
+ '--sand-keyguide-bg-color-rgb': '#6B6D73',
+ '--sand-slider-disabled-knob-bg-color': '#666666',
+ '--sand-alert-overlay-bg-color-rgb': '#CACBCC',
+ '--sand-alert-overlay-text-color-rgb': '#2E3239',
+ '--sand-alert-overlay-text-sub-color': '#2E3239',
+ '--sand-alert-overlay-focus-text-color': '#575E66',
+ '--sand-alert-overlay-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-disabled-selected-bg-color': '#788688',
+ '--sand-alert-overlay-disabled-selected-focus-color': '#E6E6E6',
+ '--sand-alert-overlay-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-alert-overlay-progress-color-rgb': '#6B6D73',
+ '--sand-alert-overlay-progress-bg-color-rgb': '#A1A1A1',
+ '--sand-alert-overlay-checkbox-color': '#858B92',
+ '--sand-alert-overlay-checkbox-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-formcheckboxitem-focus-text-color': '#575E66',
+ '--sand-alert-overlay-item-disabled-focus-bg-color': '#989CA2'
+ },
+ purpleColorSet2: {
+ '--sand-bg-color': '#2B1941',
+ '--sand-text-color-rgb': '#E6E6E6',
+ '--sand-text-sub-color': '#848290',
+ '--sand-shadow-color-rgb': '#000000',
+ '--sand-component-text-color-rgb': '#E6E6E6',
+ '--sand-component-text-sub-color-rgb': '#ABAEB3',
+ '--sand-component-bg-color': '#7D848C',
+ '--sand-component-active-indicator-bg-color': '#E6E6E6',
+ '--sand-component-inactive-indicator-bg-color': '#9DA2A7',
+ '--sand-focus-text-color': '#FFFFFF',
+ '--sand-focus-bg-color-rgb': '#E6E6E6',
+ '--sand-component-focus-text-color-rgb': '#4D198E',
+ '--sand-component-focus-active-indicator-bg-color': '#4C5059',
+ '--sand-component-focus-inactive-indicator-bg-color': '#B8B9BB',
+ '--sand-selected-color-rgb': '#E6E6E6',
+ '--sand-selected-text-color': '#E6E6E6',
+ '--sand-selected-bg-color': '#76618E',
+ '--sand-disabled-focus-bg-color': '#ABAEB3',
+ '--sand-disabled-selected-color': '#4C5059',
+ '--sand-disabled-selected-bg-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-fullscreen-bg-color': '#000000',
+ '--sand-overlay-bg-color-rgb': '#2F1F43',
+ '--sand-selection-color': '#4C5059',
+ '--sand-selection-bg-color': '#3399FF',
+ '--sand-toggle-off-color': '#80778C',
+ '--sand-toggle-off-bg-color': '#54416C',
+ '--sand-toggle-on-color': '#E6E6E6',
+ '--sand-toggle-on-bg-color': '#8A75A2',
+ '--sand-progress-color-rgb': '#E6E6E6',
+ '--sand-progress-buffer-color': '#6B6D73',
+ '--sand-progress-bg-color-rgb': '#373A41',
+ '--sand-progress-highlighted-color': '#FFFFFF',
+ '--sand-progress-slider-color': '#8D9298',
+ '--sand-spinner-color-rgb': '#FFFFFF',
+ '--sand-checkbox-color': '#E6E6E6',
+ '--sand-item-disabled-focus-bg-color': '#E6E6E6',
+ '--sand-keyguide-bg-color-rgb': '#6B6D73',
+ '--sand-slider-disabled-knob-bg-color': '#666666',
+ '--sand-alert-overlay-bg-color-rgb': '#CACBCC',
+ '--sand-alert-overlay-text-color-rgb': '#2E3239',
+ '--sand-alert-overlay-text-sub-color': '#2E3239',
+ '--sand-alert-overlay-focus-text-color': '#575E66',
+ '--sand-alert-overlay-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-disabled-selected-bg-color': '#788688',
+ '--sand-alert-overlay-disabled-selected-focus-color': '#E6E6E6',
+ '--sand-alert-overlay-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-alert-overlay-progress-color-rgb': '#6B6D73',
+ '--sand-alert-overlay-progress-bg-color-rgb': '#A1A1A1',
+ '--sand-alert-overlay-checkbox-color': '#858B92',
+ '--sand-alert-overlay-checkbox-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-formcheckboxitem-focus-text-color': '#575E66',
+ '--sand-alert-overlay-item-disabled-focus-bg-color': '#989CA2'
+ },
+ redColorSet2: {
+ '--sand-bg-color': '#3D1A1A',
+ '--sand-text-color-rgb': '#E6E6E6',
+ '--sand-text-sub-color': '#807477',
+ '--sand-shadow-color-rgb': '#000000',
+ '--sand-component-text-color-rgb': '#E6E6E6',
+ '--sand-component-text-sub-color-rgb': '#ABAEB3',
+ '--sand-component-bg-color': '#7D848C',
+ '--sand-component-active-indicator-bg-color': '#E6E6E6',
+ '--sand-component-inactive-indicator-bg-color': '#9DA2A7',
+ '--sand-focus-text-color': '#851919',
+ '--sand-focus-bg-color-rgb': '#E6E6E6',
+ '--sand-component-focus-text-color-rgb': '#841F1F',
+ '--sand-selected-color-rgb': '#E6E6E6',
+ '--sand-selected-text-color': '#E6E6E6',
+ '--sand-selected-bg-color': '#8E6161',
+ '--sand-disabled-focus-bg-color': '#ABAEB3',
+ '--sand-disabled-selected-color': '#4C5059',
+ '--sand-disabled-selected-bg-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-color': '#E7E7E7',
+ '--sand-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-fullscreen-bg-color': '#000000',
+ '--sand-overlay-bg-color-rgb': '#3F2020',
+ '--sand-selection-color': '#4C5059',
+ '--sand-selection-bg-color': '#3399FF',
+ '--sand-toggle-off-color': '#927A7A',
+ '--sand-toggle-off-bg-color': '#784747',
+ '--sand-toggle-on-color': '#E6E6E6',
+ '--sand-toggle-on-bg-color': '#BB7D7D',
+ '--sand-progress-color-rgb': '#E6E6E6',
+ '--sand-progress-buffer-color': '#6B6D73',
+ '--sand-progress-bg-color-rgb': '#373A41',
+ '--sand-progress-highlighted-color': '#FFFFFF',
+ '--sand-progress-slider-color': '#8D9298',
+ '--sand-spinner-color-rgb': '#FFFFFF',
+ '--sand-checkbox-color': '#E6E6E6',
+ '--sand-item-disabled-focus-bg-color': '#E6E6E6',
+ '--sand-keyguide-bg-color-rgb': '#6B6D73',
+ '--sand-slider-disabled-knob-bg-color': '#666666',
+ '--sand-alert-overlay-bg-color-rgb': '#CACBCC',
+ '--sand-alert-overlay-text-color-rgb': '#2E3239',
+ '--sand-alert-overlay-text-sub-color': '#2E3239',
+ '--sand-alert-overlay-focus-text-color': '#575E66',
+ '--sand-alert-overlay-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-disabled-selected-bg-color': '#788688',
+ '--sand-alert-overlay-disabled-selected-focus-color': '#E6E6E6',
+ '--sand-alert-overlay-disabled-selected-focus-bg-color': '#4C5059',
+ '--sand-alert-overlay-progress-color-rgb': '#6B6D73',
+ '--sand-alert-overlay-progress-bg-color-rgb': '#A1A1A1',
+ '--sand-alert-overlay-checkbox-color': '#858B92',
+ '--sand-alert-overlay-checkbox-disabled-selected-color': '#FFFFFF',
+ '--sand-alert-overlay-formcheckboxitem-focus-text-color': '#575E66',
+ '--sand-alert-overlay-item-disabled-focus-bg-color': '#989CA2'
+ }
+};
diff --git a/limestone/feature-custom-skin-generator/src/index.js b/limestone/feature-custom-skin-generator/src/index.js
new file mode 100644
index 000000000..eb163ed13
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/index.js
@@ -0,0 +1,19 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './App';
+
+const appElement = ();
+
+// In a browser environment, render instead of exporting
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/feature-custom-skin-generator/src/utils.js b/limestone/feature-custom-skin-generator/src/utils.js
new file mode 100644
index 000000000..9afecd3fb
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/utils.js
@@ -0,0 +1,221 @@
+// Function that checks if two variables are both hex colors
+const hexColors = (color1, color2) => {
+ return /^#[0-9A-F]{6}$/i.test(color1) && /^#[0-9A-F]{6}$/i.test(color2);
+ // /^#[0-9A-F]{6}$/i.test(test_string) tests if test_string represents a color in hex.
+};
+
+// Function that converts a hex color to an array representing a RGB color
+const convertHexToRGB = (hex) => {
+ let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return result ? [
+ parseInt(result[1], 16),
+ parseInt(result[2], 16),
+ parseInt(result[3], 16)
+ ] : null;
+};
+
+// Function that converts an array representing a RGB color to a hex color
+const convertRGBToHex = (RGBColor) => {
+ const [r, g, b] = RGBColor;
+ return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
+};
+
+// Function that works as part of the getRandomColor method
+const colorAlgorithm = (array, lowerValues, highestValue, inc) => {
+ return array.map(value => {
+ if (value !== lowerValues[0]) {
+ if (lowerValues[0] < value - 20 * (inc + 1)) {
+ return value - 20 * (inc + 1);
+ }
+ return value;
+ } else {
+ if (value >= highestValue - 20 * (inc + 1) ) {
+ return value - 20 * (inc + 1);
+ }
+ return value;
+ }
+ });
+};
+
+// Function that generates the content that will populate the css file that gets exported
+const generateCSS = (colors, skinName, varNames) => {
+ if (!varNames) return;
+ return '.limestone-theme {\n' +
+ ` /* Skin Name: ${skinName ? skinName : 'Untitled'}; */\n` +
+ colors?.map((color, index) => {
+ if (!color) return '';
+
+ const [r, g, b] = hexColors(color, '#000000') ? convertHexToRGB(color) : convertHexToRGB('#000000');
+ return ` ${varNames[index]}: ${varNames[index].includes('rgb') ? `${r}, ${g}, ${b}` : `${color}`};\n`;
+ }).join('') + `}\n`;
+};
+
+// Function that generates the css file that gets exported
+const generateCSSFile = (fileName, colors) => {
+ if (typeof window !== 'undefined') {
+ let link = document.createElement('a');
+ link.download = 'custom_skin.css';
+ let blob = new window.Blob([colors], {type: 'text/css'});
+ link.href = URL.createObjectURL(blob);
+ link.click();
+ URL.revokeObjectURL(link.href);
+ }
+};
+
+// Function that generates a color related to a provided one based on an increment value
+const getRandomColor = (colorToBeConverted, inc) => {
+ const color = convertHexToRGB(colorToBeConverted);
+
+ const highestValue = Math.max(...color);
+ const lowerValues = color.filter(value => value !== highestValue);
+ let newColor = color;
+
+ switch (lowerValues.length) {
+ case 0: {
+ if (highestValue < 85) {
+ newColor = color.map(value => {
+ return value + 20 * (inc + 1);
+ });
+ } else if (highestValue < 170) {
+ if (!inc % 2) {
+ newColor = color.map(value => {
+ return value - 20 * (inc + 1);
+ });
+ } else {
+ newColor = color.map(value => {
+ return value + 20 * inc;
+ });
+ }
+ } else {
+ newColor = color.map(value => {
+ return value - 20 * (inc + 1);
+ });
+ }
+ break;
+ }
+ case 1: {
+ if (highestValue < 85) {
+ newColor = color.map(value => {
+ if (value !== lowerValues[0]) {
+ return value + 20 * (inc + 1);
+ }
+ return value;
+ });
+ } else if (highestValue < 170) {
+ if (!inc % 2) {
+ newColor = colorAlgorithm(color, lowerValues, highestValue, inc);
+ } else {
+ newColor = color.map(value => {
+ if (value !== lowerValues[0]) {
+ return value + 20 * inc;
+ }
+ return value;
+ });
+ }
+ } else {
+ newColor = colorAlgorithm(color, lowerValues, highestValue, inc);
+ }
+ break;
+ }
+ case 2: {
+ newColor = color.map((value) => {
+ if (value === highestValue) {
+ if (value + 20 * (inc + 1) < 255) {
+ return value + 20 * (inc + 1);
+ }
+ return value;
+ } else {
+ if (highestValue + 20 * (inc + 1) < 255) {
+ return value;
+ }
+ if (highestValue > lowerValues[0] + 20 * (inc + 1) && highestValue > lowerValues[1] + 20 * (inc + 1)) {
+ return value + 20 * (inc + 1);
+ }
+ if (value - 20 * (inc + 1) > 0) {
+ return value - 20 * (inc + 1);
+ }
+ return 0;
+ }
+ });
+ break;
+ }
+ }
+
+ return convertRGBToHex(newColor);
+};
+
+// Function that generates an array of colors to be used as background colors
+const generateBGColors = (background, limit) => {
+ let color = background;
+ let colorsArray = [];
+
+ for (let i = 0; i <= limit; i++) {
+ let rc = getRandomColor(color, i);
+ colorsArray.push(rc);
+ }
+
+ return colorsArray;
+};
+
+// Function that generates an array of colors to be used as text colors
+const generateTextColors = (text, limit) => {
+ let color = text;
+ let colorsArray = [];
+
+ for (let i = 0; i <= limit; i++) {
+ let rc = getRandomColor(color, i);
+ colorsArray.push(rc);
+ }
+
+ return colorsArray;
+};
+
+// Function that generates an array of colors to be used in Main Panel from 2 colors
+const generateColors = (background, text) => {
+ const bgColors = generateBGColors(background, 20); // +2
+ const textColors = generateTextColors(text, 10);
+
+ return [
+ textColors[0].toUpperCase(), textColors[1].toUpperCase(), text, textColors[0].toUpperCase(), bgColors[2].toUpperCase(),
+ bgColors[3].toUpperCase(), bgColors[4].toUpperCase(), textColors[2].toUpperCase(), bgColors[3].toUpperCase(),
+ textColors[3].toUpperCase(), bgColors[5].toUpperCase(), bgColors[6].toUpperCase(), text, text, bgColors[7].toUpperCase(),
+ bgColors[8].toUpperCase(), textColors[3].toUpperCase(), bgColors[3].toUpperCase(), text, bgColors[3].toUpperCase(), background,
+ bgColors[9].toUpperCase(), textColors[3].toUpperCase(), bgColors[10].toUpperCase(), textColors[4].toUpperCase(),
+ bgColors[11].toUpperCase(), text, bgColors[12].toUpperCase(), text, textColors[5].toUpperCase(), bgColors[13].toUpperCase(),
+ textColors[2].toUpperCase(), textColors[6].toUpperCase(), textColors[2].toUpperCase(), text, bgColors[3].toUpperCase(),
+ bgColors[13].toUpperCase(), bgColors[10].toUpperCase(), bgColors[11].toUpperCase(), textColors[7].toUpperCase(), textColors[7].toUpperCase(),
+ textColors[8].toUpperCase(), textColors[2].toUpperCase(), bgColors[16].toUpperCase(), text, bgColors[5].toUpperCase(),
+ textColors[9].toUpperCase(), bgColors[19].toUpperCase(), textColors[10].toUpperCase(), textColors[2].toUpperCase(), textColors[8].toUpperCase(),
+ bgColors[18].toUpperCase()
+ ];
+};
+
+// Function that returns an array of colors from a string provided from a css file with the same format as the one we create
+const getColorsFromString = (colors) => {
+ try {
+ let colorSets = colors.map(color => color.split(':'));
+ colorSets = colorSets.map(colorSet => [colorSet[0], colorSet[1].split(';')[0].slice(1)]);
+ colorSets[0][1] = colorSets[0][1].split('*/')[0];
+
+ return colorSets;
+ } catch (err) {
+ throw new Error(err);
+ }
+};
+
+// Function that returns an array of colors different from a given preset
+const getPresetDifferences = (actualColors, preset) => {
+ const values = Object.values(preset);
+ return actualColors.map((color, index) => color !== values[index] && color);
+};
+
+export {
+ convertHexToRGB,
+ convertRGBToHex,
+ generateColors,
+ generateCSS,
+ generateCSSFile,
+ getColorsFromString,
+ getPresetDifferences,
+ hexColors
+};
diff --git a/limestone/feature-custom-skin-generator/src/views/MainPanel.js b/limestone/feature-custom-skin-generator/src/views/MainPanel.js
new file mode 100644
index 000000000..e6b7e8186
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/views/MainPanel.js
@@ -0,0 +1,409 @@
+/* eslint-disable react/jsx-no-bind */
+
+import Alert from '@enact/limestone/Alert';
+import BodyText from '@enact/limestone/BodyText';
+import Button from '@enact/limestone/Button';
+import CheckboxItem from '@enact/limestone/CheckboxItem';
+import Dropdown from '@enact/limestone/Dropdown';
+import Heading from '@enact/limestone/Heading';
+import Popup from '@enact/limestone/Popup';
+import RangePicker from '@enact/limestone/RangePicker';
+import Scroller from '@enact/limestone/Scroller';
+import Slider from '@enact/limestone/Slider';
+import Switch from '@enact/limestone/Switch';
+import SwitchItem from '@enact/limestone/SwitchItem';
+import TooltipDecorator from '@enact/limestone/TooltipDecorator';
+import {Cell, Column, Layout, Row} from '@enact/ui/Layout';
+import {useCallback, useEffect, useState} from 'react';
+import ReactDOM from 'react-dom';
+
+import AutoPopup from '../components/AutoPopup/AutoPopup';
+import ColorFields from '../components/ColorFields/ColorFields';
+import ImportSkin from '../components/ImportSkin/ImportSkin';
+import OutputField from '../components/OutputField/OutputField';
+
+import {presets} from '../constants';
+
+import {
+ convertHexToRGB,
+ convertRGBToHex,
+ generateColors,
+ generateCSS,
+ getColorsFromString,
+ hexColors
+} from '../utils';
+
+import styles from '../common/styles.module.less';
+import css from './MainPanel.module.less';
+
+const TooltipButton = TooltipDecorator({tooltipDestinationProp: 'decoration'}, Button);
+
+function scrollTo (ref) {
+ scrollTo = ref; //eslint-disable-line
+}
+
+const MainPanel = () => {
+ const [skinName, setSkinName] = useState('');
+
+ // All the colors to be used by the app
+ const [BGColor, setBGColor] = useState('#000000');
+ const [TextColorRGB, setTextColorRGB] = useState('#E6E6E6');
+ const [TextSubColor, setTextSubColor] = useState('#ABAEB3');
+ const [ShadowColorRGB, setShadowColorRGB] = useState('#000000');
+ const [ComponentTextColorRGB, setComponentTextColorRGB] = useState('#E6E6E6');
+ const [ComponentTextSubColorRGB, setComponentTextSubColorRGB] = useState('#ABAEB3');
+ const [ComponentBGColor, setComponentBGColor] = useState('#7D848C');
+ const [ComponentActiveIndicatorBgColor, setComponentActiveIndicatorBgColor] = useState('#E6E6E6');
+ const [ComponentInactiveIndicatorBgColor, setComponentInactiveIndicatorBgColor] = useState('#9DA2A7');
+ const [FocusTextColor, setFocusTextColor] = useState('#FFFFFF');
+ const [FocusBGColorRGB, setFocusBGColorRGB] = useState('#E6E6E6');
+ const [ComponentFocusTextColorRGB, setComponentFocusTextColorRGB] = useState('#4C5059');
+ const [ComponentFocusActiveIndicatorBgColor, setComponentFocusActiveIndicatorBgColor] = useState('#4C5059');
+ const [ComponentFocusInactiveIndicatorBgColor, setComponentFocusInactiveIndicatorBgColor] = useState('#B8B9BB');
+ const [SelectedColorRGB, setSelectedColorRGB] = useState('#E6E6E6');
+ const [SelectedTextColor, setSelectedTextColor] = useState('#E6E6E6');
+ const [SelectedBGColor, setSelectedBGColor] = useState('#3E454D');
+ const [DisabledFocusBGColor, setDisabledFocusBGColor] = useState('#ABAEB3');
+ const [DisabledSelectedColor, setDisabledSelectedColor] = useState('#4C5059');
+ const [DisabledSelectedBGColor, setDisabledSelectedBgColor] = useState('#E6E6E6');
+ const [DisabledSelectedFocusColor, setDisabledSelectedFocusColor] = useState('#E6E6E6');
+ const [DisabledSelectedFocusBGColor, setDisabledSelectedFocusBGColor] = useState('#4C5059');
+ const [FullscreenBGColor, setFullscreenBGColor] = useState('#000000');
+ const [OverlayBGColorRGB, setOverlayBGColorRGB] = useState('#575E66');
+ const [SelectionColor, setSelectionColor] = useState('#4C5059');
+ const [SelectionBGColor, setSelectionBGColor] = useState('#3399FF');
+ const [ToggleOffColor, setToggleOffColor] = useState('#AEAEAE');
+ const [ToggleOffBGColor, setToggleOffBGColor] = useState('#777777');
+ const [ToggleOnColor, setToggleOnColor] = useState('#E6E6E6');
+ const [ToggleOnBGColor, setToggleOnBGColor] = useState('#30AD6B');
+ const [ProgressColorRGB, setProgressColorRGB] = useState('#E6E6E6');
+ const [ProgressBufferColor, setProgressBufferColor] = useState('#6B6D73');
+ const [ProgressBGColorRGB, setProgressBGColorRGB] = useState('#373A41');
+ const [ProgressHighlightedColor, setProgressHighlightedColor] = useState('#FFFFFF');
+ const [ProgressSliderColor, setProgressSliderColor] = useState('#8D9298');
+ const [SpinnerColorRGB, setSpinnerColorRGB] = useState('#FFFFFF');
+ const [CheckboxColor, setCheckboxColor] = useState('#E6E6E6');
+ const [ItemDisabledFocusBGColor, setItemDisabledFocusBGColor] = useState('#E6E6E6');
+ const [KeyguideBGColorRGB, setKeyguideBGColorRGB] = useState('#373A41');
+ const [SliderDisabledKnobBgColor, setSliderDisabledKnobBgColor] = useState('#666666');
+ const [AlertOverlayBGColorRGB, setAlertOverlayBGColorRGB] = useState('#CACBCC');
+ const [AlertOverlayTextColorRGB, setAlertOverlayTextColorRGB] = useState('#2E3239');
+ const [AlertOverlayTextSubColor, setAlertOverlayTextSubColor] = useState('#2E3239');
+ const [AlertOverlayFocusTextColor, setAlertOverlayFocusTextColor] = useState('#575E66');
+ const [AlertOverlayDisabledSelectedColor, setAlertOverlayDisabledSelectedColor] = useState('#FFFFFF');
+ const [AlertOverlayDisabledSelectedBGColor, setAlertOverlayDisabledSelectedBGColor] = useState('#788688');
+ const [AlertOverlayDisabledSelectedFocusColor, setAlertOverlayDisabledSelectedFocusColor] = useState('#E6E6E6');
+ const [AlertOverlayDisabledSelectedFocusBGColor, setAlertOverlayDisabledSelectedFocusBGColor] = useState('#4C5059');
+ const [AlertOverlayProgressColorRGB, setAlertOverlayProgressColorRGB] = useState('#373A41');
+ const [AlertOverlayProgressBGColorRGB, setAlertOverlayProgressBGColorRGB] = useState('#A1A1A1');
+ const [AlertOverlayCheckboxColor, setAlertOverlayCheckboxColor] = useState('#858B92');
+ const [AlertOverlayCheckboxDisabledSelectedColor, setAlertOverlayCheckboxDisabledSelectedColor] = useState('#FFFFFF');
+ const [AlertOverlayFormcheckboxitemFocusTextColor, setAlertOverlayFormcheckboxitemFocusTextColor] = useState('#575E66');
+ const [AlertOverlayItemDisabledFocusBGColor, setAlertOverlayItemDisabledFocusBGColor] = useState('#989CA2');
+
+ // Array containing all the values for our colors
+ const colors = [BGColor, TextColorRGB, TextSubColor, ShadowColorRGB, ComponentTextColorRGB, ComponentTextSubColorRGB, ComponentBGColor, ComponentActiveIndicatorBgColor,
+ ComponentInactiveIndicatorBgColor, FocusTextColor, FocusBGColorRGB, ComponentFocusTextColorRGB, ComponentFocusActiveIndicatorBgColor, ComponentFocusInactiveIndicatorBgColor,
+ SelectedColorRGB, SelectedTextColor, SelectedBGColor, DisabledFocusBGColor, DisabledSelectedColor, DisabledSelectedBGColor, DisabledSelectedFocusColor,
+ DisabledSelectedFocusBGColor, FullscreenBGColor, OverlayBGColorRGB, SelectionColor, SelectionBGColor, ToggleOffColor, ToggleOffBGColor, ToggleOnColor, ToggleOnBGColor,
+ ProgressColorRGB, ProgressBufferColor, ProgressBGColorRGB, ProgressHighlightedColor, ProgressSliderColor, SpinnerColorRGB, CheckboxColor, ItemDisabledFocusBGColor, KeyguideBGColorRGB, SliderDisabledKnobBgColor, AlertOverlayBGColorRGB,
+ AlertOverlayTextColorRGB, AlertOverlayTextSubColor, AlertOverlayFocusTextColor, AlertOverlayDisabledSelectedColor, AlertOverlayDisabledSelectedBGColor,
+ AlertOverlayDisabledSelectedFocusColor, AlertOverlayDisabledSelectedFocusBGColor, AlertOverlayProgressColorRGB, AlertOverlayProgressBGColorRGB, AlertOverlayCheckboxColor,
+ AlertOverlayCheckboxDisabledSelectedColor, AlertOverlayFormcheckboxitemFocusTextColor, AlertOverlayItemDisabledFocusBGColor
+ ];
+
+ // Array containing all the setter functions for our colors
+ const setColors = [setBGColor, setTextColorRGB, setTextSubColor, setShadowColorRGB, setComponentTextColorRGB, setComponentTextSubColorRGB, setComponentBGColor, setComponentActiveIndicatorBgColor,
+ setComponentInactiveIndicatorBgColor, setFocusTextColor, setFocusBGColorRGB,
+ setComponentFocusTextColorRGB, setComponentFocusActiveIndicatorBgColor, setComponentFocusInactiveIndicatorBgColor, setSelectedColorRGB, setSelectedTextColor, setSelectedBGColor, setDisabledFocusBGColor, setDisabledSelectedColor,
+ setDisabledSelectedBgColor, setDisabledSelectedFocusColor, setDisabledSelectedFocusBGColor, setFullscreenBGColor, setOverlayBGColorRGB,
+ setSelectionColor, setSelectionBGColor, setToggleOffColor, setToggleOffBGColor, setToggleOnColor, setToggleOnBGColor, setProgressColorRGB, setProgressBufferColor,
+ setProgressBGColorRGB, setProgressHighlightedColor, setProgressSliderColor, setSpinnerColorRGB, setCheckboxColor, setItemDisabledFocusBGColor, setKeyguideBGColorRGB, setSliderDisabledKnobBgColor, setAlertOverlayBGColorRGB,
+ setAlertOverlayTextColorRGB, setAlertOverlayTextSubColor, setAlertOverlayFocusTextColor, setAlertOverlayDisabledSelectedColor, setAlertOverlayDisabledSelectedBGColor,
+ setAlertOverlayDisabledSelectedFocusColor, setAlertOverlayDisabledSelectedFocusBGColor, setAlertOverlayProgressColorRGB, setAlertOverlayProgressBGColorRGB,
+ setAlertOverlayCheckboxColor, setAlertOverlayCheckboxDisabledSelectedColor, setAlertOverlayFormcheckboxitemFocusTextColor, setAlertOverlayItemDisabledFocusBGColor
+ ];
+
+ // Array containing all the names for our colors
+ const propNames = ['Background Color', 'Text Color RGB', 'Text Sub Color', 'Shadow Color RGB', 'Component Text Color RGB', 'Component Text Sub Color RGB', 'Component Bg Color', 'Component Active Indicator Bg Color', 'Component Inactive Indicator Bg Color', 'Focus Text Color', 'Focus Bg Color RGB',
+ 'Component Focus Text Color RGB', 'Component Focus Active Indicator Bg Color', 'Component Focus Inactive Indicator Bg Color', 'Selected Color RGB', 'Selected Text Color', 'Selected Bg Color', 'Disabled Focus Bg Color', 'Disabled Selected Color',
+ 'Disabled Selected Bg Color', 'Disabled Selected Focus Color', 'Disabled Selected Focus Bg Color', 'Fullscreen Bg Color', 'Overlay Bg Color RGB',
+ 'Selection Color', 'Selection Bg Color', 'Toggle Off Color', 'Toggle Off Bg Color', 'Toggle On Color', 'Toggle On Bg Color', 'Progress Color RGB',
+ 'Progress Buffer Color', 'Progress Bg Color RGB', 'Progress Highlighted Color', 'Progress Slider Color', 'Spinner Color RGB', 'Checkbox Color', 'Item Disabled Focus Bg Color', 'Keyguide Bg Color RGB', 'Slider Disabled Knob Bg Color',
+ 'Alert Overlay Bg Color RGB', 'Alert Overlay Text Color RGB', 'Alert Overlay Text Sub Color', 'Alert Overlay Focus Text Color', 'Alert Overlay Disabled Selected Color',
+ 'Alert Overlay Disabled Selected Bg Color', 'Alert Overlay Disabled Selected Focus Color', 'Alert Overlay Disabled Selected Focus Bg Color',
+ 'Alert Overlay Progress Color RGB', 'Alert Overlay Progress Bg Color RGB', 'Alert Overlay Checkbox Color', 'Alert Overlay Checkbox Disabled Selected Color',
+ 'Alert Overlay Formcheckboxitem Focus Text Color', 'Alert Overlay Item Disabled Focus Bg Color'
+ ];
+
+ // Array containing all the css names for our colors
+ const varNames = ['--sand-bg-color', '--sand-text-color-rgb', '--sand-text-sub-color', '--sand-shadow-color-rgb', '--sand-component-text-color-rgb', '--sand-component-text-sub-color-rgb', '--sand-component-bg-color',
+ '--sand-component-active-indicator-bg-color', '--sand-component-inactive-indicator-bg-color', '--sand-focus-text-color', '--sand-focus-bg-color-rgb', '--sand-component-focus-text-color-rgb', '--sand-component-focus-active-indicator-bg-color',
+ '--sand-component-focus-inactive-indicator-bg-color', '--sand-selected-color-rgb', '--sand-selected-text-color',
+ '--sand-selected-bg-color', '--sand-disabled-focus-bg-color', '--sand-disabled-selected-color', '--sand-disabled-selected-bg-color', '--sand-disabled-selected-focus-color',
+ '--sand-disabled-selected-focus-bg-color', '--sand-fullscreen-bg-color', '--sand-overlay-bg-color-rgb', '--sand-selection-color', '--sand-selection-bg-color',
+ '--sand-toggle-off-color', '--sand-toggle-off-bg-color', '--sand-toggle-on-color', '--sand-toggle-on-bg-color', '--sand-progress-color-rgb', '--sand-progress-buffer-color',
+ '--sand-progress-bg-color-rgb', '--sand-progress-highlighted-color', '--sand-progress-slider-color', '--sand-spinner-color-rgb', '--sand-checkbox-color', '--sand-item-disabled-focus-bg-color', '--sand-keyguide-bg-color-rgb', '--sand-slider-disabled-knob-bg-color',
+ '--sand-alert-overlay-bg-color-rgb', '--sand-alert-overlay-text-color-rgb', '--sand-alert-overlay-text-sub-color', '--sand-alert-overlay-focus-text-color',
+ '--sand-alert-overlay-disabled-selected-color', '--sand-alert-overlay-disabled-selected-bg-color', '--sand-alert-overlay-disabled-selected-focus-color',
+ '--sand-alert-overlay-disabled-selected-focus-bg-color', '--sand-alert-overlay-progress-color-rgb', '--sand-alert-overlay-progress-bg-color-rgb',
+ '--sand-alert-overlay-checkbox-color', '--sand-alert-overlay-checkbox-disabled-selected-color', '--sand-alert-overlay-formcheckboxitem-focus-text-color',
+ '--sand-alert-overlay-item-disabled-focus-bg-color'
+ ];
+
+ const [alert, setAlert] = useState(false);
+ const [auto, setAuto] = useState(false);
+ const [changes, setChanges] = useState(0);
+ const [fullCSS, setFullCSS] = useState(false);
+ const [openPopup, setOpenPopup] = useState(false);
+ const [openWarning, setOpenWarning] = useState(false);
+ const [presetActive, setActivePreset] = useState('defaultTheme');
+
+ // Function that sets all the colors to auto
+ const setColorsToAuto = (autoColors) => {
+ for (let i = 0; i < autoColors.length; i++) {
+ setColors[i + 2](autoColors[i]);
+ }
+ };
+
+ useEffect(() => {
+ if (auto && hexColors(BGColor, TextColorRGB)) {
+ setColorsToAuto(generateColors(BGColor, TextColorRGB));
+ }
+ // eslint-disable-next-line
+ }, [auto, BGColor, TextColorRGB]);
+
+ // Function that sets the colors to those imported from a css file
+ function setColorsFromImport (newColors) {
+ const colorSet = getColorsFromString(newColors);
+ setActivePreset('defaultTheme');
+
+ if (colorSet !== null) {
+ Promise.resolve().then(() => {
+ ReactDOM.unstable_batchedUpdates(() => {
+ setAuto(false);
+ if (colorSet[0][0].includes('Skin Name')) {
+ setSkinName(colorSet[0][1]);
+ colorSet.shift();
+ }
+ colorSet.map((item) => {
+ const index = varNames.indexOf(item[0]);
+ if (index !== -1) {
+ if (item[0].includes('rgb')) {
+ const [r, g, b] = item[1].split(', ');
+ setColors[index](convertRGBToHex([parseInt(r), parseInt(g), parseInt(b)]));
+ } else {
+ setColors[index](item[1]);
+ }
+ }
+ });
+ });
+ });
+ } else {
+ setAlert(true);
+ }
+ }
+
+ // Function that sets the colors to those of a preset
+ function setColorsFromPreset (presetColors) {
+ const colorSet = presets[`${presetColors}`];
+ setActivePreset(presetColors);
+
+ for (let color in colorSet) {
+ const index = varNames.indexOf(color);
+ setColors[index](colorSet[color]);
+ }
+ }
+
+ // Function that sets a color to that received from an input
+ function onChangeInput (props) {
+ const event = props?.event;
+ const index = props?.index;
+ const name = props?.name;
+ let value = event?.value;
+
+ if (name === 'Skin Name') {
+ setSkinName(value);
+ } else {
+ setColors[index](value.toUpperCase());
+ if (!auto) {
+ setChanges(1);
+ }
+ }
+ }
+
+ // Function that handles the switch to auto behavior
+ function onChangeSwitch () {
+ if (auto) {
+ setAuto(!auto);
+ } else if (changes !== 0) {
+ setOpenWarning(true);
+ } else {
+ setAuto(!auto);
+ }
+ }
+
+ // Removes some css styles included by the handleOpen and handleFocus handlers.
+ function handleOnBlur () {
+ if (typeof document !== 'undefined') {
+ document.querySelector('#temporaryStylesheet')?.remove();
+ }
+ }
+
+ // Appends some styles via javascript. The styles must be appended for the
+ // non live demo components to have the basic limestone appearance.
+ function handleOnFocus () {
+ if (typeof document !== 'undefined') {
+ const sheet = document.createElement('style');
+ sheet.id = 'temporaryStylesheet';
+ sheet.innerHTML = `.limestone-theme {
+ --sand-component-focus-text-color-rgb: 76, 80, 89;
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ --sand-shadow-color-rgb: none;
+ }`;
+ document.body?.appendChild(sheet);
+ }
+ }
+
+ const handleFullCSS = useCallback(() => {
+ setFullCSS((val) => !val);
+ }, []);
+
+ function handleOpenPopup () {
+ setOpenPopup(!openPopup);
+ }
+
+ function turnAlertOff () {
+ setAlert(false);
+ }
+
+ // Function that sets the colors to those default to the selected preset
+ function setDefaultState () {
+ setAuto(false);
+ setColorsFromPreset(presetActive);
+ }
+
+ function handleScrollTop () {
+ return scrollTo({position: {x: 0, y: 0}});
+ }
+
+ // Appends some styles via javascript. The styles must be appended for the
+ // live demo components to have the skin appearance.
+ if (typeof document !== 'undefined') {
+ const sheet = document.createElement('style');
+ sheet.id = 'custom-skin';
+ sheet.innerHTML = generateCSS(colors, skinName, varNames);
+ document.getElementById('custom-skin')?.remove();
+ document.body?.appendChild(sheet);
+ }
+
+ let screenWidth = typeof window !== 'undefined' ? window.screen.width : 0;
+ let windowWidth = typeof window !== 'undefined' ? window.innerWidth : 0;
+ let previewDropdownWidth = () => {
+ if (screenWidth <= 1920) {
+ if (windowWidth < 1080) {
+ return 'tiny';
+ } else {
+ return 'medium';
+ }
+ } else if ( screenWidth > 1920) {
+ if (windowWidth < 2160) {
+ return 'tiny';
+ } else {
+ return 'medium';
+ }
+ }
+ };
+
+ return (
+
+
+ {scrollTo = fn}} //eslint-disable-line
+ focusableScrollbar
+ horizontalScrollbar="hidden"
+ >
+ Custom skin generator
+
+
+ Wrong type of file imported!
+
+
+
+
+
+
+
+
+ Generate colors automatically
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Live DEMO
+
+
+
+
+
+
+
+
+
+
+ Checkbox
+ Toggle
+
+
+
+ {['Item 1', 'Item 2', 'Item 3']}
+
+
+
+ Hello
+
+
+
+
+
+ );
+};
+
+export default MainPanel;
diff --git a/limestone/feature-custom-skin-generator/src/views/MainPanel.module.less b/limestone/feature-custom-skin-generator/src/views/MainPanel.module.less
new file mode 100644
index 000000000..3dea8366f
--- /dev/null
+++ b/limestone/feature-custom-skin-generator/src/views/MainPanel.module.less
@@ -0,0 +1,96 @@
+// MainPanel.module.less
+
+.mainPanel {
+ height: 100%;
+
+ .customizeSection {
+ padding: 24px;
+
+ .appTitle {
+ color: #FB9039;
+ padding-bottom: 48px;
+ border: none;
+ font-weight: 200;
+ }
+
+ .switchLabel {
+ display: inline-block;
+ color: #e6e6e6;
+ }
+
+ .switchControl {
+ --sand-focus-bg-color-rgb: 125, 132, 140;
+ --sand-selected-color-rgb: 0, 0, 0; /* Selected Color (Must be RGB comma separated format) */
+ --sand-shadow-color-rgb: none;
+ --sand-toggle-on-color: #505050;
+ --sand-toggle-on-bg-color: #e6e6e6; /* Toggle On Background Color */
+ --sand-toggle-off-color: #4c596a; /* Toggle Off Color */
+ --sand-toggle-off-bg-color: #abaeb3; /* Toggle Off Background Color */
+ margin: 18px;
+ }
+
+ .generateStyleContainer {
+ flex-wrap: wrap;
+ }
+ }
+
+ .topButtonContainer {
+ align-self: flex-end;
+ text-align: center;
+
+ .topButton {
+ --sand-component-bg-color: #7D848C;
+ --sand-component-focus-text-color-rgb: 76, 80, 89;
+ --sand-component-text-color-rgb: 230, 230, 230;
+ --sand-focus-bg-color-rgb: 255, 255, 255;
+ --sand-shadow-color-rgb: none;
+ color: #e6e6e6;
+ margin-left: 18px;
+ margin-right: 18px;
+ text-transform: uppercase;
+ }
+ }
+
+ .previewSection {
+ background-color: var(--sand-bg-color, #4c596a);
+ border-radius: 12px;
+ margin: 24px;
+ padding: 0 24px;
+ display: flex;
+ justify-content: center;
+
+ .previewTitle {
+ --sand-text-color: #e6e6e6;
+ display: flex;
+ justify-content: center;
+ min-height: auto;
+ padding: 24px 0;
+ margin: 0 24px;
+ font-weight: 200;
+ }
+
+ .previewComponents {
+ justify-content: space-around;
+ width: 100%;
+
+ .previewButtons {
+ flex-wrap: wrap;
+ justify-content: center;
+
+ .button {
+ min-width: 339px;
+ margin: 12px;
+ }
+
+ & > * {
+ margin: 15px;
+ }
+ }
+
+ .previewDropdown,
+ .previewPopup {
+ align-self: center;
+ }
+ }
+ }
+}
diff --git a/limestone/pattern-account-icon/.gitignore b/limestone/pattern-account-icon/.gitignore
new file mode 100644
index 000000000..f94ea516b
--- /dev/null
+++ b/limestone/pattern-account-icon/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-account-icon/LICENSE b/limestone/pattern-account-icon/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/pattern-account-icon/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/pattern-account-icon/README.md b/limestone/pattern-account-icon/README.md
new file mode 100644
index 000000000..7380c3a1a
--- /dev/null
+++ b/limestone/pattern-account-icon/README.md
@@ -0,0 +1,9 @@
+## Account Icon pattern
+
+A sample Enact application that uses AccountIcon (login/logout).
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/pattern-account-icon/package.json b/limestone/pattern-account-icon/package.json
new file mode 100644
index 000000000..09dfb297a
--- /dev/null
+++ b/limestone/pattern-account-icon/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "pattern-account-icon",
+ "version": "1.0.0",
+ "description": "An Enact application demonstrating AccountIcon.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "Account Icon Pattern",
+ "isomorphic": true,
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-account-icon/resources/ilibmanifest.json b/limestone/pattern-account-icon/resources/ilibmanifest.json
new file mode 100644
index 000000000..f32ee1d3f
--- /dev/null
+++ b/limestone/pattern-account-icon/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-account-icon/src/App/App.js b/limestone/pattern-account-icon/src/App/App.js
new file mode 100644
index 000000000..6b30039b7
--- /dev/null
+++ b/limestone/pattern-account-icon/src/App/App.js
@@ -0,0 +1,24 @@
+import kind from '@enact/core/kind';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+
+import MainPanel from '../views/MainPanel';
+
+const AppBase = kind({
+ name: 'App',
+
+ render: function (props) {
+ return (
+
+
+
+ );
+ }
+});
+
+const App = ThemeDecorator(AppBase);
+
+export default App;
+export {
+ App,
+ AppBase
+};
diff --git a/limestone/pattern-account-icon/src/App/package.json b/limestone/pattern-account-icon/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/pattern-account-icon/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/pattern-account-icon/src/components/AccountIcon.js b/limestone/pattern-account-icon/src/components/AccountIcon.js
new file mode 100644
index 000000000..8d1cf6c1e
--- /dev/null
+++ b/limestone/pattern-account-icon/src/components/AccountIcon.js
@@ -0,0 +1,21 @@
+import Icon from '@enact/limestone/Icon';
+import PropTypes from 'prop-types';
+
+// For custom style of account icon
+import css from './AccountIcon.module.less';
+
+const AccountIcon = ({bgColor, children}) => {
+ const accountStyle = {
+ backgroundColor: bgColor
+ };
+
+ return (
+ {children}
+ );
+};
+
+AccountIcon.propTypes = {
+ bgColor: PropTypes.string
+};
+
+export default AccountIcon;
diff --git a/limestone/pattern-account-icon/src/components/AccountIcon.module.less b/limestone/pattern-account-icon/src/components/AccountIcon.module.less
new file mode 100644
index 000000000..001d39070
--- /dev/null
+++ b/limestone/pattern-account-icon/src/components/AccountIcon.module.less
@@ -0,0 +1,6 @@
+.icon {
+ border-radius: 50%;
+ margin: 0;
+ font-size: 48px;
+ overflow: visible;
+}
diff --git a/limestone/pattern-account-icon/src/components/README.md b/limestone/pattern-account-icon/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/pattern-account-icon/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/pattern-account-icon/src/index.js b/limestone/pattern-account-icon/src/index.js
new file mode 100644
index 000000000..57c20e6fd
--- /dev/null
+++ b/limestone/pattern-account-icon/src/index.js
@@ -0,0 +1,19 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './App';
+
+const appElement = ();
+
+// In a browser environment, render the app to the document.
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/pattern-account-icon/src/views/MainPanel.js b/limestone/pattern-account-icon/src/views/MainPanel.js
new file mode 100644
index 000000000..0056579b2
--- /dev/null
+++ b/limestone/pattern-account-icon/src/views/MainPanel.js
@@ -0,0 +1,42 @@
+import kind from '@enact/core/kind';
+import Button from '@enact/limestone/Button';
+import {Header, Panel} from '@enact/limestone/Panels';
+import {Cell, Row} from '@enact/ui/Layout';
+import PropTypes from 'prop-types';
+
+import AccountIcon from '../components/AccountIcon';
+
+// In limestone theme, the text color changes to gray tone when the button is focused.
+// A custom style is needed to prevent this and maintain the white tone text color.
+import css from './MainPanel.module.less';
+
+const MainPanel = kind({
+ name: 'MainPanel',
+
+ propTypes: {
+ bgColor: PropTypes.string,
+ initials: PropTypes.string
+ },
+
+ defaultProps: {
+ bgColor: 'red'
+ },
+
+ render: ({bgColor, initials, ...rest}) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+});
+
+export default MainPanel;
diff --git a/limestone/pattern-account-icon/src/views/MainPanel.module.less b/limestone/pattern-account-icon/src/views/MainPanel.module.less
new file mode 100644
index 000000000..be5662bd1
--- /dev/null
+++ b/limestone/pattern-account-icon/src/views/MainPanel.module.less
@@ -0,0 +1,18 @@
+@import "~@enact/spotlight/styles/mixins.less";
+@import "~@enact/limestone/styles/skin.less";
+
+.button {
+ .applySkins({
+ .focus({
+ color: #e6e6e6;
+
+ .disabled({
+ color: #e6e6e6; //It can be changed according to the GUI guide when account button is focused and disabled.
+ })
+ });
+
+ .disabled({
+ color: #e6e6e6; //It can be changed according to the GUI guide when account button is disabled.
+ });
+ });
+}
diff --git a/limestone/pattern-account-icon/src/views/README.md b/limestone/pattern-account-icon/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/pattern-account-icon/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/pattern-account-icon/webos-meta/appinfo.json b/limestone/pattern-account-icon/webos-meta/appinfo.json
new file mode 100644
index 000000000..e97629ef3
--- /dev/null
+++ b/limestone/pattern-account-icon/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.pattern-account-icon",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Account Icon Pattern",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/pattern-account-icon/webos-meta/icon-large.png b/limestone/pattern-account-icon/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/pattern-account-icon/webos-meta/icon-large.png differ
diff --git a/limestone/pattern-account-icon/webos-meta/icon-mini.png b/limestone/pattern-account-icon/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/pattern-account-icon/webos-meta/icon-mini.png differ
diff --git a/limestone/pattern-account-icon/webos-meta/icon.png b/limestone/pattern-account-icon/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/pattern-account-icon/webos-meta/icon.png differ
diff --git a/limestone/pattern-analytics-webostv/.gitignore b/limestone/pattern-analytics-webostv/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-analytics-webostv/LICENSE b/limestone/pattern-analytics-webostv/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/pattern-analytics-webostv/README.md b/limestone/pattern-analytics-webostv/README.md
new file mode 100644
index 000000000..717faad20
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/README.md
@@ -0,0 +1,9 @@
+## Analytics webOS TV pattern
+
+A sample Enact application that demonstrates how to use `@enact/analytics` with the webOS TV preset.
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/pattern-analytics-webostv/analytics.cfg b/limestone/pattern-analytics-webostv/analytics.cfg
new file mode 100644
index 000000000..912850a81
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/analytics.cfg
@@ -0,0 +1,25 @@
+{
+ "enabled": true,
+ "messageId": "MYCUSTOMID",
+ "rules": [
+ {
+ "include": {
+ "label": ["ZOOM"]
+ },
+ "data": {
+ "messageId": "NL_ZOOM"
+ }
+ },
+ {
+ "data": {
+ "panel": {
+ "closest": "article[role='region']",
+ "value": {
+ "selector": "header h1",
+ "value": ""
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/limestone/pattern-analytics-webostv/package.json b/limestone/pattern-analytics-webostv/package.json
new file mode 100644
index 000000000..2d96c01c7
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "pattern-analytics-webostv",
+ "version": "1.0.0",
+ "description": "An Enact application demonstrating @enact/analytics with webOS TV preset.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "Analytics webOSTV Pattern",
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/analytics": "^1.0.1",
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-analytics-webostv/resources/ilibmanifest.json b/limestone/pattern-analytics-webostv/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-analytics-webostv/src/App/App.js b/limestone/pattern-analytics-webostv/src/App/App.js
new file mode 100644
index 000000000..df98f9409
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/src/App/App.js
@@ -0,0 +1,26 @@
+import kind from '@enact/core/kind';
+import Panels from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+
+import MainPanel from '../views/MainPanel';
+
+import css from './App.module.less';
+
+const App = kind({
+ name: 'App',
+
+ styles: {
+ css,
+ className: 'app'
+ },
+
+ render: (props) => (
+
+
+
+
+
+ )
+});
+
+export default ThemeDecorator(App);
diff --git a/limestone/pattern-analytics-webostv/src/App/App.module.less b/limestone/pattern-analytics-webostv/src/App/App.module.less
new file mode 100644
index 000000000..23782defd
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/src/App/App.module.less
@@ -0,0 +1,3 @@
+.app {
+ // styles can be put here
+}
diff --git a/limestone/pattern-analytics-webostv/src/App/package.json b/limestone/pattern-analytics-webostv/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/pattern-analytics-webostv/src/components/README.md b/limestone/pattern-analytics-webostv/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/pattern-analytics-webostv/src/index.js b/limestone/pattern-analytics-webostv/src/index.js
new file mode 100644
index 000000000..cf36c6526
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/src/index.js
@@ -0,0 +1,68 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {configure} from '@enact/analytics/preset/webostv';
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import analytics from '../analytics.cfg';
+
+import App from './App';
+
+// The webOS and webOS TV presets will log entries to PmLogLib.
+configure({
+ // Normally this defaults to `/mnt/lg/cmn_data/whitelist/dr/enact/${appId}.json`,
+ // however for the purposes of this demo, using a local JSON-formatted file.
+ path: analytics
+});
+
+/*
+ For reference, the external JSON-formatted file includes the following setup:
+ {
+ // Enables the usage of analytics
+ "enabled": true,
+ // Option custom message ID for PmLogLib. Defaults to NL_ENACT
+ "messageId": "MYCUSTOMID",
+ "rules": [
+ // the first matching rule will be used
+ {
+ // only match messages that include ZOOM in the label field
+ "include": {
+ "label": ["ZOOM"]
+ },
+ // Example custom data to include on click/enter-key events.
+ "data": {
+ // add messageId to the message with the value NL_ZOOM
+ "messageId": "NL_ZOOM"
+ }
+ },
+ {
+ // This includes a `panel` property with the Panel header text.
+ "data": {
+ "panel": {
+ // find the first Panel ancestor
+ "closest": "article[role='region']",
+ "value": {
+ // from that Panel node, find the first h1 descendant
+ "selector": "header h1",
+ // and retrieve its textContent
+ "value": ""
+ }
+ }
+ }
+ }
+ ]
+ }
+*/
+
+const appElement = ();
+
+// In a browser environment, render the app to the document.
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/pattern-analytics-webostv/src/views/MainPanel.js b/limestone/pattern-analytics-webostv/src/views/MainPanel.js
new file mode 100644
index 000000000..0af1dc9cb
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/src/views/MainPanel.js
@@ -0,0 +1,22 @@
+import kind from '@enact/core/kind';
+import BodyText from '@enact/limestone/BodyText';
+import Button from '@enact/limestone/Button';
+import {Header, Panel} from '@enact/limestone/Panels';
+
+const MainPanel = kind({
+ name: 'MainPanel',
+
+ render: (props) => (
+
+
+
+ This app is hooked in to @enact/analytics with the webOS TV preset
+ and outputs log entries via PmLogLib.
+
+
+
+
+ )
+});
+
+export default MainPanel;
diff --git a/limestone/pattern-analytics-webostv/src/views/README.md b/limestone/pattern-analytics-webostv/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/pattern-analytics-webostv/webos-meta/appinfo.json b/limestone/pattern-analytics-webostv/webos-meta/appinfo.json
new file mode 100644
index 000000000..7d188e46e
--- /dev/null
+++ b/limestone/pattern-analytics-webostv/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.pattern-analytics-webostv",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Analytics webOSTV Pattern",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/pattern-analytics-webostv/webos-meta/icon-large.png b/limestone/pattern-analytics-webostv/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/pattern-analytics-webostv/webos-meta/icon-large.png differ
diff --git a/limestone/pattern-analytics-webostv/webos-meta/icon-mini.png b/limestone/pattern-analytics-webostv/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/pattern-analytics-webostv/webos-meta/icon-mini.png differ
diff --git a/limestone/pattern-analytics-webostv/webos-meta/icon.png b/limestone/pattern-analytics-webostv/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/pattern-analytics-webostv/webos-meta/icon.png differ
diff --git a/limestone/pattern-dynamic-panel/.gitignore b/limestone/pattern-dynamic-panel/.gitignore
new file mode 100644
index 000000000..f94ea516b
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-dynamic-panel/LICENSE b/limestone/pattern-dynamic-panel/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/pattern-dynamic-panel/README.md b/limestone/pattern-dynamic-panel/README.md
new file mode 100644
index 000000000..41360dbeb
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/README.md
@@ -0,0 +1,25 @@
+## Dynamic Panel pattern
+
+A sample Enact application that uses `Changeable` and `Cancelable` HOCs on a `Panel` to simulate file browsing.
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+#### Enact Components Used
+- `limestone/Image`
+- `limestone/Panels/Header`
+- `limestone/Panels/Panel`
+- `limestone/VirtualList`
+- `ui/Cancelable`
+- `ui/Changeable`
+
+A `FileBrowser` kind is implemented that is both `Cancelable` and `Changeable`. It renders a `Panel` that varies its children. If the path is a directory, a `VirtualList` of the directory contents is rendered and the items' `onClick` handlers drive navigation to directory contents.
+
+The `onCancel` handler ultimately drives navigation to the parent directory.
+
+You can find a more detailed view inside of [App.js](src/App/App.js).
+
+All images were obtained from [http://www.publicdomainpictures.net/](http://www.publicdomainpictures.net/) and are licensed [CC0 Public Domain](https://creativecommons.org/publicdomain/zero/1.0/).
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/pattern-dynamic-panel/assets/images/butterfly.jpg b/limestone/pattern-dynamic-panel/assets/images/butterfly.jpg
new file mode 100644
index 000000000..be4a1e0d2
Binary files /dev/null and b/limestone/pattern-dynamic-panel/assets/images/butterfly.jpg differ
diff --git a/limestone/pattern-dynamic-panel/assets/images/frozenwaterfall.jpg b/limestone/pattern-dynamic-panel/assets/images/frozenwaterfall.jpg
new file mode 100644
index 000000000..5ad17a364
Binary files /dev/null and b/limestone/pattern-dynamic-panel/assets/images/frozenwaterfall.jpg differ
diff --git a/limestone/pattern-dynamic-panel/assets/images/jellyfish.jpg b/limestone/pattern-dynamic-panel/assets/images/jellyfish.jpg
new file mode 100644
index 000000000..7d05343d6
Binary files /dev/null and b/limestone/pattern-dynamic-panel/assets/images/jellyfish.jpg differ
diff --git a/limestone/pattern-dynamic-panel/assets/images/macaw.jpg b/limestone/pattern-dynamic-panel/assets/images/macaw.jpg
new file mode 100644
index 000000000..b5ca62ca4
Binary files /dev/null and b/limestone/pattern-dynamic-panel/assets/images/macaw.jpg differ
diff --git a/limestone/pattern-dynamic-panel/assets/images/ornaments.jpg b/limestone/pattern-dynamic-panel/assets/images/ornaments.jpg
new file mode 100644
index 000000000..475541785
Binary files /dev/null and b/limestone/pattern-dynamic-panel/assets/images/ornaments.jpg differ
diff --git a/limestone/pattern-dynamic-panel/assets/images/rainbow.jpg b/limestone/pattern-dynamic-panel/assets/images/rainbow.jpg
new file mode 100644
index 000000000..71accaf92
Binary files /dev/null and b/limestone/pattern-dynamic-panel/assets/images/rainbow.jpg differ
diff --git a/limestone/pattern-dynamic-panel/package.json b/limestone/pattern-dynamic-panel/package.json
new file mode 100644
index 000000000..bea80ede3
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "pattern-dynamic-panel",
+ "version": "1.0.0",
+ "description": "An Enact application demonstrating DynamicPanel.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "Dynamic Panel Pattern",
+ "isomorphic": true,
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "@enact/webos": "^5.0.0-alpha.4",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-dynamic-panel/resources/ilibmanifest.json b/limestone/pattern-dynamic-panel/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-dynamic-panel/src/App/App.js b/limestone/pattern-dynamic-panel/src/App/App.js
new file mode 100644
index 000000000..4bae2d33d
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/src/App/App.js
@@ -0,0 +1,33 @@
+import kind from '@enact/core/kind';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+import Changeable from '@enact/ui/Changeable';
+
+import FileBrowser from '../components/FileBrowser';
+
+import css from './App.module.less';
+
+// This would be replaced by redux but Changeable is a handy single-value, single-event state HOC
+const Browser = Changeable(
+ {prop: 'path', change: 'onNavigate'},
+ FileBrowser
+);
+
+const AppBase = kind({
+ name: 'App',
+
+ styles: {
+ css,
+ className: 'app'
+ },
+
+ render: (props) => (
+
+
+
+ )
+});
+
+const App = ThemeDecorator(AppBase);
+
+export default App;
+export {App, AppBase};
diff --git a/limestone/pattern-dynamic-panel/src/App/App.module.less b/limestone/pattern-dynamic-panel/src/App/App.module.less
new file mode 100644
index 000000000..23782defd
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/src/App/App.module.less
@@ -0,0 +1,3 @@
+.app {
+ // styles can be put here
+}
diff --git a/limestone/pattern-dynamic-panel/src/App/package.json b/limestone/pattern-dynamic-panel/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/pattern-dynamic-panel/src/components/DynamicPanel/DynamicPanel.js b/limestone/pattern-dynamic-panel/src/components/DynamicPanel/DynamicPanel.js
new file mode 100644
index 000000000..ed540c649
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/src/components/DynamicPanel/DynamicPanel.js
@@ -0,0 +1,26 @@
+import kind from '@enact/core/kind';
+import {Header, Panel} from '@enact/limestone/Panels';
+import PropTypes from 'prop-types';
+
+const DynamicPanelBase = kind({
+ name: 'DynamicPanelBase',
+
+ propTypes: {
+ onNavigate: PropTypes.func,
+ path: PropTypes.string
+ },
+
+ render: ({children, path, ...rest}) => {
+ delete rest.onNavigate;
+
+ return (
+
+
+ {children}
+
+ );
+ }
+});
+
+export default DynamicPanelBase;
+export {DynamicPanelBase as DynamicPanel, DynamicPanelBase};
diff --git a/limestone/pattern-dynamic-panel/src/components/DynamicPanel/package.json b/limestone/pattern-dynamic-panel/src/components/DynamicPanel/package.json
new file mode 100644
index 000000000..5f68b6c24
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/src/components/DynamicPanel/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "DynamicPanel.js"
+}
diff --git a/limestone/pattern-dynamic-panel/src/components/FileBrowser/FileBrowser.js b/limestone/pattern-dynamic-panel/src/components/FileBrowser/FileBrowser.js
new file mode 100644
index 000000000..c8e0bc023
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/src/components/FileBrowser/FileBrowser.js
@@ -0,0 +1,161 @@
+import kind from '@enact/core/kind';
+import {Image} from '@enact/limestone/Image';
+import {Item} from '@enact/limestone/Item';
+import VirtualList from '@enact/limestone/VirtualList';
+import Cancelable from '@enact/ui/Cancelable';
+import ri from '@enact/ui/resolution';
+import PropTypes from 'prop-types';
+
+import butterfly from '/assets/images/butterfly.jpg';
+import frozenwaterfall from '/assets/images/frozenwaterfall.jpg';
+import jellyfish from '/assets/images/jellyfish.jpg';
+import macaw from '/assets/images/macaw.jpg';
+import ornaments from '/assets/images/ornaments.jpg';
+import rainbow from '/assets/images/rainbow.jpg';
+
+import DynamicPanel from '../DynamicPanel';
+
+const a = {
+ files: [
+ {name: 'b', directory: true},
+ {name: 'rainbow.jpg'},
+ {name: 'macaw.jpg'}
+ ]
+};
+
+const b = {
+ files: [
+ {name: 'c', directory: true},
+ {name: 'jellyfish.jpg'},
+ {name: 'butterfly.jpg'}
+ ]
+};
+
+const c = {
+ files: [
+ {name: 'ornaments.jpg'},
+ {name: 'frozenwaterfall.jpg'}
+ ]
+};
+
+const mockFolders = {a, b, c};
+
+const filePhotos = {
+ butterfly,
+ frozenwaterfall,
+ jellyfish,
+ macaw,
+ ornaments,
+ rainbow
+};
+
+const FileBrowserBase = kind({
+ name: 'FileBrowserBase',
+
+ propTypes: {
+ onNavigate: PropTypes.func,
+ path: PropTypes.object
+ },
+
+ handlers: {
+ // create a cached event handler forwarding to onNavigate
+ onNavigate: (ev, props) => {
+ // extract the index provided by VirtualList
+ const index = ev.currentTarget.dataset.index;
+ // extract the path object from props
+ const pathInfo = props.path;
+ // for mock system to know where to get files
+ const leaf = pathInfo.path.split('/').pop();
+ const file = mockFolders[leaf].files[index];
+ // map it to the name from the hardcoded list of files
+ const name = file.name;
+ // make the new path
+ const path = `${pathInfo.path}/${name}`;
+ const pathData = {path, directory: file.directory || false};
+ // and notify the handler
+ props.onNavigate({path: pathData});
+ }
+ },
+
+ computed: {
+ // computed component pattern is not currently Enact eslint friendly
+ // eslint-disable-next-line
+ renderItem: () => ({listItem, onNavigate, path: pathData, ...rest}) => {
+ const {path, directory} = pathData;
+ const leaf = path.split('/').pop();
+
+ const component = directory ?
+ (
+
+ {mockFolders[leaf].files[index].name}
+
+ )}
+ itemSize={ri.scale(144)}
+ /> :
+ ;
+
+ return (
+
+ {component}
+
+ );
+
+ }
+ },
+
+ render: ({renderItem: Component, ...rest}) => (
+
+ )
+});
+
+const popPath = (pathData) => {
+ const path = pathData.path;
+ let newPath = '/';
+ let lastPath = path;
+
+ if (path) {
+ const parts = path.split('/').filter((e) => e);
+ lastPath = parts.pop();
+ if (parts.length) {
+ newPath += parts.join('/');
+ if (!newPath) {
+ newPath = '/';
+ }
+ } else {
+ // nowhere to go
+ newPath += lastPath ? lastPath : '';
+ }
+ }
+ return {path: newPath, directory: true}; // assume you can't drill "up" into a file above the current location
+};
+
+// the onCancel callback from the Cancelable config receives the Cancelable's props to both
+// determine if it should cancel and how to handle the cancel. here, we're calling the onNavigate
+// event callback.
+const handleCancel = (ev, {path, onNavigate}) => {
+ // pop the path
+ const newPath = popPath(path);
+ // and if there's an onNavigate callback
+ if (onNavigate) {
+ // call it with the new path
+ onNavigate({
+ path: newPath
+ });
+
+ // then return true to indicate it was handled
+ ev.stopPropagation();
+ }
+};
+
+const FileBrowser = Cancelable(
+ // modal lets it handle any cancel event that isn't handled by the spotted component. Useful for
+ // full-screen containers like Panels (or Panel in this case)
+ {modal: true, onCancel: handleCancel},
+ FileBrowserBase
+);
+
+export default FileBrowser;
+export {FileBrowser, FileBrowserBase};
diff --git a/limestone/pattern-dynamic-panel/src/components/FileBrowser/package.json b/limestone/pattern-dynamic-panel/src/components/FileBrowser/package.json
new file mode 100644
index 000000000..3af8e1b91
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/src/components/FileBrowser/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "FileBrowser.js"
+}
diff --git a/limestone/pattern-dynamic-panel/src/components/README.md b/limestone/pattern-dynamic-panel/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/pattern-dynamic-panel/src/index.js b/limestone/pattern-dynamic-panel/src/index.js
new file mode 100644
index 000000000..57c20e6fd
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/src/index.js
@@ -0,0 +1,19 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './App';
+
+const appElement = ();
+
+// In a browser environment, render the app to the document.
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/pattern-dynamic-panel/src/views/README.md b/limestone/pattern-dynamic-panel/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/pattern-dynamic-panel/webos-meta/appinfo.json b/limestone/pattern-dynamic-panel/webos-meta/appinfo.json
new file mode 100644
index 000000000..552fcc25f
--- /dev/null
+++ b/limestone/pattern-dynamic-panel/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.pattern-dynamic-panel",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Dynamic Panel Pattern",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/pattern-dynamic-panel/webos-meta/icon-large.png b/limestone/pattern-dynamic-panel/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/pattern-dynamic-panel/webos-meta/icon-large.png differ
diff --git a/limestone/pattern-dynamic-panel/webos-meta/icon-mini.png b/limestone/pattern-dynamic-panel/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/pattern-dynamic-panel/webos-meta/icon-mini.png differ
diff --git a/limestone/pattern-dynamic-panel/webos-meta/icon.png b/limestone/pattern-dynamic-panel/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/pattern-dynamic-panel/webos-meta/icon.png differ
diff --git a/limestone/pattern-layout/.gitignore b/limestone/pattern-layout/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/pattern-layout/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-layout/README.md b/limestone/pattern-layout/README.md
new file mode 100644
index 000000000..0058ad0a7
--- /dev/null
+++ b/limestone/pattern-layout/README.md
@@ -0,0 +1,17 @@
+## Layout pattern
+
+A sample application to demonstrate elaborate usages of the `ui/Layout` component.
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+#### Enact Components Used
+- `limestone/Button`
+- `limestone/Item`
+- `limestone/Panels/Header`
+- `limestone/Panels/Panel`
+- `limestone/VirtualList`
+- `ui/Layout`
+
+---
+
+This project was bootstrapped with [enact-cli](https://github.com/enactjs/cli).
diff --git a/limestone/pattern-layout/assets/images/details.jpg b/limestone/pattern-layout/assets/images/details.jpg
new file mode 100644
index 000000000..7a5b70df9
Binary files /dev/null and b/limestone/pattern-layout/assets/images/details.jpg differ
diff --git a/limestone/pattern-layout/assets/images/favorites-list.jpg b/limestone/pattern-layout/assets/images/favorites-list.jpg
new file mode 100644
index 000000000..6bfd409fc
Binary files /dev/null and b/limestone/pattern-layout/assets/images/favorites-list.jpg differ
diff --git a/limestone/pattern-layout/package.json b/limestone/pattern-layout/package.json
new file mode 100644
index 000000000..7ca3374ae
--- /dev/null
+++ b/limestone/pattern-layout/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "pattern-layout",
+ "version": "1.0.0",
+ "description": "A sample application to demonstrate elaborate usages of the Layout component.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enyojs/enact-samples",
+ "enact": {
+ "title": "Layout sample",
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "@enact/webos": "^5.0.0-alpha.4",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "query-string": "^9.1.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-layout/resources/ilibmanifest.json b/limestone/pattern-layout/resources/ilibmanifest.json
new file mode 100644
index 000000000..5916671d2
--- /dev/null
+++ b/limestone/pattern-layout/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
\ No newline at end of file
diff --git a/limestone/pattern-layout/src/App/App.js b/limestone/pattern-layout/src/App/App.js
new file mode 100644
index 000000000..e5b609895
--- /dev/null
+++ b/limestone/pattern-layout/src/App/App.js
@@ -0,0 +1,138 @@
+import {adaptEvent, forward, handle} from '@enact/core/handle';
+import hoc from '@enact/core/hoc';
+import kind from '@enact/core/kind';
+import Button from '@enact/limestone/Button';
+import {Panels} from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+import PropTypes from 'prop-types';
+import {useCallback, useState} from 'react';
+
+import detail from '/assets/images/details.jpg';
+import favoritesList from '/assets/images/favorites-list.jpg';
+
+import Details from '../views/Details';
+import FavoritesList from '../views/FavoritesList';
+import MainPanel from '../views/MainPanel';
+
+const items = [];
+const thumbs = {'details.jpg': detail, 'favorites-list.jpg': favoritesList};
+
+const itemPusher = (title, subTitle, component, image) => {
+ items.push({
+ title,
+ subTitle,
+ component,
+ image
+ });
+};
+
+// Add all of our Layout Patterns
+itemPusher('Favorites List', 'Two list columns with focusable buttons in the center', FavoritesList, thumbs['favorites-list.jpg']);
+itemPusher('Details View', 'Show off details about an item', Details, thumbs['details.jpg']);
+
+const Placeholder = kind({name: 'Placeholder'});
+
+const Sample = kind({
+ name: 'LayoutApp',
+
+ propTypes: {
+ debug: PropTypes.bool,
+ itemIndex: PropTypes.number,
+ onChangePanel: PropTypes.func,
+ onToggleDebug: PropTypes.func
+ },
+
+ computed: {
+ DebugButton: ({onToggleDebug, debug}) => ()
+ },
+
+ handlers: {
+ onSelectBreadcrumb: handle(
+ adaptEvent((ev, props) => ({index: (props.index - 1), itemIndex: null}), forward('onChangePanel')),
+ forward('onSelectBreadcrumb')
+ ),
+ onChangePanel: handle(
+ adaptEvent(({index}) => ({index: 1, itemIndex: index}), forward('onChangePanel'))
+ )
+ },
+
+ render: ({debug, DebugButton, itemIndex, onChangePanel, onSelectBreadcrumb, ...rest}) => {
+ delete rest.onToggleDebug;
+
+ let secondaryPanel = ;
+ const item = items[itemIndex];
+ if (item) {
+ const ItemPanel = item.component;
+ secondaryPanel = (
+
+ );
+ }
+ return (
+
+
+ {secondaryPanel}
+
+ );
+ }
+});
+
+const AppDecorator = hoc((config, Wrapped) => {
+ const Component = ({defaultDebug, defaultIndex, defaultItemIndex, ...rest}) => {
+ const [debug, setDebug] = useState(defaultDebug);
+ const [index, setIndex] = useState(defaultIndex);
+ const [itemIndex, setItemIndex] = useState(defaultItemIndex);
+
+ const handleChangePanel = useCallback((ev) => {
+ forward('onChangePanel', ev, rest);
+ setIndex(ev.index);
+ setItemIndex(ev.itemIndex);
+ }, [rest]);
+
+ const handleToggleDebug = useCallback(() => {
+ setDebug((prevDebug) => {
+ const nextDebug = {debug: !prevDebug};
+ forward('onToggleDebug', nextDebug, rest);
+
+ return !prevDebug;
+ });
+ }, [rest]);
+
+ return (
+
+ );
+ };
+
+ Component.displayName = 'AppDecorator';
+
+ Component.propTypes = {
+ defaultDebug: PropTypes.bool,
+ defaultIndex: PropTypes.number,
+ defaultItemIndex: PropTypes.number
+ };
+
+ Component.defaultProps = {
+ defaultDebug: false,
+ defaultIndex: 0,
+ defaultItemIndex: 0
+ };
+
+ return Component;
+});
+
+const AppBase = AppDecorator(Sample);
+const App = ThemeDecorator(AppBase);
+
+export default App;
+export {App, AppBase};
diff --git a/limestone/pattern-layout/src/App/App.less b/limestone/pattern-layout/src/App/App.less
new file mode 100644
index 000000000..23782defd
--- /dev/null
+++ b/limestone/pattern-layout/src/App/App.less
@@ -0,0 +1,3 @@
+.app {
+ // styles can be put here
+}
diff --git a/limestone/pattern-layout/src/App/package.json b/limestone/pattern-layout/src/App/package.json
new file mode 100644
index 000000000..441552583
--- /dev/null
+++ b/limestone/pattern-layout/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/pattern-layout/src/components/README.md b/limestone/pattern-layout/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/pattern-layout/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/pattern-layout/src/components/util.js b/limestone/pattern-layout/src/components/util.js
new file mode 100644
index 000000000..a5b89eb93
--- /dev/null
+++ b/limestone/pattern-layout/src/components/util.js
@@ -0,0 +1,23 @@
+//
+// Utility Functions
+//
+
+import qs from 'query-string';
+
+/*
+ * Take an object, prune out the null/undefined values, and save that to the QUERY_STRING in the URL
+ */
+const saveObjToQueryString = (obj) => {
+ const params = qs.parse(window.location.search);
+ const allParams = Object.assign(params, obj); // Merge objects, preferring values in `obj`
+
+ // Remove null and unassigned params
+ Object.keys(allParams).forEach((p) => (allParams[p] == null) && delete allParams[p]);
+
+ const stringified = qs.stringify(allParams);
+ window.history.pushState(obj, '', (stringified ? `?${stringified}` : ''));
+};
+
+export {
+ saveObjToQueryString
+};
diff --git a/limestone/pattern-layout/src/index.js b/limestone/pattern-layout/src/index.js
new file mode 100644
index 000000000..2f28c6b50
--- /dev/null
+++ b/limestone/pattern-layout/src/index.js
@@ -0,0 +1,52 @@
+/* eslint-disable react/jsx-no-bind */
+/* global ENACT_PACK_ISOMORPHIC */
+import qs from 'query-string';
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './App';
+import {saveObjToQueryString} from './components/util';
+
+let appElement = ;
+
+// In a browser environment, render instead of exporting
+if (typeof window !== 'undefined') {
+ // On initial load, we'll read the query-string and set up the default values
+ const args = qs.parse(window.location.search);
+ const debug = (args.debug === 'true');
+ const index = parseInt(args.index || 0);
+ const itemIndex = parseInt(args.itemIndex || 0);
+
+ const handleChangePanel = (ev) => {
+ // Clone the ev object so we don't taint other callbacks
+ const newParams = Object.assign({}, ev);
+
+ // Remove `index` if it's set to `0`, null, or undefined. We can simply not have it, since 0 is the default.
+ if (!newParams.index) {
+ newParams.index = null;
+ }
+ saveObjToQueryString(newParams);
+ };
+
+ const handleChangeDebug = (ev) => {
+ // Clone the ev object so we don't taint other callbacks
+ const newParams = Object.assign({}, ev);
+ if (!newParams.debug) {
+ newParams.debug = null;
+ }
+ saveObjToQueryString(newParams);
+ };
+
+ appElement = (
+
+ );
+
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/pattern-layout/src/views/Details.js b/limestone/pattern-layout/src/views/Details.js
new file mode 100644
index 000000000..ea30c8596
--- /dev/null
+++ b/limestone/pattern-layout/src/views/Details.js
@@ -0,0 +1,79 @@
+import kind from '@enact/core/kind';
+import BodyText from '@enact/limestone/BodyText';
+import Button from '@enact/limestone/Button';
+import Image from '@enact/limestone/Image';
+import Item from '@enact/limestone/Item';
+import Marquee from '@enact/limestone/Marquee';
+import {Header, Panel} from '@enact/limestone/Panels';
+import {Cell, Column, Layout, Row} from '@enact/ui/Layout';
+import ri from '@enact/ui/resolution';
+import Toggleable from '@enact/ui/Toggleable';
+import PropTypes from 'prop-types';
+
+const FieldRow = kind({
+ name: 'FieldRow',
+ propTypes: {
+ label: PropTypes.string
+ },
+ render: ({label, ...rest}) => (
+
+
+ {label}
+
+
+
+ )
+});
+
+const Details = kind({
+ name: 'Details',
+
+ propTypes: {
+ changeOrientation: PropTypes.func,
+ DebugButton: PropTypes.object,
+ orientation: PropTypes.bool,
+
+ /**
+ * A title string appear on header
+ * @type {String}
+ */
+ title: PropTypes.string,
+ titleBelow: PropTypes.string
+ },
+
+ render: ({DebugButton, changeOrientation, orientation, title, titleBelow, ...rest}) => (
+
+
+
+ {DebugButton}
+
+
+
+
+
+ The alumni cast of a space opera television series have to play their roles as the real thing when an alien race needs their help. However, they also have to defend both Earth and the alien race from a reptilian warlord.
+
+
+
+
+
+
+
+ By Grabthar's hammer, by the sons of Warvan, you shall be avenged.
+
+ Dean Parisot
+ David Howard (story), David Howard (screenplay)
+ Tim Allen, Sigourney Weaver, Alan Rickman
+
+
+
+
+
+
+ )
+});
+
+export default Toggleable({prop: 'orientation', toggle: 'changeOrientation'}, Details);
+
+// Text content lovingly borrowed from IMDB:
+// http://www.imdb.com/title/tt0177789/
diff --git a/limestone/pattern-layout/src/views/FavoritesList.js b/limestone/pattern-layout/src/views/FavoritesList.js
new file mode 100644
index 000000000..b1c85fea3
--- /dev/null
+++ b/limestone/pattern-layout/src/views/FavoritesList.js
@@ -0,0 +1,86 @@
+import kind from '@enact/core/kind';
+import Button from '@enact/limestone/Button';
+import Heading from '@enact/limestone/Heading';
+import Item from '@enact/limestone/Item';
+import {Header, Panel} from '@enact/limestone/Panels';
+import RadioItem from '@enact/limestone/RadioItem';
+import VirtualList from '@enact/limestone/VirtualList';
+import {SpotlightContainerDecorator} from '@enact/spotlight/SpotlightContainerDecorator';
+import {Cell, Column, Row} from '@enact/ui/Layout';
+import ri from '@enact/ui/resolution';
+import PropTypes from 'prop-types';
+
+const SpottableContainer = SpotlightContainerDecorator({enterTo: 'last-focused'}, 'div');
+
+const items = Array(80).fill().map((_, i) => 'Item ' + (i + 1));
+
+// eslint-disable-next-line enact/display-name, enact/prop-types
+const renderItem = () => ({index, ...rest}) => (
+
+ {items[index]}
+
+);
+
+const ItemPanel = kind({
+ name: 'ItemPanel',
+
+ propTypes: {
+ DebugButton: PropTypes.object,
+
+ /**
+ * A title string appear on header
+ * @type {String}
+ */
+ title: PropTypes.string,
+ titleBelow: PropTypes.string
+ },
+
+ computed: {
+ itemRenderer: renderItem
+ },
+
+ render: ({title, titleBelow, itemRenderer, DebugButton, ...rest}) => (
+
+
+
+ {DebugButton}
+
+
+
+
+ Photo Items
+ Video Items
+ Audio Items
+
+
+
+
+ All Items
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Selected Items
+ Item 1
+ Item 2
+
+
+
+ )
+});
+
+export default ItemPanel;
diff --git a/limestone/pattern-layout/src/views/MainPanel.js b/limestone/pattern-layout/src/views/MainPanel.js
new file mode 100644
index 000000000..53bb99143
--- /dev/null
+++ b/limestone/pattern-layout/src/views/MainPanel.js
@@ -0,0 +1,93 @@
+import kind from '@enact/core/kind';
+import ImageItem from '@enact/limestone/ImageItem';
+import {Header, Panel} from '@enact/limestone/Panels';
+import {VirtualGridList} from '@enact/limestone/VirtualList';
+import ri from '@enact/ui/resolution';
+import PropTypes from 'prop-types';
+
+const GridItem = kind({
+ name: 'GridItem',
+ propTypes: {
+ index: PropTypes.number,
+ items: PropTypes.array,
+ onSelect: PropTypes.func
+ },
+ handlers: {
+ onSelect: (ev, {index, onSelect}) => onSelect({index})
+ },
+ render: ({index, items, onSelect, ...rest}) => {
+ if (items && items[index]) {
+ return (
+
+ {items[index].title}
+
+ );
+ }
+ }
+});
+
+// eslint-disable-next-line enact/display-name, enact/prop-types
+const renderItem = ({items, onChangePanel}) => ({index, ...rest}) => {
+ if (items && items[index]) {
+ return (
+
+ );
+ }
+};
+
+const MainPanel = kind({
+ name: 'MainPanel',
+
+ propTypes: {
+ /**
+ * A collection of all of the items to be rendered by this VirtualGridList
+ * @type {Array}
+ */
+ items: PropTypes.array,
+
+ /**
+ * A function to run when the panel changes
+ * @type {Function}
+ */
+ onChangePanel: PropTypes.func
+ },
+
+ computed: {
+ itemRenderer: renderItem
+ },
+
+ render: ({items, itemRenderer, ...rest}) => {
+ delete rest.onChangePanel;
+ return (
+
+
+ Example Layouts
+ Choose a layout
+
+
+
+
+ );
+ }
+});
+
+export default MainPanel;
diff --git a/limestone/pattern-layout/src/views/README.md b/limestone/pattern-layout/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/pattern-layout/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/pattern-layout/webos-meta/appinfo.json b/limestone/pattern-layout/webos-meta/appinfo.json
new file mode 100644
index 000000000..bbfa6fcc9
--- /dev/null
+++ b/limestone/pattern-layout/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "pattern-layout",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Layout Pattern",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/pattern-layout/webos-meta/icon-large.png b/limestone/pattern-layout/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/pattern-layout/webos-meta/icon-large.png differ
diff --git a/limestone/pattern-layout/webos-meta/icon-mini.png b/limestone/pattern-layout/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/pattern-layout/webos-meta/icon-mini.png differ
diff --git a/limestone/pattern-layout/webos-meta/icon.png b/limestone/pattern-layout/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/pattern-layout/webos-meta/icon.png differ
diff --git a/limestone/pattern-locale-switching/.gitignore b/limestone/pattern-locale-switching/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/pattern-locale-switching/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-locale-switching/LICENSE b/limestone/pattern-locale-switching/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/pattern-locale-switching/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/pattern-locale-switching/README.md b/limestone/pattern-locale-switching/README.md
new file mode 100644
index 000000000..e6970674c
--- /dev/null
+++ b/limestone/pattern-locale-switching/README.md
@@ -0,0 +1,7 @@
+# Locale Switching Sample
+
+This sample is a very simple sample with 3 components (one `Input` and two
+`Button`s). This sample will demonstrate how to update the locale of an app
+using either `context` or `redux`.
+
+> NOTE: An app should adopt one of these two approaches, not both.
diff --git a/limestone/pattern-locale-switching/package.json b/limestone/pattern-locale-switching/package.json
new file mode 100644
index 000000000..5fb978bc4
--- /dev/null
+++ b/limestone/pattern-locale-switching/package.json
@@ -0,0 +1,49 @@
+{
+ "name": "pattern-locale-switching",
+ "version": "1.0.0",
+ "description": "An Enact application demonstrating Locale switching.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "Locale Switching Pattern",
+ "isomorphic": true,
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "@enact/webos": "^5.0.0-alpha.4",
+ "@reduxjs/toolkit": "^2.3.0",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-redux": "^9.1.2",
+ "redux": "^5.0.1"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-locale-switching/resources/ilibmanifest.json b/limestone/pattern-locale-switching/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/pattern-locale-switching/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-locale-switching/src/App/App.js b/limestone/pattern-locale-switching/src/App/App.js
new file mode 100644
index 000000000..8b46ec19e
--- /dev/null
+++ b/limestone/pattern-locale-switching/src/App/App.js
@@ -0,0 +1,36 @@
+import kind from '@enact/core/kind';
+import I18nDecorator from '@enact/i18n/I18nDecorator';
+import {Panels} from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+import {connect} from 'react-redux';
+
+import MainPanel from '../views/MainPanel';
+
+import css from './App.module.less';
+
+const Sample = kind({
+ name: 'App',
+
+ styles: {
+ css,
+ className: 'app'
+ },
+
+ render: (props) => {
+ return (
+
+ );
+};
+
+LocaleSwitchBase.propTypes = {
+ rtl: PropTypes.bool,
+ updateLocale: PropTypes.func
+};
+
+const LocaleSwitch = I18nContextDecorator(
+ {updateLocaleProp: 'updateLocale', 'rtlProp': 'rtl'},
+ LocaleSwitchBase
+);
+
+export default LocaleSwitch;
diff --git a/limestone/pattern-locale-switching/src/components/LocaleSwitch/package.json b/limestone/pattern-locale-switching/src/components/LocaleSwitch/package.json
new file mode 100644
index 000000000..137c6e255
--- /dev/null
+++ b/limestone/pattern-locale-switching/src/components/LocaleSwitch/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "LocaleSwitch.js"
+}
diff --git a/limestone/pattern-locale-switching/src/components/README.md b/limestone/pattern-locale-switching/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/pattern-locale-switching/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/pattern-locale-switching/src/index.js b/limestone/pattern-locale-switching/src/index.js
new file mode 100644
index 000000000..164498fb9
--- /dev/null
+++ b/limestone/pattern-locale-switching/src/index.js
@@ -0,0 +1,19 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './main';
+
+let appElement = ;
+
+// In a browser environment, render instead of exporting
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/pattern-locale-switching/src/main.js b/limestone/pattern-locale-switching/src/main.js
new file mode 100644
index 000000000..0a6be63f5
--- /dev/null
+++ b/limestone/pattern-locale-switching/src/main.js
@@ -0,0 +1,19 @@
+import {Provider} from 'react-redux';
+
+import App, {AppBase} from './App';
+import store from './store';
+
+let appElementBase = () => (
+
+
+
+);
+
+let appElement = () => (
+
+
+
+);
+
+export default appElement;
+export {appElement, appElementBase};
diff --git a/limestone/pattern-locale-switching/src/store/index.js b/limestone/pattern-locale-switching/src/store/index.js
new file mode 100644
index 000000000..1360cbaa3
--- /dev/null
+++ b/limestone/pattern-locale-switching/src/store/index.js
@@ -0,0 +1,20 @@
+import {configureStore, createSlice} from '@reduxjs/toolkit';
+
+const localeSlice = createSlice({
+ name: 'localeReducer',
+ initialState: {
+ locale : 'en-US'
+ },
+ reducers: {
+ updateLocale: (state, action) => {
+ state.locale = action.payload;
+ }
+ }
+});
+
+const store = configureStore({
+ reducer: localeSlice.reducer
+});
+
+export const {updateLocale} = localeSlice.actions;
+export default store;
diff --git a/limestone/pattern-locale-switching/src/views/MainPanel.js b/limestone/pattern-locale-switching/src/views/MainPanel.js
new file mode 100644
index 000000000..50a36f970
--- /dev/null
+++ b/limestone/pattern-locale-switching/src/views/MainPanel.js
@@ -0,0 +1,17 @@
+import kind from '@enact/core/kind';
+import {Header, Panel} from '@enact/limestone/Panels';
+
+import LocaleSwitch from '../components/LocaleSwitch';
+
+const MainPanel = kind({
+ name: 'MainPanel',
+
+ render: (props) => (
+
+
+
+
+ )
+});
+
+export default MainPanel;
diff --git a/limestone/pattern-locale-switching/src/views/README.md b/limestone/pattern-locale-switching/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/pattern-locale-switching/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/pattern-locale-switching/webos-meta/appinfo.json b/limestone/pattern-locale-switching/webos-meta/appinfo.json
new file mode 100644
index 000000000..717f6e377
--- /dev/null
+++ b/limestone/pattern-locale-switching/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.pattern-locale-switching",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Locale Switching Pattern",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/pattern-locale-switching/webos-meta/icon-large.png b/limestone/pattern-locale-switching/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/pattern-locale-switching/webos-meta/icon-large.png differ
diff --git a/limestone/pattern-locale-switching/webos-meta/icon-mini.png b/limestone/pattern-locale-switching/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/pattern-locale-switching/webos-meta/icon-mini.png differ
diff --git a/limestone/pattern-locale-switching/webos-meta/icon.png b/limestone/pattern-locale-switching/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/pattern-locale-switching/webos-meta/icon.png differ
diff --git a/limestone/pattern-ls2request-camera/.gitignore b/limestone/pattern-ls2request-camera/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/pattern-ls2request-camera/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-ls2request-camera/LICENSE b/limestone/pattern-ls2request-camera/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/pattern-ls2request-camera/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/pattern-ls2request-camera/README.md b/limestone/pattern-ls2request-camera/README.md
new file mode 100644
index 000000000..ac0ea2c36
--- /dev/null
+++ b/limestone/pattern-ls2request-camera/README.md
@@ -0,0 +1,32 @@
+## LS2Request Camera pattern
+
+A sample Enact application that shows off how to use camera API by LS2Request
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can access it from a webOS device. To create an installable application, use `npm run pack` and then use the packaging tools to package the **dist** folder.
+
+#### Enact Components Used
+- `limestone/Button`
+- `limestone/Panels/Header`
+- `limestone/Panels/Panel`
+- `webos/LS2Request`
+
+Most webOS apps require interaction with LS2Request. In this sample, we show how to use LS2Request with Redux.
+
+To use camera API in your web app, you need to set permissions for methods.
+First, you need to identify the ACG information for the camera methods.
+Then specify the method's ACG information in the `appinfo.json` file.
+
+appinfo.json
+```json
+{
+ ...
+ "requiredPermissions": ["camera.query", "camera.operation"]
+ ...
+}
+```
+
+https://www.webosose.org/docs/guides/development/web-apps/using-ls2-api-in-web-apps/#identify-the-acg-group-of-the-methods
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/pattern-ls2request-camera/package.json b/limestone/pattern-ls2request-camera/package.json
new file mode 100644
index 000000000..0403c0aa0
--- /dev/null
+++ b/limestone/pattern-ls2request-camera/package.json
@@ -0,0 +1,49 @@
+{
+ "name": "pattern-ls2request-camera",
+ "version": "1.0.0",
+ "description": "An Enact application demonstrating camera API using ls2request.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "LS2Request Camera Pattern",
+ "isomorphic": true,
+ "ri": {
+ "baseSize": 24
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "@enact/webos": "^5.0.0-alpha.4",
+ "@reduxjs/toolkit": "^2.3.0",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-redux": "^9.1.2",
+ "redux": "^5.0.1"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-ls2request-camera/resources/ilibmanifest.json b/limestone/pattern-ls2request-camera/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/pattern-ls2request-camera/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-ls2request-camera/src/App/App.js b/limestone/pattern-ls2request-camera/src/App/App.js
new file mode 100644
index 000000000..ea6326e3d
--- /dev/null
+++ b/limestone/pattern-ls2request-camera/src/App/App.js
@@ -0,0 +1,14 @@
+import {Panels} from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+
+import MainPanel from '../views/MainPanel';
+
+const App = (props) => {
+ return (
+
+
+
+ );
+};
+
+export default ThemeDecorator(App);
diff --git a/limestone/pattern-ls2request-camera/src/App/package.json b/limestone/pattern-ls2request-camera/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/pattern-ls2request-camera/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/pattern-ls2request-camera/src/components/CameraView.js b/limestone/pattern-ls2request-camera/src/components/CameraView.js
new file mode 100644
index 000000000..c6ec312c6
--- /dev/null
+++ b/limestone/pattern-ls2request-camera/src/components/CameraView.js
@@ -0,0 +1,86 @@
+import Button from '@enact/limestone/Button';
+import Heading from '@enact/limestone/Heading';
+import Item from '@enact/limestone/Item';
+import VirtualList from '@enact/limestone/VirtualList';
+import {Column} from '@enact/ui/Layout';
+import ri from '@enact/ui/resolution';
+import {useEffect, useRef} from 'react';
+import {useDispatch, useSelector} from 'react-redux';
+
+import {closeCamera, getCameraIds, startCamera} from '../store';
+
+const CameraView = () => {
+ const videoRef = useRef(null);
+ const dispatch = useDispatch();
+ const cameraIds = useSelector((store) => store.cameraIds);
+ const cameraStatus = useSelector((store) => store.cameraStatus);
+
+ let cameraOption;
+
+ useEffect(() => {
+ if (typeof window === 'object' && typeof (window.webOSSystem ?? window.PalmSystem) === 'object') {
+ dispatch(getCameraIds({}));
+ }
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
+
+ useEffect(() => {
+ if (videoRef.current) {
+ videoRef.current.load();
+ }
+ }, [cameraStatus]);
+
+ const checkSystem = () => {
+ if (typeof window !== 'object' || typeof (window.webOSSystem ?? window.PalmSystem) !== 'object') {
+ return
This test will only function correctly on webOS systems!
+
+ If you click `Batched update!` the value will go from {value} to {value === 0 ? 1000 : 0}!
+ The current value is {value}!
+ There have been {renders} re-renders!
+
+
+
+
+ );
+ }
+});
+
+export default BatchedAssign;
+
diff --git a/limestone/pattern-react18-new/src/views/Batching/Batching.js b/limestone/pattern-react18-new/src/views/Batching/Batching.js
new file mode 100644
index 000000000..a2f10b041
--- /dev/null
+++ b/limestone/pattern-react18-new/src/views/Batching/Batching.js
@@ -0,0 +1,35 @@
+import kind from '@enact/core/kind';
+import BodyText from '@enact/limestone/BodyText';
+import {Panel} from '@enact/limestone/Panels';
+import {Cell, Row} from '@enact/ui/Layout';
+
+import BatchedAssign from './BatchedAssign';
+import NotBatchedAssign from './NotBatchedAssign';
+
+const Batching = kind({
+ name: 'Batching',
+
+ render: (props) => (
+
+
+
+
+ Batching is when React groups multiple state updates into a single re-render for better performance.
+
+
+ In this example we have a fake api call and in it`s .then() method a For Loop that will call a state
+ updating function a 1000 times incrementing or decrementing the value by 1 each time.
+
+
+
+
+
+
+
+
+
+
+ )
+});
+
+export default Batching;
diff --git a/limestone/pattern-react18-new/src/views/Batching/NotBatchedAssign.js b/limestone/pattern-react18-new/src/views/Batching/NotBatchedAssign.js
new file mode 100644
index 000000000..1107193a0
--- /dev/null
+++ b/limestone/pattern-react18-new/src/views/Batching/NotBatchedAssign.js
@@ -0,0 +1,60 @@
+/* eslint-disable react-hooks/rules-of-hooks, react/jsx-no-bind */
+
+import kind from '@enact/core/kind';
+import BodyText from '@enact/limestone/BodyText';
+import Button from '@enact/limestone/Button';
+import {Column} from '@enact/ui/Layout';
+import {useEffect, useState} from 'react';
+import {flushSync} from 'react-dom';
+
+const NotBatchedAssign = kind({
+ name: 'NotBatchedAssign',
+
+ functional: true,
+
+ render: () => {
+ const [renders, setRenders] = useState(-1);
+ const [value, setValue] = useState(0);
+
+ useEffect(() => {
+ setRenders(r => r + 1);
+ }, [value]);
+
+ const fakeApiCall = async () => {
+ setTimeout(() => {}, 300);
+ };
+
+ const assignFunctionNotBatched = () => {
+ fakeApiCall().then(() => {
+ if (value === 0) {
+ for (let i = 0; i < 1000; ++i) {
+ flushSync(() => {
+ setValue(i + 1);
+ });
+ }
+ } else {
+ for (let i = 1000; i >= 0; --i) {
+ flushSync(() => {
+ setValue(i);
+ });
+ }
+ }
+ });
+ };
+
+ return (
+
+
+ If you click `Not Batched update!` the value will go from {value} to {value === 0 ? 1000 : 0}!
+ The current value is {value}!
+ There have been {renders} re-renders!
+
+
+
+
+
+ With React18's 'useTransition' hook, the previous state of the UI can be held until the data is ready. The fetching of the new data is wrapped inside 'startTransition' while the 'isPending' data tells if the content is currently being loaded or not. This allows the possibility to show a loading indicator. Its 'timeoutMs' property specifies how long we’re willing to wait for the transition to finish.
+
+
+
+ Until React 17, when needing to fetch data before showing some UI that depends on that data, a loading state would have had to manually be set in its place.
+
+ When one of the buttons is clicked, the 'Content' component fetches some data. While that data is fetched, a 'Spinner' is loaded in the content's place.
+
+
+
+ );
+};
+
+export default UseTransition;
diff --git a/limestone/pattern-react18-new/src/views/UseTransition/UseTransition.module.less b/limestone/pattern-react18-new/src/views/UseTransition/UseTransition.module.less
new file mode 100644
index 000000000..c2e6b8c32
--- /dev/null
+++ b/limestone/pattern-react18-new/src/views/UseTransition/UseTransition.module.less
@@ -0,0 +1,25 @@
+.pendingBlock {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.useTransitionContent {
+ display: inline-block;
+ vertical-align: middle;
+ margin: 9px 36px;
+}
+
+.content {
+ margin: 9px 0;
+}
+
+.guideText {
+ font-size: 39px;
+ font-style: italic;
+ margin-top: 30px;
+}
+
+.demoContainer {
+ height: 300px;
+ background-color: #333333;
+}
diff --git a/limestone/pattern-react18-new/src/views/UseTransition/package.json b/limestone/pattern-react18-new/src/views/UseTransition/package.json
new file mode 100644
index 000000000..d23c9130c
--- /dev/null
+++ b/limestone/pattern-react18-new/src/views/UseTransition/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "UseTransition.js"
+}
diff --git a/limestone/pattern-routable-panels/.gitignore b/limestone/pattern-routable-panels/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/pattern-routable-panels/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-routable-panels/LICENSE b/limestone/pattern-routable-panels/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/pattern-routable-panels/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/pattern-routable-panels/README.md b/limestone/pattern-routable-panels/README.md
new file mode 100644
index 000000000..d3c48a617
--- /dev/null
+++ b/limestone/pattern-routable-panels/README.md
@@ -0,0 +1,35 @@
+## Routable panel pattern
+
+A sample Enact application that shows off how to use Routable, Routes and Panels
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+#### Enact Components Used
+- `limestone/BodyText`
+- `limestone/Button`
+- `limestone/Panels/Panel`
+- `limestone/Panels/Routable` (Higher Order Component)
+- `limestone/Panels/Route`
+- `limestone/Scroller`
+- `ui/ViewManager/SlideLeftArranger`
+
+Using `Routes` and `Routable` you will have a very simple, declarative way to view and navigate through your panels. Just set up your routes and `RoutablePanels` will take care of the history logic.
+
+Here's what your panels' JSX will end up looking like:
+
+```
+
+
+
+
+
+
+
+
+```
+
+You can find a more detailed view inside of [App.js](src/App/App.js)
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/pattern-routable-panels/package.json b/limestone/pattern-routable-panels/package.json
new file mode 100644
index 000000000..8d8c29084
--- /dev/null
+++ b/limestone/pattern-routable-panels/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "pattern-routable-panels",
+ "version": "1.0.0",
+ "description": "An Enact application demonstrating RoutablePanels with Redux.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "Routable Panels Pattern",
+ "isomorphic": true,
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "@reduxjs/toolkit": "^2.3.0",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-redux": "^9.1.2",
+ "redux": "^5.0.1"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-routable-panels/resources/ilibmanifest.json b/limestone/pattern-routable-panels/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/pattern-routable-panels/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-routable-panels/src/App/App.js b/limestone/pattern-routable-panels/src/App/App.js
new file mode 100644
index 000000000..fc525f8f4
--- /dev/null
+++ b/limestone/pattern-routable-panels/src/App/App.js
@@ -0,0 +1,47 @@
+import kind from '@enact/core/kind';
+import {Panels, Routable, Route} from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+import {SlideLeftArranger} from '@enact/ui/ViewManager';
+import PropTypes from 'prop-types';
+
+import AboutPanel from '../views/AboutPanel';
+import MainPanel from '../views/MainPanel';
+
+import AppStateDecorator from './AppStateDecorator';
+
+const RoutablePanels = Routable({navigate: 'onBack'}, Panels);
+
+const Sample = kind({
+ name: 'App',
+
+ propTypes: {
+ onNavigate: PropTypes.func,
+ path: PropTypes.string
+ },
+
+ handlers: {
+ onFirstPanel: (ev, {onNavigate}) => onNavigate({path: '/first'}),
+ onSecondPanel: (ev, {onNavigate}) => onNavigate({path: '/first/second'}),
+ onThirdPanel: (ev, {onNavigate}) => onNavigate({path: '/first/third'}),
+ onFourthPanel: (ev, {onNavigate}) => onNavigate({path: '/first/third/fourth'})
+ },
+
+ render: ({onFirstPanel, onFourthPanel, onNavigate, onSecondPanel, onThirdPanel, path, ...rest}) => {
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
+});
+
+const AppBase = AppStateDecorator(Sample);
+const App = ThemeDecorator(AppBase);
+
+export default App;
+export {App, AppBase};
diff --git a/limestone/pattern-routable-panels/src/App/App.module.less b/limestone/pattern-routable-panels/src/App/App.module.less
new file mode 100644
index 000000000..23782defd
--- /dev/null
+++ b/limestone/pattern-routable-panels/src/App/App.module.less
@@ -0,0 +1,3 @@
+.app {
+ // styles can be put here
+}
diff --git a/limestone/pattern-routable-panels/src/App/AppStateDecorator.js b/limestone/pattern-routable-panels/src/App/AppStateDecorator.js
new file mode 100644
index 000000000..67ad38cf8
--- /dev/null
+++ b/limestone/pattern-routable-panels/src/App/AppStateDecorator.js
@@ -0,0 +1,18 @@
+import {connect} from 'react-redux';
+
+import {navigate} from '../store';
+
+const mapStateToProps = ({path}) => ({
+ path
+});
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onNavigate: ({path}) => dispatch(navigate(path))
+ };
+};
+
+const AppStateDecorator = connect(mapStateToProps, mapDispatchToProps);
+
+export default AppStateDecorator;
+export {AppStateDecorator};
diff --git a/limestone/pattern-routable-panels/src/App/package.json b/limestone/pattern-routable-panels/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/pattern-routable-panels/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/pattern-routable-panels/src/components/README.md b/limestone/pattern-routable-panels/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/pattern-routable-panels/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/pattern-routable-panels/src/index.js b/limestone/pattern-routable-panels/src/index.js
new file mode 100644
index 000000000..164498fb9
--- /dev/null
+++ b/limestone/pattern-routable-panels/src/index.js
@@ -0,0 +1,19 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './main';
+
+let appElement = ;
+
+// In a browser environment, render instead of exporting
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/pattern-routable-panels/src/main.js b/limestone/pattern-routable-panels/src/main.js
new file mode 100644
index 000000000..93f46c26b
--- /dev/null
+++ b/limestone/pattern-routable-panels/src/main.js
@@ -0,0 +1,22 @@
+import {Provider} from 'react-redux';
+
+import App, {AppBase} from './App';
+import configureAppStore from './store';
+
+// set default launch path
+const store = configureAppStore();
+
+let appElementBase = () => (
+
+
+
+);
+
+let appElement = () => (
+
+
+
+);
+
+export default appElement;
+export {appElement, appElementBase};
diff --git a/limestone/pattern-routable-panels/src/store/index.js b/limestone/pattern-routable-panels/src/store/index.js
new file mode 100644
index 000000000..46c415151
--- /dev/null
+++ b/limestone/pattern-routable-panels/src/store/index.js
@@ -0,0 +1,30 @@
+import {configureStore, createSlice} from '@reduxjs/toolkit';
+
+const naviSlice = createSlice({
+ name: 'naviReducer',
+ initialState: {
+ path : '/first'
+ },
+ reducers: {
+ navigate: (state, action) => {
+ state.path = action.payload;
+ }
+ }
+});
+
+export const {navigate} = naviSlice.actions;
+export default function configureAppStore (initialState) {
+ const store = configureStore({
+ reducer: naviSlice.reducer,
+ initialState
+ });
+
+ if (module.hot) {
+ // Enable Webpack hot module replacement for reducers
+ module.hot.accept('./index.js', () => {
+ store.replaceReducer(naviSlice.reducer);
+ });
+ }
+
+ return store;
+}
diff --git a/limestone/pattern-routable-panels/src/views/AboutPanel.js b/limestone/pattern-routable-panels/src/views/AboutPanel.js
new file mode 100644
index 000000000..506a43992
--- /dev/null
+++ b/limestone/pattern-routable-panels/src/views/AboutPanel.js
@@ -0,0 +1,59 @@
+import kind from '@enact/core/kind';
+import BodyText from '@enact/limestone/BodyText';
+import Button from '@enact/limestone/Button';
+import {Header, Panel} from '@enact/limestone/Panels';
+import Scroller from '@enact/limestone/Scroller';
+import PropTypes from 'prop-types';
+
+const example =
+`
+
+
+
+
+
+
+`;
+
+const AboutPanel = kind({
+ name: 'AboutPanel',
+
+ propTypes: {
+ onClick: PropTypes.func,
+ title: PropTypes.string
+ },
+
+ render: ({title, onClick, ...rest}) => (
+
+
+
+
+
+
+
+ This pattern illustrates the use of the Routable HOC to navigate a
+ hierarchal tree of Panels.
+
+
+
+ A Routable panels accepts Routes as children which
+ themselves can contain child Routes. Each Route must have
+ a path property indicating its name within the subtree and a
+ component property indicating the component to render when that path is
+ active. Any additional props will be passed onto the component.
+
+
+
+ Instead of setting the index of the active panel, you set
+ the path (e.g. '/first/second') and Routable
+ derives the correct index. When using breadcrumbs or the back/ESC key, the user is
+ routed back up the path until it reaches the top-most panel.
+
+
+
{example}
+
+
+ )
+});
+
+export default AboutPanel;
diff --git a/limestone/pattern-routable-panels/src/views/MainPanel.js b/limestone/pattern-routable-panels/src/views/MainPanel.js
new file mode 100644
index 000000000..7244fb8ab
--- /dev/null
+++ b/limestone/pattern-routable-panels/src/views/MainPanel.js
@@ -0,0 +1,33 @@
+import kind from '@enact/core/kind';
+import Button from '@enact/limestone/Button';
+import {Header, Panel} from '@enact/limestone/Panels';
+import PropTypes from 'prop-types';
+
+import RouteTree from './RouteTree';
+
+const MainPanel = kind({
+ name: 'MainPanel',
+
+ propTypes: {
+ next: PropTypes.string,
+ onClick: PropTypes.func,
+ title: PropTypes.string
+ },
+
+ computed: {
+ text: ({next}) => `To ${next} Panel`
+ },
+
+ render: ({title, onClick, text, ...rest}) => {
+ delete rest.next;
+ return (
+
+
+
+
+
+ );
+ }
+});
+
+export default MainPanel;
diff --git a/limestone/pattern-routable-panels/src/views/README.md b/limestone/pattern-routable-panels/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/pattern-routable-panels/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/pattern-routable-panels/src/views/RouteTree.js b/limestone/pattern-routable-panels/src/views/RouteTree.js
new file mode 100644
index 000000000..17940df89
--- /dev/null
+++ b/limestone/pattern-routable-panels/src/views/RouteTree.js
@@ -0,0 +1,16 @@
+const tree = ` First
+ | \\
+Second Third
+ |
+ Fourth
+
+`;
+
+const RouteTree = () => (
+
+
Route Tree
+
{tree}
+
+);
+
+export default RouteTree;
diff --git a/limestone/pattern-routable-panels/webos-meta/appinfo.json b/limestone/pattern-routable-panels/webos-meta/appinfo.json
new file mode 100644
index 000000000..58c7a375c
--- /dev/null
+++ b/limestone/pattern-routable-panels/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.pattern-routable-panels",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Routable Panels Pattern",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/pattern-routable-panels/webos-meta/icon-large.png b/limestone/pattern-routable-panels/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/pattern-routable-panels/webos-meta/icon-large.png differ
diff --git a/limestone/pattern-routable-panels/webos-meta/icon-mini.png b/limestone/pattern-routable-panels/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/pattern-routable-panels/webos-meta/icon-mini.png differ
diff --git a/limestone/pattern-routable-panels/webos-meta/icon.png b/limestone/pattern-routable-panels/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/pattern-routable-panels/webos-meta/icon.png differ
diff --git a/limestone/pattern-single-panel-redux/.gitignore b/limestone/pattern-single-panel-redux/.gitignore
new file mode 100644
index 000000000..f94ea516b
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-single-panel-redux/LICENSE b/limestone/pattern-single-panel-redux/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/pattern-single-panel-redux/README.md b/limestone/pattern-single-panel-redux/README.md
new file mode 100644
index 000000000..e46faeb02
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/README.md
@@ -0,0 +1,31 @@
+## Single panel pattern // Profile Photo Picker with Redux
+
+A sample single panel Enact application where you can pick a profile photo from a given number of photos and adjust the photo size. State is managed with Redux.
+
+This is a more scalable version of the Profile Photo Picker. It is hooked up with [Redux](http://redux.js.org/), using the presentational/container component pattern as laid out by Dan Abramov here: [Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.sidi8whzp).
+
+Run `npm install` then
+`npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+#### Enact Limestone Components used
+- `limestone/Button`
+- `limestone/Image`
+- `limestone/Picker`
+- `limestone/Popup`
+- `limestone/Slider`
+
+#### Running Tests
+
+The sample includes examples on how to use unit tests with Enact. To execute the tests, issue the following command:
+
+```bash
+npm run test
+```
+
+### Photo credits
+
+All photos are found in [Pexels.com](https://www.pexels.com) under Public Domain CC0 license.
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/pattern-single-panel-redux/assets/images/car.jpeg b/limestone/pattern-single-panel-redux/assets/images/car.jpeg
new file mode 100644
index 000000000..a8fa4a973
Binary files /dev/null and b/limestone/pattern-single-panel-redux/assets/images/car.jpeg differ
diff --git a/limestone/pattern-single-panel-redux/assets/images/city.jpeg b/limestone/pattern-single-panel-redux/assets/images/city.jpeg
new file mode 100644
index 000000000..22191b606
Binary files /dev/null and b/limestone/pattern-single-panel-redux/assets/images/city.jpeg differ
diff --git a/limestone/pattern-single-panel-redux/assets/images/mural.jpeg b/limestone/pattern-single-panel-redux/assets/images/mural.jpeg
new file mode 100644
index 000000000..205b74231
Binary files /dev/null and b/limestone/pattern-single-panel-redux/assets/images/mural.jpeg differ
diff --git a/limestone/pattern-single-panel-redux/assets/images/space-shuttle.jpg b/limestone/pattern-single-panel-redux/assets/images/space-shuttle.jpg
new file mode 100644
index 000000000..e0cc3f172
Binary files /dev/null and b/limestone/pattern-single-panel-redux/assets/images/space-shuttle.jpg differ
diff --git a/limestone/pattern-single-panel-redux/assets/images/violin.jpeg b/limestone/pattern-single-panel-redux/assets/images/violin.jpeg
new file mode 100644
index 000000000..0f2c9b1d6
Binary files /dev/null and b/limestone/pattern-single-panel-redux/assets/images/violin.jpeg differ
diff --git a/limestone/pattern-single-panel-redux/package.json b/limestone/pattern-single-panel-redux/package.json
new file mode 100644
index 000000000..04c9feae7
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "pattern-single-panel-redux",
+ "version": "1.0.0",
+ "description": "An Enact application demonstrating SinglePanel with Redux.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "Single Panel Pattern with Redux",
+ "isomorphic": true,
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "4.9.4",
+ "@reduxjs/toolkit": "^2.3.0",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-redux": "^9.1.2",
+ "redux": "^5.0.1"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-single-panel-redux/resources/ilibmanifest.json b/limestone/pattern-single-panel-redux/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-single-panel-redux/src/App/App.js b/limestone/pattern-single-panel-redux/src/App/App.js
new file mode 100644
index 000000000..26b93d5b4
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/App/App.js
@@ -0,0 +1,27 @@
+import kind from '@enact/core/kind';
+import {Panels} from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+
+import MainPanel from '../views/MainPanel';
+
+import css from './App.module.less';
+
+const AppBase = kind({
+ name: 'App',
+
+ styles: {
+ css,
+ className: 'app'
+ },
+
+ render: (props) => (
+
+
+
+ )
+});
+
+const App = ThemeDecorator(AppBase);
+
+export default App;
+export {App, AppBase};
diff --git a/limestone/pattern-single-panel-redux/src/App/App.module.less b/limestone/pattern-single-panel-redux/src/App/App.module.less
new file mode 100644
index 000000000..23782defd
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/App/App.module.less
@@ -0,0 +1,3 @@
+.app {
+ // styles can be put here
+}
diff --git a/limestone/pattern-single-panel-redux/src/App/package.json b/limestone/pattern-single-panel-redux/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/pattern-single-panel-redux/src/components/PhotoPicker.js b/limestone/pattern-single-panel-redux/src/components/PhotoPicker.js
new file mode 100644
index 000000000..1d24ee830
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/components/PhotoPicker.js
@@ -0,0 +1,47 @@
+import kind from '@enact/core/kind';
+import BodyText from '@enact/limestone/BodyText';
+import Image from '@enact/limestone/Image';
+import Picker from '@enact/limestone/Picker';
+import PropTypes from 'prop-types';
+
+const ProfilePhotoPickerContainer = kind({
+ name: 'ProfilePhotoPickerContainer',
+
+ propTypes: {
+ changePhotoIndex: PropTypes.func.isRequired,
+ imageNames: PropTypes.array.isRequired,
+ imageURLs: PropTypes.array.isRequired,
+ photoIndex: PropTypes.number.isRequired
+ },
+
+ handlers: {
+ onChange: (ev, {changePhotoIndex}) => {
+ const index = ev.value;
+ changePhotoIndex(index);
+ }
+ },
+
+ computed: {
+ imageComponents: ({imageURLs}) => {
+ return imageURLs.map((url) => ());
+ }
+ },
+
+ render: ({imageComponents, imageNames, photoIndex, onChange, ...rest}) => {
+ delete rest.changePhotoIndex;
+ delete rest.imageURLs;
+
+ return (
+
+ );
+ }
+});
+
+const mapStateToProps = (state) => {
+ return ({
+ saved: state.saved
+ });
+};
+
+const mapDispatchToProps = (dispatch) => ({
+ saveToState: (saved) => {
+ // Dispatch the change to state.saved
+ dispatch(save(saved));
+
+ // Add other things you want to do when the state.saved is changed
+ }
+});
+
+const FooterContainer = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Footer);
+
+export default FooterContainer;
diff --git a/limestone/pattern-single-panel-redux/src/containers/PhotoPickerContainer.js b/limestone/pattern-single-panel-redux/src/containers/PhotoPickerContainer.js
new file mode 100644
index 000000000..e471a20a9
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/containers/PhotoPickerContainer.js
@@ -0,0 +1,26 @@
+import {connect} from 'react-redux';
+
+import {changePhotoIndex} from '../store';
+import PhotoPicker from '../components/PhotoPicker';
+
+const mapStateToProps = (state) => {
+ return ({
+ photoIndex: state.photoIndex
+ });
+};
+
+const mapDispatchToProps = (dispatch) => ({
+ // Dispatch the change to state.photo
+ changePhotoIndex: (newIndex) => {
+ dispatch(changePhotoIndex(newIndex));
+
+ // Add other things you want to do when the state.photo is changed
+ }
+});
+
+const PhotoPickerContainer = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(PhotoPicker);
+
+export default PhotoPickerContainer;
diff --git a/limestone/pattern-single-panel-redux/src/containers/PhotoPreviewContainer.js b/limestone/pattern-single-panel-redux/src/containers/PhotoPreviewContainer.js
new file mode 100644
index 000000000..6a7a62b99
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/containers/PhotoPreviewContainer.js
@@ -0,0 +1,43 @@
+import kind from '@enact/core/kind';
+import Image from '@enact/limestone/Image';
+import PropTypes from 'prop-types';
+import {connect} from 'react-redux';
+
+import css from './containerStyles.module.less';
+
+const PhotoPreview = kind({
+ name: 'PhotoPreview',
+
+ propTypes: {
+ imageURLs: PropTypes.array.isRequired,
+ photoIndex: PropTypes.number.isRequired,
+ photoPosition: PropTypes.number.isRequired,
+ dispatch: PropTypes.func
+ },
+
+ styles: {
+ css,
+ className: 'profilePhoto'
+ },
+
+ render: ({imageURLs, photoIndex, photoPosition, ...rest}) => {
+ delete rest.dispatch;
+
+ return (
+
+ );
+ }
+});
+
+const mapStateToProps = (state) => {
+ return ({
+ photoIndex: state.photoIndex,
+ photoPosition: state.photoPosition
+ });
+};
+
+const PhotoPreviewContainer = connect(
+ mapStateToProps
+)(PhotoPreview);
+
+export default PhotoPreviewContainer;
diff --git a/limestone/pattern-single-panel-redux/src/containers/PhotoSliderContainer.js b/limestone/pattern-single-panel-redux/src/containers/PhotoSliderContainer.js
new file mode 100644
index 000000000..34f822421
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/containers/PhotoSliderContainer.js
@@ -0,0 +1,27 @@
+import {connect} from 'react-redux';
+
+import {changePhotoPosition} from '../store';
+import PhotoSlider from '../components/PhotoSlider';
+
+const mapStateToProps = (state) => {
+ return ({
+ photoPosition: state.photoPosition
+ });
+};
+
+const mapDispatchToProps = (dispatch) => ({
+ changePhotoPosition: (photoPosition) => {
+ // Dispatch the change to state if photoPosition is not undefined
+ if (photoPosition) {
+ dispatch(changePhotoPosition(photoPosition));
+ }
+ // Add other things you want to do when the state.photo is changed
+ }
+});
+
+const SliderContainer = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(PhotoSlider);
+
+export default SliderContainer;
diff --git a/limestone/pattern-single-panel-redux/src/containers/containerStyles.module.less b/limestone/pattern-single-panel-redux/src/containers/containerStyles.module.less
new file mode 100644
index 000000000..a8e5861d6
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/containers/containerStyles.module.less
@@ -0,0 +1,4 @@
+.profilePhoto {
+ min-height: 300px;
+ background-color: grey;
+}
diff --git a/limestone/pattern-single-panel-redux/src/index.js b/limestone/pattern-single-panel-redux/src/index.js
new file mode 100644
index 000000000..164498fb9
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/index.js
@@ -0,0 +1,19 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './main';
+
+let appElement = ;
+
+// In a browser environment, render instead of exporting
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/pattern-single-panel-redux/src/main.js b/limestone/pattern-single-panel-redux/src/main.js
new file mode 100644
index 000000000..93f46c26b
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/main.js
@@ -0,0 +1,22 @@
+import {Provider} from 'react-redux';
+
+import App, {AppBase} from './App';
+import configureAppStore from './store';
+
+// set default launch path
+const store = configureAppStore();
+
+let appElementBase = () => (
+
+
+
+);
+
+let appElement = () => (
+
+
+
+);
+
+export default appElement;
+export {appElement, appElementBase};
diff --git a/limestone/pattern-single-panel-redux/src/store/package.json b/limestone/pattern-single-panel-redux/src/store/package.json
new file mode 100644
index 000000000..3f33f7033
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/store/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "store.js"
+}
diff --git a/limestone/pattern-single-panel-redux/src/store/store.js b/limestone/pattern-single-panel-redux/src/store/store.js
new file mode 100644
index 000000000..0b1a5fb96
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/store/store.js
@@ -0,0 +1,39 @@
+import {configureStore, createSlice} from '@reduxjs/toolkit';
+
+const photoSlice = createSlice({
+ name: 'photoReducer',
+ initialState: {
+ photoIndex: 0,
+ photoPosition: -50,
+ saved: false
+ },
+ reducers: {
+ changePhotoIndex: (state, action) => {
+ state.photoIndex = action.payload;
+ },
+ changePhotoPosition: (state, action) => {
+ state.photoPosition = action.payload;
+ },
+ save: (state, action) => {
+ state.saved = action.payload;
+ }
+ }
+});
+
+export const {changePhotoIndex, changePhotoPosition, save} = photoSlice.actions;
+
+export default function configureAppStore (initialState) {
+ const store = configureStore({
+ reducer: photoSlice.reducer,
+ initialState
+ });
+
+ if (module.hot) {
+ // Enable Webpack hot module replacement for reducers
+ module.hot.accept('./store.js', () => {
+ store.replaceReducer(photoSlice.reducer);
+ });
+ }
+
+ return store;
+}
diff --git a/limestone/pattern-single-panel-redux/src/views/MainPanel.js b/limestone/pattern-single-panel-redux/src/views/MainPanel.js
new file mode 100644
index 000000000..f07276bd7
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/src/views/MainPanel.js
@@ -0,0 +1,66 @@
+import kind from '@enact/core/kind';
+import {Header, Panel} from '@enact/limestone/Panels';
+import {Cell, Column} from '@enact/ui/Layout';
+
+import FooterContainer from '../containers/FooterContainer';
+import PhotoPickerContainer from '../containers/PhotoPickerContainer';
+import PhotoPreviewContainer from '../containers/PhotoPreviewContainer';
+import PhotoSliderContainer from '../containers/PhotoSliderContainer';
+
+import car from '/assets/images/car.jpeg';
+import city from '/assets/images/city.jpeg';
+import mural from '/assets/images/mural.jpeg';
+import spaceShuttle from '/assets/images/space-shuttle.jpg';
+import violin from '/assets/images/violin.jpeg';
+
+const imageNames = [
+ 'mural',
+ 'violin',
+ 'car',
+ 'city',
+ 'spaceShuttle'
+];
+const imageURLs = [
+ mural,
+ violin,
+ car,
+ city,
+ spaceShuttle
+];
+
+const MainPanel = kind({
+ name: 'MainPanel',
+
+ render: (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+});
+
+export default MainPanel;
diff --git a/limestone/pattern-single-panel-redux/webos-meta/appinfo.json b/limestone/pattern-single-panel-redux/webos-meta/appinfo.json
new file mode 100644
index 000000000..188ca4236
--- /dev/null
+++ b/limestone/pattern-single-panel-redux/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.pattern-single-panel-redux",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Single Panel Pattern with Redux",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/pattern-single-panel-redux/webos-meta/icon-large.png b/limestone/pattern-single-panel-redux/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/pattern-single-panel-redux/webos-meta/icon-large.png differ
diff --git a/limestone/pattern-single-panel-redux/webos-meta/icon-mini.png b/limestone/pattern-single-panel-redux/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/pattern-single-panel-redux/webos-meta/icon-mini.png differ
diff --git a/limestone/pattern-single-panel-redux/webos-meta/icon.png b/limestone/pattern-single-panel-redux/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/pattern-single-panel-redux/webos-meta/icon.png differ
diff --git a/limestone/pattern-single-panel/.gitignore b/limestone/pattern-single-panel/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/pattern-single-panel/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-single-panel/LICENSE b/limestone/pattern-single-panel/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/pattern-single-panel/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/pattern-single-panel/README.md b/limestone/pattern-single-panel/README.md
new file mode 100644
index 000000000..b19eb5708
--- /dev/null
+++ b/limestone/pattern-single-panel/README.md
@@ -0,0 +1,33 @@
+## Single panel pattern // Profile Photo Picker
+
+A sample single panel Enact application where you can pick a profile photo from a given number of photos.
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+#### Enact Components Used
+- `limestone/Button`
+- `limestone/Image`
+- `limestone/Picker`
+- `limestone/Popup`
+- `limestone/Slider`
+
+Take a look at the custom component `./src/components/ProfilePhotoPicker.js`
+Slider controls the position of the main photo using `handleSliderChange()` which changes the state of `photoPosition`
+
+The `Popup` component is used in `./src/components/SaveButton.js`
+
+#### Running Tests
+
+The sample includes examples on how to use unit tests with Enact. To execute the tests, issue the following command:
+
+```bash
+npm run test
+```
+
+### Photo credits
+
+All photos are found in [Pexels.com](https://www.pexels.com) under Public Domain CC0 license.
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/pattern-single-panel/assets/images/car.jpeg b/limestone/pattern-single-panel/assets/images/car.jpeg
new file mode 100644
index 000000000..a8fa4a973
Binary files /dev/null and b/limestone/pattern-single-panel/assets/images/car.jpeg differ
diff --git a/limestone/pattern-single-panel/assets/images/city.jpeg b/limestone/pattern-single-panel/assets/images/city.jpeg
new file mode 100644
index 000000000..22191b606
Binary files /dev/null and b/limestone/pattern-single-panel/assets/images/city.jpeg differ
diff --git a/limestone/pattern-single-panel/assets/images/mural.jpeg b/limestone/pattern-single-panel/assets/images/mural.jpeg
new file mode 100644
index 000000000..205b74231
Binary files /dev/null and b/limestone/pattern-single-panel/assets/images/mural.jpeg differ
diff --git a/limestone/pattern-single-panel/assets/images/space-shuttle.jpg b/limestone/pattern-single-panel/assets/images/space-shuttle.jpg
new file mode 100644
index 000000000..e0cc3f172
Binary files /dev/null and b/limestone/pattern-single-panel/assets/images/space-shuttle.jpg differ
diff --git a/limestone/pattern-single-panel/assets/images/violin.jpeg b/limestone/pattern-single-panel/assets/images/violin.jpeg
new file mode 100644
index 000000000..0f2c9b1d6
Binary files /dev/null and b/limestone/pattern-single-panel/assets/images/violin.jpeg differ
diff --git a/limestone/pattern-single-panel/package.json b/limestone/pattern-single-panel/package.json
new file mode 100644
index 000000000..6082907e4
--- /dev/null
+++ b/limestone/pattern-single-panel/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "pattern-single-panel",
+ "version": "1.0.0",
+ "description": "An Enact application demonstrating SinglePanel.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "Single Panel Pattern",
+ "isomorphic": true,
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-single-panel/resources/ilibmanifest.json b/limestone/pattern-single-panel/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/pattern-single-panel/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-single-panel/src/App/App.js b/limestone/pattern-single-panel/src/App/App.js
new file mode 100644
index 000000000..26b93d5b4
--- /dev/null
+++ b/limestone/pattern-single-panel/src/App/App.js
@@ -0,0 +1,27 @@
+import kind from '@enact/core/kind';
+import {Panels} from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+
+import MainPanel from '../views/MainPanel';
+
+import css from './App.module.less';
+
+const AppBase = kind({
+ name: 'App',
+
+ styles: {
+ css,
+ className: 'app'
+ },
+
+ render: (props) => (
+
+
+
+ )
+});
+
+const App = ThemeDecorator(AppBase);
+
+export default App;
+export {App, AppBase};
diff --git a/limestone/pattern-single-panel/src/App/App.module.less b/limestone/pattern-single-panel/src/App/App.module.less
new file mode 100644
index 000000000..23782defd
--- /dev/null
+++ b/limestone/pattern-single-panel/src/App/App.module.less
@@ -0,0 +1,3 @@
+.app {
+ // styles can be put here
+}
diff --git a/limestone/pattern-single-panel/src/App/package.json b/limestone/pattern-single-panel/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/pattern-single-panel/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/pattern-single-panel/src/components/ProfilePhotoPicker.js b/limestone/pattern-single-panel/src/components/ProfilePhotoPicker.js
new file mode 100644
index 000000000..6848cd8ec
--- /dev/null
+++ b/limestone/pattern-single-panel/src/components/ProfilePhotoPicker.js
@@ -0,0 +1,81 @@
+import BodyText from '@enact/limestone/BodyText';
+import Image from '@enact/limestone/Image';
+import Picker from '@enact/limestone/Picker';
+import Slider from '@enact/limestone/Slider';
+import {Cell, Column} from '@enact/ui/Layout';
+import {useCallback, useState} from 'react';
+
+import car from '../../assets/images/car.jpeg';
+import city from '../../assets/images/city.jpeg';
+import mural from '../../assets/images/mural.jpeg';
+import spaceShuttle from '../../assets/images/space-shuttle.jpg';
+import violin from '../../assets/images/violin.jpeg';
+
+import css from './ProfilePhotoPicker.module.less';
+
+const imageURLs = [
+ car,
+ city,
+ mural,
+ spaceShuttle,
+ violin
+];
+
+const imageNames = ['Vintage Car', 'City', 'Mural', 'Space Shuttle', 'Violin'];
+
+const convertedImageUrl = (url) => {
+ return url.split(/(?=assets)/g).at(-1);
+};
+
+const imageComponents = imageURLs.map(url => {
+ return ();
+});
+
+const ProfilePhotoPicker = (props) => {
+ const [photoIndex, setPhotoIndex] = useState(0);
+ const [photoPosition, setPhotoPosition] = useState(-100);
+ const handlePickerChange = useCallback((ev) => setPhotoIndex(ev.value), []);
+ const handleSliderChange = useCallback((ev) => setPhotoPosition(ev.value), []);
+
+ return (
+
+
+
+
+ {imageNames[photoIndex]} :: {photoIndex + 1} of {imageURLs.length} photos
+
+
+ {imageComponents}
+
+
+ );
+};
+
+export default ProfilePhotoPicker;
+export {ProfilePhotoPicker, imageURLs};
diff --git a/limestone/pattern-single-panel/src/components/ProfilePhotoPicker.module.less b/limestone/pattern-single-panel/src/components/ProfilePhotoPicker.module.less
new file mode 100644
index 000000000..b559778fd
--- /dev/null
+++ b/limestone/pattern-single-panel/src/components/ProfilePhotoPicker.module.less
@@ -0,0 +1,8 @@
+.profilePhoto {
+ min-height: 300px;
+ background-color: grey;
+}
+
+.slider {
+ min-width: 360px;
+}
diff --git a/limestone/pattern-single-panel/src/components/README.md b/limestone/pattern-single-panel/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/pattern-single-panel/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/pattern-single-panel/src/components/SaveButton.js b/limestone/pattern-single-panel/src/components/SaveButton.js
new file mode 100644
index 000000000..d7a65e04c
--- /dev/null
+++ b/limestone/pattern-single-panel/src/components/SaveButton.js
@@ -0,0 +1,23 @@
+import Button from '@enact/limestone/Button';
+import Popup from '@enact/limestone/Popup';
+import {useCallback, useState} from 'react';
+
+const SaveButton = (props) => {
+ const [saved, setSaved] = useState(false);
+
+ const handleOnSave = useCallback(() => setSaved(true), []);
+ const handleOnClose = useCallback(() => setSaved(false), []);
+
+ return (
+
+
+
+ Saved!
+
+
+ );
+};
+
+export default SaveButton;
diff --git a/limestone/pattern-single-panel/src/components/tests/ProfilePhotoPicker-specs.js b/limestone/pattern-single-panel/src/components/tests/ProfilePhotoPicker-specs.js
new file mode 100644
index 000000000..6f9b2b6d0
--- /dev/null
+++ b/limestone/pattern-single-panel/src/components/tests/ProfilePhotoPicker-specs.js
@@ -0,0 +1,45 @@
+import '@testing-library/jest-dom';
+import {fireEvent, render, screen} from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import ProfilePhotoPicker, {imageURLs} from '../ProfilePhotoPicker.js';
+
+const focus = (slider) => fireEvent.focus(slider);
+const keyDown = (keyCode) => (slider) => fireEvent.keyDown(slider, {keyCode});
+const rightKeyDown = keyDown(39);
+
+describe('ProfilePhotoPicker specs', () => {
+
+ test('should change ProfilePhoto image src', async function () {
+ const user = userEvent.setup();
+
+ render();
+
+ const button = screen.getByLabelText(/next/);
+
+ await user.click(button);
+ await user.click(button);
+
+ const profilePhoto = screen.getAllByRole('img');
+
+ const actual = profilePhoto[0].children.item(0);
+ const expected = imageURLs[2];
+
+ expect(actual).toHaveAttribute('src', expected);
+ });
+
+ test('should change ProfilePhoto background-position', function () {
+ render();
+
+ const slider = screen.getByRole('slider');
+
+ focus(slider);
+ rightKeyDown(slider);
+
+ const profilePhoto = screen.getAllByRole('img');
+ const actual = profilePhoto[0];
+ const expected = '-99px';
+
+ expect(actual).toHaveStyle(`background-position: ${expected}`);
+ });
+});
diff --git a/limestone/pattern-single-panel/src/components/tests/SaveButton-specs.js b/limestone/pattern-single-panel/src/components/tests/SaveButton-specs.js
new file mode 100644
index 000000000..a28946e39
--- /dev/null
+++ b/limestone/pattern-single-panel/src/components/tests/SaveButton-specs.js
@@ -0,0 +1,31 @@
+import {FloatingLayerDecorator} from '@enact/ui/FloatingLayer';
+import '@testing-library/jest-dom';
+import {render, screen} from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import SaveButton from '../SaveButton.js';
+
+const FloatingLayerController = FloatingLayerDecorator('div');
+
+describe('SaveButton specs', () => {
+
+ test('should open Popup on Button click', async function () {
+ const message = 'Saved!';
+ const user = userEvent.setup();
+
+ render(
+
+
+
+ );
+
+ const button = screen.getByRole('button');
+
+ await user.click(button);
+
+ const actual = screen.getByText(message);
+
+ expect(actual).toBeInTheDocument();
+ });
+});
+
diff --git a/limestone/pattern-single-panel/src/index.js b/limestone/pattern-single-panel/src/index.js
new file mode 100644
index 000000000..57c20e6fd
--- /dev/null
+++ b/limestone/pattern-single-panel/src/index.js
@@ -0,0 +1,19 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './App';
+
+const appElement = ();
+
+// In a browser environment, render the app to the document.
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/pattern-single-panel/src/views/MainPanel.js b/limestone/pattern-single-panel/src/views/MainPanel.js
new file mode 100644
index 000000000..45e72f648
--- /dev/null
+++ b/limestone/pattern-single-panel/src/views/MainPanel.js
@@ -0,0 +1,24 @@
+import kind from '@enact/core/kind';
+import {Header, Panel} from '@enact/limestone/Panels';
+import {Cell, Column} from '@enact/ui/Layout';
+
+import ProfilePhotoPicker from '../components/ProfilePhotoPicker';
+import SaveButton from '../components/SaveButton';
+
+const MainPanel = kind({
+ name: 'MainPanel',
+
+ render: (props) => (
+
+
+
+
+
+
+
+
+
+ )
+});
+
+export default MainPanel;
diff --git a/limestone/pattern-single-panel/src/views/README.md b/limestone/pattern-single-panel/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/pattern-single-panel/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/pattern-single-panel/webos-meta/appinfo.json b/limestone/pattern-single-panel/webos-meta/appinfo.json
new file mode 100644
index 000000000..0527aac73
--- /dev/null
+++ b/limestone/pattern-single-panel/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.pattern-single-panel",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Single Panel Pattern",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/pattern-single-panel/webos-meta/icon-large.png b/limestone/pattern-single-panel/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/pattern-single-panel/webos-meta/icon-large.png differ
diff --git a/limestone/pattern-single-panel/webos-meta/icon-mini.png b/limestone/pattern-single-panel/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/pattern-single-panel/webos-meta/icon-mini.png differ
diff --git a/limestone/pattern-single-panel/webos-meta/icon.png b/limestone/pattern-single-panel/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/pattern-single-panel/webos-meta/icon.png differ
diff --git a/limestone/pattern-video-player-custom/.gitignore b/limestone/pattern-video-player-custom/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/pattern-video-player-custom/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-video-player-custom/LICENSE b/limestone/pattern-video-player-custom/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/pattern-video-player-custom/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/pattern-video-player-custom/README.md b/limestone/pattern-video-player-custom/README.md
new file mode 100644
index 000000000..c903a4b1b
--- /dev/null
+++ b/limestone/pattern-video-player-custom/README.md
@@ -0,0 +1,29 @@
+## VideoPlayerCustom Pattern
+
+A sample Enact application that demonstrates how to add and play custom videos in the VideoPlayer.
+
+Run `npm install` followed by `npm run serve` to start the app on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+#### Enact Components Used
+- `limestone/Button`
+- `limestone/Group`
+- `limestone/Panels/Header`
+- `limestone/Panels/Panel`
+- `limestone/RadioItem`
+- `limestone/Scroller`
+- `limestone/VideoPlayer`
+
+The video data, including the URL, mime type, and subtitle, are stored in [videos.js](src/App/videos.js). For `m3u8` videos, the mime type should be `application/x-mpegURL`.
+To enable `m3u8` video playback, the application uses [hls.js](https://github.com/video-dev/hls.js), which is imported in [App.js](src/App/App.js).
+
+In [App.js](src/App/App.js), the application retrieves the video data from [videos.js](src/App/videos.js) and assigns the source and video element to the VideoPlayer based on the mime type.
+
+This application demonstrates how to enable users to select the resolution of the `m3u8` video. The available resolutions are extracted from the `m3u8` file using `hls.js` and displayed in a dropdown menu. The selected resolution is then applied to the video playback.
+
+Additionally, this example shows how to support subtitles in the WebVTT format (.vtt files). These subtitles can be displayed in the VideoPlayer. Please note that HTML video only supports subtitles in the WebVTT format.
+
+For a more detailed view, check inside [App.js](src/App/App.js).
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
\ No newline at end of file
diff --git a/limestone/pattern-video-player-custom/package.json b/limestone/pattern-video-player-custom/package.json
new file mode 100644
index 000000000..9cc83c8be
--- /dev/null
+++ b/limestone/pattern-video-player-custom/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "pattern-video-player-custom",
+ "version": "1.0.0",
+ "description": "An Enact application demonstrating how to add and play the custom videos in VideoPlayer.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "Video Player Custom Pattern",
+ "isomorphic": true,
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "hls.js": "^1.5.17",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-video-player-custom/resources/ilibmanifest.json b/limestone/pattern-video-player-custom/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/pattern-video-player-custom/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-video-player-custom/src/App/App.js b/limestone/pattern-video-player-custom/src/App/App.js
new file mode 100644
index 000000000..41b52dc2c
--- /dev/null
+++ b/limestone/pattern-video-player-custom/src/App/App.js
@@ -0,0 +1,240 @@
+import Button from '@enact/limestone/Button';
+import Dropdown from '@enact/limestone/Dropdown';
+import {MediaControls} from '@enact/limestone/MediaPlayer';
+import {Header, Panels, Panel} from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+import VideoPlayer from '@enact/limestone/VideoPlayer';
+import Hls from 'hls.js';
+import PropTypes from 'prop-types';
+import {useCallback, useEffect, useRef, useState} from 'react';
+
+import VideoSelectionPanel from '../views/VideoSelectionPanel';
+import SubtitleSelectionPanel from '../views/SubtitleSelectionPanel';
+
+import videos from './videos.js';
+
+import css from './App.module.less';
+
+const getVideo = (index) => videos[index];
+
+const AppBase = ({className, subtitleId, videoId, ...rest}) => {
+ const [resolutionDropdownVisible, setResolutionDropdownVisible] = useState(false);
+ const [resolutions, setResolutions] = useState([]);
+ const [selectedResolution, setSelectedResolution] = useState();
+ const [subtitleIndex, setSubtitleIndex] = useState(subtitleId);
+ const [subtitlePanelsVisible, setSubtitlePanelsVisible] = useState(false);
+ const [videoIndex, setVideoIndex] = useState(videoId);
+ const [videoPanelsVisible, setVideoPanelsVisible] = useState(false);
+ const hlsRef = useRef(null);
+ const videoRef = useRef(null);
+
+ const getHls = () => {
+ if (hlsRef.current === null) {
+ hlsRef.current = new Hls();
+ }
+ return hlsRef.current;
+ };
+ const handleSelectResolution = useCallback(({data, selected}) => {
+ const hls = getHls();
+
+ // According to hls.js, automatic level selection should set -1.
+ if (data === 'auto') {
+ hls.nextLevel = -1;
+ setSelectedResolution(-1);
+ } else {
+ hls.nextLevel = selected;
+ setSelectedResolution(selected);
+ }
+ }, []);
+ const handleHideResolution = useCallback(() => setResolutionDropdownVisible(false), []);
+ const handleHideSubtitlePanelsClick = useCallback(() => setSubtitlePanelsVisible(false), []);
+ const handleHideVideoPanelsClick = useCallback(() => setVideoPanelsVisible(false), []);
+ const handleShowResolution = useCallback(() => {
+ videoRef.current.hideControls();
+ setVideoPanelsVisible(false);
+ setResolutionDropdownVisible(true);
+ }, []);
+ const handleShowSubtitlePanelsClick = useCallback(() => {
+ videoRef.current.hideControls();
+ setSubtitlePanelsVisible(true);
+ }, []);
+ const handleShowVideoPanelsClick = useCallback(() => {
+ videoRef.current.hideControls();
+ setVideoPanelsVisible(true);
+ }, []);
+ const handleSubtitleIndexChange = useCallback((index) => {
+ setSubtitleIndex(index);
+ }, []);
+ const handleVideoIndexChange = useCallback((index) => {
+ setVideoIndex(index);
+ setSubtitleIndex(0);
+ }, []);
+
+ const {desc, source, subtitles, type, ...restVideo} = getVideo(videoIndex);
+ const subtitle = subtitles[subtitleIndex - 1];
+
+ // Get video source depending on video type
+ useEffect(() => {
+ const hls = getHls();
+ if (type === 'application/x-mpegURL') {
+ hls.loadSource(source);
+ hls.attachMedia(videoRef.current.getVideoNode().media);
+ } else {
+ hls.detachMedia();
+ videoRef.current.getVideoNode().media.src = source;
+ }
+ }, [source, type]);
+
+ useEffect(() => {
+ const hls = getHls();
+
+ const onLevelSwitched = () => {
+ const list = hls.levels.map((level) => level._attrs[0].RESOLUTION);
+ list.push('auto');
+ setResolutions(list);
+ };
+
+ hls.on(Hls.Events.LEVEL_SWITCHED, onLevelSwitched);
+ return () => {
+ hls.off(Hls.Events.LEVEL_SWITCHED, onLevelSwitched);
+ };
+ }, []);
+
+ // Add or remove subtitle
+ useEffect(() => {
+ const video = videoRef.current.getVideoNode().media;
+ let track = document.getElementById('track');
+ if (subtitle) {
+ if (!document.getElementById('track')) {
+ track = document.createElement('track');
+ track.id = "track";
+ video.appendChild(track);
+ }
+ video.textTracks[0].mode = "hidden";
+ track.src = subtitle.file;
+ track.kind = "subtitles";
+ track.srclang = subtitle.lang;
+ video.textTracks[0].mode = "showing";
+ } else if (video.textTracks[0]) {
+ video.textTracks[0].mode = "hidden";
+ }
+ }, [subtitle]);
+
+ let content = null;
+
+ if (subtitlePanelsVisible) {
+ content = (
+
+
+
+ );
+ } else if (videoPanelsVisible) {
+ content = (
+
+
+
+ );
+ } else if (resolutionDropdownVisible) {
+ content = (
+
+
+
+
+
+ -1 ? selectedResolution : resolutions.length - 1}
+ onSelect={handleSelectResolution}
+ >
+ {resolutions}
+
+
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+AppBase.propTypes = {
+ /**
+ * Assign an alternate initial subtitle to load first.
+ *
+ * @type {Number}
+ * @default 0
+ * @public
+ */
+ subtitleId: PropTypes.number,
+
+ /**
+ * Assign an alternate initial video to load first.
+ *
+ * @type {Number}
+ * @default 0
+ * @public
+ */
+ videoId: PropTypes.number
+};
+
+AppBase.defaultProps = {
+ subtitleId: 0,
+ videoId: 0
+};
+
+const App = ThemeDecorator(AppBase);
+
+export default App;
+export {App, AppBase};
diff --git a/limestone/pattern-video-player-custom/src/App/App.module.less b/limestone/pattern-video-player-custom/src/App/App.module.less
new file mode 100644
index 000000000..3dbaa80dd
--- /dev/null
+++ b/limestone/pattern-video-player-custom/src/App/App.module.less
@@ -0,0 +1,7 @@
+.app {
+ // styles can be put here
+
+ .player {
+ position: absolute;
+ }
+}
diff --git a/limestone/pattern-video-player-custom/src/App/package.json b/limestone/pattern-video-player-custom/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/pattern-video-player-custom/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/pattern-video-player-custom/src/App/subtitle-en.vtt b/limestone/pattern-video-player-custom/src/App/subtitle-en.vtt
new file mode 100644
index 000000000..efc161f69
--- /dev/null
+++ b/limestone/pattern-video-player-custom/src/App/subtitle-en.vtt
@@ -0,0 +1,80 @@
+WEBVTT
+
+
+00:00:00.000 --> 00:00:02.000
+This is a subtitle test.
+
+00:00:02.000 --> 00:00:04.000
+This is a subtitle test.
+
+00:00:04.000 --> 00:00:06.000
+This is a subtitle test.
+
+00:00:06.000 --> 00:00:08.000
+This is a subtitle test.
+
+00:00:08.000 --> 00:00:10.000
+This is a subtitle test.
+
+00:00:10.000 --> 00:00:12.000
+This is a subtitle test.
+
+00:00:12.000 --> 00:00:14.000
+This is a subtitle test.
+
+00:00:14.000 --> 00:00:16.000
+This is a subtitle test.
+
+00:00:16.000 --> 00:00:18.000
+This is a subtitle test.
+
+00:00:18.000 --> 00:00:20.000
+This is a subtitle test.
+
+00:00:20.000 --> 00:00:22.000
+This is a subtitle test.
+
+00:00:22.000 --> 00:00:24.000
+This is a subtitle test.
+
+00:00:24.000 --> 00:00:26.000
+This is a subtitle test.
+
+00:00:26.000 --> 00:00:28.000
+This is a subtitle test.
+
+00:00:28.000 --> 00:00:30.000
+This is a subtitle test.
+
+00:00:30.000 --> 00:00:32.000
+This is a subtitle test.
+
+00:00:32.000 --> 00:00:34.000
+This is a subtitle test.
+
+00:00:34.000 --> 00:00:36.000
+This is a subtitle test.
+
+00:00:36.000 --> 00:00:38.000
+This is a subtitle test.
+
+00:00:38.000 --> 00:00:40.000
+This is a subtitle test.
+
+00:00:40.000 --> 00:00:42.000
+This is a subtitle test.
+
+00:00:42.000 --> 00:00:44.000
+This is a subtitle test.
+
+00:00:44.000 --> 00:00:46.000
+This is a subtitle test.
+
+00:00:46.000 --> 00:00:48.000
+This is a subtitle test.
+
+00:00:48.000 --> 00:00:50.000
+This is a subtitle test.
+
+00:00:50.000 --> 00:00:52.000
+This is a subtitle test.
diff --git a/limestone/pattern-video-player-custom/src/App/subtitle-ko.vtt b/limestone/pattern-video-player-custom/src/App/subtitle-ko.vtt
new file mode 100644
index 000000000..33dbb6cf4
--- /dev/null
+++ b/limestone/pattern-video-player-custom/src/App/subtitle-ko.vtt
@@ -0,0 +1,80 @@
+WEBVTT
+
+
+00:00:00.000 --> 00:00:02.000
+자막 테스트입니다.
+
+00:00:02.000 --> 00:00:04.000
+자막 테스트입니다.
+
+00:00:04.000 --> 00:00:06.000
+자막 테스트입니다.
+
+00:00:06.000 --> 00:00:08.000
+자막 테스트입니다.
+
+00:00:08.000 --> 00:00:10.000
+자막 테스트입니다.
+
+00:00:10.000 --> 00:00:12.000
+자막 테스트입니다.
+
+00:00:12.000 --> 00:00:14.000
+자막 테스트입니다.
+
+00:00:14.000 --> 00:00:16.000
+자막 테스트입니다.
+
+00:00:16.000 --> 00:00:18.000
+자막 테스트입니다.
+
+00:00:18.000 --> 00:00:20.000
+자막 테스트입니다.
+
+00:00:20.000 --> 00:00:22.000
+자막 테스트입니다.
+
+00:00:22.000 --> 00:00:24.000
+자막 테스트입니다.
+
+00:00:24.000 --> 00:00:26.000
+자막 테스트입니다.
+
+00:00:26.000 --> 00:00:28.000
+자막 테스트입니다.
+
+00:00:28.000 --> 00:00:30.000
+자막 테스트입니다.
+
+00:00:30.000 --> 00:00:32.000
+자막 테스트입니다.
+
+00:00:32.000 --> 00:00:34.000
+자막 테스트입니다.
+
+00:00:34.000 --> 00:00:36.000
+자막 테스트입니다.
+
+00:00:36.000 --> 00:00:38.000
+자막 테스트입니다.
+
+00:00:38.000 --> 00:00:40.000
+자막 테스트입니다.
+
+00:00:40.000 --> 00:00:42.000
+자막 테스트입니다.
+
+00:00:42.000 --> 00:00:44.000
+자막 테스트입니다.
+
+00:00:44.000 --> 00:00:46.000
+자막 테스트입니다.
+
+00:00:46.000 --> 00:00:48.000
+자막 테스트입니다.
+
+00:00:48.000 --> 00:00:50.000
+자막 테스트입니다.
+
+00:00:50.000 --> 00:00:52.000
+자막 테스트입니다.
diff --git a/limestone/pattern-video-player-custom/src/App/videos.js b/limestone/pattern-video-player-custom/src/App/videos.js
new file mode 100644
index 000000000..d35129a0a
--- /dev/null
+++ b/limestone/pattern-video-player-custom/src/App/videos.js
@@ -0,0 +1,26 @@
+import subtitleFile1 from './subtitle-en.vtt'; // import subtitle file
+import subtitleFile2 from './subtitle-ko.vtt'; // import subtitle file
+
+// Videos List
+const videos = [
+ {
+ title: 'mp4 Video Source',
+ poster: 'http://media.w3.org/2010/05/sintel/poster.png',
+ source: 'http://media.w3.org/2010/05/sintel/trailer.mp4',
+ subtitles: [
+ {file: subtitleFile1, lang: "en"},
+ {file: subtitleFile2, lang: "ko"}
+ ],
+ type: 'video/mp4',
+ desc: 'Custom mp4 video source.'
+ },
+ {
+ title: 'm3u8 Video Source',
+ source: '', // put m3u8 video url here
+ subtitles: [],
+ type: 'application/x-mpegURL', // m3u8 mime type
+ desc: 'Custom m3u8 video source.'
+ }
+];
+
+export default videos;
diff --git a/limestone/pattern-video-player-custom/src/components/README.md b/limestone/pattern-video-player-custom/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/pattern-video-player-custom/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/pattern-video-player-custom/src/index.js b/limestone/pattern-video-player-custom/src/index.js
new file mode 100644
index 000000000..57c20e6fd
--- /dev/null
+++ b/limestone/pattern-video-player-custom/src/index.js
@@ -0,0 +1,19 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './App';
+
+const appElement = ();
+
+// In a browser environment, render the app to the document.
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/pattern-video-player-custom/src/views/README.md b/limestone/pattern-video-player-custom/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/pattern-video-player-custom/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/pattern-video-player-custom/src/views/SubtitleSelectionPanel.js b/limestone/pattern-video-player-custom/src/views/SubtitleSelectionPanel.js
new file mode 100644
index 000000000..4c505a3f3
--- /dev/null
+++ b/limestone/pattern-video-player-custom/src/views/SubtitleSelectionPanel.js
@@ -0,0 +1,87 @@
+import {handle} from '@enact/core/handle';
+import kind from '@enact/core/kind';
+import Button from '@enact/limestone/Button';
+import {Header, Panel} from '@enact/limestone/Panels';
+import RadioItem from '@enact/limestone/RadioItem';
+import Scroller from '@enact/limestone/Scroller';
+import Group from '@enact/ui/Group';
+import PropTypes from 'prop-types';
+
+import videos from '../App/videos.js';
+
+const SubtitleSelectionPanel = kind({
+ name: 'SubtitleSelectionPanel',
+
+ propTypes: {
+ /**
+ * A function to hide the Panels.
+ * @type {Function}
+ */
+ onHidePanels: PropTypes.func,
+
+ /**
+ * A function that receives the selected subtitle's index.
+ * @type {Function}
+ */
+ onSubtitleIndexChange: PropTypes.func,
+
+ /**
+ * The index number of the selected subtitle.
+ *
+ * @type {Number}
+ */
+ subtitleIndex: PropTypes.number,
+
+ /**
+ * A title string appear on header
+ * @type {String}
+ */
+ title: PropTypes.string,
+
+ /**
+ * The index number of the selected video.
+ *
+ * @type {Number}
+ */
+ videoIndex: PropTypes.number
+ },
+
+ computed: {
+ subtitlesList: ({videoIndex}) => {
+ const subtitlesList = videos[videoIndex].subtitles.map((subtitle) => subtitle.lang);
+ subtitlesList.unshift("off");
+ return subtitlesList;
+ }
+ },
+
+ handlers: {
+ onSubtitleIndexChange: handle(
+ (ev, {onSubtitleIndexChange}) => onSubtitleIndexChange(ev.selected)
+ )
+ },
+
+ render: ({title, onHidePanels, onSubtitleIndexChange, subtitleIndex, subtitlesList, ...rest}) => {
+ delete rest.videoIndex;
+
+ return (
+
+
+
+
+
+
+ {subtitlesList}
+
+
+
+ );
+ }
+});
+
+export default SubtitleSelectionPanel;
diff --git a/limestone/pattern-video-player-custom/src/views/VideoSelectionPanel.js b/limestone/pattern-video-player-custom/src/views/VideoSelectionPanel.js
new file mode 100644
index 000000000..a47026ab0
--- /dev/null
+++ b/limestone/pattern-video-player-custom/src/views/VideoSelectionPanel.js
@@ -0,0 +1,74 @@
+import {handle} from '@enact/core/handle';
+import kind from '@enact/core/kind';
+import Button from '@enact/limestone/Button';
+import {Header, Panel} from '@enact/limestone/Panels';
+import RadioItem from '@enact/limestone/RadioItem';
+import Scroller from '@enact/limestone/Scroller';
+import Group from '@enact/ui/Group';
+import PropTypes from 'prop-types';
+
+import videos from '../App/videos.js';
+
+// Remap our titles from `videos` to strings in a new array
+// videos[{title: 'value'}] -> videosList['value']
+const videosList = videos.map((video) => video.title);
+
+const VideoSelectionPanel = kind({
+ name: 'VideoSelectionPanel',
+
+ propTypes: {
+ /**
+ * A function to hide the Panels.
+ * @type {Function}
+ */
+ onHidePanels: PropTypes.func,
+
+ /**
+ * A function that receives the selected video's index.
+ * @type {Function}
+ */
+ onVideoIndexChange: PropTypes.func,
+
+ /**
+ * A title string appear on header
+ * @type {String}
+ */
+ title: PropTypes.string,
+
+ /**
+ * The index number of the selected video.
+ *
+ * @type {Number}
+ */
+ videoIndex: PropTypes.number
+ },
+
+ handlers: {
+ onVideoIndexChange: handle(
+ (ev, {onVideoIndexChange}) => onVideoIndexChange(ev.selected)
+ )
+ },
+
+ render: ({title, onHidePanels, onVideoIndexChange, videoIndex, ...rest}) => {
+ return (
+
+
+
+
+
+
+ {videosList}
+
+
+
+ );
+ }
+});
+
+export default VideoSelectionPanel;
diff --git a/limestone/pattern-video-player-custom/webos-meta/appinfo.json b/limestone/pattern-video-player-custom/webos-meta/appinfo.json
new file mode 100644
index 000000000..54cadc5de
--- /dev/null
+++ b/limestone/pattern-video-player-custom/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.pattern-video-player-custom",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Video Player Custom Pattern",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/pattern-video-player-custom/webos-meta/icon-large.png b/limestone/pattern-video-player-custom/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/pattern-video-player-custom/webos-meta/icon-large.png differ
diff --git a/limestone/pattern-video-player-custom/webos-meta/icon-mini.png b/limestone/pattern-video-player-custom/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/pattern-video-player-custom/webos-meta/icon-mini.png differ
diff --git a/limestone/pattern-video-player-custom/webos-meta/icon.png b/limestone/pattern-video-player-custom/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/pattern-video-player-custom/webos-meta/icon.png differ
diff --git a/limestone/pattern-video-player/.gitignore b/limestone/pattern-video-player/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/pattern-video-player/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-video-player/LICENSE b/limestone/pattern-video-player/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/pattern-video-player/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/pattern-video-player/README.md b/limestone/pattern-video-player/README.md
new file mode 100644
index 000000000..2bfa3d4d2
--- /dev/null
+++ b/limestone/pattern-video-player/README.md
@@ -0,0 +1,21 @@
+## VideoPlayer pattern
+
+A sample Enact application that demonstrates how to use full screen VideoPlayer combined with AlwaysViewingPanels.
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+#### Enact Components Used
+- `limestone/Button`
+- `limestone/Group`
+- `limestone/Item`
+- `limestone/Panels/Header`
+- `limestone/Panels/Panel`
+- `limestone/RadioItem`
+- `limestone/Scroller`
+- `limestone/VideoPlayer`
+
+You can find a more detailed view inside of [App.js](src/App/App.js)
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/pattern-video-player/package.json b/limestone/pattern-video-player/package.json
new file mode 100644
index 000000000..7d1eeeb6d
--- /dev/null
+++ b/limestone/pattern-video-player/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "pattern-video-player",
+ "version": "1.0.0",
+ "description": "An Enact application demonstrating AlwaysViewingPanels with VideoPlayer.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "Video Player Pattern",
+ "isomorphic": true,
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-video-player/resources/ilibmanifest.json b/limestone/pattern-video-player/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/pattern-video-player/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-video-player/src/App/App.js b/limestone/pattern-video-player/src/App/App.js
new file mode 100644
index 000000000..0212f3d5d
--- /dev/null
+++ b/limestone/pattern-video-player/src/App/App.js
@@ -0,0 +1,107 @@
+import Button from '@enact/limestone/Button';
+import {MediaControls} from '@enact/limestone/MediaPlayer';
+import {Panels} from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+import VideoPlayer from '@enact/limestone/VideoPlayer';
+import Spotlight from '@enact/spotlight';
+import PropTypes from 'prop-types';
+import {useCallback, useEffect, useRef, useState} from 'react';
+
+import ItemPanel from '../views/ItemPanel';
+import MainPanel from '../views/MainPanel';
+
+import videos from './videos.js';
+
+import css from './App.module.less';
+
+const getVideo = (index) => videos[index];
+
+const AppBase = ({className, panelId, videoId, ...rest}) => {
+ const [panelIndex, setPanelIndex] = useState(panelId);
+ const [panelsVisible, setPanelsVisible] = useState(false);
+ const [videoIndex, setVideoIndex] = useState(videoId);
+ const videoRef = useRef(null);
+
+ useEffect(() => {
+ // After displaying the panels, move the focus to the main panel
+ if (panelsVisible) {
+ Spotlight.focus('main-panel');
+ }
+ }, [panelsVisible]);
+
+ const handleNextPanelClick = useCallback(() => setPanelIndex(prevPanelIndex => (prevPanelIndex + 1)), []);
+ const handleBack = useCallback(({index}) => setPanelIndex(index), []);
+ const handleHidePanelsClick = useCallback(() => setPanelsVisible(false), []);
+ const handleShowPanelsClick = useCallback(() => {
+ videoRef.current.hideControls();
+ setPanelsVisible(true);
+ }, []);
+ const handleVideoIndexChange = useCallback((index) => setVideoIndex(index), []);
+ const {source, desc, ...restVideo} = getVideo(videoIndex);
+
+ return (
+
+ );
+};
+
+AppBase.propTypes = {
+ /**
+ * Assign an alternate panel index to start on.
+ *
+ * @type {Number}
+ * @default 0
+ * @public
+ */
+ panelId: PropTypes.number,
+
+ /**
+ * Assign an alternate initial video to load first.
+ *
+ * @type {Number}
+ * @default 0
+ * @public
+ */
+ videoId: PropTypes.number
+};
+
+AppBase.defaultProps = {
+ panelId: 0,
+ videoId: 0
+};
+
+const App = ThemeDecorator(AppBase);
+
+export default App;
+export {App, AppBase};
diff --git a/limestone/pattern-video-player/src/App/App.module.less b/limestone/pattern-video-player/src/App/App.module.less
new file mode 100644
index 000000000..3dbaa80dd
--- /dev/null
+++ b/limestone/pattern-video-player/src/App/App.module.less
@@ -0,0 +1,7 @@
+.app {
+ // styles can be put here
+
+ .player {
+ position: absolute;
+ }
+}
diff --git a/limestone/pattern-video-player/src/App/package.json b/limestone/pattern-video-player/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/pattern-video-player/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/pattern-video-player/src/App/videos.js b/limestone/pattern-video-player/src/App/videos.js
new file mode 100644
index 000000000..044d6ffc9
--- /dev/null
+++ b/limestone/pattern-video-player/src/App/videos.js
@@ -0,0 +1,30 @@
+// Videos List
+const videos = [
+ {
+ title: 'Sintel',
+ poster: 'http://media.w3.org/2010/05/sintel/poster.png',
+ source: 'http://media.w3.org/2010/05/sintel/trailer.mp4',
+ desc: 'The film follows a girl named Sintel who is searching for a baby dragon she calls Scales. A flashback reveals that Sintel found Scales with its wing injured and helped care for it, forming a close bond with it.'
+ },
+ {
+ title: 'Big Buck Bunny',
+ poster: 'http://media.w3.org/2010/05/bunny/poster.png',
+ source: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4',
+ desc: '\'Big\' Buck wakes up in his rabbit hole, only to be pestered by three critters, Gimera, Frank and Rinky. When Gimera kills a butterfly, Buck decides on a payback Predator-style.'
+ },
+ {
+ title: 'VideoTest',
+ poster: 'http://media.w3.org/2010/05/video/poster.png',
+ source: 'http://media.w3.org/2010/05/video/movie_300.mp4',
+ desc: 'A test video with unusual proportions'
+ },
+ {
+ title: 'Bad Video Source',
+ poster: 'http://media.w3.org/2010/05/video/poster.png',
+ // Intentionally invalid video to demonstrate source error state
+ source: 'https://github.com/mderrick/react-html5video',
+ desc: 'Intentionally invalid video to demonstrate source error state'
+ }
+];
+
+export default videos;
diff --git a/limestone/pattern-video-player/src/components/README.md b/limestone/pattern-video-player/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/pattern-video-player/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/pattern-video-player/src/index.js b/limestone/pattern-video-player/src/index.js
new file mode 100644
index 000000000..57c20e6fd
--- /dev/null
+++ b/limestone/pattern-video-player/src/index.js
@@ -0,0 +1,19 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './App';
+
+const appElement = ();
+
+// In a browser environment, render the app to the document.
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/pattern-video-player/src/views/ItemPanel.js b/limestone/pattern-video-player/src/views/ItemPanel.js
new file mode 100644
index 000000000..7cfb92e84
--- /dev/null
+++ b/limestone/pattern-video-player/src/views/ItemPanel.js
@@ -0,0 +1,28 @@
+import kind from '@enact/core/kind';
+import Item from '@enact/limestone/Item';
+import {Header, Panel} from '@enact/limestone/Panels';
+import PropTypes from 'prop-types';
+
+const ItemPanel = kind({
+ name: 'ItemPanel',
+
+ propTypes: {
+ /**
+ * A title string appear on header
+ * @type {String}
+ */
+ title: PropTypes.string
+ },
+
+ render: ({title, ...rest}) => (
+
+
+ Item 1
+ Item 2
+ Item 3
+ Item 4
+
+ )
+});
+
+export default ItemPanel;
diff --git a/limestone/pattern-video-player/src/views/MainPanel.js b/limestone/pattern-video-player/src/views/MainPanel.js
new file mode 100644
index 000000000..6458d3bba
--- /dev/null
+++ b/limestone/pattern-video-player/src/views/MainPanel.js
@@ -0,0 +1,81 @@
+import {handle} from '@enact/core/handle';
+import kind from '@enact/core/kind';
+import Button from '@enact/limestone/Button';
+import {Header, Panel} from '@enact/limestone/Panels';
+import RadioItem from '@enact/limestone/RadioItem';
+import Scroller from '@enact/limestone/Scroller';
+import Group from '@enact/ui/Group';
+import PropTypes from 'prop-types';
+
+import videos from '../App/videos.js';
+
+// Remap our titles from `videos` to strings in a new array
+// videos[{title: 'value'}] -> videosList['value']
+const videosList = videos.map((video) => video.title);
+
+const MainPanel = kind({
+ name: 'MainPanel',
+
+ propTypes: {
+ /**
+ * A function to hide the Panels.
+ * @type {Function}
+ */
+ onHidePanels: PropTypes.func,
+
+ /**
+ * A function to activate the next Panel.
+ * @type {Function}
+ */
+ onNextPanel: PropTypes.func,
+
+ /**
+ * A function that receives the selected video's index.
+ * @type {Function}
+ */
+ onVideoIndexChange: PropTypes.func,
+
+ /**
+ * A title string appear on header
+ * @type {String}
+ */
+ title: PropTypes.string,
+
+ /**
+ * The index number of the selected video.
+ *
+ * @type {Number}
+ */
+ videoIndex: PropTypes.number
+ },
+
+ handlers: {
+ onVideoIndexChange: handle(
+ (ev, {onVideoIndexChange}) => onVideoIndexChange(ev.selected)
+ )
+ },
+
+ render: ({title, onNextPanel, onHidePanels, onVideoIndexChange, videoIndex, ...rest}) => {
+ return (
+
+
+
+
+
+
+
+ {videosList}
+
+
+
+ );
+ }
+});
+
+export default MainPanel;
diff --git a/limestone/pattern-video-player/src/views/README.md b/limestone/pattern-video-player/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/pattern-video-player/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/pattern-video-player/webos-meta/appinfo.json b/limestone/pattern-video-player/webos-meta/appinfo.json
new file mode 100644
index 000000000..4fdc423ff
--- /dev/null
+++ b/limestone/pattern-video-player/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.pattern-video-player",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Video Player Pattern",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/pattern-video-player/webos-meta/icon-large.png b/limestone/pattern-video-player/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/pattern-video-player/webos-meta/icon-large.png differ
diff --git a/limestone/pattern-video-player/webos-meta/icon-mini.png b/limestone/pattern-video-player/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/pattern-video-player/webos-meta/icon-mini.png differ
diff --git a/limestone/pattern-video-player/webos-meta/icon.png b/limestone/pattern-video-player/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/pattern-video-player/webos-meta/icon.png differ
diff --git a/limestone/pattern-virtualgridlist-api/.gitignore b/limestone/pattern-virtualgridlist-api/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-api/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-virtualgridlist-api/LICENSE b/limestone/pattern-virtualgridlist-api/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-api/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/pattern-virtualgridlist-api/README.md b/limestone/pattern-virtualgridlist-api/README.md
new file mode 100644
index 000000000..2af0af9df
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-api/README.md
@@ -0,0 +1,17 @@
+## VirtualGridList add/remove/select/deselect pattern // My Gallery
+
+A sample Enact application that shows off how to add/remove/select/deselect items of VirtualGridList
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+#### Enact Components Used
+- `limestone/Button`
+- `limestone/ImageItem`
+- `limestone/Panels/Header`
+- `limestone/VirtualList/VirtualGridList`
+
+In this app, you can add, remove, select, deselect, selectAll, and deselectAll items using redux.
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/pattern-virtualgridlist-api/package.json b/limestone/pattern-virtualgridlist-api/package.json
new file mode 100644
index 000000000..db9f8aad1
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-api/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "pattern-virtualgridlist-api",
+ "version": "1.0.0",
+ "description": "An Enact application demonstrating VirtualGridList API with Redux.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "VirtualGridList API Pattern",
+ "isomorphic": true,
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "4.9.4",
+ "@reduxjs/toolkit": "^2.3.0",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-redux": "^9.1.2",
+ "redux": "^5.0.1"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-virtualgridlist-api/resources/ilibmanifest.json b/limestone/pattern-virtualgridlist-api/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-api/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-virtualgridlist-api/src/App/App.js b/limestone/pattern-virtualgridlist-api/src/App/App.js
new file mode 100644
index 000000000..4d67781ee
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-api/src/App/App.js
@@ -0,0 +1,26 @@
+import kind from '@enact/core/kind';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+
+import MainView from '../views/MainView';
+
+import css from './App.module.less';
+
+const AppBase = kind({
+ name: 'App',
+
+ styles: {
+ css,
+ className: 'app'
+ },
+
+ render: (props) => (
+
+ );
+};
+
+MainView.propTypes = {
+ album: PropTypes.string,
+ onChangeAlbum: PropTypes.func
+};
+
+export default AppStateDecorator(MainView);
diff --git a/limestone/pattern-virtualgridlist-api/src/views/MainView.module.less b/limestone/pattern-virtualgridlist-api/src/views/MainView.module.less
new file mode 100755
index 000000000..ba193e912
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-api/src/views/MainView.module.less
@@ -0,0 +1,21 @@
+.mainView {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.content {
+ display: flex;
+ padding-top: 12px;
+ box-sizing: border-box;
+ flex: 1 0;
+ overflow: hidden;
+}
+
+.sideBar {
+ flex: 1;
+}
+
+.list {
+ flex: 3;
+}
diff --git a/limestone/pattern-virtualgridlist-api/webos-meta/appinfo.json b/limestone/pattern-virtualgridlist-api/webos-meta/appinfo.json
new file mode 100644
index 000000000..0ffa9689b
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-api/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.pattern-virtualgridlist-api",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "VirtualGridList API Pattern",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/pattern-virtualgridlist-api/webos-meta/icon-large.png b/limestone/pattern-virtualgridlist-api/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/pattern-virtualgridlist-api/webos-meta/icon-large.png differ
diff --git a/limestone/pattern-virtualgridlist-api/webos-meta/icon-mini.png b/limestone/pattern-virtualgridlist-api/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/pattern-virtualgridlist-api/webos-meta/icon-mini.png differ
diff --git a/limestone/pattern-virtualgridlist-api/webos-meta/icon.png b/limestone/pattern-virtualgridlist-api/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/pattern-virtualgridlist-api/webos-meta/icon.png differ
diff --git a/limestone/pattern-virtualgridlist-incremental-load/.gitignore b/limestone/pattern-virtualgridlist-incremental-load/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-incremental-load/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-virtualgridlist-incremental-load/README.md b/limestone/pattern-virtualgridlist-incremental-load/README.md
new file mode 100644
index 000000000..a74065795
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-incremental-load/README.md
@@ -0,0 +1,18 @@
+## Incremental Data Loading in VirtualGridList Pattern
+
+A sample Enact application that demonstrates how to load items incrementally in a VirtualGridList.
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+#### Enact Components Used
+- `limestone/ImageItem`
+- `limestone/Panels/Header`
+- `limestone/Panels/Panel`
+- `limestone/VirtualList/VirtualGridList`
+
+In this app, data is loaded as you scroll to the bottom of the VirtualGridList. Use your API server to fetch data to load.
+Performance may decrease when using the `onScroll` handler, so use the `onScrollStop` handler as shown in the sample.
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/pattern-virtualgridlist-incremental-load/package.json b/limestone/pattern-virtualgridlist-incremental-load/package.json
new file mode 100644
index 000000000..d1ea7c462
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-incremental-load/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "pattern-virtualgridlist-incremental-load",
+ "version": "1.0.0",
+ "description": "A general template for an Enact Limestone application.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "UNLICENSED",
+ "private": true,
+ "repository": "",
+ "enact": {
+ "theme": "limestone"
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy"
+ },
+ "eslintIgnore": [
+ "node_modules/*",
+ "build/*",
+ "dist/*"
+ ],
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-virtualgridlist-incremental-load/resources/ilibmanifest.json b/limestone/pattern-virtualgridlist-incremental-load/resources/ilibmanifest.json
new file mode 100644
index 000000000..b736f135b
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-incremental-load/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-virtualgridlist-incremental-load/src/App/App.js b/limestone/pattern-virtualgridlist-incremental-load/src/App/App.js
new file mode 100644
index 000000000..8c799a7c9
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-incremental-load/src/App/App.js
@@ -0,0 +1,27 @@
+import kind from '@enact/core/kind';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+import Panels from '@enact/limestone/Panels';
+
+import MainView from '../views/MainView';
+
+import css from './App.module.less';
+
+const App = kind({
+ name: 'App',
+
+ styles: {
+ css,
+ className: 'app'
+ },
+
+ render: (props) => (
+
+
+
+
+
+ )
+});
+
+export default ThemeDecorator(App);
+export {App};
diff --git a/limestone/pattern-virtualgridlist-incremental-load/src/App/App.module.less b/limestone/pattern-virtualgridlist-incremental-load/src/App/App.module.less
new file mode 100644
index 000000000..d78135e9c
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-incremental-load/src/App/App.module.less
@@ -0,0 +1,8 @@
+@import '~@enact/ui/styles/mixins.less';
+
+.app {
+ position: absolute;
+ .position(0);
+ padding: 0.5rem;
+ box-sizing: border-box;
+}
diff --git a/limestone/pattern-virtualgridlist-incremental-load/src/App/package.json b/limestone/pattern-virtualgridlist-incremental-load/src/App/package.json
new file mode 100644
index 000000000..441552583
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-incremental-load/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/pattern-virtualgridlist-incremental-load/src/components/README.md b/limestone/pattern-virtualgridlist-incremental-load/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-incremental-load/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/pattern-virtualgridlist-incremental-load/src/index.js b/limestone/pattern-virtualgridlist-incremental-load/src/index.js
new file mode 100644
index 000000000..a601c327f
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-incremental-load/src/index.js
@@ -0,0 +1,17 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './App';
+
+const appElement = ();
+
+// In a browser environment, render instead of exporting
+if (typeof window !== 'undefined') {
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(document.getElementById('root'), appElement);
+ } else {
+ createRoot(document.getElementById('root')).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/pattern-virtualgridlist-incremental-load/src/views/MainView.js b/limestone/pattern-virtualgridlist-incremental-load/src/views/MainView.js
new file mode 100644
index 000000000..6faa46f22
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-incremental-load/src/views/MainView.js
@@ -0,0 +1,89 @@
+import {ImageItem} from '@enact/limestone/ImageItem';
+import {Panel, Header} from '@enact/limestone/Panels';
+import ri from '@enact/ui/resolution';
+import {VirtualGridList} from '@enact/limestone/VirtualList';
+import {useCallback, useState, useEffect} from 'react';
+
+const defaultSize = 50;
+const addedListSize = 30;
+
+const MainView = (props) => {
+ const [items, setItems] = useState([]);
+ const [isFetching, setIsFetching] = useState(false);
+
+ const createInitialData = useCallback((itemSize) => {
+ const newItems = Array.from(items);
+
+ for (let i = 0; i < itemSize; i++) {
+ const text = `Item ${i}`,
+ subText = `SubItem ${i}`,
+ color = Math.floor(Math.random() * (0x1000000 - 0x101010) + 0x101010).toString(16),
+ source = `https://placehold.co/300x300/` + color + '/ffffff/png?text=Image+' + i;
+
+ newItems.push({text, subText, source});
+ }
+ setItems(newItems);
+ }, [items]);
+
+ const loadMoreData = useCallback(() => {
+ // Inside this function, you can fetch data from your API server.
+ // Here, assuming that the fetch is successful, we only proceed with attaching the data.
+
+ const newItems = Array.from(items);
+ const length = items.length;
+
+ for (let i = length; i < length + addedListSize; i++) {
+ const text = `Item ${i}`,
+ subText = `SubItem ${i}`,
+ color = Math.floor(Math.random() * (0x1000000 - 0x101010) + 0x101010).toString(16),
+ source = `https://placehold.co/300x300/` + color + '/ffffff/png?text=Image+' + i;
+
+ newItems.push({text, subText, source});
+ }
+ setItems(newItems);
+ }, [items]);
+
+ const renderItem = useCallback(({index, ...rest}) => {
+ const {text, subText, source} = items[index];
+ return (
+
+ {text}
+
+ );
+ }, [items]);
+
+ const handleScrollStop = useCallback((event) => {
+ const reachedBottom = event.reachedEdgeInfo.bottom;
+
+ if (reachedBottom) {
+ setIsFetching(true);
+ }
+ }, []);
+
+ useEffect(() => {
+ createInitialData(defaultSize);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ useEffect(() => {
+ if (isFetching) {
+ loadMoreData();
+ setIsFetching(false);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isFetching]);
+
+ return (
+
+
+
+
+ );
+};
+
+export default MainView;
diff --git a/limestone/pattern-virtualgridlist-incremental-load/src/views/README.md b/limestone/pattern-virtualgridlist-incremental-load/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/pattern-virtualgridlist-incremental-load/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/pattern-virtuallist-preserving-focus/.gitignore b/limestone/pattern-virtuallist-preserving-focus/.gitignore
new file mode 100644
index 000000000..f94ea516b
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/pattern-virtuallist-preserving-focus/LICENSE b/limestone/pattern-virtuallist-preserving-focus/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/pattern-virtuallist-preserving-focus/README.md b/limestone/pattern-virtuallist-preserving-focus/README.md
new file mode 100644
index 000000000..e07d004d0
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/README.md
@@ -0,0 +1,69 @@
+## Preserving focus in VirtualList Pattern
+
+A sample Enact application that shows how to preserve focus in `VirtualList` when navigating `Panel`s.
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+#### Enact Components Used
+- `limestone/Button`
+- `limestone/Item`
+- `limestone/Panels/Header`
+- `limestone/Panels/Panel`
+- `limestone/VirtualList`
+
+To preserve the last focus, you need to set a unique `containerId` to `VirtualList`.
+In this sample, we passed `id` prop from `MainPanel` to `VirtualList`.
+
+If you do this, `Spotlight` and `VirtualList` will recover the last focus when the `Panel` is mounting.
+
+In addition, if you want to preserve the last scroll position, you can save the scroll position from `onScrollStop` callback and restore it with `scrollTo` method.
+You may need to use redux for managing each scroll position for VirtualLists.
+
+In `PatternList` view, `containerId` is set and `onScrollStop` calls dispatching `saveLastScrollInfo` action.
+```javascript
+
+render = () => {
+ const {onScrollStop, id, ...rest} = this.props;
+ delete rest.scrollLeft;
+ delete rest.scrollTop;
+
+ return (
+
+ );
+}
+
+const mapDispatchToProps = (dispatch, {index}) => ({
+ onScrollStop: (info) => dispatch(saveLastScrollInfo(index, info))
+});
+
+```
+
+`lastScrollInfo` will be injected as props to the list and it can be restore it with `scrollTo` method.
+
+```javascript
+componentDidMount () {
+ const {scrollLeft, scrollTop} = this.props;
+ this.scrollTo({position: {x: scrollLeft, y: scrollTop}, animate: false});
+}
+
+const mapStateToProps = ({lastScrollInfo}, {index}) => {
+ const info = lastScrollInfo[index];
+ return {
+ scrollLeft: info ? info.scrollLeft : 0,
+ scrollTop: info ? info.scrollTop : 0
+ };
+};
+
+```
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/pattern-virtuallist-preserving-focus/package.json b/limestone/pattern-virtuallist-preserving-focus/package.json
new file mode 100644
index 000000000..c405d05dc
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "pattern-virtuallist-preserving-focus",
+ "version": "1.0.0",
+ "description": "An Enact application demonstrating VirtualList with preserving focus.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "VirtualList Preserving Focus Pattern",
+ "isomorphic": true,
+ "ri": {
+ "baseSize": 48
+ }
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "@reduxjs/toolkit": "^2.3.0",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-redux": "^9.1.2",
+ "redux": "^5.0.1"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/pattern-virtuallist-preserving-focus/resources/ilibmanifest.json b/limestone/pattern-virtuallist-preserving-focus/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/pattern-virtuallist-preserving-focus/src/App/App.js b/limestone/pattern-virtuallist-preserving-focus/src/App/App.js
new file mode 100644
index 000000000..ef1679d33
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/src/App/App.js
@@ -0,0 +1,50 @@
+import kind from '@enact/core/kind';
+import {Panels} from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+import PropTypes from 'prop-types';
+import {connect} from 'react-redux';
+
+import {decreaseIndex, increaseIndex} from '../store';
+import MainPanel from '../views/MainPanel';
+
+const Sample = kind({
+ name: 'App',
+
+ propTypes: {
+ index: PropTypes.number,
+ popPanel: PropTypes.func,
+ pushPanel: PropTypes.func
+ },
+
+ defaultProps: {
+ index: 0
+ },
+
+ render: ({index, pushPanel, popPanel, ...rest}) => {
+ return (
+
+
+
+
+
+
+ );
+ }
+});
+
+const mapStateToProps = ({index}) => ({
+ index
+});
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ pushPanel: () => dispatch(increaseIndex()),
+ popPanel: () => dispatch(decreaseIndex())
+ };
+};
+
+const AppBase = connect(mapStateToProps, mapDispatchToProps)(Sample);
+const App = ThemeDecorator(AppBase);
+
+export default App;
+export {App, AppBase};
diff --git a/limestone/pattern-virtuallist-preserving-focus/src/App/App.module.less b/limestone/pattern-virtuallist-preserving-focus/src/App/App.module.less
new file mode 100644
index 000000000..23782defd
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/src/App/App.module.less
@@ -0,0 +1,3 @@
+.app {
+ // styles can be put here
+}
diff --git a/limestone/pattern-virtuallist-preserving-focus/src/App/package.json b/limestone/pattern-virtuallist-preserving-focus/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/pattern-virtuallist-preserving-focus/src/components/README.md b/limestone/pattern-virtuallist-preserving-focus/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/pattern-virtuallist-preserving-focus/src/index.js b/limestone/pattern-virtuallist-preserving-focus/src/index.js
new file mode 100644
index 000000000..542a8ac2d
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/src/index.js
@@ -0,0 +1,19 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './main';
+
+const appElement = ;
+
+// In a browser environment, render the app to the document.
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/pattern-virtuallist-preserving-focus/src/main.js b/limestone/pattern-virtuallist-preserving-focus/src/main.js
new file mode 100644
index 000000000..7e713a9d7
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/src/main.js
@@ -0,0 +1,22 @@
+import {Provider} from 'react-redux';
+
+import App, {AppBase} from './App';
+import configureAppStore from './store';
+
+// set default launch path
+const store = configureAppStore();
+
+const appElementBase = () => (
+
+
+
+);
+
+const appElement = () => (
+
+
+
+);
+
+export default appElement;
+export {appElement, appElementBase};
diff --git a/limestone/pattern-virtuallist-preserving-focus/src/store/package.json b/limestone/pattern-virtuallist-preserving-focus/src/store/package.json
new file mode 100644
index 000000000..3f33f7033
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/src/store/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "store.js"
+}
diff --git a/limestone/pattern-virtuallist-preserving-focus/src/store/store.js b/limestone/pattern-virtuallist-preserving-focus/src/store/store.js
new file mode 100644
index 000000000..a96988618
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/src/store/store.js
@@ -0,0 +1,34 @@
+import {configureStore, createSlice} from '@reduxjs/toolkit';
+
+const indexSlice = createSlice({
+ name: 'indexReducer',
+ initialState: {
+ index : 0
+ },
+ reducers: {
+ increaseIndex: (state) => {
+ state.index += 1;
+ },
+ decreaseIndex: (state) => {
+ state.index = state.index > 0 ? state.index - 1 : 0;
+ }
+ }
+});
+
+export const {increaseIndex, decreaseIndex} = indexSlice.actions;
+
+export default function configureAppStore (initialState) {
+ const store = configureStore({
+ reducer: indexSlice.reducer,
+ initialState
+ });
+
+ if (module.hot) {
+ // Enable Webpack hot module replacement for reducers
+ module.hot.accept('./store.js', () => {
+ store.replaceReducer(indexSlice.reducer);
+ });
+ }
+
+ return store;
+}
diff --git a/limestone/pattern-virtuallist-preserving-focus/src/views/MainPanel.js b/limestone/pattern-virtuallist-preserving-focus/src/views/MainPanel.js
new file mode 100644
index 000000000..d4dd72096
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/src/views/MainPanel.js
@@ -0,0 +1,35 @@
+import kind from '@enact/core/kind';
+import Button from '@enact/limestone/Button';
+import {Header, Panel} from '@enact/limestone/Panels';
+import PropTypes from 'prop-types';
+
+import {PatternList} from './PatternList';
+
+const MainPanel = kind({
+ name: 'MainPanel',
+
+ propTypes: {
+ /**
+ * A function to run on click event
+ * @type {Function}
+ */
+ onClick: PropTypes.func,
+
+ /**
+ * A title string appear on header
+ * @type {String}
+ */
+ title: PropTypes.string
+ },
+
+ render: ({title, onClick, ...rest}) => (
+
+
+
+
+
+
+ )
+});
+
+export default MainPanel;
diff --git a/limestone/pattern-virtuallist-preserving-focus/src/views/PatternList.js b/limestone/pattern-virtuallist-preserving-focus/src/views/PatternList.js
new file mode 100644
index 000000000..ac8cd7c58
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/src/views/PatternList.js
@@ -0,0 +1,37 @@
+import Item from '@enact/limestone/Item';
+import VirtualList from '@enact/limestone/VirtualList';
+import ri from '@enact/ui/resolution';
+import PropTypes from 'prop-types';
+import {useCallback} from 'react';
+
+import css from './PatternList.module.less';
+
+const items = Array.from(new Array(1000)).map((n, i) => `Item ${('00' + i).slice(-3)}`);
+
+const PatternList = ({id, onClick, ...rest}) => {
+ const renderItem = useCallback(({index, ...restProps}) => (
+
+ {items[index]}
+
+ ), [onClick]);
+
+ return (
+
+ );
+};
+
+PatternList.propTypes = {
+ id: PropTypes.string,
+ onClick: PropTypes.func
+};
+
+export default PatternList;
+export {PatternList};
diff --git a/limestone/pattern-virtuallist-preserving-focus/src/views/PatternList.module.less b/limestone/pattern-virtuallist-preserving-focus/src/views/PatternList.module.less
new file mode 100644
index 000000000..6604bc594
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/src/views/PatternList.module.less
@@ -0,0 +1,5 @@
+/* PatternList.module.less */
+
+.list {
+ height: 720px;
+}
diff --git a/limestone/pattern-virtuallist-preserving-focus/src/views/README.md b/limestone/pattern-virtuallist-preserving-focus/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/pattern-virtuallist-preserving-focus/webos-meta/appinfo.json b/limestone/pattern-virtuallist-preserving-focus/webos-meta/appinfo.json
new file mode 100644
index 000000000..136274c72
--- /dev/null
+++ b/limestone/pattern-virtuallist-preserving-focus/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.pattern-virtuallist-preserving-focus",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "VirtualList Preserving Focus Pattern",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/pattern-virtuallist-preserving-focus/webos-meta/icon-large.png b/limestone/pattern-virtuallist-preserving-focus/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/pattern-virtuallist-preserving-focus/webos-meta/icon-large.png differ
diff --git a/limestone/pattern-virtuallist-preserving-focus/webos-meta/icon-mini.png b/limestone/pattern-virtuallist-preserving-focus/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/pattern-virtuallist-preserving-focus/webos-meta/icon-mini.png differ
diff --git a/limestone/pattern-virtuallist-preserving-focus/webos-meta/icon.png b/limestone/pattern-virtuallist-preserving-focus/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/pattern-virtuallist-preserving-focus/webos-meta/icon.png differ
diff --git a/limestone/tutorial-hello-enact/.gitignore b/limestone/tutorial-hello-enact/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/tutorial-hello-enact/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/tutorial-hello-enact/LICENSE b/limestone/tutorial-hello-enact/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/tutorial-hello-enact/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/tutorial-hello-enact/README.md b/limestone/tutorial-hello-enact/README.md
new file mode 100644
index 000000000..e048055fd
--- /dev/null
+++ b/limestone/tutorial-hello-enact/README.md
@@ -0,0 +1,9 @@
+## Hello, Enact
+
+The completed Hello, Enact app from the Enact tutorial.
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/tutorial-hello-enact/package.json b/limestone/tutorial-hello-enact/package.json
new file mode 100644
index 000000000..e9f68258c
--- /dev/null
+++ b/limestone/tutorial-hello-enact/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "tutorial-hello-enact",
+ "version": "1.0.0",
+ "description": "Most basic app for getting started with Enact.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "Hello, Enact",
+ "isomorphic": true,
+ "theme": "limestone"
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/tutorial-hello-enact/resources/ilibmanifest.json b/limestone/tutorial-hello-enact/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/tutorial-hello-enact/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/tutorial-hello-enact/src/App/App.js b/limestone/tutorial-hello-enact/src/App/App.js
new file mode 100644
index 000000000..fdb1e9400
--- /dev/null
+++ b/limestone/tutorial-hello-enact/src/App/App.js
@@ -0,0 +1,29 @@
+import kind from '@enact/core/kind';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+
+import css from './App.module.less';
+
+const AppBase = kind({
+ name: 'App',
+
+ styles: {
+ css,
+ className: 'app'
+ },
+
+ render: function (props) {
+ return (
+
+ Hello Enact!
+
+ );
+ }
+});
+
+const App = ThemeDecorator(AppBase);
+
+export default App;
+export {
+ App,
+ AppBase
+};
diff --git a/limestone/tutorial-hello-enact/src/App/App.module.less b/limestone/tutorial-hello-enact/src/App/App.module.less
new file mode 100644
index 000000000..ea7063440
--- /dev/null
+++ b/limestone/tutorial-hello-enact/src/App/App.module.less
@@ -0,0 +1,3 @@
+.app {
+ font-size: 48px;
+}
diff --git a/limestone/tutorial-hello-enact/src/App/package.json b/limestone/tutorial-hello-enact/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/tutorial-hello-enact/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/tutorial-hello-enact/src/components/README.md b/limestone/tutorial-hello-enact/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/tutorial-hello-enact/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/tutorial-hello-enact/src/index.js b/limestone/tutorial-hello-enact/src/index.js
new file mode 100644
index 000000000..57c20e6fd
--- /dev/null
+++ b/limestone/tutorial-hello-enact/src/index.js
@@ -0,0 +1,19 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './App';
+
+const appElement = ();
+
+// In a browser environment, render the app to the document.
+if (typeof window !== 'undefined') {
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/tutorial-hello-enact/src/views/README.md b/limestone/tutorial-hello-enact/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/tutorial-hello-enact/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/tutorial-hello-enact/webos-meta/appinfo.json b/limestone/tutorial-hello-enact/webos-meta/appinfo.json
new file mode 100644
index 000000000..8fa00b30d
--- /dev/null
+++ b/limestone/tutorial-hello-enact/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.tutorial-hello-enact",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Hello, Enact",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/tutorial-hello-enact/webos-meta/icon-large.png b/limestone/tutorial-hello-enact/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/tutorial-hello-enact/webos-meta/icon-large.png differ
diff --git a/limestone/tutorial-hello-enact/webos-meta/icon-mini.png b/limestone/tutorial-hello-enact/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/tutorial-hello-enact/webos-meta/icon-mini.png differ
diff --git a/limestone/tutorial-hello-enact/webos-meta/icon.png b/limestone/tutorial-hello-enact/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/tutorial-hello-enact/webos-meta/icon.png differ
diff --git a/limestone/tutorial-kitten-browser/.gitignore b/limestone/tutorial-kitten-browser/.gitignore
new file mode 100644
index 000000000..49c033854
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/.gitignore
@@ -0,0 +1,15 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+
+# testing
+coverage
+
+# production
+build
+dist
+
+# misc
+.DS_Store
+npm-debug.log
diff --git a/limestone/tutorial-kitten-browser/LICENSE b/limestone/tutorial-kitten-browser/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/limestone/tutorial-kitten-browser/README.md b/limestone/tutorial-kitten-browser/README.md
new file mode 100644
index 000000000..79dbc3406
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/README.md
@@ -0,0 +1,9 @@
+## Kitten Browser
+
+The completed Kitten Browser app from the Enact tutorial.
+
+Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser.
+
+---
+
+This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli).
diff --git a/limestone/tutorial-kitten-browser/package.json b/limestone/tutorial-kitten-browser/package.json
new file mode 100644
index 000000000..3219c6093
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "tutorial-kitten-browser",
+ "version": "1.0.0",
+ "description": "A kitten browser app to introduce some intermediate ES6, React, and Enact concepts.",
+ "author": "",
+ "main": "src/index.js",
+ "scripts": {
+ "serve": "enact serve",
+ "pack": "enact pack",
+ "pack-p": "enact pack -p",
+ "watch": "enact pack --watch",
+ "clean": "enact clean",
+ "lint": "enact lint --strict .",
+ "license": "enact license",
+ "test": "enact test",
+ "test-watch": "enact test --watch"
+ },
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": "https://github.com/enactjs/samples",
+ "enact": {
+ "title": "Kitten Browser",
+ "isomorphic": true,
+ "theme": "limestone"
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "dependencies": {
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
+ "@enact/webos": "^5.0.0-alpha.4",
+ "ilib": "^14.21.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.9"
+ }
+}
diff --git a/limestone/tutorial-kitten-browser/resources/ilibmanifest.json b/limestone/tutorial-kitten-browser/resources/ilibmanifest.json
new file mode 100644
index 000000000..d946318dc
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/limestone/tutorial-kitten-browser/src/App/App.js b/limestone/tutorial-kitten-browser/src/App/App.js
new file mode 100644
index 000000000..e0ea04387
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/src/App/App.js
@@ -0,0 +1,75 @@
+import kind from '@enact/core/kind';
+import {Panels} from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
+import Changeable from '@enact/ui/Changeable';
+import PropTypes from 'prop-types';
+
+import Detail from '../views/Detail';
+import List from '../views/List';
+
+const kittens = [
+ 'Garfield',
+ 'Nermal',
+ 'Simba',
+ 'Nala',
+ 'Tiger',
+ 'Kitty'
+];
+
+const Sample = kind({
+ name: 'App',
+
+ propTypes: {
+ kittenIndex: PropTypes.number,
+ onKittenIndexChange: PropTypes.func,
+ onPanelIndexChange: PropTypes.func,
+ panelIndex: PropTypes.number
+ },
+
+ defaultProps: {
+ kittenIndex: 0,
+ panelIndex: 0
+ },
+
+ handlers: {
+ onSelectKitten: (ev, {onKittenIndexChange, onPanelIndexChange}) => {
+ if (onKittenIndexChange) {
+ onKittenIndexChange({
+ kittenIndex: ev.index
+ });
+ }
+
+ // navigate to the detail panel on selection
+ if (onPanelIndexChange) {
+ onPanelIndexChange({
+ panelIndex: 1
+ });
+ }
+ }
+ },
+
+ render: ({kittenIndex, onPanelIndexChange, onSelectKitten, panelIndex, ...rest}) => {
+ delete rest.onKittenIndexChange;
+
+ return (
+
+ {kittens}
+
+
+ );
+ }
+});
+
+const AppBase = Changeable({prop: 'panelIndex', change: 'onPanelIndexChange'},
+ Changeable({prop: 'kittenIndex', change: 'onKittenIndexChange'},
+ Sample
+ )
+);
+
+const App = ThemeDecorator(AppBase);
+
+export default App;
+export {
+ App,
+ AppBase
+};
diff --git a/limestone/tutorial-kitten-browser/src/App/App.module.less b/limestone/tutorial-kitten-browser/src/App/App.module.less
new file mode 100644
index 000000000..f9db12c85
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/src/App/App.module.less
@@ -0,0 +1,3 @@
+.app {
+ /* styles can be put here */
+}
diff --git a/limestone/tutorial-kitten-browser/src/App/package.json b/limestone/tutorial-kitten-browser/src/App/package.json
new file mode 100644
index 000000000..bf7e48160
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/limestone/tutorial-kitten-browser/src/components/Kitten/Kitten.js b/limestone/tutorial-kitten-browser/src/components/Kitten/Kitten.js
new file mode 100644
index 000000000..8711aeb90
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/src/components/Kitten/Kitten.js
@@ -0,0 +1,61 @@
+import kind from '@enact/core/kind';
+import Spottable from '@enact/spotlight/Spottable';
+import PropTypes from 'prop-types';
+
+import css from './Kitten.module.less';
+
+const SpottableDiv = Spottable('div');
+
+const KittenBase = kind({
+ name: 'Kitten',
+
+ propTypes: {
+ children: PropTypes.string,
+ index: PropTypes.number,
+ onSelect: PropTypes.func,
+ size: PropTypes.number
+ },
+
+ defaultProps: {
+ size: 300
+ },
+
+ styles: {
+ css,
+ className: 'kitten'
+ },
+
+ handlers: {
+ handleClick: (ev, {index, onSelect}) => {
+ if (onSelect) {
+ onSelect({index});
+ }
+ }
+ },
+
+ computed: {
+ url: ({index, size}) => {
+ return `//loremflickr.com/${size}/${size}/kitten?random=${index}`;
+ }
+ },
+
+ render: ({children, handleClick, size, url, ...rest}) => {
+ delete rest.index;
+ delete rest.onSelect;
+
+ return (
+
+
+
{children}
+
+ );
+ }
+});
+
+const Kitten = KittenBase;
+
+export default Kitten;
+export {
+ Kitten,
+ KittenBase
+};
diff --git a/limestone/tutorial-kitten-browser/src/components/Kitten/Kitten.module.less b/limestone/tutorial-kitten-browser/src/components/Kitten/Kitten.module.less
new file mode 100644
index 000000000..79a2a5402
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/src/components/Kitten/Kitten.module.less
@@ -0,0 +1,12 @@
+@import "~@enact/limestone/styles/mixins.less";
+
+.kitten {
+ display: inline-block;
+ padding: 24px;
+ text-align: center;
+ background-color: transparent;
+ .focus({
+ background-color: #e6e6e6; // light-grey
+ color: black;
+ });
+}
diff --git a/limestone/tutorial-kitten-browser/src/components/Kitten/package.json b/limestone/tutorial-kitten-browser/src/components/Kitten/package.json
new file mode 100644
index 000000000..048345b89
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/src/components/Kitten/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "Kitten.js"
+}
diff --git a/limestone/tutorial-kitten-browser/src/components/Kitten/tests/Kitten-specs.js b/limestone/tutorial-kitten-browser/src/components/Kitten/tests/Kitten-specs.js
new file mode 100644
index 000000000..e53666eb3
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/src/components/Kitten/tests/Kitten-specs.js
@@ -0,0 +1,34 @@
+import '@testing-library/jest-dom';
+import {render, screen} from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import Kitten from '../Kitten';
+
+describe('Kitten Specs', () => {
+ test('should render a Kitten with content', function () {
+ const content = 'Hello Kitten!';
+
+ render({content});
+
+ const actual = screen.getByText(content);
+
+ expect(actual).toBeInTheDocument();
+ });
+
+ test('should callback with index when clicked', async function () {
+ let index = 0;
+ const handleSelect = jest.fn();
+ const user = userEvent.setup();
+
+ render();
+
+ const kitten = screen.getByTestId('kitten');
+
+ await user.click(kitten);
+
+ const expected = index;
+ const actual = handleSelect.mock.calls[0][0].index;
+
+ expect(actual).toEqual(expected);
+ });
+});
diff --git a/limestone/tutorial-kitten-browser/src/components/README.md b/limestone/tutorial-kitten-browser/src/components/README.md
new file mode 100644
index 000000000..b1a7853e3
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/src/components/README.md
@@ -0,0 +1 @@
+Reusable components for your application go here
\ No newline at end of file
diff --git a/limestone/tutorial-kitten-browser/src/index.js b/limestone/tutorial-kitten-browser/src/index.js
new file mode 100644
index 000000000..191b6cf24
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/src/index.js
@@ -0,0 +1,20 @@
+/* global ENACT_PACK_ISOMORPHIC */
+import {createRoot, hydrateRoot} from 'react-dom/client';
+
+import App from './App';
+
+const appElement = ();
+
+// In a browser environment, render the app to the document.
+if (typeof window !== 'undefined') {
+ // 'root' div is provided by Enact's HTML template
+ const container = document.getElementById('root');
+
+ if (ENACT_PACK_ISOMORPHIC) {
+ hydrateRoot(container, appElement);
+ } else {
+ createRoot(container).render(appElement);
+ }
+}
+
+export default appElement;
diff --git a/limestone/tutorial-kitten-browser/src/views/Detail.js b/limestone/tutorial-kitten-browser/src/views/Detail.js
new file mode 100644
index 000000000..6900f2dbf
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/src/views/Detail.js
@@ -0,0 +1,40 @@
+import kind from '@enact/core/kind';
+import {Header, Panel} from '@enact/limestone/Panels';
+import PropTypes from 'prop-types';
+
+const genders = {
+ m: 'Male',
+ f: 'Female'
+};
+
+const DetailBase = kind({
+ name: 'Detail',
+
+ propTypes: {
+ color: PropTypes.string,
+ gender: PropTypes.oneOf(['m', 'f']),
+ name: PropTypes.string,
+ weight: PropTypes.number
+ },
+
+ defaultProps: {
+ color: 'Tabby',
+ gender: 'm',
+ weight: 9
+ },
+
+ render: ({color, gender, name, weight, ...rest}) => (
+
+
+
Gender: {genders[gender]}
+
Color: {color}
+
Weight: {weight}oz
+
+ )
+});
+
+export default DetailBase;
+export {
+ DetailBase as Detail,
+ DetailBase
+};
diff --git a/limestone/tutorial-kitten-browser/src/views/List.js b/limestone/tutorial-kitten-browser/src/views/List.js
new file mode 100644
index 000000000..11b7b4ff7
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/src/views/List.js
@@ -0,0 +1,33 @@
+import kind from '@enact/core/kind';
+import {Header, Panel} from '@enact/limestone/Panels';
+import Scroller from '@enact/limestone/Scroller';
+import Repeater from '@enact/ui/Repeater';
+import PropTypes from 'prop-types';
+
+import Kitten from '../components/Kitten';
+
+const ListBase = kind({
+ name: 'List',
+
+ propTypes: {
+ children: PropTypes.array,
+ onSelectKitten: PropTypes.func
+ },
+
+ render: ({children, onSelectKitten, ...rest}) => (
+
+
+
+
+ {children}
+
+
+
+ )
+});
+
+export default ListBase;
+export {
+ ListBase as List,
+ ListBase
+};
diff --git a/limestone/tutorial-kitten-browser/src/views/README.md b/limestone/tutorial-kitten-browser/src/views/README.md
new file mode 100644
index 000000000..e18ab3d1c
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/src/views/README.md
@@ -0,0 +1 @@
+Composite components that make up a distinct view go here
\ No newline at end of file
diff --git a/limestone/tutorial-kitten-browser/webos-meta/appinfo.json b/limestone/tutorial-kitten-browser/webos-meta/appinfo.json
new file mode 100644
index 000000000..ea9ab5084
--- /dev/null
+++ b/limestone/tutorial-kitten-browser/webos-meta/appinfo.json
@@ -0,0 +1,12 @@
+{
+ "id": "com.enactjs.app.tutorial-kitten-browser",
+ "version": "1.0.0",
+ "vendor": "LGE-SVL",
+ "type": "web",
+ "main": "index.html",
+ "title": "Kitten Browser",
+ "icon": "icon.png",
+ "miniicon": "icon-mini.png",
+ "largeIcon": "icon-large.png",
+ "uiRevision": 2
+}
diff --git a/limestone/tutorial-kitten-browser/webos-meta/icon-large.png b/limestone/tutorial-kitten-browser/webos-meta/icon-large.png
new file mode 100644
index 000000000..d237e9fb1
Binary files /dev/null and b/limestone/tutorial-kitten-browser/webos-meta/icon-large.png differ
diff --git a/limestone/tutorial-kitten-browser/webos-meta/icon-mini.png b/limestone/tutorial-kitten-browser/webos-meta/icon-mini.png
new file mode 100644
index 000000000..9771fac4c
Binary files /dev/null and b/limestone/tutorial-kitten-browser/webos-meta/icon-mini.png differ
diff --git a/limestone/tutorial-kitten-browser/webos-meta/icon.png b/limestone/tutorial-kitten-browser/webos-meta/icon.png
new file mode 100644
index 000000000..e616273a2
Binary files /dev/null and b/limestone/tutorial-kitten-browser/webos-meta/icon.png differ
diff --git a/tutorial-typescript/package.json b/tutorial-typescript/package.json
index 86b0ae706..dfd6000de 100644
--- a/tutorial-typescript/package.json
+++ b/tutorial-typescript/package.json
@@ -19,7 +19,7 @@
"private": true,
"repository": "",
"enact": {
- "theme": "sandstone"
+ "theme": "limestone"
},
"eslintConfig": {
"extends": "enact-proxy"
@@ -30,21 +30,21 @@
"dist/*"
],
"dependencies": {
- "@enact/core": "^4.9.4",
- "@enact/i18n": "^4.9.4",
- "@enact/sandstone": "^2.9.5",
- "@enact/spotlight": "^4.9.4",
- "@enact/ui": "^4.9.4",
+ "@enact/core": "^5.0.0-alpha.4",
+ "@enact/i18n": "^5.0.0-alpha.4",
+ "@enact/limestone": "enactjs/limestone",
+ "@enact/spotlight": "^5.0.0-alpha.4",
+ "@enact/ui": "^5.0.0-alpha.4",
"@types/node": "^22.9.1",
"@types/react": "^19.0.7",
"@types/react-dom": "^19.0.3",
- "ilib": "^14.20.0",
+ "ilib": "^14.21.0",
"prop-types": "^15.8.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"typescript": "^5.6.3"
},
"devDependencies": {
- "eslint-config-enact-proxy": "^1.0.8"
+ "eslint-config-enact-proxy": "^1.0.9"
}
}
diff --git a/tutorial-typescript/src/App/App.tsx b/tutorial-typescript/src/App/App.tsx
index 950a682eb..df98f9409 100644
--- a/tutorial-typescript/src/App/App.tsx
+++ b/tutorial-typescript/src/App/App.tsx
@@ -1,6 +1,6 @@
import kind from '@enact/core/kind';
-import Panels from '@enact/sandstone/Panels';
-import ThemeDecorator from '@enact/sandstone/ThemeDecorator';
+import Panels from '@enact/limestone/Panels';
+import ThemeDecorator from '@enact/limestone/ThemeDecorator';
import MainPanel from '../views/MainPanel';
diff --git a/tutorial-typescript/src/components/Counter/Counter.tsx b/tutorial-typescript/src/components/Counter/Counter.tsx
index 123c6b991..99d7dd955 100644
--- a/tutorial-typescript/src/components/Counter/Counter.tsx
+++ b/tutorial-typescript/src/components/Counter/Counter.tsx
@@ -1,6 +1,6 @@
import {adaptEvent, forward, handle} from '@enact/core/handle';
import kind from '@enact/core/kind';
-import Button from '@enact/sandstone/Button';
+import Button from '@enact/limestone/Button';
import Changeable from '@enact/ui/Changeable';
interface Props {
diff --git a/tutorial-typescript/src/views/MainPanel.tsx b/tutorial-typescript/src/views/MainPanel.tsx
index eacaf613c..f8435ed56 100644
--- a/tutorial-typescript/src/views/MainPanel.tsx
+++ b/tutorial-typescript/src/views/MainPanel.tsx
@@ -1,5 +1,5 @@
import kind from '@enact/core/kind';
-import {Header, Panel} from '@enact/sandstone/Panels';
+import {Header, Panel} from '@enact/limestone/Panels';
// Custom component
import Counter from '../components/Counter';