diff --git a/client/package.json b/client/package.json
index 386e251974..243b855f22 100644
--- a/client/package.json
+++ b/client/package.json
@@ -58,6 +58,7 @@
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.14.1",
"@loadable/component": "^5.16.7",
+ "@melloware/coloris": "^0.25.0",
"@vidstack/react": "^1.12.13",
"ace-builds": "^1.x",
"actioncable": "~5.0.7",
diff --git a/client/src/backend/components/hero/Builder/forms/JournalDescription.js b/client/src/backend/components/hero/Builder/forms/JournalDescription.js
index b983708daf..c4c8470fa1 100644
--- a/client/src/backend/components/hero/Builder/forms/JournalDescription.js
+++ b/client/src/backend/components/hero/Builder/forms/JournalDescription.js
@@ -124,11 +124,12 @@ function JournalDescription({
altTextName="attributes[logoAltText]"
altTextLabel={t("journals.forms.logo_alt_label")}
/>
-
+
+
+
+ <>
+
+ {t("navigation.return_home")}
+
+
+ >
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/client/src/backend/containers/features/Detail.js b/client/src/backend/containers/features/Detail.js
index 6aad66687e..0f3ad1e9a1 100644
--- a/client/src/backend/containers/features/Detail.js
+++ b/client/src/backend/containers/features/Detail.js
@@ -113,13 +113,8 @@ class FeatureDetailContainer extends PureComponent {
previewableFeature(props) {
const { session } = props;
if (!session) return null;
- const { source, dirty } = session;
- const previewAttributes = {
- ...source.attributes,
- ...dirty.attributes
- };
- const preview = { ...source, attributes: previewAttributes };
- return preview;
+ const { dirty } = session;
+ return dirty.attributes;
}
isNew(props) {
@@ -214,7 +209,11 @@ class FeatureDetailContainer extends PureComponent {
label={t("records.features.preview.section_title")}
instructions={t("records.features.preview.instructions")}
>
-
+
) : null}
{this.renderRoutes()}
diff --git a/client/src/backend/containers/features/Properties.js b/client/src/backend/containers/features/Properties.js
index 5a50876e1a..713d8a9d88 100644
--- a/client/src/backend/containers/features/Properties.js
+++ b/client/src/backend/containers/features/Properties.js
@@ -88,20 +88,20 @@ class FeaturesPropertiesContainer extends PureComponent {
}
]}
/>
-
-
-
{
+ return {
+ settings: select(requests.settings, state.entityStore)
+ };
+ };
+
+ static displayName = "Settings.Content";
+
+ static propTypes = {
+ settings: PropTypes.object
+ };
+
+ render() {
+ if (!this.props.settings) return null;
+ const t = this.props.t;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default withTranslation()(
+ connect(SettingsContentContainer.mapStateToProps)(SettingsContentContainer)
+);
diff --git a/client/src/backend/containers/settings/Theme.js b/client/src/backend/containers/settings/Theme.js
index 3de8e80312..4c1655485b 100644
--- a/client/src/backend/containers/settings/Theme.js
+++ b/client/src/backend/containers/settings/Theme.js
@@ -1,4 +1,4 @@
-import React, { PureComponent } from "react";
+import { PureComponent } from "react";
import PropTypes from "prop-types";
import { withTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -8,6 +8,7 @@ import FormContainer from "global/containers/form";
import { settingsAPI, requests } from "api";
import { select } from "utils/entityUtils";
import PageHeader from "backend/components/layout/PageHeader";
+import HeaderPreview from "backend/components/theme/HeaderPreview";
export class SettingsThemeContainer extends PureComponent {
static mapStateToProps = state => {
@@ -25,6 +26,7 @@ export class SettingsThemeContainer extends PureComponent {
render() {
if (!this.props.settings) return null;
const t = this.props.t;
+
return (
@@ -36,178 +38,118 @@ export class SettingsThemeContainer extends PureComponent {
create={settingsAPI.update}
className="form-secondary"
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {getValue => (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
diff --git a/client/src/backend/containers/settings/index.js b/client/src/backend/containers/settings/index.js
index 1f5f7a6ff9..134c6c2852 100644
--- a/client/src/backend/containers/settings/index.js
+++ b/client/src/backend/containers/settings/index.js
@@ -6,6 +6,7 @@ import Integrations from "./Integrations";
import Email from "./Email";
import Ingestion from "./Ingestion";
import Subjects from "./subjects";
+import Content from "./Content";
export default {
Properties,
@@ -15,5 +16,6 @@ export default {
Integrations,
Email,
Ingestion,
- Subjects
+ Subjects,
+ Content
};
diff --git a/client/src/backend/routes.js b/client/src/backend/routes.js
index 33333135e5..75cdb59d95 100644
--- a/client/src/backend/routes.js
+++ b/client/src/backend/routes.js
@@ -1133,6 +1133,13 @@ const routes = {
path: "/backend/settings/theme",
helper: () => "/backend/settings/theme"
},
+ {
+ name: "backendSettingsContent",
+ exact: true,
+ component: "SettingsContent",
+ path: "/backend/settings/content",
+ helper: () => "/backend/settings/content"
+ },
{
name: "backendSettingsIntegrations",
exact: true,
diff --git a/client/src/config/app/locale/en-US/json/backend/records.json b/client/src/config/app/locale/en-US/json/backend/records.json
index 5dc5f78557..edadb54bef 100644
--- a/client/src/config/app/locale/en-US/json/backend/records.json
+++ b/client/src/config/app/locale/en-US/json/backend/records.json
@@ -113,7 +113,7 @@
"no_title": "Untitled #{{position}}",
"unauthorized_create": "You are not allowed to create features.",
"unauthorized_update": "You are not allowed to update features.",
- "instructions": "This is an approximate preview of your feature. Foreground, background, and markdown will not be displayed until the feature is saved."
+ "instructions": "This is an approximate preview of your feature. Foreground and background images, and parsed markdown will not be displayed until the feature is saved."
},
"untitled_record": "Untitled #{{number}}",
"new_header": "New Feature",
diff --git a/client/src/config/app/locale/en-US/json/backend/settings.json b/client/src/config/app/locale/en-US/json/backend/settings.json
index ae362c748b..08c4ea87a8 100644
--- a/client/src/config/app/locale/en-US/json/backend/settings.json
+++ b/client/src/config/app/locale/en-US/json/backend/settings.json
@@ -72,16 +72,31 @@
"offset_label": "Header Navigation Offset",
"offset_instructions": "Use this field to adjust the vertical position of header navigation. For example, enter \"5\" to move the header down 5 pixels. Enter \"-5\" to move it up 5 pixels.",
"accent_color_label": "Accent Color",
- "accent_color_instructions": "Enter a color in one of the following formats: CSS color keyword, hexadecimal, rgb, rgba, hsl, hsla, or hwb. Leave blank to restore default accent color.",
+ "accent_color_instructions": "Enter a color in an accepted CSS color format. Leave blank to restore default color.",
"foreground_color_label": "Header Foreground Color",
- "foreground_color_instructions": "Override the header foreground color in one of the allowed formats (see above).",
+ "foreground_color_instructions": "Override the header foreground color. Leave blank to restore default color.",
"active_foreground_color_label": "Header Foreground Active Color",
- "active_foreground_color_instructions": "Override the header foreground active state color in one of the allowed formats (see above). A link in the primary navigation is \"active\" when the user is currently on that page.",
+ "active_foreground_color_instructions": "Override the header foreground active route color. Leave blank to restore default color.",
"background_color_label": "Header Background Color",
- "background_color_instructions": "Override the header background color in one of the allowed formats (see above).",
+ "background_color_instructions": "Override the header background color. Leave blank to restore default color.",
"typography_header": "Typography",
"typekit_label": "Typekit ID",
"typekit_placeholder": "Enter Typekit ID",
+ "colors_header": "Colors"
+ },
+ "content": {
+ "header": "Content Settings",
+ "content_signup_header": "Signup",
+ "content_data_use_header": "Data Use",
+ "string_signup_terms_header": "Terms & Conditions Header",
+ "string_signup_terms_one": "Terms & Conditions First Paragraph",
+ "string_signup_terms_two": "Terms & Conditions Second Paragraph",
+ "string_data_use_header": "Page Header",
+ "string_data_use_copy": "Page Copy",
+ "data_use_copy_instructions": "This field accepts markdown",
+ "string_cookies_banner_header": "Cookies Banner Header",
+ "string_cookies_banner_copy": "Cookies Banner Body",
+ "cookies_banner_copy_instructions": "This field accepts markdown",
"top_bar_header": "Top Bar",
"text_label": "Text",
"text_placeholder": "Enter Top Bar Text",
@@ -95,18 +110,7 @@
"disabled": "Disabled",
"always": "Always Visible",
"standalone": "Only Visible in Standalone Mode"
- },
- "content_signup_header": "Content: Signup",
- "content_data_use_header": "Content: Data Use",
- "string_signup_terms_header": "Terms & Conditions Header",
- "string_signup_terms_one": "Terms & Conditions First Paragraph",
- "string_signup_terms_two": "Terms & Conditions Second Paragraph",
- "string_data_use_header": "Page Header",
- "string_data_use_copy": "Page Copy",
- "data_use_copy_instructions": "This field accepts markdown",
- "string_cookies_banner_header": "Cookies Banner Header",
- "string_cookies_banner_copy": "Cookies Banner Body",
- "cookies_banner_copy_instructions": "This field accepts markdown"
+ }
},
"integrations": {
"header": "External Integrations Settings",
diff --git a/client/src/config/app/locale/en-US/json/shared/page-titles.json b/client/src/config/app/locale/en-US/json/shared/page-titles.json
index 8c8a2c9706..5c1543a2c6 100644
--- a/client/src/config/app/locale/en-US/json/shared/page-titles.json
+++ b/client/src/config/app/locale/en-US/json/shared/page-titles.json
@@ -8,6 +8,7 @@
"assets": "Assets",
"collaborators": "People",
"comments": "Comments",
+ "content": "Content",
"contents": "Table of Contents",
"dashboard": "Dashboard",
"details": "Details",
diff --git a/client/src/frontend/components/layout/Splash/index.js b/client/src/frontend/components/layout/Splash/index.js
index 9fe22b4821..e4266f790d 100644
--- a/client/src/frontend/components/layout/Splash/index.js
+++ b/client/src/frontend/components/layout/Splash/index.js
@@ -6,7 +6,7 @@ import { Link } from "react-router-dom";
import * as Styled from "./styles";
export default function Splash(props) {
- const { preview, feature, authenticated } = props;
+ const { preview, feature, authenticated, previewAttrs } = props;
const {
style: mode,
backgroundColor,
@@ -28,6 +28,21 @@ export default function Splash(props) {
includeSignUp
} = feature.attributes;
+ const renderedHeader = () => {
+ if (preview && previewAttrs?.header) return previewAttrs.header;
+ return headerFormatted || header;
+ };
+
+ const renderedSubheader = () => {
+ if (preview && previewAttrs?.subheader) return previewAttrs.subheader;
+ return subheaderFormatted || subheader;
+ };
+
+ const renderedBody = () => {
+ if (preview && previewAttrs?.body) return previewAttrs.body;
+ return bodyFormatted || body;
+ };
+
return (
{(subheaderFormatted || subheader) && (
)}
{linkText && linkUrl ? (
diff --git a/client/src/global/components/form/BaseInput/index.js b/client/src/global/components/form/BaseInput/index.js
index 5a3bb61ac4..4e15da7f08 100644
--- a/client/src/global/components/form/BaseInput/index.js
+++ b/client/src/global/components/form/BaseInput/index.js
@@ -107,6 +107,39 @@ export class FormBaseInput extends PureComponent {
return props.renderValue(props.value);
}
+ renderInputComponent() {
+ const { id, idForError, idForInstructions, ariaRequired } = this.props;
+
+ const InputComponent =
+ this.context?.styleType === "secondary"
+ ? Styled.SecondaryInput
+ : Styled.PrimaryInput;
+
+ return (
+ {
+ this.inputElement = input;
+ if (this.props.colorRef) this.props.colorRef.current = input;
+ }}
+ id={id}
+ name={this.props.name}
+ disabled={this.props.isDisabled}
+ type={this.props.inputType ?? this.props.type}
+ placeholder={this.props.placeholder}
+ onChange={this.props.onChange}
+ onKeyDown={e => {
+ if (this.props.onKeyDown) this.props.onKeyDown(e, this.inputElement);
+ }}
+ value={this.renderValue(this.props)}
+ aria-describedby={`${idForError || ""} ${idForInstructions || ""}`}
+ autoComplete={this.props.autoComplete}
+ defaultValue={this.props.defaultValue}
+ required={this.props.required}
+ aria-required={ariaRequired}
+ />
+ );
+ }
+
render() {
const {
id,
@@ -115,8 +148,7 @@ export class FormBaseInput extends PureComponent {
buttons,
instructions,
wide,
- className,
- ariaRequired
+ className
} = this.props;
const fieldClasses = classnames(className, {
@@ -124,11 +156,6 @@ export class FormBaseInput extends PureComponent {
});
const Wrapper = buttons ? Styled.WrapperWithActions : Errorable;
- const InputComponent =
- this.context?.styleType === "secondary"
- ? Styled.SecondaryInput
- : Styled.PrimaryInput;
-
return (
- {
- this.inputElement = input;
- }}
- id={id}
- name={this.props.name}
- disabled={this.props.isDisabled}
- type={this.props.inputType ?? this.props.type}
- placeholder={this.props.placeholder}
- onChange={this.props.onChange}
- onKeyDown={e => {
- if (this.props.onKeyDown)
- this.props.onKeyDown(e, this.inputElement);
- }}
- value={this.renderValue(this.props)}
- aria-describedby={`${idForError || ""} ${idForInstructions || ""}`}
- autoComplete={this.props.autoComplete}
- defaultValue={this.props.defaultValue}
- required={this.props.required}
- aria-required={ariaRequired}
- />
+ {this.props.inputType === "color" ? (
+
+ {this.renderInputComponent()}
+ {this.renderValue(this.props)}
+
+ ) : (
+ this.renderInputComponent()
+ )}
{buttons && this.renderButtons(buttons)}
{this.props.instructions && (
{
+ Coloris.init({ parent: container });
+
+ const setColor = event => {
+ const isTarget = event.detail.currentEl.id === inputId;
+ if (isTarget) colorRef.current = event.detail.color;
+ };
+
+ document.addEventListener("coloris:pick", setColor);
+
+ const handleEnter = e => {
+ if (e.key !== "Enter") return;
+ e.preventDefault();
+ };
+
+ const picker = document.querySelector("#clr-picker");
+ picker.addEventListener("keydown", handleEnter);
+
+ return () => {
+ document.removeEventListener("coloris:pick", setColor);
+ picker.removeEventListener("keydown", handleEnter);
+ };
+ }, [inputId, container, props]);
+
+ useEffect(() => {
+ const onClose = () => {
+ props.onChange({ target: { value: colorRef.current } });
+ };
+
+ const inputEl = inputRef.current;
+
+ if (inputEl) {
+ Coloris({
+ el: inputEl,
+ format: "mixed",
+ formatToggle: true,
+ themeMode: "dark",
+ clearButton: true,
+ defaultColor: defaultValue,
+ margin: 5,
+ parent: container
+ });
+
+ inputEl.addEventListener("close", onClose);
+
+ return () => inputEl.removeEventListener("close", onClose);
+ }
+ }, [inputRef, defaultValue, colorRef, container, props]);
+
+ return (
+
+ );
+}
+
+ColorInput.propTypes = {
+ defaultValue: PropTypes.string.isRequired,
+ // By default the color picker renders as a dialog in .
+ // Pass a selector if rendering in a focus trap.
+ container: PropTypes.string
+ // See BaseInput for remaining propTypes
+};
+
+export default setter(ColorInput);
+
+ColorInput.displayName = "Form.ColorInput";
diff --git a/client/src/global/components/form/ColorInput/index.js b/client/src/global/components/form/ColorInput/index.js
new file mode 100644
index 0000000000..d808257eaf
--- /dev/null
+++ b/client/src/global/components/form/ColorInput/index.js
@@ -0,0 +1,7 @@
+import loadable from "@loadable/component";
+
+const ColorInput = loadable(() =>
+ import(/* webpackChunkName: "coloris" */ "./ColorInput")
+);
+
+export default ColorInput;
diff --git a/client/src/global/components/form/ColorInput/styles.js b/client/src/global/components/form/ColorInput/styles.js
new file mode 100644
index 0000000000..684c093ed6
--- /dev/null
+++ b/client/src/global/components/form/ColorInput/styles.js
@@ -0,0 +1,23 @@
+import styled from "@emotion/styled";
+import { FormBaseInput } from "../BaseInput";
+
+export const ColorInput = styled(FormBaseInput)`
+ --ColorInput-default-color: ${({ $defaultColor }) => $defaultColor};
+
+ input[type="color"] {
+ width: 24px;
+ height: 24px;
+ display: inline-block;
+ cursor: pointer;
+ border: none;
+ }
+
+ span.ColorInput-wrapper {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ font-family: var(--font-family-sans);
+ padding-block-end: 8px;
+ border-bottom: 1px solid var(--input-border-color);
+ }
+`;
diff --git a/client/src/global/components/form/index.js b/client/src/global/components/form/index.js
index 4f867b87d9..9fe8a4a053 100644
--- a/client/src/global/components/form/index.js
+++ b/client/src/global/components/form/index.js
@@ -31,6 +31,7 @@ import Label from "./BaseLabel";
import FieldWrapper from "./FieldWrapper";
import { InputGroupPrimary, InputGroupSecondary } from "./InputGroup/styles";
import DrawerButtons from "./DrawerButtons";
+import ColorInput from "./ColorInput";
export default {
CoverUploadPlaceholder,
@@ -65,7 +66,8 @@ export default {
FieldWrapper,
InputGroupPrimary,
InputGroupSecondary,
- DrawerButtons
+ DrawerButtons,
+ ColorInput
};
export const Unwrapped = {
diff --git a/client/src/helpers/router/navigation.js b/client/src/helpers/router/navigation.js
index d419ab0645..43d90184ce 100644
--- a/client/src/helpers/router/navigation.js
+++ b/client/src/helpers/router/navigation.js
@@ -153,6 +153,10 @@ class Navigation {
label: "titles.theme",
route: "backendSettingsTheme"
},
+ {
+ label: "titles.content",
+ route: "backendSettingsContent"
+ },
{
label: "titles.ingestion",
route: "backendSettingsIngestion"
@@ -530,6 +534,12 @@ class Navigation {
entity: "settings",
ability: "update"
},
+ {
+ label: "titles.content",
+ route: "backendSettingsContent",
+ entity: "settings",
+ ability: "update"
+ },
{
label: "titles.integrations",
route: "backendSettingsIntegrations",
diff --git a/client/src/theme/styles/components/frontend/layout/header/libraryHeader.js b/client/src/theme/styles/components/frontend/layout/header/libraryHeader.js
index faa8e00a61..e7fba95221 100644
--- a/client/src/theme/styles/components/frontend/layout/header/libraryHeader.js
+++ b/client/src/theme/styles/components/frontend/layout/header/libraryHeader.js
@@ -32,6 +32,19 @@ export default `
}
}
+ &__inner-preview {
+ ${headerContainerPrimary}
+ display: grid;
+ grid-template: "logo breadcrumbs hamburger" / 1fr auto 1fr;
+ width: 100%;
+ height: 100%;
+
+ ${respond(
+ `grid-template: 'logo site-nav . user-nav' / max-content max-content 1fr max-content;`,
+ 82
+ )}
+ }
+
.header-logo,
.breadcrumb-list,
.mobile-nav-toggle {
diff --git a/client/src/theme/styles/vendor/coloris.js b/client/src/theme/styles/vendor/coloris.js
new file mode 100644
index 0000000000..a9c31b607c
--- /dev/null
+++ b/client/src/theme/styles/vendor/coloris.js
@@ -0,0 +1,616 @@
+export default `
+ .clr-picker {
+ --clr-picker-focus-color: var(--hover-color);
+
+ display: none;
+ flex-wrap: wrap;
+ position: absolute;
+ width: 200px;
+ z-index: 1000;
+ border-radius: 10px;
+ background-color: #fff;
+ justify-content: flex-end;
+ direction: ltr;
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.05), 0 5px 20px rgba(0, 0, 0, 0.1);
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ user-select: none;
+ font-family: var(--font-family-sans);
+ }
+
+ .clr-picker.clr-open,
+ .clr-picker[data-inline="true"] {
+ display: flex;
+ }
+
+ .clr-picker[data-inline="true"] {
+ position: relative;
+ }
+
+ .clr-gradient {
+ position: relative;
+ width: 100%;
+ height: 100px;
+ margin-bottom: 15px;
+ border-radius: 3px 3px 0 0;
+ background-image: linear-gradient(rgba(0, 0, 0, 0), #000),
+ linear-gradient(90deg, #fff, currentColor);
+ cursor: pointer;
+ }
+
+ .clr-marker {
+ position: absolute;
+ width: 12px;
+ height: 12px;
+ margin: -6px 0 0 -6px;
+ border: 1px solid #fff;
+ border-radius: 50%;
+ background-color: currentColor;
+ cursor: pointer;
+ }
+
+ .clr-picker input[type="range"]::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 16px;
+ }
+
+ .clr-picker input[type="range"]::-webkit-slider-thumb {
+ width: 16px;
+ height: 16px;
+ -webkit-appearance: none;
+ }
+
+ .clr-picker input[type="range"]::-moz-range-track {
+ width: 100%;
+ height: 16px;
+ border: 0;
+ }
+
+ .clr-picker input[type="range"]::-moz-range-thumb {
+ width: 16px;
+ height: 16px;
+ border: 0;
+ }
+
+ .clr-hue {
+ background-image: linear-gradient(
+ to right,
+ #f00 0%,
+ #ff0 16.66%,
+ #0f0 33.33%,
+ #0ff 50%,
+ #00f 66.66%,
+ #f0f 83.33%,
+ #f00 100%
+ );
+ }
+
+ .clr-hue,
+ .clr-alpha {
+ position: relative;
+ width: calc(100% - 40px);
+ height: 8px;
+ margin: 5px 20px;
+ border-radius: 4px;
+ }
+
+ .clr-alpha span {
+ display: block;
+ height: 100%;
+ width: 100%;
+ border-radius: inherit;
+ background-image: linear-gradient(90deg, rgba(0, 0, 0, 0), currentColor);
+ }
+
+ .clr-hue input[type="range"],
+ .clr-alpha input[type="range"] {
+ position: absolute;
+ width: calc(100% + 32px);
+ height: 16px;
+ left: -16px;
+ top: -4px;
+ margin: 0;
+ background-color: transparent;
+ opacity: 0;
+ cursor: pointer;
+ appearance: none;
+ -webkit-appearance: none;
+ }
+
+ .clr-hue div,
+ .clr-alpha div {
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ left: 0;
+ top: 50%;
+ margin-left: -8px;
+ transform: translateY(-50%);
+ border: 2px solid #fff;
+ border-radius: 50%;
+ background-color: currentColor;
+ box-shadow: 0 0 1px #888;
+ pointer-events: none;
+ }
+
+ .clr-alpha div:before {
+ content: "";
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ left: 0;
+ top: 0;
+ border-radius: 50%;
+ background-color: currentColor;
+ }
+
+ .clr-format {
+ display: none;
+ order: 1;
+ width: calc(100% - 40px);
+ margin: 0 20px 20px;
+ }
+
+ .clr-segmented {
+ display: flex;
+ position: relative;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ border: 1px solid #ddd;
+ border-radius: 15px;
+ box-sizing: border-box;
+ color: #999;
+ font-size: 12px;
+ }
+
+ .clr-segmented input,
+ .clr-segmented legend {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ left: 0;
+ top: 0;
+ opacity: 0;
+ pointer-events: none;
+ }
+
+ .clr-segmented label {
+ flex-grow: 1;
+ margin: 0;
+ padding: 4px 0;
+ font-size: inherit;
+ font-weight: normal;
+ line-height: initial;
+ text-align: center;
+ cursor: pointer;
+ }
+
+ .clr-segmented label:first-of-type {
+ border-radius: 10px 0 0 10px;
+ }
+
+ .clr-segmented label:last-of-type {
+ border-radius: 0 10px 10px 0;
+ }
+
+ .clr-segmented input:checked + label {
+ color: #fff;
+ background-color: #666;
+ }
+
+ .clr-swatches {
+ order: 2;
+ width: calc(100% - 32px);
+ margin: 0 16px;
+ }
+
+ .clr-swatches div {
+ display: flex;
+ flex-wrap: wrap;
+ padding-bottom: 12px;
+ justify-content: center;
+ }
+
+ .clr-swatches button {
+ position: relative;
+ width: 20px;
+ height: 20px;
+ margin: 0 4px 6px 4px;
+ padding: 0;
+ border: 0;
+ border-radius: 50%;
+ color: inherit;
+ text-indent: -1000px;
+ white-space: nowrap;
+ overflow: hidden;
+ cursor: pointer;
+ }
+
+ .clr-swatches button:after {
+ content: "";
+ display: block;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ left: 0;
+ top: 0;
+ border-radius: inherit;
+ background-color: currentColor;
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
+ }
+
+ input.clr-color {
+ order: 1;
+ width: calc(100% - 80px);
+ height: 32px;
+ margin: 15px 20px 20px auto;
+ padding: 0 10px;
+ border: 1px solid #ddd;
+ border-radius: 16px;
+ color: #444;
+ background-color: #fff;
+ font-family: sans-serif;
+ font-size: 14px;
+ text-align: center;
+ box-shadow: none;
+ }
+
+ input.clr-color:focus {
+ outline: none;
+ border: 1px solid var(--clr-picker-focus-color);
+ }
+
+ .clr-close,
+ .clr-clear {
+ display: block;
+ order: 2;
+ margin: 0 20px 20px;
+ padding: 5px 20px 8px;
+ border: 0;
+ border-radius: 12px;
+ color: #fff;
+ background-color: #666;
+ font-family: inherit;
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 1;
+ cursor: pointer;
+ }
+
+ .clr-close {
+ margin: 0 20px 20px auto;
+ }
+
+ .clr-preview {
+ position: relative;
+ width: 32px;
+ height: 32px;
+ margin: 15px 0 20px 20px;
+ border-radius: 50%;
+ overflow: hidden;
+ }
+
+ .clr-preview:before,
+ .clr-preview:after {
+ content: "";
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ left: 0;
+ top: 0;
+ border: 1px solid #fff;
+ border-radius: 50%;
+ }
+
+ .clr-preview:after {
+ border: 0;
+ background-color: currentColor;
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
+ }
+
+ .clr-preview button {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ border-radius: 50%;
+ outline-offset: -2px;
+ background-color: transparent;
+ text-indent: -9999px;
+ cursor: pointer;
+ overflow: hidden;
+ }
+
+ .clr-marker,
+ .clr-hue div,
+ .clr-alpha div,
+ .clr-color {
+ box-sizing: border-box;
+ }
+
+ .clr-field {
+ display: inline-block;
+ position: relative;
+ color: var(--ColorInput-default-color, transparent);
+ }
+
+ .clr-field input {
+ margin: 0;
+ padding-inline-start: 30px !important;
+ direction: ltr;
+ color: var(--input-color);
+ }
+
+ .clr-field.clr-rtl input {
+ text-align: right;
+ }
+
+ .clr-field button {
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ margin: 0;
+ padding: 0;
+ border: 1px solid var(--input-color);
+ color: inherit;
+ text-indent: -1000px;
+ white-space: nowrap;
+ overflow: hidden;
+ pointer-events: none;
+ }
+
+ .clr-field.clr-rtl button {
+ right: auto;
+ left: 0;
+ }
+
+ .clr-field button:after {
+ content: "";
+ display: block;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ left: 0;
+ top: 0;
+ border-radius: inherit;
+ background-color: currentColor;
+ box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5);
+ }
+
+ .clr-alpha,
+ .clr-alpha div,
+ .clr-swatches button,
+ .clr-preview:before,
+ .clr-field button {
+ background-image: repeating-linear-gradient(
+ 45deg,
+ #aaa 25%,
+ transparent 25%,
+ transparent 75%,
+ #aaa 75%,
+ #aaa
+ ),
+ repeating-linear-gradient(
+ 45deg,
+ #aaa 25%,
+ #fff 25%,
+ #fff 75%,
+ #aaa 75%,
+ #aaa
+ );
+ background-position: 0 0, 4px 4px;
+ background-size: 8px 8px;
+ }
+
+ .clr-marker:focus {
+ outline: none;
+ }
+
+ .clr-keyboard-nav .clr-marker:focus,
+ .clr-keyboard-nav .clr-hue input:focus + div,
+ .clr-keyboard-nav .clr-alpha input:focus + div,
+ .clr-keyboard-nav .clr-segmented input:focus + label {
+ outline: none;
+ box-shadow: 0 0 0 2px var(--clr-picker-focus-color), 0 0 2px 2px #fff;
+ }
+
+ .clr-picker[data-alpha="false"] .clr-alpha {
+ display: none;
+ }
+
+ .clr-picker[data-minimal="true"] {
+ padding-top: 16px;
+ }
+
+ .clr-picker[data-minimal="true"] .clr-gradient,
+ .clr-picker[data-minimal="true"] .clr-hue,
+ .clr-picker[data-minimal="true"] .clr-alpha,
+ .clr-picker[data-minimal="true"] .clr-color,
+ .clr-picker[data-minimal="true"] .clr-preview {
+ display: none;
+ }
+
+ .clr-dark {
+ background-color: #444;
+ }
+
+ .clr-dark .clr-segmented {
+ border-color: #777;
+ }
+
+ .clr-dark .clr-swatches button:after {
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
+ }
+
+ .clr-dark input.clr-color {
+ color: #fff;
+ border-color: #777;
+ background-color: #555;
+ }
+
+ .clr-dark input.clr-color:focus {
+ border-color: var(--clr-picker-focus-color);
+ }
+
+ .clr-dark .clr-preview:after {
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5);
+ }
+
+ .clr-dark .clr-alpha,
+ .clr-dark .clr-alpha div,
+ .clr-dark .clr-swatches button,
+ .clr-dark .clr-preview:before {
+ background-image: repeating-linear-gradient(
+ 45deg,
+ #666 25%,
+ transparent 25%,
+ transparent 75%,
+ #888 75%,
+ #888
+ ),
+ repeating-linear-gradient(
+ 45deg,
+ #888 25%,
+ #444 25%,
+ #444 75%,
+ #888 75%,
+ #888
+ );
+ }
+
+ .clr-picker.clr-polaroid {
+ border-radius: 6px;
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.1), 0 5px 30px rgba(0, 0, 0, 0.2);
+ }
+
+ .clr-picker.clr-polaroid:before {
+ content: "";
+ display: block;
+ position: absolute;
+ width: 16px;
+ height: 10px;
+ left: 20px;
+ top: -10px;
+ border: solid transparent;
+ border-width: 0 8px 10px 8px;
+ border-bottom-color: currentColor;
+ box-sizing: border-box;
+ color: #fff;
+ filter: drop-shadow(0 -4px 3px rgba(0, 0, 0, 0.1));
+ pointer-events: none;
+ }
+
+ .clr-picker.clr-polaroid.clr-dark:before {
+ color: #444;
+ }
+
+ .clr-picker.clr-polaroid.clr-left:before {
+ left: auto;
+ right: 20px;
+ }
+
+ .clr-picker.clr-polaroid.clr-top:before {
+ top: auto;
+ bottom: -10px;
+ transform: rotateZ(180deg);
+ }
+
+ .clr-polaroid .clr-gradient {
+ width: calc(100% - 20px);
+ height: 120px;
+ margin: 10px;
+ border-radius: 3px;
+ }
+
+ .clr-polaroid .clr-hue,
+ .clr-polaroid .clr-alpha {
+ width: calc(100% - 30px);
+ height: 10px;
+ margin: 6px 15px;
+ border-radius: 5px;
+ }
+
+ .clr-polaroid .clr-hue div,
+ .clr-polaroid .clr-alpha div {
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
+ }
+
+ .clr-polaroid .clr-format {
+ width: calc(100% - 20px);
+ margin: 0 10px 15px;
+ }
+
+ .clr-polaroid .clr-swatches {
+ width: calc(100% - 12px);
+ margin: 0 6px;
+ }
+
+ .clr-polaroid .clr-swatches div {
+ padding-bottom: 10px;
+ }
+
+ .clr-polaroid .clr-swatches button {
+ width: 22px;
+ height: 22px;
+ }
+
+ .clr-polaroid input.clr-color {
+ width: calc(100% - 60px);
+ margin: 10px 10px 15px auto;
+ }
+
+ .clr-polaroid .clr-clear {
+ margin: 0 10px 15px 10px;
+ }
+
+ .clr-polaroid .clr-close {
+ margin: 0 10px 15px auto;
+ }
+
+ .clr-polaroid .clr-preview {
+ margin: 10px 0 15px 10px;
+ }
+
+ .clr-picker.clr-large {
+ width: 275px;
+ }
+
+ .clr-large .clr-gradient {
+ height: 150px;
+ }
+
+ .clr-large .clr-swatches button {
+ width: 22px;
+ height: 22px;
+ }
+
+ .clr-picker.clr-pill {
+ width: 380px;
+ padding-left: 180px;
+ box-sizing: border-box;
+ }
+
+ .clr-pill .clr-gradient {
+ position: absolute;
+ width: 180px;
+ height: 100%;
+ left: 0;
+ top: 0;
+ margin-bottom: 0;
+ border-radius: 3px 0 0 3px;
+ }
+
+ .clr-pill .clr-hue {
+ margin-top: 20px;
+ }
+`;
diff --git a/client/src/theme/styles/vendor/index.js b/client/src/theme/styles/vendor/index.js
index 780e17d29b..9fc80df951 100644
--- a/client/src/theme/styles/vendor/index.js
+++ b/client/src/theme/styles/vendor/index.js
@@ -1,5 +1,7 @@
import reactDatePicker from "./react-datepicker";
+import coloris from "./coloris";
export default `
${reactDatePicker}
+ ${coloris}
`;
diff --git a/client/yarn.lock b/client/yarn.lock
index 46b45d90b7..75b046953b 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -2450,6 +2450,13 @@ __metadata:
languageName: node
linkType: hard
+"@melloware/coloris@npm:^0.25.0":
+ version: 0.25.0
+ resolution: "@melloware/coloris@npm:0.25.0"
+ checksum: 10c0/15727dc98891ae8cca394c4300e4c7ba5015bdc5e1fb7bc411d6f726207ebb14c60de843ed58ef19dfe5bc001655ed38e45dd1ba7680f0c256ca035a417945da
+ languageName: node
+ linkType: hard
+
"@npmcli/agent@npm:^3.0.0":
version: 3.0.0
resolution: "@npmcli/agent@npm:3.0.0"
@@ -9763,6 +9770,7 @@ __metadata:
"@emotion/server": "npm:^11.11.0"
"@emotion/styled": "npm:^11.14.1"
"@loadable/component": "npm:^5.16.7"
+ "@melloware/coloris": "npm:^0.25.0"
"@pmmmwh/react-refresh-webpack-plugin": "npm:^0.6.1"
"@types/react": "npm:^18.x"
"@types/react-dom": "npm:^18.x"