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"