0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-14 16:01:24 -05:00

🎉 Grid and layout UI

This commit is contained in:
alonso.torres 2020-05-14 14:08:17 +02:00 committed by Andrés Moya
parent 8c77ea463d
commit 0b4996b31a
16 changed files with 700 additions and 22 deletions

View file

@ -9,7 +9,9 @@
(:refer-clojure :exclude [concat read-string])
(:require [clojure.set :as set]
#?(:cljs [cljs.reader :as r]
:clj [clojure.edn :as r])))
:clj [clojure.edn :as r])
#?(:cljs [cljs.core :as core]
:clj [clojure.core :as core])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Structures Manipulation
@ -94,6 +96,12 @@
(persistent!
(reduce #(dissoc! %1 %2) (transient data) keys)))
(defn remove-at-index
[v index]
(vec (core/concat
(subvec v 0 index)
(subvec v (inc index)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Parsing / Conversion
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -366,6 +366,9 @@ ul.slider-dots {
// Input amounts
&.pixels {
& input {
padding-right: 20px;
}
&::after {
content: "px";

View file

@ -263,6 +263,11 @@
padding: $x-small $big $x-small $x-small;
position: relative;
& hr {
margin: 0;
border-color: $color-gray-20;
}
.dropdown-button {
position: absolute;
right: $x-small;
@ -321,6 +326,30 @@
}
}
}
& li.checked-element {
padding-left: 0;
& span {
margin: 0;
color: $color-black;
}
& svg {
visibility: hidden;
width: 8px;
height: 8px;
background: none;
margin: 0.25rem;
fill: $color-black;
}
&.is-selected {
& svg {
visibility: visible;
}
}
}
}
.editable-select {
@ -535,3 +564,113 @@
}
}
}
.custom-button {
cursor: pointer;
background: none;
border: none;
& svg {
width: 1rem;
height: 1rem;
fill: $color-gray-20;
}
}
.element-set-content .input-row {
& .element-set-subtitle {
width: 5.5rem;
}
}
.grid-option {
margin-bottom: 0.5rem;
}
.element-set-content .grid-option-main {
display: flex;
padding: 0.5rem 0;
border: 1px solid $color-black;
border-radius: 4px;
&:hover {
background: #1F1F1F;
}
& .custom-select {
height: 2rem;
border: none;
border-bottom: 1px solid #65666A;
}
& .input-element {
width: 50px;
overflow: hidden;
}
& .custom-select-dropdown {
width: 96px;
}
& .input-option {
margin-left: 0.5rem;
& .custom-select-dropdown {
width: 56px;
min-width: 56px;
max-height: 10rem;
}
}
}
.grid-option-main-actions {
display: flex;
visibility: hidden;
.grid-option:hover & {
visibility: visible;
}
}
.focus-overlay {
background: $color-black;
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
opacity: 0.4;
}
.element-set-content .advanced-options {
background-color: #303236;
border-radius: 4px;
left: -8px;
padding: 0.5rem;
position: relative;
top: 2px;
width: calc(100% + 16px);
}
.btn-options {
cursor: pointer;
border: 1px solid $color-black;
background: #1F1F1F;
border-radius: 2px;
color: #B1B2B5;
font-size: 11px;
line-height: 16px;
flex-grow: 1;
padding: 0.25rem 0;
&:first-child {
margin-right: 0.5rem;
}
&:hover {
background: $color-primary;
color: $color-black;
}
}

View file

@ -1487,6 +1487,44 @@
(rx/of (update-shape shape-id
{:interactions []}))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Layouts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn add-frame-layout [frame-id]
(ptk/reify ::set-frame-layout
dwc/IBatchedChange
ptk/UpdateEvent
(update [_ state]
(let [pid (:current-page-id state)
default-params {:size 16 :color {:value "#59B9E2" :opacity 0.9}}
prop-path [:workspace-data pid :objects frame-id :layouts]
layout {:type :square
:params default-params
:display true}]
(-> state
(update-in prop-path #(if (nil? %) [layout] (conj % layout))))))))
(defn remove-frame-layout [frame-id index]
(ptk/reify ::set-frame-layout
dwc/IBatchedChange
ptk/UpdateEvent
(update [_ state]
(let [pid (:current-page-id state)]
(-> state
(update-in [:workspace-data pid :objects frame-id :layouts] #(d/remove-at-index % index)))))))
(defn set-frame-layout [frame-id index data]
(ptk/reify ::set-frame-layout
dwc/IBatchedChange
ptk/UpdateEvent
(update [_ state]
(let [pid (:current-page-id state)]
(->
state
(assoc-in [:workspace-data pid :objects frame-id :layouts index] data))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exports
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -25,7 +25,6 @@
:onChangeComplete on-change-complete
:style {:box-shadow "none"}}]))
(def most-used-colors
(letfn [(selector [{:keys [objects]}]
(as-> {} $

View file

@ -1,3 +1,12 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.components.context-menu
(:require
[rumext.alpha :as mf]

View file

@ -1,3 +1,12 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.components.editable-label
(:require
[rumext.alpha :as mf]

View file

@ -0,0 +1,51 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.components.select
(:require
[rumext.alpha :as mf]
[uxbox.common.uuid :as uuid]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.components.dropdown :refer [dropdown]]))
(mf/defc select [{:keys [default-value options class on-change]}]
(let [state (mf/use-state {:id (uuid/next)
:is-open? false
:current-value default-value})
open-dropdown #(swap! state assoc :is-open? true)
close-dropdown #(swap! state assoc :is-open? false)
select-item (fn [value] (fn [event]
(swap! state assoc :current-value value)
(when on-change (on-change value))))
as-key-value (fn [item] (if (map? item) [(:value item) (:label item)] [item item]))
value->label (into {} (->> options
(map as-key-value))) ]
(mf/use-effect
(mf/deps options)
#(reset! state {:is-open? false
:current-value default-value}))
[:div.custom-select {:on-click open-dropdown
:class class}
[:span (-> @state :current-value value->label)]
[:span.dropdown-button i/arrow-down]
[:& dropdown {:show (:is-open? @state)
:on-close close-dropdown}
[:ul.custom-select-dropdown
(for [[index item] (map-indexed vector options)]
(cond
(= :separator item) [:hr {:key (str (:id @state) "-" index)}]
:else (let [[value label] (as-key-value item)]
[:li.checked-element
{:key (str (:id @state) "-" index)
:class (when (= value (-> @state :current-value)) "is-selected")
:on-click (select-item value)}
[:span.check-icon i/tick]
[:span label]])))]]]))

View file

@ -1,3 +1,12 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.components.tab-container
(:require [rumext.alpha :as mf]))

View file

@ -110,6 +110,7 @@
(def unlock (icon-xref :unlock))
(def uppercase (icon-xref :uppercase))
(def user (icon-xref :user))
(def tick (icon-xref :tick))
(def loader-pencil
(mf/html

View file

@ -0,0 +1,102 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.workspace.layout-display
(:require
[rumext.alpha :as mf]
[uxbox.main.refs :as refs]))
(mf/defc grid-layout [{:keys [frame zoom params] :as props}]
(let [{:keys [color size]} params
{color-value :value color-opacity :opacity} (:color params)
{frame-width :width frame-height :height :keys [x y]} frame]
[:g.grid
[:*
(for [xs (range size frame-width size)]
[:line {:key (str (:id frame) "-y-" xs)
:x1 (+ x xs)
:y1 y
:x2 (+ x xs)
:y2 (+ y frame-height)
:style {:stroke color-value
:stroke-opacity color-opacity
:stroke-width (str (/ 1 zoom))}}])
(for [ys (range size frame-height size)]
[:line {:key (str (:id frame) "-x-" ys)
:x1 x
:y1 (+ y ys)
:x2 (+ x frame-width)
:y2 (+ y ys)
:style {:stroke color-value
:stroke-opacity color-opacity
:stroke-width (str (/ 1 zoom))}}])]]))
(defn calculate-column-layout [frame size gutter margin item-width layout-type]
(let [{:keys [width height x y]} frame
parts (/ width size)
item-width (or item-width (+ parts (- gutter) (/ gutter size) (- (/ (* margin 2) size))))
item-height height
initial-offset (case layout-type
:right (- width (* item-width size) (* gutter (dec size)) margin)
:center (/ (- width (* item-width size) (* gutter (dec size))) 2)
margin)
gutter (if (= :stretch layout-type) (/ (- width (* item-width size) (* margin 2)) (dec size)) gutter)
next-x (fn [cur-val] (+ initial-offset x (* (+ item-width gutter) cur-val)))
next-y (fn [cur-val] y)]
[parts item-width item-height next-x next-y]))
(defn calculate-row-layout [frame size gutter margin item-height layout-type]
(let [{:keys [width height x y]} frame
parts (/ height size)
item-width width
item-height (or item-height (+ parts (- gutter) (/ gutter size) (- (/ (* margin 2) size))))
initial-offset (case layout-type
:right (- height (* item-height size) (* gutter (dec size)) margin)
:center (/ (- height (* item-height size) (* gutter (dec size))) 2)
margin)
gutter (if (= :stretch layout-type) (/ (- height (* item-height size) (* margin 2)) (dec size)) gutter)
next-x (fn [cur-val] x)
next-y (fn [cur-val] (+ initial-offset y (* (+ item-height gutter) cur-val)))]
[parts item-width item-height next-x next-y]))
(mf/defc flex-layout [{:keys [frame zoom params orientation]}]
(let [{:keys [color size type gutter margin item-width item-height]} params
{color-value :value color-opacity :opacity} (:color params)
;; calculates the layout configuration
[parts item-width item-height next-x next-y]
(if (= orientation :column)
(calculate-column-layout frame size gutter margin item-width type)
(calculate-row-layout frame size gutter margin item-height type))]
(for [cur-val (range 0 size)]
[:rect {:x (next-x cur-val)
:y (next-y cur-val)
:width item-width
:height item-height
:style {:pointer-events "none"
:fill color-value
:opacity color-opacity}}])))
(mf/defc layout-display [{:keys [frame]}]
(let [zoom (mf/deref refs/selected-zoom)
layouts (:layouts frame)]
(for [[index {:keys [type display params]}] (map-indexed vector layouts)]
(let [props #js {:key (str (:id frame) "-layout-" index)
:frame frame
:zoom zoom
:params params
:orientation (cond (= type :column) :column
(= type :row) :row
:else nil) }]
(when display
(case type
:square [:> grid-layout props]
:column [:> flex-layout props]
:row [:> flex-layout props]))))))

View file

@ -22,7 +22,8 @@
[uxbox.util.geom.shapes :as geom]
[uxbox.util.dom :as dom]
[uxbox.main.streams :as ms]
[uxbox.util.timers :as ts]))
[uxbox.util.timers :as ts]
[uxbox.main.ui.workspace.layout-display :refer [layout-display]]))
(defn- frame-wrapper-factory-equals?
[np op]
@ -64,15 +65,14 @@
on-context-menu (mf/use-callback (mf/deps shape)
#(common/on-context-menu % shape))
shape (geom/transform-shape shape)
{:keys [x y width height]} shape
inv-zoom (/ 1 zoom)
childs (mapv #(get objects %) (:shapes shape))
ds-modifier (get-in shape [:modifiers :displacement])
label-pos (cond-> (gpt/point x (- y 10))
(gmt/matrix? ds-modifier) (gpt/transform ds-modifier))
label-pos (gpt/point x (- y 10))
on-double-click
(mf/use-callback
@ -104,7 +104,7 @@
:on-click on-double-click}
(:name shape)]
[:& frame-shape
{:shape (geom/transform-shape shape)
:childs childs}]])))))
{:shape shape
:childs childs}]
[:& layout-display {:frame shape}]])))))

View file

@ -12,16 +12,17 @@
(:require
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.main.ui.icons :as i]
[uxbox.main.data.workspace :as udw]
[uxbox.main.store :as st]
[uxbox.main.ui.components.dropdown :refer [dropdown]]
[uxbox.main.ui.workspace.sidebar.options.fill :refer [fill-menu]]
[uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.i18n :refer [tr]]
[uxbox.util.math :as math]))
[uxbox.util.math :as math]
[uxbox.main.store :as st]
[uxbox.main.data.workspace :as udw]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.components.dropdown :refer [dropdown]]
[uxbox.main.ui.workspace.sidebar.options.fill :refer [fill-menu]]
[uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]]
[uxbox.main.ui.workspace.sidebar.options.grid-options :refer [grid-options]]))
(declare +size-presets+)
@ -82,11 +83,11 @@
[:div.custom-select.flex-grow {:on-click #(reset! show-presets-dropdown? true)}
[:span (tr "workspace.options.size-presets")]
[:span.dropdown-button i/arrow-down]
[:& dropdown {:show @show-presets-dropdown?
:on-close #(reset! show-presets-dropdown? false)}
[:ul.custom-select-dropdown
(for [size-preset +size-presets+]
(if-not (:width size-preset)
[:& dropdown {:show @show-presets-dropdown?
:on-close #(reset! show-presets-dropdown? false)}
[:ul.custom-select-dropdown
(for [size-preset +size-presets+]
(if-not (:width size-preset)
[:li.dropdown-label {:key (:name size-preset)}
[:span (:name size-preset)]]
[:li {:key (:name size-preset)
@ -202,4 +203,5 @@
[:div
[:& measures-menu {:shape shape}]
[:& fill-menu {:shape shape}]
[:& stroke-menu {:shape shape}]])
[:& stroke-menu {:shape shape}]
[:& grid-options {:shape shape}]])

View file

@ -0,0 +1,188 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.workspace.sidebar.options.grid-options
(:require
[rumext.alpha :as mf]
[uxbox.util.dom :as dom]
[uxbox.util.data :as d]
[uxbox.main.store :as st]
[uxbox.main.data.workspace :as dw]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
[uxbox.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]]
[uxbox.main.ui.components.select :refer [select]]
[uxbox.main.ui.components.dropdown :refer [dropdown]]))
(mf/defc advanced-options [{:keys [visible? on-close children]}]
(when visible?
[:*
[:div.focus-overlay {:on-click #(when on-close (do
(dom/stop-propagation %)
(on-close)))}]
[:div.advanced-options {}
children]]))
(defonce ^:private default-params
{:square {:size 16
:color {:value "#59B9E2"
:opacity 0.9}}
:column {:size 12
:type :stretch
:item-width nil
:gutter 8
:margin 0
:color {:value "#DE4762"
:opacity 0.1}}
:row {:size 12
:type :stretch
:item-height nil
:gutter 8
:margin 0
:color {:value "#DE4762"
:opacity 0.1}}})
(mf/defc grid-option [{:keys [layout on-change on-remove]}]
(let [state (mf/use-state {:show-advanced-options false
:changes {}})
{:keys [type display params] :as layout} (d/deep-merge layout (:changes @state))
toggle-advanced-options #(swap! state update :show-advanced-options not)
size-options [{:value :auto :label "Auto"}
:separator
18 12 10 8 6 4 3 2]
emit-changes! (fn [update-fn]
(swap! state update :changes update-fn)
(when on-change (on-change (d/deep-merge layout (-> @state :changes update-fn)))))
handle-toggle-visibility (fn [event]
(emit-changes! #(update % :display not)))
handle-remove-layout (fn [event]
(when on-remove (on-remove)))
handle-change-type (fn [type]
(let [defaults (type default-params)
params (merge
defaults
(select-keys (keys defaults) (-> @state :changes params)))
to-merge {:type type :params params}]
(emit-changes! #(d/deep-merge % to-merge))))
handle-change (fn [& keys]
(fn [value]
(emit-changes! #(assoc-in % keys value))))
handle-change-event (fn [& keys]
(fn [event]
(let [change-fn (apply handle-change keys)]
(-> event dom/get-target dom/get-value change-fn))))
]
[:div.grid-option
[:div.grid-option-main
[:button.custom-button {:class (when (:show-advanced-options @state) "is-active")
:on-click toggle-advanced-options} i/actions]
[:& select {:class "flex-grow"
:default-value type
:options [{:value :square :label "Square"}
{:value :column :label "Columns"}
{:value :row :label "Rows"}]
:on-change handle-change-type}]
(if (= type :square)
[:div.input-element.pixels
[:input.input-text {:type "number"
:min "0"
:no-validate true
:value (:size params)
:on-change (handle-change-event :params :size)}]]
[:& select {:default-value (:size params)
:class "input-option"
:options size-options
:on-change (handle-change :params :size)}])
[:div.grid-option-main-actions
[:button.custom-button {:on-click handle-toggle-visibility} (if display i/eye i/eye-closed)]
[:button.custom-button {:on-click handle-remove-layout} i/trash]]]
[:& advanced-options {:visible? (:show-advanced-options @state)
:on-close toggle-advanced-options}
(when (= :square type)
[:& input-row {:label "Size"
:value (:size params)
:on-change (handle-change :params :size)}])
(when (= :row type)
[:& input-row {:label "Rows"
:options size-options
:value (:size params)
:on-change (handle-change :params :size)}])
(when (= :column type)
[:& input-row {:label "Columns"
:options size-options
:value (:size params)
:on-change (handle-change :params :size)}])
(when (#{:row :column} type)
[:& input-row {:label "Type"
:options [{:value :stretch :label "Stretch"}
{:value :left :label "Left"}
{:value :center :label "Center"}
{:value :right :label "Right"}]
:value (:type params)
:on-change (handle-change :params :type)}])
(when (= :row type)
[:& input-row {:label "Height"
:value (or (:item-height params) "")
:on-change (handle-change :params :item-height)}])
(when (= :column type)
[:& input-row {:label "Width"
:value (or (:item-width params) "")
:on-change (handle-change :params :item-width)}])
(when (#{:row :column} type)
[:*
[:& input-row {:label "Gutter"
:value (:gutter params)
:on-change (handle-change :params :gutter)}]
[:& input-row {:label "Margin"
:value (:margin params)
:on-change (handle-change :params :margin)}]])
[:& color-row {:value (:color params)
:on-change (handle-change :params :color)}]
[:div.row-flex
[:button.btn-options "Use default"]
[:button.btn-options "Set as default"]]]]))
(mf/defc grid-options [{:keys [shape]}]
(let [id (:id shape)
handle-create-layout #(st/emit! (dw/add-frame-layout id))
handle-remove-layout (fn [index] #(st/emit! (dw/remove-frame-layout id index)))
handle-edit-layout (fn [index] #(st/emit! (dw/set-frame-layout id index %)))]
[:div.element-set
[:div.element-set-title
[:span "Grid & Layout"]
[:div.add-page {:on-click handle-create-layout} i/close]]
[:div.element-set-content
(for [[index layout] (map-indexed vector (:layouts shape))]
[:& grid-option {:key (str (:id shape) "-" index)
:layout layout
:on-change (handle-edit-layout index)
:on-remove (handle-remove-layout index)}])]]))

View file

@ -0,0 +1,90 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.workspace.sidebar.options.rows.color-row
(:require
[rumext.alpha :as mf]
[uxbox.util.math :as math]
[uxbox.util.dom :as dom]
[uxbox.main.ui.modal :as modal]
[uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
[uxbox.common.data :as d]))
(defn color-picker-callback [color handle-change-color]
(fn [event]
(let [x (.-clientX event)
y (.-clientY event)
props {:x x
:y y
:on-change handle-change-color
:value (:value color)
:transparent? true}]
(modal/show! colorpicker-modal props))))
(defn opacity->string [opacity]
(str (-> opacity
(d/coalesce 1)
(* 100)
(math/round))))
(defn string->opacity [opacity-str]
(-> opacity-str
(d/parse-integer 1)
(/ 100)))
(mf/defc color-row [{:keys [value on-change]}]
(let [state (mf/use-state value)
change-color (fn [color]
(let [update-color (fn [state] (assoc state :value color))]
(swap! state update-color)
(when on-change (on-change (update-color @state)))))
change-opacity (fn [opacity]
(let [update-opacity (fn [state] (assoc state :opacity opacity))]
(swap! state update-opacity)
(when on-change (on-change (update-opacity @state)))))
handle-pick-color (fn [color]
(change-color color))
handle-input-color-change (fn [event]
(let [target (dom/get-target event)
value (dom/get-value target)]
(when (dom/valid? target)
(change-color value))))
handle-opacity-change (fn [event]
(-> event
dom/get-target
dom/get-value
string->opacity
change-opacity))]
[:div.row-flex.color-data
[:span.color-th
{:style {:background-color (-> @state :value)}
:on-click (color-picker-callback @state handle-pick-color)}]
[:div.color-info
[:input {:value (-> @state :value)
:pattern "^#(?:[0-9a-fA-F]{3}){1,2}$"
:on-change handle-input-color-change}]]
[:div.input-element.percentail
[:input.input-text {:type "number"
:value (-> @state :opacity opacity->string)
:on-change handle-opacity-change
:min "0"
:max "100"}]]
[:input.slidebar {:type "range"
:min "0"
:max "100"
:value (-> @state :opacity opacity->string)
:step "1"
:on-change handle-opacity-change}]]))

View file

@ -0,0 +1,30 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.workspace.sidebar.options.rows.input-row
(:require
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.main.ui.components.select :refer [select]]
[uxbox.util.dom :as dom]))
(mf/defc input-row [{:keys [label options value on-change]}]
[:div.row-flex.input-row
[:span.element-set-subtitle label]
[:div.input-element
(if options
[:& select {:default-value value
:class "input-option"
:options options
:on-change on-change}]
[:input.input-text
{:placeholder label
:type "number"
:on-change #(-> % dom/get-target dom/get-value d/parse-integer on-change)
:value value}])]])