0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-10 09:08:31 -05:00

🎉 New colorpicker

This commit is contained in:
alonso.torres 2020-09-03 08:11:27 +02:00 committed by Andrey Antukh
parent 4e694ff194
commit 12a2b35b28
4 changed files with 428 additions and 5 deletions

View file

@ -5,6 +5,201 @@
// Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
// Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
.colorpicker-v2 {
display: flex;
flex-direction: column;
width: 13rem;
padding: 0.5rem;
background-color: $color-white;
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
.handler {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
z-index: 1;
}
.value-selector {
background-color: rgba(var(--hue));
position: relative;
height: 6.75rem;
width: 100%;
.handler {
box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset;
transform: translate(-6px, -6px);
left: 50%;
top: 50%;
}
}
.value-selector::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
background: linear-gradient(to right, #fff, rgba(255,255,255,0));
}
.value-selector::after {
content: "";
position: absolute;
width: 100%;
height: 100%;
background: linear-gradient(to top, #000, rgba(0,0,0,0));
}
.shade-selector {
display: grid;
justify-items: center;
align-items: center;
grid-template-areas: "color hue" "color opacity";
grid-template-columns: 2.5rem 1fr;
height: 3.5rem;
grid-row-gap: 0.5rem;
}
.color-bullet {
grid-area: color;
width: 20px;
height: 20px;
background-color: rgba(var(--color));
border-radius: 12px;
border: 1px solid $color-gray-10;
}
.hue-selector {
align-self: end;
grid-area: hue;
height: 0.5rem;
width: 100%;
background: linear-gradient(
to right,
#f00 0%, #ff0 17%, #0f0 33%, #0ff 50%,
#00f 67%, #f0f 83%, #f00 100%);
position: relative;
}
.hue-selector .handler,
.opacity-selector .handler {
background-color: rgb(248, 248, 248);
box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px;
transform: translate(-6px, -2px);
left: 50%;
cursor: pointer;
}
.opacity-selector {
align-self: start;
grid-area: opacity;
height: 0.5rem;
width: 100%;
position: relative;
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") left center;
}
.opacity-selector::after {
content: "";
background: linear-gradient(to right, rgba(var(--color), 0) 0%, rgba(var(--color), 1.0) 100%);
position: absolute;
width: 100%;
height: 100%;
}
.color-values {
display: grid;
grid-template-columns: 3.5rem repeat(4, 1fr);
grid-row-gap: 0.25rem;
justify-items: center;
grid-column-gap: 0.25rem;
input {
width: 100%;
margin: 0;
border: 1px solid $color-gray-10;
border-radius: 2px;
font-size: $fs11;
height: 1.5rem;
padding: 0 $x-small;
color: $color-gray-40;
}
label {
font-size: $fs11;
}
}
.libraries {
border-top: 1px solid $color-gray-10;
padding-top: 0.5rem;
margin-top: 0.25rem;
select {
background-image: url(/images/icons/arrow-down.svg);
background-repeat: no-repeat;
background-position: 95% 48%;
background-size: 10px;
margin: 0;
margin-bottom: 0.5rem;
width: 100%;
padding: 2px 0.25rem;
font-size: 0.75rem;
color: $color-gray-40;
border-color: $color-gray-10;
border-radius: 2px;
option {
padding: 0;
}
}
.selected-colors {
display: grid;
grid-template-columns: repeat(8, 1fr);
justify-content: space-between;
margin-right: -8px;
overflow: scroll;
max-height: 5.5rem;
}
.selected-colors::after {
content: "";
flex: auto;
}
.selected-colors .color-bullet {
grid-area: auto;
margin-bottom: 0.25rem;
cursor: pointer;
&:hover {
border-color: $color-primary;
}
&.button {
display: flex;
align-items: center;
justify-content: center;
}
&.button svg {
width: 12px;
height: 12px;
fill: $color-gray-30;
}
&.plus-button svg {
width: 8px;
height: 8px;
fill: $color-black;
}
}
}
}
.color-picker {
display: flex;
flex-direction: column;

View file

@ -9,20 +9,231 @@
(:require
[rumext.alpha :as mf]
[app.main.store :as st]
[app.main.ui.colorpicker :as cp]))
[app.main.ui.colorpicker :as cp]
[cuerdas.core :as str]
[app.util.dom :as dom]
[app.util.color :as uc]
[app.main.ui.icons :as i]
[app.common.math :as math]))
;; --- Color Picker Modal
(mf/defc value-selector [{:keys [saturation luminance on-change]}]
(let [dragging? (mf/use-state false)]
[:div.value-selector
{:on-mouse-down #(reset! dragging? true)
:on-mouse-up #(reset! dragging? false)
:on-mouse-move
(fn [ev]
(when @dragging?
(let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
{:keys [x y]} (-> ev dom/get-client-position)
px (/ (- x left) (- right left))
py (/ (- y top) (- bottom top))
luminance (* (- 1.0 py) (- 1 (* 0.5 px)))]
(on-change px luminance))))}
[:div.handler {:style {:pointer-events "none"
:left (str (* saturation 100) "%")
:top (str (* (- 1 (/ luminance (- 1 (* 0.5 saturation))) ) 100) "%")}}]]))
(mf/defc hue-selector [{:keys [hue on-change]}]
(let [dragging? (mf/use-state false)]
[:div.hue-selector
{:on-mouse-down #(reset! dragging? true)
:on-mouse-up #(reset! dragging? false)
:on-mouse-move
(fn [ev]
(when @dragging?
(let [{:keys [left right]} (-> ev dom/get-target dom/get-bounding-rect)
{:keys [x]} (-> ev dom/get-client-position)
px (/ (- x left) (- right left))]
(on-change (* px 360)))))}
[:div.handler {:style {:pointer-events "none"
:left (str (* (/ hue 360) 100) "%")}}]]))
(mf/defc opacity-selector [{:keys [opacity on-change]}]
(let [dragging? (mf/use-state false)]
[:div.opacity-selector
{:on-mouse-down #(reset! dragging? true)
:on-mouse-up #(reset! dragging? false)
:on-mouse-move
(fn [ev]
(when @dragging?
(let [{:keys [left right]} (-> ev dom/get-target dom/get-bounding-rect)
{:keys [x]} (-> ev dom/get-client-position)
px (/ (- x left) (- right left))]
(on-change px))))}
[:div.handler {:style {:pointer-events "none"
:left (str (* opacity 100) "%")}}]]))
(defn as-color-state [value opacity]
(let [[r g b] (uc/hex->rgb (or value "000000"))
[h s l] (uc/hex->hsl (or value "000000"))]
{:hex (or value "000000")
:alpha (or opacity 1)
:r r
:g g
:b b
:h h
:s s
:l l}))
(mf/defc colorpicker
[{:keys [value opacity]}]
(let [state (mf/use-state (as-color-state value opacity))
ref-picker (mf/use-ref)]
(mf/use-effect (mf/deps value opacity)
(fn []
(reset! state (as-color-state value opacity))))
(mf/use-effect (mf/deps state)
(fn [] (let [node (mf/ref-val ref-picker)
rgb [(:r @state) (:g @state) (:b @state)]
hue-rgb (uc/hsl->rgb (:h @state) 1.0 0.5)]
(dom/set-css-property node "--color" (str/join ", " rgb))
(dom/set-css-property node "--hue" (str/join ", " hue-rgb)))))
[:div.colorpicker-v2 {:ref ref-picker}
[:& value-selector {:luminance (:l @state)
:saturation (:s @state)
:on-change (fn [s l]
(let [hex (uc/hsl->hex (:h @state) s l)
[r g b] (uc/hex->rgb hex)]
(swap! state assoc
:hex hex
:r r :g g :b b
:s s :l l)))}]
[:div.shade-selector
[:div.color-bullet]
[:& hue-selector {:hue (:h @state)
:on-change (fn [h]
(let [hex (uc/hsl->hex h (:s @state) (:l @state))
[r g b] (uc/hex->rgb hex)]
(swap! state assoc
:hex hex
:r r :g g :b b
:h h )))}]
[:& opacity-selector {:opacity (:alpha @state)
:on-change (fn [alpha]
(swap! state assoc
:alpha alpha))}]]
[:div.color-values
[:input.hex-value {:id "hex-value"
:value (:hex @state)
:on-change (fn [e]
(let [val (-> e dom/get-target dom/get-value)
val (if (= (first val) \#) val (str \# val))]
(swap! state assoc :hex val)
(when (uc/hex? val)
(let [[r g b] (uc/hex->rgb val)
[h s l] (uc/hex->hsl val)]
(swap! state assoc
:r r :g g :b b
:h h :s s :l l)))))}]
[:input.red-value {:id "red-value"
:type "number"
:min 0
:max 255
:value (:r @state)
:on-change (fn [e]
(let [val (-> e dom/get-target dom/get-value)
val (if (> val 255) 255 val)
val (if (< val 0) 0 val)]
(swap! state assoc :r val)
(when (not (nil? val))
(let [{:keys [g b]} @state
hex (uc/rgb->hex [val g b])
[h s l] (uc/hex->hsl hex)]
(swap! state assoc
:hex hex
:h h :s s :l l)))))}]
[:input.green-value {:id "green-value"
:type "number"
:min 0
:max 255
:value (:g @state)
:on-change (fn [e]
(let [val (-> e dom/get-target dom/get-value)
val (if (> val 255) 255 val)
val (if (< val 0) 0 val)]
(swap! state assoc :g val)
(when (not (nil? val))
(let [{:keys [r b]} @state
hex (uc/rgb->hex [r val b])
[h s l] (uc/hex->hsl hex)]
(swap! state assoc
:hex hex
:h h :s s :l l)))))}]
[:input.blue-value {:id "blue-value"
:type "number"
:min 0
:max 255
:value (:b @state)
:on-change (fn [e]
(let [val (-> e dom/get-target dom/get-value)
val (if (> val 255) 255 val)
val (if (< val 0) 0 val)]
(swap! state assoc :b val)
(when (not (nil? val))
(let [{:keys [r g]} @state
hex (uc/rgb->hex [r g val])
[h s l] (uc/hex->hsl hex)]
(swap! state assoc
:hex hex
:h h :s s :l l)))))}]
[:input.alpha-value {:id "alpha-value"
:type "number"
:min 0
:step 0.1
:max 1
:value (math/precision (:alpha @state) 2)
:on-change (fn [e]
(let [val (-> e dom/get-target dom/get-value)
val (if (> val 1) 1 val)
val (if (< val 0) 0 val)]
(swap! state assoc :alpha val)))}]
[:label.hex-label {:for "hex-value"} "HEX"]
[:label.red-label {:for "red-value"} "R"]
[:label.green-label {:for "green-value"} "G"]
[:label.blue-label {:for "blue-value"} "B"]
[:label.alpha-label {:for "alpha-value"} "A"]]
[:div.libraries
[:select
[:option {:value :recent} "Recent colors"]
[:option {:value :file} "File library"]
[:option {:value #uuid "f5d51910-ab23-11ea-ac38-e1abed64181a" } "TAIGA library"]]
[:div.selected-colors
[:div.color-bullet.button.plus-button {:style {:background-color "white"}}
i/plus]
[:div.color-bullet.button {:style {:background-color "white"}}
i/palette]
#_(for [j (range 0 40)]
[:div.color-bullet {:style {:background-color "#E8E9EA"}}])]]])
)
(mf/defc colorpicker-modal
[{:keys [x y default value opacity page on-change disable-opacity] :as props}]
[:div.modal-overlay.transparent
[:div.colorpicker-tooltip
{:style {:left (str (- x 270) "px")
:top (str (- y 50) "px")}}
[:& cp/colorpicker {:value (or value default)
#_[:& cp/colorpicker {:value (or value default)
:opacity (or opacity 1)
:colors (into-array @cp/most-used-colors)
:on-change on-change
:disable-opacity disable-opacity}]]])
:disable-opacity disable-opacity}]
[:& colorpicker {:value (or value default)
:opacity (or opacity 1)
:colors (into-array @cp/most-used-colors)
:on-change on-change
:disable-opacity disable-opacity}]
]
])

View file

@ -52,6 +52,20 @@
(-> (hex->rgb data)
(conj opacity)))
(defn hex->hsl [hex]
(try
(into [] (gcolor/hexToHsl hex))
(catch :default e (do
(.log js/console e)
[0 0 0]))))
(defn hsl->rgb
[h s l]
(gcolor/hslToRgb h s l))
(defn hsl->hex [h s l]
(gcolor/hslToHex h s l))
(defn hex?
[v]
(and (string? v)

View file

@ -203,3 +203,6 @@
[b]
{:pre [(blob? b)]}
(js/URL.createObjectURL b))
(defn set-css-property [node property value]
(.setProperty (.-style node) property value))