diff --git a/sandstone/custom-skin/.gitignore b/sandstone/feature-custom-skin-generator/.gitignore
similarity index 100%
rename from sandstone/custom-skin/.gitignore
rename to sandstone/feature-custom-skin-generator/.gitignore
diff --git a/sandstone/custom-skin/README-devs.md b/sandstone/feature-custom-skin-generator/README-devs.md
similarity index 100%
rename from sandstone/custom-skin/README-devs.md
rename to sandstone/feature-custom-skin-generator/README-devs.md
diff --git a/sandstone/custom-skin/README.md b/sandstone/feature-custom-skin-generator/README.md
similarity index 100%
rename from sandstone/custom-skin/README.md
rename to sandstone/feature-custom-skin-generator/README.md
diff --git a/sandstone/custom-skin/package.json b/sandstone/feature-custom-skin-generator/package.json
similarity index 96%
rename from sandstone/custom-skin/package.json
rename to sandstone/feature-custom-skin-generator/package.json
index 5e6de64c5..ca38ed127 100644
--- a/sandstone/custom-skin/package.json
+++ b/sandstone/feature-custom-skin-generator/package.json
@@ -1,5 +1,5 @@
{
- "name": "custom-skin",
+ "name": "custom-skin-generator",
"version": "1.0.0",
"description": "A general template for an Enact Sandstone application.",
"author": "",
diff --git a/sandstone/custom-skin/resources/ilibmanifest.json b/sandstone/feature-custom-skin-generator/resources/ilibmanifest.json
similarity index 100%
rename from sandstone/custom-skin/resources/ilibmanifest.json
rename to sandstone/feature-custom-skin-generator/resources/ilibmanifest.json
diff --git a/sandstone/custom-skin/screenTypes.json b/sandstone/feature-custom-skin-generator/screenTypes.json
similarity index 100%
rename from sandstone/custom-skin/screenTypes.json
rename to sandstone/feature-custom-skin-generator/screenTypes.json
diff --git a/sandstone/custom-skin/src/App/App.js b/sandstone/feature-custom-skin-generator/src/App/App.js
similarity index 100%
rename from sandstone/custom-skin/src/App/App.js
rename to sandstone/feature-custom-skin-generator/src/App/App.js
diff --git a/sandstone/custom-skin/src/App/App.module.less b/sandstone/feature-custom-skin-generator/src/App/App.module.less
similarity index 100%
rename from sandstone/custom-skin/src/App/App.module.less
rename to sandstone/feature-custom-skin-generator/src/App/App.module.less
diff --git a/sandstone/custom-skin/src/App/package.json b/sandstone/feature-custom-skin-generator/src/App/package.json
similarity index 100%
rename from sandstone/custom-skin/src/App/package.json
rename to sandstone/feature-custom-skin-generator/src/App/package.json
diff --git a/sandstone/custom-skin/src/common/styles.module.less b/sandstone/feature-custom-skin-generator/src/common/styles.module.less
similarity index 100%
rename from sandstone/custom-skin/src/common/styles.module.less
rename to sandstone/feature-custom-skin-generator/src/common/styles.module.less
diff --git a/sandstone/custom-skin/src/components/AutoPopup/AutoPopup.js b/sandstone/feature-custom-skin-generator/src/components/AutoPopup/AutoPopup.js
similarity index 100%
rename from sandstone/custom-skin/src/components/AutoPopup/AutoPopup.js
rename to sandstone/feature-custom-skin-generator/src/components/AutoPopup/AutoPopup.js
diff --git a/sandstone/custom-skin/src/components/ColorField/ColorField.js b/sandstone/feature-custom-skin-generator/src/components/ColorField/ColorField.js
similarity index 100%
rename from sandstone/custom-skin/src/components/ColorField/ColorField.js
rename to sandstone/feature-custom-skin-generator/src/components/ColorField/ColorField.js
diff --git a/sandstone/custom-skin/src/components/ColorFields/ColorFields.js b/sandstone/feature-custom-skin-generator/src/components/ColorFields/ColorFields.js
similarity index 100%
rename from sandstone/custom-skin/src/components/ColorFields/ColorFields.js
rename to sandstone/feature-custom-skin-generator/src/components/ColorFields/ColorFields.js
diff --git a/sandstone/custom-skin/src/components/ColorPicker/ColorPicker.js b/sandstone/feature-custom-skin-generator/src/components/ColorPicker/ColorPicker.js
similarity index 100%
rename from sandstone/custom-skin/src/components/ColorPicker/ColorPicker.js
rename to sandstone/feature-custom-skin-generator/src/components/ColorPicker/ColorPicker.js
diff --git a/sandstone/custom-skin/src/components/ColorPicker/ColorPicker.module.less b/sandstone/feature-custom-skin-generator/src/components/ColorPicker/ColorPicker.module.less
similarity index 100%
rename from sandstone/custom-skin/src/components/ColorPicker/ColorPicker.module.less
rename to sandstone/feature-custom-skin-generator/src/components/ColorPicker/ColorPicker.module.less
diff --git a/sandstone/custom-skin/src/components/ImportSkin/ImportSkin.js b/sandstone/feature-custom-skin-generator/src/components/ImportSkin/ImportSkin.js
similarity index 100%
rename from sandstone/custom-skin/src/components/ImportSkin/ImportSkin.js
rename to sandstone/feature-custom-skin-generator/src/components/ImportSkin/ImportSkin.js
diff --git a/sandstone/custom-skin/src/components/ImportSkin/ImportSkin.module.less b/sandstone/feature-custom-skin-generator/src/components/ImportSkin/ImportSkin.module.less
similarity index 100%
rename from sandstone/custom-skin/src/components/ImportSkin/ImportSkin.module.less
rename to sandstone/feature-custom-skin-generator/src/components/ImportSkin/ImportSkin.module.less
diff --git a/sandstone/custom-skin/src/components/NameField/NameField.js b/sandstone/feature-custom-skin-generator/src/components/NameField/NameField.js
similarity index 100%
rename from sandstone/custom-skin/src/components/NameField/NameField.js
rename to sandstone/feature-custom-skin-generator/src/components/NameField/NameField.js
diff --git a/sandstone/custom-skin/src/components/NameField/NameField.module.less b/sandstone/feature-custom-skin-generator/src/components/NameField/NameField.module.less
similarity index 100%
rename from sandstone/custom-skin/src/components/NameField/NameField.module.less
rename to sandstone/feature-custom-skin-generator/src/components/NameField/NameField.module.less
diff --git a/sandstone/custom-skin/src/components/OutputField/OutputField.js b/sandstone/feature-custom-skin-generator/src/components/OutputField/OutputField.js
similarity index 100%
rename from sandstone/custom-skin/src/components/OutputField/OutputField.js
rename to sandstone/feature-custom-skin-generator/src/components/OutputField/OutputField.js
diff --git a/sandstone/custom-skin/src/components/OutputField/OutputField.module.less b/sandstone/feature-custom-skin-generator/src/components/OutputField/OutputField.module.less
similarity index 100%
rename from sandstone/custom-skin/src/components/OutputField/OutputField.module.less
rename to sandstone/feature-custom-skin-generator/src/components/OutputField/OutputField.module.less
diff --git a/sandstone/custom-skin/src/components/SingleField/SingleField.js b/sandstone/feature-custom-skin-generator/src/components/SingleField/SingleField.js
similarity index 100%
rename from sandstone/custom-skin/src/components/SingleField/SingleField.js
rename to sandstone/feature-custom-skin-generator/src/components/SingleField/SingleField.js
diff --git a/sandstone/custom-skin/src/components/SingleField/SingleField.module.less b/sandstone/feature-custom-skin-generator/src/components/SingleField/SingleField.module.less
similarity index 100%
rename from sandstone/custom-skin/src/components/SingleField/SingleField.module.less
rename to sandstone/feature-custom-skin-generator/src/components/SingleField/SingleField.module.less
diff --git a/sandstone/custom-skin/src/components/TripleField/TripleField.js b/sandstone/feature-custom-skin-generator/src/components/TripleField/TripleField.js
similarity index 100%
rename from sandstone/custom-skin/src/components/TripleField/TripleField.js
rename to sandstone/feature-custom-skin-generator/src/components/TripleField/TripleField.js
diff --git a/sandstone/custom-skin/src/components/TripleField/TripleField.module.less b/sandstone/feature-custom-skin-generator/src/components/TripleField/TripleField.module.less
similarity index 100%
rename from sandstone/custom-skin/src/components/TripleField/TripleField.module.less
rename to sandstone/feature-custom-skin-generator/src/components/TripleField/TripleField.module.less
diff --git a/sandstone/custom-skin/src/constants.js b/sandstone/feature-custom-skin-generator/src/constants.js
similarity index 100%
rename from sandstone/custom-skin/src/constants.js
rename to sandstone/feature-custom-skin-generator/src/constants.js
diff --git a/sandstone/custom-skin/src/index.js b/sandstone/feature-custom-skin-generator/src/index.js
similarity index 100%
rename from sandstone/custom-skin/src/index.js
rename to sandstone/feature-custom-skin-generator/src/index.js
diff --git a/sandstone/custom-skin/src/utils.js b/sandstone/feature-custom-skin-generator/src/utils.js
similarity index 100%
rename from sandstone/custom-skin/src/utils.js
rename to sandstone/feature-custom-skin-generator/src/utils.js
diff --git a/sandstone/custom-skin/src/views/MainPanel.js b/sandstone/feature-custom-skin-generator/src/views/MainPanel.js
similarity index 100%
rename from sandstone/custom-skin/src/views/MainPanel.js
rename to sandstone/feature-custom-skin-generator/src/views/MainPanel.js
diff --git a/sandstone/custom-skin/src/views/MainPanel.module.less b/sandstone/feature-custom-skin-generator/src/views/MainPanel.module.less
similarity index 100%
rename from sandstone/custom-skin/src/views/MainPanel.module.less
rename to sandstone/feature-custom-skin-generator/src/views/MainPanel.module.less
diff --git a/sandstone/pattern-ls2request-custom-colors/.gitignore b/sandstone/pattern-ls2request-custom-colors/.gitignore
new file mode 100644
index 000000000..f94ea516b
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/.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/sandstone/pattern-ls2request-custom-colors/README.md b/sandstone/pattern-ls2request-custom-colors/README.md
new file mode 100644
index 000000000..3258cc0e9
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/README.md
@@ -0,0 +1,20 @@
+## Custom Colors Generator
+
+A sample Enact application that uses dynamic color change feature to style components and create a personalized theme.
+
+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 choose a preset theme and/or customize the Sandstone UI components for your application.
+On the left side of the app, you can see all the presets and/or customizable color fields, while on the right side is the `Theme Preview` area.
+Any value changes you make to the color pickers will be reflected in `Theme Preview`.
+
+#### Customize Themes
+
+The radio buttons on the first Panel allow you to choose a preset, including Sandstone default theme.
+You can also choose to activate dynamic color mode which will modify the luminosity and saturation of your theme colors depending on the current time.
+In addition to this, you can opt to adjust skin automatically, which means that the system will choose between Sandstone neutral and light modes according to the colors you have set.
+On the second Panel you can interact with colors by clicking the colored circle which will open the basic color picker.
+
+#### Reset Theme
+
+The `Reset` button will revert all the changes to the active selected preset.
diff --git a/sandstone/pattern-ls2request-custom-colors/package.json b/sandstone/pattern-ls2request-custom-colors/package.json
new file mode 100644
index 000000000..c176da753
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/package.json
@@ -0,0 +1,49 @@
+{
+ "name": "pattern-ls2request-custom-colors",
+ "version": "1.0.0",
+ "description": "A general template for an Enact Sandstone 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": "sandstone"
+ },
+ "eslintConfig": {
+ "extends": "enact-proxy/strict"
+ },
+ "eslintIgnore": [
+ "node_modules/*",
+ "build/*",
+ "dist/*"
+ ],
+ "dependencies": {
+ "@enact/core": "^4.7.5",
+ "@enact/i18n": "^4.7.5",
+ "@enact/sandstone": "^2.7.9",
+ "@enact/spotlight": "^4.7.5",
+ "@enact/ui": "^4.7.5",
+ "@enact/webos": "^4.7.2",
+ "classnames": "^2.3.2",
+ "ilib": "^14.18.0",
+ "prop-types": "^15.8.1",
+ "ramda": "^0.29.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "eslint-config-enact-proxy": "^1.0.6"
+ }
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/resources/ilibmanifest.json b/sandstone/pattern-ls2request-custom-colors/resources/ilibmanifest.json
new file mode 100644
index 000000000..b736f135b
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/resources/ilibmanifest.json
@@ -0,0 +1,3 @@
+{
+ "files": []
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/screenTypes.json b/sandstone/pattern-ls2request-custom-colors/screenTypes.json
new file mode 100644
index 000000000..4a66543a8
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/screenTypes.json
@@ -0,0 +1,10 @@
+[
+ {"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": "wqhd", "pxPerRem": 32, "width": 3440, "height": 1440, "aspectRatioName": "cinema"},
+ {"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/sandstone/pattern-ls2request-custom-colors/src/App/App.js b/sandstone/pattern-ls2request-custom-colors/src/App/App.js
new file mode 100644
index 000000000..68c0de603
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/App/App.js
@@ -0,0 +1,82 @@
+import ThemeDecorator from '@enact/sandstone/ThemeDecorator';
+import LS2Request from '@enact/webos/LS2Request';
+import {useEffect, useState} from 'react';
+
+import {generateStylesheet} from '../hooks/generateStylesheet';
+import MainView from '../views/MainView';
+import screenTypes from '../../screenTypes.json';
+
+import {AppContext, customColorsContext} from '../constants';
+
+const request = new LS2Request();
+
+const defaultKeyThemeValue = JSON.stringify({
+ "version": "0.1",
+ "activeTheme": "defaultTheme",
+ "dynamicColor": "off",
+ "handleSkin": "off",
+ "backgroundColor": "#000000",
+ "componentBackgroundColor": "#7D848C",
+ "focusBackgroundColor": "#E6E6E6",
+ "popupBackgroundColor": "#575E66",
+ "subtitleTextColor": "#ABAEB3",
+ "textColor": "#E6E6E6",
+ colors: generateStylesheet(
+ "#000000",
+ "#7D848C",
+ "#E6E6E6",
+ "#575E66",
+ "#ABAEB3",
+ "#E6E6E6"
+ )
+});
+
+const App = (props) => {
+ const [context, setContext] = useState(customColorsContext);
+ const [responseStatus, setResponseStatus] = useState(false);
+
+ useEffect(() => {
+ // check if app is running on webOS system
+ if (typeof window === 'object' && window.webOSSystem && window.webOSSystem.launchParams) {
+ // make a GET call to service settings to check the value of `theme` key
+ request.send({
+ service: 'luna://com.webos.service.settings/',
+ method: 'getSystemSettings',
+ parameters: {
+ category: 'customUi',
+ keys: ['theme']
+ },
+ onSuccess: (res) => {
+ setResponseStatus(res.returnValue);
+ // if `theme` key is populated, update the context with key value
+ if (res.settings.theme !== '' && res) {
+ const parsedKeyData = JSON.parse(res.settings.theme);
+ setContext({...parsedKeyData});
+ // if `theme` key is an empty string, update the context with a default value, then make a SET call to service settings and set
+ // `theme` key with a default value
+ } else if (res.settings.theme === '') {
+ setContext(JSON.parse(defaultKeyThemeValue));
+ request.send({
+ service: 'luna://com.webos.service.settings/',
+ method: 'setSystemSettings',
+ parameters: {
+ category: 'customUi',
+ settings: {
+ theme: defaultKeyThemeValue
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+ }, []);
+
+ return (
+
+
+
+ );
+};
+
+export default ThemeDecorator({ri: {screenTypes}}, App);
diff --git a/sandstone/pattern-ls2request-custom-colors/src/App/App.module.less b/sandstone/pattern-ls2request-custom-colors/src/App/App.module.less
new file mode 100644
index 000000000..2774c5018
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/App/App.module.less
@@ -0,0 +1,3 @@
+.app {
+ // general app styles
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/src/App/package.json b/sandstone/pattern-ls2request-custom-colors/src/App/package.json
new file mode 100644
index 000000000..441552583
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/App/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "App.js"
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/src/components/ColorPicker/ColorPicker.js b/sandstone/pattern-ls2request-custom-colors/src/components/ColorPicker/ColorPicker.js
new file mode 100644
index 000000000..b524a1932
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/components/ColorPicker/ColorPicker.js
@@ -0,0 +1,333 @@
+/**
+ * A Sandstone component that allows the user to choose a color.
+ *
+ * @example
+ *
+ *
+ * @module sandstone/ColorPicker
+ * @exports ColorPicker
+ * @exports ColorPickerBase
+ * @exports ColorPickerDecorator
+ * @private
+ */
+
+import kind from '@enact/core/kind';
+import BodyText from '@enact/sandstone/BodyText';
+import Button, {ButtonBase} from '@enact/sandstone/Button';
+import Icon from '@enact/sandstone/Icon';
+import Item from '@enact/sandstone/Item';
+import Popup from '@enact/sandstone/Popup';
+import Skinnable from '@enact/sandstone/Skinnable';
+import Slider from '@enact/sandstone/Slider';
+import Spottable from '@enact/spotlight/Spottable';
+import {Cell, Column, Row} from '@enact/ui/Layout';
+import Toggleable from '@enact/ui/Toggleable';
+import PropTypes from 'prop-types';
+import compose from 'ramda/src/compose';
+import {useCallback, useEffect, useState} from 'react';
+
+import {hexToHSL, HSLToHex} from '../../hooks/utils';
+
+import componentCss from './ColorPicker.module.less';
+
+const SpottableButton = Spottable(ButtonBase);
+
+/**
+ * A component that contains the content for the {@link sandstone/ColorPicker|ColorPicker} popup.
+ *
+ * @class PopupContent
+ * @memberof sandstone/ColorPicker
+ * @ui
+ * @private
+ */
+const PopupContent = ({color, colorHandler, css}) => {
+ const [hue, setHue] = useState(0);
+ const [saturation, setSaturation] = useState(0);
+ const [lightness, setLightness] = useState(0);
+
+ useEffect(() => {
+ let {h, s, l} = hexToHSL(color);
+
+ setHue(h);
+ setSaturation(s);
+ setLightness(l);
+ }, [color]);
+
+ const changeHue = useCallback((ev) => {
+ setHue(ev.value);
+ }, []);
+
+ const changeLightness = useCallback((ev) => {
+ setLightness(ev.value);
+ }, []);
+
+ const changeSaturation = useCallback((ev) => {
+ setSaturation(ev.value);
+ }, []);
+
+ const onSliderValueChange = useCallback(() => {
+ colorHandler(HSLToHex({h: hue, s: saturation, l: lightness}));
+ }, [colorHandler, hue, lightness, saturation]);
+
+ return (
+
+
+
+ Hue {hue}
+
+ Saturation {saturation}%
+
+ Lightness {lightness}%
+
+
+
+
+ |
+ );
+};
+
+PopupContent.propTypes = {
+ /**
+ * Indicates the color.
+ *
+ * @type {String}
+ * @private
+ */
+ color: PropTypes.string,
+
+ /**
+ * Called when color is modified.
+ *
+ * @type {Function}
+ * @private
+ */
+ colorHandler: PropTypes.func,
+
+ /**
+ * Customizes the component by mapping the supplied collection of CSS class names to the
+ * corresponding internal elements and states of this component.
+ *
+ * The following classes are supported:
+ *
+ * `colorPicker` - The root class name
+ * `coloredDiv` - A class name used for a single div
+ *
+ * @type {Object}
+ * @private
+ */
+ css: PropTypes.object,
+
+ /**
+ * Contains an array with a couple of possible preset colors.
+ *
+ * @type {Array}
+ * @private
+ */
+ presetColors: PropTypes.array
+};
+
+/**
+ * The color picker base component which sets-up the component's structure.
+ *
+ * This component is most often not used directly but may be composed within another component as it
+ * is within {@link sandstone/ColorPicker|ColorPicker}.
+ *
+ * @class ColorPickerBase
+ * @memberof sandstone/ColorPicker
+ * @ui
+ * @private
+ */
+const ColorPickerBase = kind({
+ name: 'ColorPicker',
+
+ functional: true,
+
+ propTypes: {
+ /**
+ * Indicates the color.
+ *
+ * @type {String}
+ * @private
+ */
+ color: PropTypes.string,
+
+ /**
+ * Called when color is modified.
+ *
+ * @type {Function}
+ * @private
+ */
+ colorHandler: PropTypes.func,
+
+ /**
+ * Customizes the component by mapping the supplied collection of CSS class names to the
+ * corresponding internal elements and states of this component.
+ *
+ * The following classes are supported:
+ *
+ * `colorPicker` - The root class name
+ * `colorPopup` - A class name used for the popup
+ *
+ * @type {Object}
+ * @private
+ */
+ css: PropTypes.object,
+
+ /**
+ * Applies the `disabled` class.
+ *
+ * When `true`, the color picker is shown as disabled.
+ *
+ * @type {Boolean}
+ * @default false
+ * @public
+ */
+ disabled: PropTypes.bool,
+
+ /**
+ * Called to open or close the color picker.
+ *
+ * @type {Function}
+ * @public
+ */
+ onTogglePopup: PropTypes.func,
+
+ /**
+ * Indicates if the color picker is open.
+ *
+ * When `true`, contextual popup opens.
+ *
+ * @type {Boolean}
+ * @default false
+ * @private
+ */
+ popupOpen: PropTypes.bool,
+
+ /**
+ * Contains the text that shows next to the color picker.
+ *
+ * @type {String}
+ * @public
+ */
+ text: PropTypes.string
+ },
+
+ defaultProps: {
+ disabled: false,
+ popupOpen: false
+ },
+
+ handlers: {
+ handleClosePopup: (ev, {onTogglePopup}) => {
+ onTogglePopup();
+ },
+ handleOpenPopup: (ev, {disabled, onTogglePopup}) => {
+ if (!disabled) {
+ onTogglePopup();
+ }
+ }
+ },
+
+ styles: {
+ css: componentCss,
+ publicClassNames: true
+ },
+
+ render: ({color, colorHandler, css, disabled, handleClosePopup, handleOpenPopup, popupOpen, text, ...rest}) => {
+ delete rest.onTogglePopup;
+
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const CloseIcon = useCallback((props) => , [css]);
+ const slotAfter = ;
+
+ return (
+
+ -
+ {text}
+
+
+
+
+ {text}
+ |
+
+
+ |
+
+
+
+ |
+ );
+ }
+});
+
+/**
+ * Applies Sandstone specific behaviors to {@link sandstone/ColorPicker.ColorPickerBase|ColorPicker} components.
+ *
+ * @hoc
+ * @memberof sandstone/ColorPicker
+ * @mixes sandstone/Skinnable.Skinnable
+ * @mixes ui/Toggleable.Toggleable
+ * @private
+ */
+const ColorPickerDecorator = compose(
+ Skinnable,
+ Toggleable({prop: 'popupOpen', toggle: 'onTogglePopup'})
+);
+
+/**
+ * A color picker component, ready to use in Sandstone applications.
+ *
+ * @class ColorPicker
+ * @memberof sandstone/ColorPicker
+ * @extends sandstone/ColorPicker.ColorPickerBase
+ * @mixes sandstone/ColorPicker.ColorPickerDecorator
+ * @ui
+ * @private
+ */
+const ColorPicker = ColorPickerDecorator(ColorPickerBase);
+
+export default ColorPicker;
diff --git a/sandstone/pattern-ls2request-custom-colors/src/components/ColorPicker/ColorPicker.module.less b/sandstone/pattern-ls2request-custom-colors/src/components/ColorPicker/ColorPicker.module.less
new file mode 100644
index 000000000..c470890a5
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/components/ColorPicker/ColorPicker.module.less
@@ -0,0 +1,100 @@
+// ColorPicker.module.less
+//
+@import '~@enact/sandstone/styles/colors.less';
+@import '~@enact/sandstone/styles/mixins.less';
+@import '~@enact/sandstone/styles/variables.less';
+
+.colorPicker {
+ align-items: center;
+ justify-content: space-between;
+
+ .colorsRow {
+ width: 522px;
+ }
+
+ .coloredButton {
+ border-radius: 72px;
+ border: 3px solid #ffffff;
+ height: 120px;
+ margin-left: 0;
+ min-width: 120px;
+ padding: 0;
+ transform: none;
+
+ .focus({
+ transform: scale(1.1);
+ });
+
+ .disabled({
+ opacity: @sand-disabled-opacity;
+ })
+ }
+
+ .coloredDiv,
+ .coloredInput {
+ border-radius: 24px;
+ }
+
+ .coloredDiv {
+ display: block;
+ height: 81px;
+ border: 3px solid #ffffff;
+
+ &.button, &.focusExpand {
+ margin: 24px;
+ }
+
+ .focus({
+ transform: scale(1.1);
+ });
+ }
+
+ .coloredInput {
+ opacity: 0;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+}
+
+.colorPopup {
+ --sand-overlay-bg-color-rgb: 87, 94, 102;
+ height: max-content;
+
+ .colorPopupHeader {
+ --sand-text-color-rgb: 230, 230, 230;
+ font-size: 66px;
+ text-transform: capitalize;
+ margin: 0 0 0 15px;
+ }
+
+ .closeButton {
+ --sand-text-color-rgb: 230, 230, 230;
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ --sand-component-focus-text-color-rgb: 66, 75, 90;
+
+ &.button, &.focusExpand {
+ margin: 0;
+ }
+
+ .icon {
+ font-size: 48px;
+ }
+ }
+
+ .colorPickerSliders {
+ padding: 45px 0;
+
+ .colorSlider {
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ margin: 0 27px;
+ }
+
+ .colorSliderText {
+ color: #E6E6E6;
+ font-size: 54px;
+ margin: 0 24px;
+ }
+ }
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/src/components/ColorPicker/package.json b/sandstone/pattern-ls2request-custom-colors/src/components/ColorPicker/package.json
new file mode 100644
index 000000000..bdf7e58c7
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/components/ColorPicker/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "ColorPicker.js"
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/src/components/PreviewSection/PreviewSection.js b/sandstone/pattern-ls2request-custom-colors/src/components/PreviewSection/PreviewSection.js
new file mode 100644
index 000000000..02e63897f
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/components/PreviewSection/PreviewSection.js
@@ -0,0 +1,69 @@
+import {useCallback, useState} from 'react';
+import BodyText from '@enact/sandstone/BodyText';
+import Button from '@enact/sandstone/Button';
+import CheckboxItem from '@enact/sandstone/CheckboxItem';
+import Dropdown from '@enact/sandstone/Dropdown';
+import Heading from '@enact/sandstone/Heading';
+import Popup from '@enact/sandstone/Popup';
+import RangePicker from '@enact/sandstone/RangePicker';
+import Slider from '@enact/sandstone/Slider';
+import SwitchItem from '@enact/sandstone/SwitchItem';
+import {Cell, Column, Layout} from '@enact/ui/Layout';
+
+import css from './PreviewSection.module.less';
+
+const PreviewSection = () => {
+ const [openPopup, setOpenPopup] = useState(false);
+
+ const handleOpenPopup = useCallback(() => {
+ setOpenPopup(!openPopup);
+ }, [openPopup]);
+
+ 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 (
+
+
+ Theme Preview
+
+
+
+
+
+
+ Checkbox
+ Toggle
+
+
+
+ {['Item 1', 'Item 2', 'Item 3']}
+
+
+
+ Hello
+
+
+
+ |
+ );
+};
+
+export default PreviewSection;
diff --git a/sandstone/pattern-ls2request-custom-colors/src/components/PreviewSection/PreviewSection.module.less b/sandstone/pattern-ls2request-custom-colors/src/components/PreviewSection/PreviewSection.module.less
new file mode 100644
index 000000000..130ee25b5
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/components/PreviewSection/PreviewSection.module.less
@@ -0,0 +1,45 @@
+// PreviewSection.module.less
+//
+@import '~@enact/sandstone/styles/colors.less';
+
+.previewSection {
+ background-color: @sand-bg-color;
+ border-radius: 12px;
+ margin: 24px;
+ padding: 0 24px;
+ display: flex;
+ justify-content: center;
+
+ .previewTitle {
+ 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/sandstone/pattern-ls2request-custom-colors/src/components/PreviewSection/package.json b/sandstone/pattern-ls2request-custom-colors/src/components/PreviewSection/package.json
new file mode 100644
index 000000000..42f3f5add
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/components/PreviewSection/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "PreviewSection.js"
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/src/constants.js b/sandstone/pattern-ls2request-custom-colors/src/constants.js
new file mode 100644
index 000000000..c24e78678
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/constants.js
@@ -0,0 +1,27 @@
+import {createContext} from "react";
+
+export const presets = {
+ defaultColorSet: "Default Color Set",
+ blueColorSet1: "Light Blue Color Set",
+ blueColorSet2: "Blue Color Set",
+ greenColorSet1: "Light Green Color Set",
+ greenColorSet2: "Green Color Set",
+ purpleColorSet1: "Light Purple Color Set",
+ purpleColorSet2: "Purple Color Set",
+ redColorSet1: "Light Red Color Set",
+ redColorSet2: "Red Color Set"
+};
+
+export const customColorsContext = {
+ activeTheme: 'defaultColorSet',
+ dynamicColor: 'off',
+ handleSkin: 'off',
+ backgroundColor: '#000000',
+ componentBackgroundColor: '#7D848C',
+ focusBackgroundColor: '#E6E6E6',
+ popupBackgroundColor: '#575E66',
+ subtitleTextColor: '#ABAEB3',
+ textColor: '#E6E6E6'
+};
+
+export const AppContext = createContext(null);
diff --git a/sandstone/pattern-ls2request-custom-colors/src/hooks/generateStylesheet.js b/sandstone/pattern-ls2request-custom-colors/src/hooks/generateStylesheet.js
new file mode 100644
index 000000000..91a402c54
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/hooks/generateStylesheet.js
@@ -0,0 +1,535 @@
+import {hexToRGB} from './utils';
+
+// Function that returns a Sandstone stylesheet based on 6 colors and a preset
+export const generateStylesheet = (backgroundColor, componentBackgroundColor, focusBackgroundColor, popupBackgroundColor, subTextColor, textColor, preset) => {
+ const fbgRGB = hexToRGB(focusBackgroundColor).join(', ');
+ const pbgRGB = hexToRGB(popupBackgroundColor).join(', ');
+ const stRGB = hexToRGB(subTextColor).join(', ');
+ const tRGB = hexToRGB(textColor).join(', ');
+
+ // This switch will return a different color combination based on preset
+ switch (preset) {
+ case 'blueColorSet1':
+ return `.sandstone-theme {
+ /* Skin Name: Blue 1; */
+ --sand-bg-color: ${backgroundColor};
+ --sand-text-color-rgb: ${tRGB};
+ --sand-text-sub-color: ${subTextColor};
+ --sand-shadow-color-rgb: 0, 0, 0;
+ --sand-component-text-color-rgb: ${tRGB};
+ --sand-component-text-sub-color-rgb: ${stRGB};
+ --sand-component-bg-color: ${componentBackgroundColor};
+ --sand-component-active-indicator-bg-color: ${focusBackgroundColor};
+ --sand-component-inactive-indicator-bg-color: #9DA2A7;
+ --sand-focus-text-color: #303030;
+ --sand-focus-bg-color-rgb: ${fbgRGB};
+ --sand-component-focus-text-color-rgb: 51, 51, 51;
+ --sand-component-focus-active-indicator-bg-color: #4C5059;
+ --sand-component-focus-inactive-indicator-bg-color: #B8B9BB;
+ --sand-selected-color-rgb: ${tRGB};
+ --sand-selected-text-color: #E6E6E6;
+ --sand-selected-bg-color: #61688E;
+ --sand-disabled-focus-bg-color: ${subTextColor};
+ --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: ${pbgRGB};
+ --sand-selection-color: #4C5059;
+ --sand-selection-bg-color: #3399FF;
+ --sand-toggle-off-color: #AEAEAE;
+ --sand-toggle-off-bg-color: #494949;
+ --sand-toggle-on-color: ${focusBackgroundColor};
+ --sand-toggle-on-bg-color: #61688E;
+ --sand-progress-color-rgb: ${tRGB};
+ --sand-progress-buffer-color: #6B6D73;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-highlighted-color: #FFFFFF;
+ --sand-progress-slider-color: #8D9298;
+ --sand-spinner-color-rgb: 255, 255, 255;
+ --sand-checkbox-color: ${focusBackgroundColor};
+ --sand-item-disabled-focus-bg-color: #E6E6E6;
+ --sand-keyguide-bg-color-rgb: 107, 109, 115;
+ --sand-slider-disabled-knob-bg-color: #666666;
+ --sand-alert-overlay-bg-color-rgb: 202, 203, 204;
+ --sand-alert-overlay-text-color-rgb: 46, 50, 57;
+ --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: ${focusBackgroundColor};
+ --sand-alert-overlay-disabled-selected-focus-bg-color: #4C5059;
+ --sand-alert-overlay-progress-color-rgb: 107, 109, 115;
+ --sand-alert-overlay-progress-bg-color-rgb: 161, 161, 161;
+ --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;
+ }`;
+ case 'blueColorSet2':
+ return `.sandstone-theme {
+ /* Skin Name: Blue 2; */
+ --sand-bg-color: ${backgroundColor};
+ --sand-text-color-rgb: ${tRGB};
+ --sand-text-sub-color: ${subTextColor};
+ --sand-shadow-color-rgb: 0, 0, 0;
+ --sand-component-text-color-rgb: ${tRGB};
+ --sand-component-text-sub-color-rgb: ${stRGB};
+ --sand-component-bg-color: ${componentBackgroundColor};
+ --sand-component-active-indicator-bg-color: ${focusBackgroundColor};
+ --sand-component-inactive-indicator-bg-color: #9DA2A7;
+ --sand-focus-text-color: #152DAC;
+ --sand-focus-bg-color-rgb: ${fbgRGB};
+ --sand-component-focus-text-color-rgb: 28, 49, 170;
+ --sand-component-focus-active-indicator-bg-color: #4C5059;
+ --sand-component-focus-inactive-indicator-bg-color: #B8B9BB;
+ --sand-selected-color-rgb: ${tRGB};
+ --sand-selected-text-color: #E6E6E6;
+ --sand-selected-bg-color: #61688E;
+ --sand-disabled-focus-bg-color: ${subTextColor};
+ --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: ${pbgRGB};
+ --sand-selection-color: #4C5059;
+ --sand-selection-bg-color: #3399FF;
+ --sand-toggle-off-color: #787C90;
+ --sand-toggle-off-bg-color: #444C73;
+ --sand-toggle-on-color: ${focusBackgroundColor};
+ --sand-toggle-on-bg-color: #7B84B2;
+ --sand-progress-color-rgb: ${tRGB};
+ --sand-progress-buffer-color: #6B6D73;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-highlighted-color: #FFFFFF;
+ --sand-progress-slider-color: #8D9298;
+ --sand-spinner-color-rgb: 255, 255, 255;
+ --sand-checkbox-color: ${focusBackgroundColor};
+ --sand-item-disabled-focus-bg-color: #E6E6E6;
+ --sand-keyguide-bg-color-rgb: 107, 109, 115;
+ --sand-slider-disabled-knob-bg-color: #666666;
+ --sand-alert-overlay-bg-color-rgb: 202, 203, 204;
+ --sand-alert-overlay-text-color-rgb: 46, 50, 57;
+ --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: ${focusBackgroundColor};
+ --sand-alert-overlay-disabled-selected-focus-bg-color: #4C5059;
+ --sand-alert-overlay-progress-color-rgb: 107, 109, 115;
+ --sand-alert-overlay-progress-bg-color-rgb: 161, 161, 161;
+ --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;
+ }`;
+ case 'greenColorSet1':
+ return `.sandstone-theme {
+ /* Skin Name: Green 1; */
+ --sand-bg-color: ${backgroundColor};
+ --sand-text-color-rgb: ${tRGB};
+ --sand-text-sub-color: ${subTextColor};
+ --sand-shadow-color-rgb: 0, 0, 0;
+ --sand-component-text-color-rgb: ${tRGB};
+ --sand-component-text-sub-color-rgb: ${stRGB};
+ --sand-component-bg-color: ${componentBackgroundColor};
+ --sand-component-active-indicator-bg-color: ${focusBackgroundColor};
+ --sand-component-inactive-indicator-bg-color: #9DA2A7;
+ --sand-focus-text-color: #303030;
+ --sand-focus-bg-color-rgb: ${fbgRGB};
+ --sand-component-focus-text-color-rgb: 51, 51, 51;
+ --sand-component-focus-active-indicator-bg-color: #4C5059;
+ --sand-component-focus-inactive-indicator-bg-color: #B8B9BB;
+ --sand-selected-color-rgb: ${tRGB};
+ --sand-selected-text-color: #E6E6E6;
+ --sand-selected-bg-color: #61828E;
+ --sand-disabled-focus-bg-color: ${subTextColor};
+ --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: ${pbgRGB};
+ --sand-selection-color: #4C5059;
+ --sand-selection-bg-color: #3399FF;
+ --sand-toggle-off-color: #AEAEAE;
+ --sand-toggle-off-bg-color: #494949;
+ --sand-toggle-on-color: ${focusBackgroundColor};
+ --sand-toggle-on-bg-color: #61828E;
+ --sand-progress-color-rgb: ${tRGB};
+ --sand-progress-buffer-color: #6B6D73;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-highlighted-color: #FFFFFF;
+ --sand-progress-slider-color: #8D9298;
+ --sand-spinner-color-rgb: 255, 255, 255;
+ --sand-checkbox-color: ${focusBackgroundColor};
+ --sand-item-disabled-focus-bg-color: #E6E6E6;
+ --sand-keyguide-bg-color-rgb: 107, 109, 115;
+ --sand-slider-disabled-knob-bg-color: #666666;
+ --sand-alert-overlay-bg-color-rgb: 202, 203, 204;
+ --sand-alert-overlay-text-color-rgb: 46, 50, 57;
+ --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: ${focusBackgroundColor};
+ --sand-alert-overlay-disabled-selected-focus-bg-color: #4C5059;
+ --sand-alert-overlay-progress-color-rgb: 107, 109, 115;
+ --sand-alert-overlay-progress-bg-color-rgb: 161, 161, 161;
+ --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;
+ }`;
+ case 'greenColorSet2':
+ return `.sandstone-theme {
+ /* Skin Name: Green 2; */
+ --sand-bg-color: ${backgroundColor};
+ --sand-text-color-rgb: ${tRGB};
+ --sand-text-sub-color: ${subTextColor};
+ --sand-shadow-color-rgb: 0, 0, 0;
+ --sand-component-text-color-rgb: ${tRGB};
+ --sand-component-text-sub-color-rgb: ${stRGB};
+ --sand-component-bg-color: ${componentBackgroundColor};
+ --sand-component-active-indicator-bg-color: ${focusBackgroundColor};
+ --sand-component-inactive-indicator-bg-color: #9DA2A7;
+ --sand-focus-text-color: #02435F;
+ --sand-focus-bg-color-rgb: ${fbgRGB};
+ --sand-component-focus-text-color-rgb: 8, 69, 95;
+ --sand-component-focus-active-indicator-bg-color: #4C5059;
+ --sand-component-focus-inactive-indicator-bg-color: #B8B9BB;
+ --sand-selected-color-rgb: ${tRGB};
+ --sand-selected-text-color: #E6E6E6;
+ --sand-selected-bg-color: #61828E;
+ --sand-disabled-focus-bg-color: ${subTextColor};
+ --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: ${pbgRGB};
+ --sand-selection-color: #4C5059;
+ --sand-selection-bg-color: #3399FF;
+ --sand-toggle-off-color: #6F7E84;
+ --sand-toggle-off-bg-color: #31505B;
+ --sand-toggle-on-color: ${focusBackgroundColor};
+ --sand-toggle-on-bg-color: #6B95A4;
+ --sand-progress-color-rgb: ${tRGB};
+ --sand-progress-buffer-color: #6B6D73;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-highlighted-color: #FFFFFF;
+ --sand-progress-slider-color: #8D9298;
+ --sand-spinner-color-rgb: 255, 255, 255;
+ --sand-checkbox-color: ${focusBackgroundColor};
+ --sand-item-disabled-focus-bg-color: #E6E6E6;
+ --sand-keyguide-bg-color-rgb: 107, 109, 115;
+ --sand-slider-disabled-knob-bg-color: #666666;
+ --sand-alert-overlay-bg-color-rgb: 202, 203, 204;
+ --sand-alert-overlay-text-color-rgb: 46, 50, 57;
+ --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: ${focusBackgroundColor};
+ --sand-alert-overlay-disabled-selected-focus-bg-color: #4C5059;
+ --sand-alert-overlay-progress-color-rgb: 107, 109, 115;
+ --sand-alert-overlay-progress-bg-color-rgb: 161, 161, 161;
+ --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;
+ }`;
+ case 'purpleColorSet1':
+ return `.sandstone-theme {
+ /* Skin Name: Purple 1; */
+ --sand-bg-color: ${backgroundColor};
+ --sand-text-color-rgb: ${tRGB};
+ --sand-text-sub-color: ${subTextColor};
+ --sand-shadow-color-rgb: 0, 0, 0;
+ --sand-component-text-color-rgb: ${tRGB};
+ --sand-component-text-sub-color-rgb: ${stRGB};
+ --sand-component-bg-color: ${componentBackgroundColor};
+ --sand-component-active-indicator-bg-color: ${focusBackgroundColor};
+ --sand-component-inactive-indicator-bg-color: #9DA2A7;
+ --sand-focus-text-color: #303030;
+ --sand-focus-bg-color-rgb: ${fbgRGB};
+ --sand-component-focus-text-color-rgb: 51, 51, 51;
+ --sand-component-focus-active-indicator-bg-color: #4C5059;
+ --sand-component-focus-inactive-indicator-bg-color: #B8B9BB;
+ --sand-selected-color-rgb: ${tRGB};
+ --sand-selected-text-color: #E6E6E6;
+ --sand-selected-bg-color: #75518E;
+ --sand-disabled-focus-bg-color: ${subTextColor};
+ --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: ${pbgRGB};
+ --sand-selection-color: #4C5059;
+ --sand-selection-bg-color: #3399FF;
+ --sand-toggle-off-color: #AEAEAE;
+ --sand-toggle-off-bg-color: #494949;
+ --sand-toggle-on-color: ${focusBackgroundColor};
+ --sand-toggle-on-bg-color: #755183;
+ --sand-progress-color-rgb: ${tRGB};
+ --sand-progress-buffer-color: #6B6D73;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-highlighted-color: #FFFFFF;
+ --sand-progress-slider-color: #8D9298;
+ --sand-spinner-color-rgb: 255, 255, 255;
+ --sand-checkbox-color: ${focusBackgroundColor};
+ --sand-item-disabled-focus-bg-color: #E6E6E6;
+ --sand-keyguide-bg-color-rgb: 107, 109, 115;
+ --sand-slider-disabled-knob-bg-color: #666666;
+ --sand-alert-overlay-bg-color-rgb: 107, 109, 115;
+ --sand-alert-overlay-text-color-rgb: 46, 50, 57;
+ --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: ${focusBackgroundColor};
+ --sand-alert-overlay-disabled-selected-focus-bg-color: #4C5059;
+ --sand-alert-overlay-progress-color-rgb: 107, 109, 115;
+ --sand-alert-overlay-progress-bg-color-rgb: 161, 161, 161;
+ --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;
+ }`;
+ case 'purpleColorSet2':
+ return `.sandstone-theme {
+ /* Skin Name: Purple 2; */
+ --sand-bg-color: ${backgroundColor};
+ --sand-text-color-rgb: ${tRGB};
+ --sand-text-sub-color: ${subTextColor};
+ --sand-shadow-color-rgb: 0, 0, 0;
+ --sand-component-text-color-rgb: ${tRGB};
+ --sand-component-text-sub-color-rgb: 171, 174, 179;
+ --sand-component-bg-color: ${componentBackgroundColor};
+ --sand-component-active-indicator-bg-color: ${focusBackgroundColor};
+ --sand-component-inactive-indicator-bg-color: #9DA2A7;
+ --sand-focus-text-color: #FFFFFF;
+ --sand-focus-bg-color-rgb: ${fbgRGB};
+ --sand-component-focus-text-color-rgb: 77, 25, 142;
+ --sand-component-focus-active-indicator-bg-color: #4C5059;
+ --sand-component-focus-inactive-indicator-bg-color: #B8B9BB;
+ --sand-selected-color-rgb: ${tRGB};
+ --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: ${pbgRGB};
+ --sand-selection-color: #4C5059;
+ --sand-selection-bg-color: #3399FF;
+ --sand-toggle-off-color: #80778C;
+ --sand-toggle-off-bg-color: #54416C;
+ --sand-toggle-on-color: ${focusBackgroundColor};
+ --sand-toggle-on-bg-color: #8A75A2;
+ --sand-progress-color-rgb: ${tRGB};
+ --sand-progress-buffer-color: #6B6D73;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-highlighted-color: #FFFFFF;
+ --sand-progress-slider-color: #8D9298;
+ --sand-spinner-color-rgb: 255, 255, 255;
+ --sand-checkbox-color: ${focusBackgroundColor};
+ --sand-item-disabled-focus-bg-color: #E6E6E6;
+ --sand-keyguide-bg-color-rgb: 107, 109, 115;
+ --sand-slider-disabled-knob-bg-color: #666666;
+ --sand-alert-overlay-bg-color-rgb: 202, 203, 204;
+ --sand-alert-overlay-text-color-rgb: 46, 50, 57;
+ --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: ${focusBackgroundColor};
+ --sand-alert-overlay-disabled-selected-focus-bg-color: #4C5059;
+ --sand-alert-overlay-progress-color-rgb: 107, 109, 115;
+ --sand-alert-overlay-progress-bg-color-rgb: 161, 161, 161;
+ --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;
+ }`;
+ case 'redColorSet1':
+ return `.sandstone-theme {
+ /* Skin Name: Red 1; */
+ --sand-bg-color: ${backgroundColor};
+ --sand-text-color-rgb: ${tRGB};
+ --sand-text-sub-color: ${subTextColor};
+ --sand-shadow-color-rgb: 0, 0, 0;
+ --sand-component-text-color-rgb: ${tRGB};
+ --sand-component-text-sub-color-rgb: ${stRGB};
+ --sand-component-bg-color: ${componentBackgroundColor};
+ --sand-component-active-indicator-bg-color: ${focusBackgroundColor};
+ --sand-component-inactive-indicator-bg-color: #9DA2A7;
+ --sand-focus-text-color: #303030;
+ --sand-focus-bg-color-rgb: ${fbgRGB};
+ --sand-component-focus-text-color-rgb: 51, 51, 51;
+ --sand-component-focus-active-indicator-bg-color: #4C5059;
+ --sand-component-focus-inactive-indicator-bg-color: #B8B9BB;
+ --sand-selected-color-rgb: ${tRGB};
+ --sand-selected-text-color: #E6E6E6;
+ --sand-selected-bg-color: #8E6161;
+ --sand-disabled-focus-bg-color: ${subTextColor};
+ --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: ${pbgRGB};
+ --sand-selection-color: #4C5059;
+ --sand-selection-bg-color: #3399FF;
+ --sand-toggle-off-color: #AEAEAE;
+ --sand-toggle-off-bg-color: #494949;
+ --sand-toggle-on-color: ${focusBackgroundColor};
+ --sand-toggle-on-bg-color: #8E6161;
+ --sand-progress-color-rgb: ${tRGB};
+ --sand-progress-buffer-color: #6B6D73;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-highlighted-color: #FFFFFF;
+ --sand-progress-slider-color: #8D9298;
+ --sand-spinner-color-rgb: 255, 255, 255;
+ --sand-checkbox-color: ${focusBackgroundColor};
+ --sand-item-disabled-focus-bg-color: #E6E6E6;
+ --sand-keyguide-bg-color-rgb: 107, 109, 115;
+ --sand-slider-disabled-knob-bg-color: #666666;
+ --sand-alert-overlay-bg-color-rgb: 202, 203, 204;
+ --sand-alert-overlay-text-color-rgb: 46, 50, 57;
+ --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: ${focusBackgroundColor};
+ --sand-alert-overlay-disabled-selected-focus-bg-color: #4C5059;
+ --sand-alert-overlay-progress-color-rgb: 107, 109, 115;
+ --sand-alert-overlay-progress-bg-color-rgb: 161, 161, 161;
+ --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;
+ }`;
+ case 'redColorSet2':
+ return `.sandstone-theme {
+ /* Skin Name: Red 2; */
+ --sand-bg-color: ${backgroundColor};
+ --sand-text-color-rgb: ${tRGB};
+ --sand-text-sub-color: ${subTextColor};
+ --sand-shadow-color-rgb: 0, 0, 0;
+ --sand-component-text-color-rgb: ${tRGB};
+ --sand-component-text-sub-color-rgb: 171, 174, 179;
+ --sand-component-bg-color: ${componentBackgroundColor};
+ --sand-component-active-indicator-bg-color: ${focusBackgroundColor};
+ --sand-component-inactive-indicator-bg-color: #9DA2A7;
+ --sand-focus-text-color: #851919;
+ --sand-focus-bg-color-rgb: ${fbgRGB};
+ --sand-component-focus-text-color-rgb: 132, 31, 31;
+ --sand-component-focus-active-indicator-bg-color: #4C5059;
+ --sand-component-focus-inactive-indicator-bg-color: #B8B9BB;
+ --sand-selected-color-rgb: ${tRGB};
+ --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: ${pbgRGB};
+ --sand-selection-color: #4C5059;
+ --sand-selection-bg-color: #3399FF;
+ --sand-toggle-off-color: #927A7A;
+ --sand-toggle-off-bg-color: #784747;
+ --sand-toggle-on-color: ${focusBackgroundColor};
+ --sand-toggle-on-bg-color: #BB7D7D;
+ --sand-progress-color-rgb: ${tRGB};
+ --sand-progress-buffer-color: #6B6D73;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-highlighted-color: #FFFFFF;
+ --sand-progress-slider-color: #8D9298;
+ --sand-spinner-color-rgb: 255, 255, 255;
+ --sand-checkbox-color: ${focusBackgroundColor};
+ --sand-item-disabled-focus-bg-color: #E6E6E6;
+ --sand-keyguide-bg-color-rgb: 107, 109, 115;
+ --sand-slider-disabled-knob-bg-color: #666666;
+ --sand-alert-overlay-bg-color-rgb: 202, 203, 204;
+ --sand-alert-overlay-text-color-rgb: 46, 50, 57;
+ --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: ${focusBackgroundColor};
+ --sand-alert-overlay-disabled-selected-focus-bg-color: #4C5059;
+ --sand-alert-overlay-progress-color-rgb: 107, 109, 115;
+ --sand-alert-overlay-progress-bg-color-rgb: 161, 161, 161;
+ --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;
+ }`;
+ default:
+ return `.sandstone-theme {
+ /* Skin Name: Default; */
+ --sand-bg-color: ${backgroundColor};
+ --sand-text-color-rgb: ${tRGB};
+ --sand-text-sub-color: ${subTextColor};
+ --sand-shadow-color-rgb: 0, 0, 0;
+ --sand-component-text-color-rgb: ${tRGB};
+ --sand-component-text-sub-color-rgb: ${stRGB};
+ --sand-component-bg-color: ${componentBackgroundColor};
+ --sand-component-active-indicator-bg-color: ${focusBackgroundColor};
+ --sand-component-inactive-indicator-bg-color: #9DA2A7;
+ --sand-focus-text-color: #FFFFFF;
+ --sand-focus-bg-color-rgb: ${fbgRGB};
+ --sand-component-focus-text-color-rgb: 76, 80, 89;
+ --sand-component-focus-active-indicator-bg-color: #4C5059;
+ --sand-component-focus-inactive-indicator-bg-color: #B8B9BB;
+ --sand-selected-color-rgb: ${tRGB};
+ --sand-selected-text-color: #E6E6E6;
+ --sand-selected-bg-color: #3E454D;
+ --sand-disabled-focus-bg-color: ${subTextColor};
+ --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: ${pbgRGB};
+ --sand-selection-color: #4C5059;
+ --sand-selection-bg-color: #3399FF;
+ --sand-toggle-off-color: #AEAEAE;
+ --sand-toggle-off-bg-color: #777777;
+ --sand-toggle-on-color: ${focusBackgroundColor};
+ --sand-toggle-on-bg-color: #30AD6B;
+ --sand-progress-color-rgb: ${tRGB};
+ --sand-progress-buffer-color: #6B6D73;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-highlighted-color: #FFFFFF;
+ --sand-progress-slider-color: #8D9298;
+ --sand-spinner-color-rgb: 255, 255, 255;
+ --sand-checkbox-color: ${focusBackgroundColor};
+ --sand-item-disabled-focus-bg-color: #E6E6E6;
+ --sand-keyguide-bg-color-rgb: 55, 58, 65;
+ --sand-slider-disabled-knob-bg-color: #666666;
+ --sand-alert-overlay-bg-color-rgb: 202, 203, 204;
+ --sand-alert-overlay-text-color-rgb: 46, 50, 57;
+ --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: ${focusBackgroundColor};
+ --sand-alert-overlay-disabled-selected-focus-bg-color: #4C5059;
+ --sand-alert-overlay-progress-color-rgb: 55, 58, 65;
+ --sand-alert-overlay-progress-bg-color-rgb: 161, 161, 161;
+ --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/sandstone/pattern-ls2request-custom-colors/src/hooks/package.json b/sandstone/pattern-ls2request-custom-colors/src/hooks/package.json
new file mode 100644
index 000000000..0905a9472
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/hooks/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "dynamicColor.js"
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/src/hooks/useDynamicColor.js b/sandstone/pattern-ls2request-custom-colors/src/hooks/useDynamicColor.js
new file mode 100644
index 000000000..ec6d909ba
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/hooks/useDynamicColor.js
@@ -0,0 +1,100 @@
+import {useContext, useEffect, useState} from 'react';
+import {generateStylesheet} from './generateStylesheet';
+import {useLinearColor} from './useLinearColor';
+import {generateTimestamps, getIndex} from './utils';
+
+import {AppContext} from '../constants';
+
+let fakeIndex = 0;
+let timestamps = generateTimestamps(5);
+
+const useLinearSkinColor = () => {
+ const fakeTimeEnabled = true;
+ const [linearSkinVariants, setLinearSkinVariants] = useState('neutral');
+ const {dynamicColor: enableLinearSkin, handleSkin: skinVariants, activeTheme: preset, backgroundColor, componentBackgroundColor, focusBackgroundColor, popupBackgroundColor, subtitleTextColor, textColor, colors} = useContext(AppContext).context;
+ const [linearBackgroundColor, setLinearBackgroundColor] = useLinearColor(backgroundColor);
+ const [linearComponentBackgroundColor, setLinearComponentBackgroundColor] = useLinearColor(componentBackgroundColor);
+ const [linearFocusBackgroundColor, setLinearFocusBackgroundColor] = useLinearColor(focusBackgroundColor);
+ const [linearPopupBackgroundColor, setLinearPopupBackgroundColor] = useLinearColor(popupBackgroundColor);
+ const [linearSubTextColor, setLinearSubTextColor] = useLinearColor(subtitleTextColor);
+ const [linearTextColor, setLinearTextColor] = useLinearColor(textColor);
+
+ useEffect(() => {
+ // If linear skin is not enabled we skip this useEffect's content
+ if (enableLinearSkin !== 'on') return;
+
+ // Change color luminosity and saturation at a given interval (0.5s if fakeTime is enabled, 30s if it is disabled)
+ let changeColor = setInterval(() => {
+ // Get index depending on timestamp
+ const index = getIndex();
+ if (!fakeTimeEnabled) {
+ if (skinVariants === 'on') {
+ // Set skin variant based on timestamp
+ if (index >= '05:00' && index < '17:00') {
+ setLinearSkinVariants('neutral');
+ } else {
+ setLinearSkinVariants('light');
+ }
+ }
+
+ // Set colors from the generated colors array at a specific index depending on timestamp
+ setLinearBackgroundColor(index);
+ setLinearComponentBackgroundColor(index);
+ setLinearFocusBackgroundColor(index);
+ setLinearPopupBackgroundColor(index);
+ setLinearSubTextColor(index);
+ setLinearTextColor(index);
+ } else {
+ if (skinVariants === 'on') {
+ // Set skin variant based on timestamp
+ // The reason we chose the values 60 and 203 is that each increase in fakeIndex represents 5 minutes
+ // This way the skin changes coincides with '05:00' and '17:00'
+ if (60 <= fakeIndex && fakeIndex <= 203) {
+ setLinearSkinVariants('neutral');
+ } else {
+ setLinearSkinVariants('light');
+ }
+ }
+
+ // Set colors from the generated colors array at a specific index depending on timestamp
+ setLinearBackgroundColor(timestamps[fakeIndex]);
+ setLinearComponentBackgroundColor(timestamps[fakeIndex]);
+ setLinearFocusBackgroundColor(timestamps[fakeIndex]);
+ setLinearPopupBackgroundColor(timestamps[fakeIndex]);
+ setLinearSubTextColor(timestamps[fakeIndex]);
+ setLinearTextColor(timestamps[fakeIndex]);
+
+ // The reason we chose the value 287 is that each increase in fakeIndex represents 5 minutes
+ // This way from 0 to 287 we have the time interval between '00:00' to '23:55'
+ if (fakeIndex < 287) {
+ fakeIndex++;
+ } else {
+ fakeIndex = 0;
+ }
+ }
+ }, fakeTimeEnabled ? 500 : 30 * 1000);
+ return () => {
+ clearInterval(changeColor);
+ };
+ }, [enableLinearSkin, fakeTimeEnabled, setLinearBackgroundColor, setLinearComponentBackgroundColor, setLinearFocusBackgroundColor, setLinearPopupBackgroundColor, setLinearSubTextColor, setLinearTextColor, skinVariants]);
+
+ useEffect(() => {
+ // Appends a stylesheet containing the generated colors
+ if (typeof document !== 'undefined') {
+ document.getElementById('custom-skin')?.remove();
+ const root = document.getElementById('root');
+ const sheet = document.createElement('style');
+ sheet.id = 'custom-skin';
+ if (enableLinearSkin === 'on') {
+ sheet.innerHTML = generateStylesheet(linearBackgroundColor, linearComponentBackgroundColor, linearFocusBackgroundColor, linearPopupBackgroundColor, linearSubTextColor, linearTextColor, preset);
+ } else {
+ sheet.innerHTML = colors;
+ }
+ root.appendChild(sheet);
+ }
+ }, [colors, enableLinearSkin, linearBackgroundColor, linearComponentBackgroundColor, linearFocusBackgroundColor, linearPopupBackgroundColor, linearSubTextColor, linearTextColor, preset]);
+
+ return [skinVariants === 'on', linearSkinVariants];
+};
+
+export default useLinearSkinColor;
diff --git a/sandstone/pattern-ls2request-custom-colors/src/hooks/useLinearColor.js b/sandstone/pattern-ls2request-custom-colors/src/hooks/useLinearColor.js
new file mode 100644
index 000000000..ab9d994a2
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/hooks/useLinearColor.js
@@ -0,0 +1,20 @@
+import {useCallback, useMemo, useState} from 'react';
+
+import {generateColorObject, generateColors} from './utils';
+
+// This hook manages a dynamic color
+export const useLinearColor = (color) => {
+ const [linearColor, setLinearColor] = useState(color);
+ // Generate an array of colors from an initial selected color
+ const colors = useMemo(() => {
+ return generateColorObject(generateColors(color));
+ }, [color]);
+
+ // Change color with a different one from the generated array
+ const setNewColor = useCallback((index) => {
+ setLinearColor(colors[index]);
+ }, [colors]);
+
+ // Return the current color and the setter function
+ return [linearColor, setNewColor];
+};
diff --git a/sandstone/pattern-ls2request-custom-colors/src/hooks/utils.js b/sandstone/pattern-ls2request-custom-colors/src/hooks/utils.js
new file mode 100644
index 000000000..a3c941b08
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/hooks/utils.js
@@ -0,0 +1,335 @@
+import LS2Request from '@enact/webos/LS2Request';
+import {generateStylesheet} from './generateStylesheet';
+
+// set `theme` key when presets or colors are changed
+export const changeSettings = (params) => {
+ return new Promise((resolve) => {
+ new LS2Request().send({
+ service: 'luna://com.webos.service.settings/',
+ method: 'setSystemSettings',
+ parameters: params,
+ onSuccess: (res) => {
+ // eslint-disable-next-line no-console
+ console.log('setSystemSettings onSuccess', params);
+ resolve(res);
+ }
+ });
+ });
+};
+
+export const hexToHSL = (hex) => {
+ // Convert hex to RGB first
+ let r = 0, g = 0, b = 0;
+ if (hex.length === 4) {
+ r = parseInt(hex[1] + hex[1], 16);
+ g = parseInt(hex[2] + hex[2], 16);
+ b = parseInt(hex[3] + hex[3], 16);
+ } else if (hex.length === 7) {
+ r = parseInt(hex.slice(1, 3), 16);
+ g = parseInt(hex.slice(3, 5), 16);
+ b = parseInt(hex.slice(5), 16);
+ }
+ // Then convert RGB to HSL
+ r /= 255;
+ g /= 255;
+ b /= 255;
+ let cmin = Math.min(r, g, b),
+ cmax = Math.max(r, g, b),
+ delta = cmax - cmin,
+ h, s, l;
+ if (delta === 0) {
+ h = 0;
+ } else if (cmax === r) {
+ h = ((g - b) / delta) % 6;
+ } else if (cmax === g) {
+ h = (b - r) / delta + 2;
+ } else {
+ h = (r - g) / delta + 4;
+ }
+ h = Math.round(h * 60);
+ if (h < 0) {
+ h += 360;
+ }
+ l = (cmax + cmin) / 2;
+ s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
+ s = +(s * 100).toFixed(1);
+ l = +(l * 100).toFixed(1);
+ return {h: Math.round(h), s: Math.round(s), l: Math.round(l)};
+};
+
+export const HSLToHex = ({h, s, l}) => {
+ s /= 100;
+ l /= 100;
+ let c = (1 - Math.abs(2 * l - 1)) * s,
+ x = c * (1 - Math.abs((h / 60) % 2 - 1)),
+ m = l - c / 2,
+ r = 0,
+ g = 0,
+ b = 0;
+ if (0 <= h && h < 60) {
+ r = c; g = x; b = 0;
+ } else if (60 <= h && h < 120) {
+ r = x; g = c; b = 0;
+ } else if (120 <= h && h < 180) {
+ r = 0; g = c; b = x;
+ } else if (180 <= h && h < 240) {
+ r = 0; g = x; b = c;
+ } else if (240 <= h && h < 300) {
+ r = x; g = 0; b = c;
+ } else if (300 <= h && h < 360) {
+ r = c; g = 0; b = x;
+ }
+ // Having obtained RGB, convert channels to hex
+ r = Math.round((r + m) * 255).toString(16);
+ g = Math.round((g + m) * 255).toString(16);
+ b = Math.round((b + m) * 255).toString(16);
+ // Prepend 0s, if necessary
+ if (r.length === 1) {
+ r = "0" + r;
+ }
+ if (g.length === 1) {
+ g = "0" + g;
+ }
+ if (b.length === 1) {
+ b = "0" + b;
+ }
+ return "#" + r + g + b;
+};
+
+const generateColorsDayMode = (baseColor, numColors) => {
+ // Create an array to hold the colors
+ let colors = [baseColor];
+ // Calculate the step size for increasing saturation and luminosity
+ let step = 0.02;
+ // Loop through the number of colors requested
+ for (let i = 0; i < numColors - 1; i++) {
+ // Convert the base color to HSL format
+ const currentColor = hexToHSL(colors[i]);
+ // Calculate the saturation for this color
+ let luminosity,
+ saturation;
+ if (i % 2) {
+ luminosity = currentColor.l - i / 2 * step;
+ saturation = currentColor.s;
+ } else {
+ luminosity = currentColor.l;
+ saturation = currentColor.s + i / 2 * step;
+ }
+ let hslColor;
+ // Create the color in HSL format
+ if (saturation <= 100 && luminosity >= 40) {
+ hslColor = {h: currentColor.h, s: saturation, l: luminosity};
+ } else if (saturation > 100 && luminosity >= 40) {
+ hslColor = {h: currentColor.h, s: 100, l: luminosity};
+ } else if (saturation <= 100 && luminosity < 40) {
+ hslColor = {h: currentColor.h, s: saturation, l: 40};
+ } else if (saturation > 100 && luminosity < 40) {
+ hslColor = {h: currentColor.h, s: 100, l: 40};
+ }
+ // Convert the color back to hex format and add it to the array
+ let hexColor = HSLToHex(hslColor);
+ colors.push(hexColor);
+ }
+ return colors;
+};
+
+const generateColorsNightMode = (baseColor, numColors) => {
+ // Create an array to hold the colors
+ let colors = [baseColor];
+ // Calculate the step size for increasing saturation and luminosity
+ let step = 0.03;
+ // Loop through the number of colors requested
+ for (let i = 0; i < numColors - 1; i++) {
+ // Convert the base color to HSL format
+ const currentColor = hexToHSL(colors[i]);
+ // Calculate the saturation for this color
+ let luminosity,
+ saturation;
+ if (i % 2) {
+ luminosity = currentColor.l + i / 2 * step;
+ saturation = currentColor.s;
+ } else {
+ luminosity = currentColor.l;
+ saturation = currentColor.s - i / 2 * step;
+ }
+ let hslColor;
+ // Create the color in HSL format
+ if (saturation >= 30 && luminosity <= 65) {
+ hslColor = {h: currentColor.h, s: saturation, l: luminosity};
+ } else if (saturation < 30 && luminosity <= 65) {
+ hslColor = {h: currentColor.h, s: 30, l: luminosity};
+ } else if (saturation >= 30 && luminosity > 65) {
+ hslColor = {h: currentColor.h, s: saturation, l: 65};
+ } else if (saturation < 30 && luminosity > 65) {
+ hslColor = {h: currentColor.h, s: 30, l: 65};
+ }
+ // Convert the color back to hex format and add it to the array
+ let hexColor = HSLToHex(hslColor);
+ colors.push(hexColor);
+ }
+ return colors;
+};
+
+export const generateTimestamps = (step) => {
+ // Generates an array of timestamps based on a specific step
+ // e.g. for (step = 5) => ['00:00', '00:05', '00:10', ..., '23:55']
+ const timestamps = [];
+ for (let hours = 0; hours < 24; hours++) {
+ for (let minutes = 0; minutes < 60; minutes += step) {
+ const formattedHours = hours.toString().padStart(2, '0');
+ const formattedMinutes = minutes.toString().padStart(2, '0');
+ const timestamp = `${formattedHours}:${formattedMinutes}`;
+ timestamps.push(timestamp);
+ }
+ }
+ return timestamps;
+};
+
+export const generateColors = (color) => {
+ // Returns an array of colors, containing 144 colors for day mode and 144 for night mode
+ const dayColorsArray = generateColorsDayMode(color, 72);
+ const nightColorsArray = generateColorsNightMode(color, 72);
+ const array = [...nightColorsArray.reverse(), ...dayColorsArray, ...dayColorsArray.reverse(), ...nightColorsArray.reverse()];
+ const offset = array.splice(0, 12);
+ return [...array, ...offset];
+};
+
+export const generateColorObject = (colorArray) => {
+ // Returns an object with timestamps for keys and colors for values
+ // (e.g. "03:05": "#e6e6e6")
+ const timestamps = generateTimestamps(5);
+ return timestamps.map((element, index) => {
+ return {key: element, value: colorArray[index]};
+ }).reduce((prev, curr) => {
+ prev[curr.key] = curr.value;
+ return prev;
+ }, {});
+};
+
+export const getIndex = () => {
+ // Creates an index from a timestamp
+ let minute = parseInt(new Date().toTimeString().substring(0, 5).slice(3));
+ let hour = parseInt(new Date().toTimeString().substring(0, 8));
+ let index;
+ while (minute % 5 !== 0) minute++;
+ if (minute >= 60) {
+ minute = 0;
+ hour++;
+ }
+ if (hour > 24) {
+ hour = 1;
+ }
+ if (hour < 10) {
+ index = '0' + hour + ':';
+ } else {
+ index = hour + ':';
+ }
+ if (minute < 10) {
+ index = index + '0' + minute;
+ } else {
+ index = index + minute;
+ }
+ return index;
+};
+
+export const hexToRGB = (hex) => {
+ // Converts a HEX color into an RGB one
+ 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;
+};
+
+export const setPreset = ({preset, context}) => {
+ // Changes the preset and default colors from context
+ const newContext = Object.assign({}, context);
+ newContext.activeTheme = preset;
+
+ switch (preset) {
+ case 'blueColorSet2':
+ newContext.backgroundColor = '#181E3D';
+ newContext.componentBackgroundColor = '#7D848C';
+ newContext.focusBackgroundColor = '#E6E6E6';
+ newContext.popupBackgroundColor = '#1E233F';
+ newContext.subtitleTextColor = '#ABAEB3';
+ newContext.textColor = '#E6E6E6';
+ break;
+ case 'blueColorSet1':
+ newContext.backgroundColor = '#272829';
+ newContext.componentBackgroundColor = '#7D848C';
+ newContext.focusBackgroundColor = '#E6E6E6';
+ newContext.popupBackgroundColor = '#292929';
+ newContext.subtitleTextColor = '#ABAEB3';
+ newContext.textColor = '#E6E6E6';
+ break;
+ case 'greenColorSet2':
+ newContext.backgroundColor = '#102933';
+ newContext.componentBackgroundColor = '#7D848C';
+ newContext.focusBackgroundColor = '#E6E6E6';
+ newContext.popupBackgroundColor = '#172D36';
+ newContext.subtitleTextColor = '#ABAEB3';
+ newContext.textColor = '#E6E6E6';
+ break;
+ case 'greenColorSet1':
+ newContext.backgroundColor = '#272829';
+ newContext.componentBackgroundColor = '#7D848C';
+ newContext.focusBackgroundColor = '#E6E6E6';
+ newContext.popupBackgroundColor = '#292929';
+ newContext.subtitleTextColor = '#ABAEB3';
+ newContext.textColor = '#E6E6E6';
+ break;
+ case 'purpleColorSet2':
+ newContext.backgroundColor = '#2B1941';
+ newContext.componentBackgroundColor = '#7D848C';
+ newContext.focusBackgroundColor = '#E6E6E6';
+ newContext.popupBackgroundColor = '#2F1F43';
+ newContext.subtitleTextColor = '#848290';
+ newContext.textColor = '#E6E6E6';
+ break;
+ case 'purpleColorSet1':
+ newContext.backgroundColor = '#272829';
+ newContext.componentBackgroundColor = '#7D848C';
+ newContext.focusBackgroundColor = '#E6E6E6';
+ newContext.popupBackgroundColor = '#292929';
+ newContext.subtitleTextColor = '#ABAEB3';
+ newContext.textColor = '#E6E6E6';
+ break;
+ case 'redColorSet2':
+ newContext.backgroundColor = '#3D1A1A';
+ newContext.componentBackgroundColor = '#7D848C';
+ newContext.focusBackgroundColor = '#E6E6E6';
+ newContext.popupBackgroundColor = '#3F2020';
+ newContext.subtitleTextColor = '#807477';
+ newContext.textColor = '#E6E6E6';
+ break;
+ case 'redColorSet1':
+ newContext.backgroundColor = '#252424';
+ newContext.componentBackgroundColor = '#7D848C';
+ newContext.focusBackgroundColor = '#E6E6E6';
+ newContext.popupBackgroundColor = '#292929';
+ newContext.subtitleTextColor = '#ABAEB3';
+ newContext.textColor = '#E6E6E6';
+ break;
+ default:
+ newContext.backgroundColor = '#000000';
+ newContext.componentBackgroundColor = '#7D848C';
+ newContext.focusBackgroundColor = '#E6E6E6';
+ newContext.popupBackgroundColor = '#575E66';
+ newContext.subtitleTextColor = '#ABAEB3';
+ newContext.textColor = '#E6E6E6';
+ break;
+ }
+
+ newContext.colors = generateStylesheet(
+ newContext.backgroundColor,
+ newContext.componentBackgroundColor,
+ newContext.focusBackgroundColor,
+ newContext.popupBackgroundColor,
+ newContext.subtitleTextColor,
+ newContext.textColor,
+ preset
+ );
+ return newContext;
+};
diff --git a/sandstone/pattern-ls2request-custom-colors/src/index.js b/sandstone/pattern-ls2request-custom-colors/src/index.js
new file mode 100644
index 000000000..eb163ed13
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/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/sandstone/pattern-ls2request-custom-colors/src/views/CustomizePanel/CustomizePanel.js b/sandstone/pattern-ls2request-custom-colors/src/views/CustomizePanel/CustomizePanel.js
new file mode 100644
index 000000000..da2efb1a1
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/views/CustomizePanel/CustomizePanel.js
@@ -0,0 +1,105 @@
+import {useCallback, useContext} from 'react';
+import Button from '@enact/sandstone/Button';
+import Scroller from '@enact/sandstone/Scroller';
+import {Cell, Layout} from '@enact/ui/Layout';
+
+import {setPreset, changeSettings} from '../../hooks/utils';
+import {generateStylesheet} from '../../hooks/generateStylesheet';
+import ColorPicker from '../../components/ColorPicker';
+import PreviewSection from '../../components/PreviewSection';
+import {AppContext} from '../../constants';
+
+import css from './CustomizePanel.module.less';
+
+const CustomizePanel = () => {
+ const {context, setContext} = useContext(AppContext);
+ const {backgroundColor, componentBackgroundColor, focusBackgroundColor, popupBackgroundColor, subtitleTextColor, textColor} = context;
+
+ const onChangeColor = useCallback((color, newColor) => {
+ // a copy of the context object is created
+ const newContext = Object.assign({}, context);
+ // update the color value on the newly created object with what gets received from `event` (handleBackgroundColor)
+ newContext[color] = newColor;
+ // generate the new stylesheet based on the updated color
+ newContext.colors = generateStylesheet(
+ newContext.backgroundColor,
+ newContext.componentBackgroundColor,
+ newContext.focusBackgroundColor,
+ newContext.popupBackgroundColor,
+ newContext.subtitleTextColor,
+ newContext.textColor,
+ newContext.preset
+ );
+ // update the app context with the new context
+ setContext(newContext);
+
+ // check if app is running on webOS environment and update the `theme`
+ if (typeof window === 'object' && window.webOSSystem && window.webOSSystem.launchParams) {
+ changeSettings({
+ category: 'customUi',
+ settings: {
+ theme: JSON.stringify(newContext)
+ }
+ });
+ }
+ }, [context, setContext]);
+
+ const handleBackgroundColor = useCallback((ev) => {
+ onChangeColor('backgroundColor', ev);
+ }, [onChangeColor]);
+
+ const handleComponentBackgroundColor = useCallback((ev) => {
+ onChangeColor('componentBackgroundColor', ev);
+ }, [onChangeColor]);
+
+ const handleFocusBackgroundColor = useCallback((ev) => {
+ onChangeColor('focusBackgroundColor', ev);
+ }, [onChangeColor]);
+
+ const handlePopupBackgroundColor = useCallback((ev) => {
+ onChangeColor('popupBackgroundColor', ev);
+ }, [onChangeColor]);
+
+ const handleSubtitleTextColor = useCallback((ev) => {
+ onChangeColor('subtitleTextColor', ev);
+ }, [onChangeColor]);
+
+ const handleTextColor = useCallback((ev) => {
+ onChangeColor('textColor', ev);
+ }, [onChangeColor]);
+
+ const handleResetButton = useCallback(() => {
+ const newContext = setPreset({preset: context.activeTheme, context});
+ setContext(newContext);
+
+ if (typeof window === 'object' && window.webOSSystem && window.webOSSystem.launchParams) {
+ changeSettings({
+ category: 'customUi',
+ settings: {
+ theme: JSON.stringify(newContext)
+ }
+ });
+ }
+ }, [context, setContext]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ );
+};
+
+export default CustomizePanel;
diff --git a/sandstone/pattern-ls2request-custom-colors/src/views/CustomizePanel/CustomizePanel.module.less b/sandstone/pattern-ls2request-custom-colors/src/views/CustomizePanel/CustomizePanel.module.less
new file mode 100644
index 000000000..01326d3e6
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/views/CustomizePanel/CustomizePanel.module.less
@@ -0,0 +1,40 @@
+// CustomizePanel.module.less
+//
+.customizePanel {
+ height: 100%;
+
+ .customizeSection {
+ padding: 24px;
+
+ .scroller {
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-color-rgb: 230, 230, 230;
+
+ .colorPicker {
+ color: #E6E6E6;
+ --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-progress-color-rgb: 230, 230, 230;
+ --sand-selected-bg-color: #3E454D;
+ --sand-selected-color-rgb: 230, 230, 230;
+ --sand-selected-text-color-rgb: 230, 230, 230;
+ --sand-text-color-rgb: 230, 230, 230;
+ --sand-toggle-off-color: #AEAEAE;
+ --sand-toggle-off-bg-color: #777777;
+ --sand-toggle-on-color: #E6E6E6;
+ --sand-toggle-on-bg-color: #30AD6B;
+ }
+ }
+
+ .resetBtn {
+ width: fit-content;
+ align-self: flex-end;
+ --sand-component-bg-color: #7D848C;
+ --sand-component-focus-text-color-rgb: 66, 75, 90;
+ --sand-component-text-color-rgb: 230, 230, 230;
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ }
+ }
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/src/views/CustomizePanel/package.json b/sandstone/pattern-ls2request-custom-colors/src/views/CustomizePanel/package.json
new file mode 100644
index 000000000..679339d0b
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/views/CustomizePanel/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "CustomizePanel.js"
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/src/views/MainView/MainView.js b/sandstone/pattern-ls2request-custom-colors/src/views/MainView/MainView.js
new file mode 100644
index 000000000..bf03b7b51
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/views/MainView/MainView.js
@@ -0,0 +1,53 @@
+import IconItem from '@enact/sandstone/IconItem';
+import {Header, Panel, Panels} from '@enact/sandstone/Panels';
+import Spinner from '@enact/sandstone/Spinner';
+import {useCallback, useState} from 'react';
+
+import CustomizePanel from '../CustomizePanel';
+import PresetPanel from '../PresetPanel';
+
+import useLinearSkinColor from '../../hooks/useDynamicColor';
+
+import css from './MainView.module.less';
+
+const MainView = ({responseStatus, ...rest}) => {
+ const [applySkin, skin] = useLinearSkinColor();
+ const [panelIndex, setPanelIndex] = useState(0);
+
+ const forward = useCallback(() => {
+ setPanelIndex(panelIndex + 1);
+ }, [panelIndex]);
+
+ const backward = useCallback(() => {
+ setPanelIndex(panelIndex - 1);
+ }, [panelIndex]);
+
+ if (!responseStatus && typeof window === 'object' && window.webOSSystem && window.webOSSystem.launchParams) {
+ return (Loading...);
+ } else {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+};
+export default MainView;
diff --git a/sandstone/pattern-ls2request-custom-colors/src/views/MainView/MainView.module.less b/sandstone/pattern-ls2request-custom-colors/src/views/MainView/MainView.module.less
new file mode 100644
index 000000000..16a8a3216
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/views/MainView/MainView.module.less
@@ -0,0 +1,11 @@
+// MainView.module.less
+//
+.mainView {
+ background-color: #1A1A1A;
+
+ .panelHeader {
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ --sand-text-color-rgb: 230, 230, 230;
+ --sand-text-sub-color: #ABAEB3;
+ }
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/src/views/MainView/package.json b/sandstone/pattern-ls2request-custom-colors/src/views/MainView/package.json
new file mode 100644
index 000000000..6035fb17d
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/views/MainView/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "MainView.js"
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/src/views/PresetPanel/PresetPanel.js b/sandstone/pattern-ls2request-custom-colors/src/views/PresetPanel/PresetPanel.js
new file mode 100644
index 000000000..aa657c325
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/views/PresetPanel/PresetPanel.js
@@ -0,0 +1,89 @@
+import RadioItem from '@enact/sandstone/RadioItem';
+import Scroller from '@enact/sandstone/Scroller';
+import SwitchItem from '@enact/sandstone/SwitchItem';
+import {Cell, Column, Layout} from '@enact/ui/Layout';
+import {useCallback, useContext} from 'react';
+
+import {changeSettings, setPreset} from '../../hooks/utils';
+import PreviewSection from '../../components/PreviewSection';
+import {AppContext, presets} from '../../constants';
+
+import css from './PresetPanel.module.less';
+
+const PresetPanel = () => {
+ const {context, setContext} = useContext(AppContext);
+ const {activeTheme, dynamicColor, handleSkin} = context;
+
+ // update `theme` key with the stringified version of the context object
+ const updateThemeKey = (newContext) => {
+ return changeSettings({
+ category: 'customUi',
+ settings: {
+ theme: JSON.stringify(newContext)
+ }
+ });
+ }
+
+ // Toggle using dynamic color mode which will modify the luminosity and saturation of your theme colors depending on the current time
+ const onClickDynamicColor = useCallback(() => {
+ // create a copy of the context object
+ const newContext = Object.assign({}, context);
+ // update the `dynamicColor` property on the new context object
+ newContext.dynamicColor = context.dynamicColor === 'on' ? 'off' : 'on';
+ // update app context with the new context object
+ setContext(newContext);
+
+ // check if app is running in webOS environment and update `theme` key
+ if (typeof window === 'object' && window.webOSSystem && window.webOSSystem.launchParams) {
+ updateThemeKey(newContext);
+ }
+
+ }, [context, setContext]);
+
+ // Toggle adjusting skin automatically, which means that the system will choose between Sandstone neutral and light modes according to the colors you have set
+ const onClickHandleSkin = useCallback(() => {
+ // create a copy of the context object
+ const newContext = Object.assign({}, context);
+ // update the `handleSkin` property on the new context object
+ newContext.handleSkin = context.handleSkin === 'on' ? 'off' : 'on';
+ // update app context with the new context object
+ setContext(newContext);
+
+ // check if app is running in webOS environment and update `theme` key
+ if (typeof window === 'object' && window.webOSSystem && window.webOSSystem.launchParams) {
+ updateThemeKey(newContext);
+ }
+ }, [context, setContext]);
+
+ // Choose from an existing preset theme
+ const onClickHandlePreset = useCallback((ev) => {
+ // create a copy of the app context and set the colors for the selected preset
+ const newContext = setPreset({preset: ev.currentTarget.id, context: context});
+ // update app context with the new context object
+ setContext(newContext);
+
+ // check if app is running in webOS environment and update `theme` key
+ if (typeof window === 'object' && window.webOSSystem && window.webOSSystem.launchParams) {
+ updateThemeKey(newContext);
+ }
+ }, [context, setContext]);
+
+ return (
+
+
+
+
+ {Object.entries(presets).map(([key, value]) =>
+ {value}
+ )}
+
+ Activate dynamic color mode
+ Adjust skin automatically
+
+ |
+
+
+ );
+};
+
+export default PresetPanel;
diff --git a/sandstone/pattern-ls2request-custom-colors/src/views/PresetPanel/PresetPanel.module.less b/sandstone/pattern-ls2request-custom-colors/src/views/PresetPanel/PresetPanel.module.less
new file mode 100644
index 000000000..9868fda54
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/views/PresetPanel/PresetPanel.module.less
@@ -0,0 +1,32 @@
+// PresetPanel.module.less
+//
+.presetPanel {
+ height: 100%;
+
+ .customizeSection {
+ padding: 24px;
+
+ .scroller {
+ --sand-focus-bg-color-rgb: 230, 230, 230;
+ --sand-progress-bg-color-rgb: 55, 58, 65;
+ --sand-progress-color-rgb: 230, 230, 230;
+ }
+
+ .radioItem,
+ .switchItem {
+ color: #E6E6E6;
+ --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-progress-color-rgb: 230, 230, 230;
+ --sand-selected-bg-color: #3E454D;
+ --sand-selected-color-rgb: 230, 230, 230;
+ --sand-selected-text-color-rgb: 230, 230, 230;
+ --sand-text-color-rgb: 230, 230, 230;
+ --sand-toggle-off-color: #AEAEAE;
+ --sand-toggle-off-bg-color: #777777;
+ --sand-toggle-on-color: #E6E6E6;
+ --sand-toggle-on-bg-color: #30AD6B;
+ }
+ }
+}
diff --git a/sandstone/pattern-ls2request-custom-colors/src/views/PresetPanel/package.json b/sandstone/pattern-ls2request-custom-colors/src/views/PresetPanel/package.json
new file mode 100644
index 000000000..639c6c0c6
--- /dev/null
+++ b/sandstone/pattern-ls2request-custom-colors/src/views/PresetPanel/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "PresetPanel.js"
+}