diff --git a/frontend/src/app/main/ui/ds/utilities/swatch.cljs b/frontend/src/app/main/ui/ds/utilities/swatch.cljs index bfce203c8..d8cbd9657 100644 --- a/frontend/src/app/main/ui/ds/utilities/swatch.cljs +++ b/frontend/src/app/main/ui/ds/utilities/swatch.cljs @@ -7,70 +7,77 @@ (ns app.main.ui.ds.utilities.swatch (:require-macros + [app.main.style :as stl]) + (:require + [app.common.data.macros :as dm] + [app.common.json :as json] + [app.common.schema :as sm] + [app.common.types.color :as ct] + [app.config :as cfg] + [app.util.color :as uc] + [app.util.i18n :refer [tr]] [cuerdas.core :as str] [rumext.v2 :as mf])) (def ^:private schema:swatch - [:map - [:background :string] + [:map {:title "SchemaSwatch"} + [:background {:optional true} ct/schema:color] [:class {:optional true} :string] - [:format {:optional true} [:enum "square" "rounded"]] [:size {:optional true} [:enum "small" "medium"]] [:active {:optional true} :boolean] [:on-click {:optional true} fn?]]) -(def hex-regex #"^#(?:[0-9a-fA-F]{3}){1,2}$") -(def rgb-regex #"^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$") -(def hsl-regex #"^hsl\((\d{1,3}),\s*(\d{1,3})%,\s*(\d{1,3})%\)$") -(def hsla-regex #"^hsla\((\d{1,3}),\s*(\d{1,3})%,\s*(\d{1,3})%,\s*(0|1|0?\.\d+)\)$") -(def rgba-regex #"^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(0|1|0?\.\d+)\)$") +(defn- color-title + [color-item] + (let [name (:name color-item) + path (:path color-item) + path-and-name (if path (str path " / " name) name) + gradient (:gradient color-item) + image (:image color-item) + color (:color color-item)] -(defn- gradient? [background] - (or - (str/starts-with? background "linear-gradient") - (str/starts-with? background "radial-gradient"))) + (if (some? name) + (cond + (some? color) + (str/ffmt "% (%)" path-and-name color) -(defn- color-solid? [background] - (boolean - (or (re-matches hex-regex background) - (or (re-matches hsl-regex background) - (re-matches rgb-regex background))))) + (some? gradient) + (str/ffmt "% (%)" path-and-name (uc/gradient-type->string (:type gradient))) -(defn- color-opacity? [background] - (boolean - (or (re-matches hsla-regex background) - (re-matches rgba-regex background)))) + (some? image) + (str/ffmt "% (%)" path-and-name (tr "media.image")) -(defn- extract-color-and-opacity [background] - (cond - (re-matches rgba-regex background) - (let [[_ r g b a] (re-matches rgba-regex background)] - {:color (dm/str "rgb(" r ", " g ", " b ")") - :opacity (js/parseFloat a)}) + :else + path-and-name) - (re-matches hsla-regex background) - (let [[_ h s l a] (re-matches hsla-regex background)] - {:color (dm/str "hsl(" h ", " s "%, " l "%)") - :opacity (js/parseFloat a)}) + (cond + (some? color) + color - :else - {:color background - :opacity 1.0})) + (some? gradient) + (uc/gradient-type->string (:type gradient)) + + (some? image) + (tr "media.image"))))) (mf/defc swatch* {::mf/props :obj - ::mf/schema schema:swatch} - [{:keys [background on-click format size active class] + ::mf/schema (sm/schema schema:swatch)} + [{:keys [background on-click size active class] :rest props}] - (let [element-type (if on-click "button" "div") - button-type (if on-click "button" nil) - format (or format "square") + (let [background (if (object? background) (json/->clj background) background) + read-only? (nil? on-click) + id? (some? (:id background)) + element-type (if read-only? "div" "button") + button-type (if (not read-only?) "button" nil) size (or size "small") active (or active false) - {:keys [color opacity]} (extract-color-and-opacity background) + gradient (:gradient background) + image (:image background) + format (if id? "rounded" "square") class (dm/str class " " (stl/css-case :swatch true :small (= size "small") @@ -79,25 +86,26 @@ :active (= active true) :interactive (= element-type "button") :rounded (= format "rounded"))) - props (mf/spread-props props {:class class :on-click on-click :type button-type})] + props (mf/spread-props props {:class class + :on-click on-click + :type button-type + :title (color-title background)})] [:> element-type props (cond - (color-solid? background) - [:span {:class (stl/css :swatch-solid) - :style {:background background}}] - (color-opacity? background) - [:span {:class (stl/css :swatch-opacity)} - [:span {:class (stl/css :swatch-solid-side) - :style {:background color}}] - [:span {:class (stl/css :swatch-opacity-side) - :style {:background color :opacity opacity}}]] - - (gradient? background) + (some? gradient) [:span {:class (stl/css :swatch-gradient) - :style {:background-image (str background ", repeating-conic-gradient(lightgray 0% 25%, white 0% 50%)")}}] + :style {:background-image (str gradient ", repeating-conic-gradient(lightgray 0% 25%, white 0% 50%)")}}] + + (some? image) + (let [uri (cfg/resolve-file-media image)] + [:span {:class (stl/css :swatch-image) + :style {:background-image (str/ffmt "url(%)" uri)}}]) :else - [:span {:class (stl/css :swatch-image) - :style {:background-image (str "url('" background "'), repeating-conic-gradient(lightgray 0% 25%, white 0% 50%)")}}])])) + [:span {:class (stl/css :swatch-opacity)} + [:span {:class (stl/css :swatch-solid-side) + :style {:background (uc/color->background (assoc background :opacity 1))}}] + [:span {:class (stl/css :swatch-opacity-side) + :style {:background (uc/color->background background)}}]])])) diff --git a/frontend/src/app/main/ui/ds/utilities/swatch.mdx b/frontend/src/app/main/ui/ds/utilities/swatch.mdx index a091a4e32..bef929f07 100644 --- a/frontend/src/app/main/ui/ds/utilities/swatch.mdx +++ b/frontend/src/app/main/ui/ds/utilities/swatch.mdx @@ -7,56 +7,47 @@ import * as SwatchStories from "./swatch.stories"; Swatches are elements that display a color, gradient or image. They can sometimes trigger an action. +## Background Property + +A swatch component can receive several props. The `background` prop is the most important and must be an object. Depending on the value of the background property we will get different variants of the component. + ## Variants -**Color** (`"color"`), displays a solid color. It can take a hexadecimal, an rgb or an rgba. +If the background prop has a hex `color` value it will display a full swatch with a solid color -**WithOpacity** (`"color"`), displays a solid color on one side and the same color with its opacity applied on the other side. It can take a hexadecimal, an rgb or an rgba. +If the background prop has a hex `color` value and an opacity value it will display a full swatch with a solid color on one side and the same color with the opacity applied on the other side. (default opacity: 1) -**Gradient** (`"gradient"`), displays a gradient. A gradient should be a `linear-gradient` or a `conic-gradient`. - - - -**Image** (`"image"`) the swatch could display any image. - - - -**Active** (`"active"`) displays the swatch as active while an interface related action is happening. - - - -**Size** (`"size"`) shows a bigger or smaller swatch. Accepts `small` and `medium` (_default_) sizes. +This component can take a size property to set the size of the swatch. In this case we can set it to `small` (default size: `medium`) -**Format** (`"format"`) displays a square or rounded swatch. Accepts `square` (_default_) and `rounded` sizes. +With the `active` property, we can display the element as being active - + + +The element can also be interactive, and execute an external function. Typically, it launches the color picker. To make it an interactive button, it accepts an onClick function. + + + +> Due to technical issues regarding the transformation between Clojurescript and Javascript, we are unable to display: + + - Swatches with gradients + - Library Swatches + - Swatches with images ## Technical Notes -### Background - -The `swatch*` component accepts a `background` prop, which must be: - -- An hexadecimal (e.g. `#996633`) -- An RGB (e.g. `rgb(125, 125, 0)`) -- An RGBA (e.g. `rgba(125, 125, 0, 0.3)`) -- A linear gradient (e.g. `linear-gradient(to right, blue, pink)`) -- A conic gradient (e.g. `conic-gradient(red, orange, yellow, green, blue)`) -- An image (e.g. `url(https://placecats.com/100/100)`) - ### onClick -> Note: If the swatch is interactive, an `aria-label` is required. More on the `Accessibility` section. +> Note: If the swatch is interactive, an `aria-label` is required. See the `Accessibility` section for more information. -The swatch button accepts an onClick prop that expect a function on the parent context. +The swatch button accepts an onClick prop that expects a function on the parent context. It should be useful for launching other tools as a color picker. -It runs when the user clics on the swatch, or presses enter or space while focusing it. +It is executed when the user clicks on the swatch, or presses Enter or Spacebar while focused. ### Accessibility diff --git a/frontend/src/app/main/ui/ds/utilities/swatch.stories.jsx b/frontend/src/app/main/ui/ds/utilities/swatch.stories.jsx index 165b7c599..08f0e3d07 100644 --- a/frontend/src/app/main/ui/ds/utilities/swatch.stories.jsx +++ b/frontend/src/app/main/ui/ds/utilities/swatch.stories.jsx @@ -15,11 +15,7 @@ export default { component: Swatch, argTypes: { background: { - control: { type: "text" }, - }, - format: { - control: "select", - options: ["square", "rounded"], + control: "object", }, size: { control: "select", @@ -30,8 +26,7 @@ export default { }, }, args: { - background: "#663399", - format: "square", + background: { color: "#7efff5" }, size: "medium", active: false, }, @@ -42,28 +37,52 @@ export const Default = {}; export const WithOpacity = { args: { - background: "rgba(255, 0, 0, 0.5)", + background: { + color: "#7efff5", + opacity: 0.5, + }, }, }; -export const LinearGradient = { - args: { - background: "linear-gradient(to right, transparent, mistyrose)", - }, -}; +// These stories are disabled because the gradient and the UUID variants cannot be translated from cljs into JS +// When the repo is updated to use the new version of rumext, these stories should be re-enabled and tested +// +// export const LinearGradient = { +// args: { +// background: { +// gradient: { +// type: "linear", +// startX: 0, +// startY: 0, +// endX: 1, +// endY: 0, +// width: 1, +// stops: [ +// { +// color: "#fabada", +// opacity: 1, +// offset: 0, +// }, +// { +// color: "#cc0000", +// opacity: 0.5, +// offset: 1, +// }, +// ], +// }, +// }, +// }, +// }; -export const Image = { - args: { - background: "images/form/never-used.png", - size: "medium", - }, -}; - -export const Rounded = { - args: { - format: "rounded", - }, -}; +// export const Rounded = { +// args: { +// background: { +// id: crypto.randomUUID(), +// color: "#7efff5", +// opacity: 0.5, +// }, +// }, +// }; export const Small = { args: { @@ -74,7 +93,6 @@ export const Small = { export const Active = { args: { active: true, - background: "#CC00CC", }, };