🎉 Inspect styles tab: fill panel

This commit is contained in:
Xavier Julian 2025-09-16 14:15:50 +02:00 committed by Xaviju
parent 49d5987b15
commit 015bd9e453
20 changed files with 412 additions and 48 deletions

View File

@ -6,9 +6,9 @@
(ns app.main.ui.ds.buttons.button
(:require-macros
[app.common.data.macros :as dm]
[app.main.style :as stl])
(:require
[app.common.data :as d]
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]]
[rumext.v2 :as mf]))
@ -18,22 +18,26 @@
[:icon {:optional true}
[:and :string [:fn #(contains? icon-list %)]]]
[:on-ref {:optional true} fn?]
[:to {:optional true} :string] ;; renders as an anchor element
[:variant {:optional true}
[:maybe [:enum "primary" "secondary" "ghost" "destructive"]]]])
(mf/defc button*
{::mf/schema schema:button}
[{:keys [variant icon children class on-ref] :rest props}]
(let [variant (or variant "primary")
class (dm/str class " " (stl/css-case :button true
:button-primary (= variant "primary")
:button-secondary (= variant "secondary")
:button-ghost (= variant "ghost")
:button-destructive (= variant "destructive")))
props (mf/spread-props props {:class class
[{:keys [variant icon children class on-ref to] :rest props}]
(let [variant (d/nilv variant "primary")
element (if to "a" "button")
internal-class (stl/css-case :button true
:button-link (some? to)
:button-primary (= variant "primary")
:button-secondary (= variant "secondary")
:button-ghost (= variant "ghost")
:button-destructive (= variant "destructive"))
props (mf/spread-props props {:class [class internal-class]
:href to
:ref (fn [node]
(when on-ref
(on-ref node)))})]
[:> "button" props
[:> element props
(when icon [:> icon* {:icon-id icon :size "m"}])
[:span {:class (stl/css :label-wrapper)} children]]))

View File

@ -33,3 +33,9 @@
.button-destructive {
@extend %base-button-destructive;
}
.button-link {
&:hover {
text-decoration: none;
}
}

View File

@ -69,6 +69,11 @@ Action buttons are a variant of `icon-button*` (`"action"`) but smaller.
<Canvas of={IconButtonStories.Action} />
### Semantics
In some cases, a button needs to be semantically an 'a' tag, but visually maintain the button style.
In that case, the button accepts a 'to' property with the expected 'href'. It will then be rendered as an anchor element.
### Accessibility
Icon buttons require an `aria-label`. This is also shown in a tooltip on hovering the button.

View File

@ -13,36 +13,15 @@
[app.common.types.color :as cc]
[app.config :as cf]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.color-bullet :as cb]
[app.main.ui.components.copy-button :refer [copy-button*]]
[app.main.ui.components.select :refer [select]]
[app.main.ui.formats :as fmt]
[app.main.ui.inspect.common.colors :as isc]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[okulary.core :as l]
[rumext.v2 :as mf]))
(def file-colors-ref
(l/derived (l/in [:viewer :file :data :colors]) st/state))
(defn make-colors-library-ref
[libraries-place file-id]
(let [get-library
(fn [state]
(get-in state [libraries-place file-id :data :colors]))]
(l/derived get-library st/state)))
(defn- use-colors-library
[{:keys [ref-file] :as color}]
(let [library (mf/with-memo [ref-file]
(make-colors-library-ref :files ref-file))]
(mf/deref library)))
;; FIXME: this breaks react hooks rule (broken code)
(defn- get-file-colors []
(or (mf/deref file-colors-ref) (mf/deref refs/workspace-file-colors)))
(defn get-css-rule-humanized [property]
(as-> property $
(d/name $)
@ -51,8 +30,10 @@
(str/capital $)))
(mf/defc color-row [{:keys [color format copy-data on-change-format]}]
(let [colors-library (use-colors-library color)
file-colors (get-file-colors)
(let [colors-library (isc/use-colors-library color)
file-colors-ref (mf/deref isc/file-colors-ref)
file-colors-wokspace (mf/deref refs/workspace-file-colors)
file-colors (or file-colors-ref file-colors-wokspace)
color-library-name (get-in (or colors-library file-colors) [(:ref-id color) :name])
color (assoc color :name color-library-name)
image (:image color)]

View File

@ -0,0 +1,27 @@
;; 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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.inspect.common.colors
(:require
[app.main.store :as st]
[okulary.core :as l]
[rumext.v2 :as mf]))
(def file-colors-ref
(l/derived (l/in [:viewer :file :data :colors]) st/state))
(defn make-colors-library-ref
[libraries-place file-id]
(let [get-library
(fn [state]
(get-in state [libraries-place file-id :data :colors]))]
(l/derived get-library st/state)))
(defn use-colors-library
[{:keys [ref-file] :as color}]
(let [library (mf/with-memo [ref-file]
(make-colors-library-ref :files ref-file))]
(mf/deref library)))

View File

@ -9,6 +9,7 @@
[app.common.types.tokens-lib :as ctob]
[app.main.data.style-dictionary :as sd]
[app.main.refs :as refs]
[app.main.ui.inspect.styles.panels.fill :refer [fill-panel*]]
[app.main.ui.inspect.styles.panels.geometry :refer [geometry-panel*]]
[app.main.ui.inspect.styles.panels.layout :refer [layout-panel*]]
[app.main.ui.inspect.styles.panels.layout-element :refer [layout-element-panel*]]
@ -48,6 +49,13 @@
:text [:visibility :geometry :text :shadow :blur :stroke :layout-element]
:variant [:variant :geometry :fill :stroke :shadow :blur :layout :layout-element]})
(defn- has-fill?
[shape]
(and
(not (contains? #{:text :group} (:type shape)))
(seq (:fills shape))))
(defn- get-shape-type
[shapes first-shape first-component]
(if (= (count shapes) 1)
@ -128,6 +136,16 @@
:objects objects
:resolved-tokens resolved-active-tokens
:layout-element-properties layout-element-properties}]])))
;; FILL PANEL
:fill
(let [shapes (filter has-fill? shapes)]
(when (seq shapes)
[:> style-box* {:panel :fill}
[:> fill-panel* {:color-space color-space
:shapes shapes
:objects objects
:resolved-tokens resolved-active-tokens}]]))
;; DEFAULT WIP
[:> style-box* {:panel panel}
[:div color-space]])])]))

View File

@ -0,0 +1,49 @@
;; 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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.inspect.styles.panels.fill
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.types.fills :as types.fills]
[app.main.ui.inspect.attributes.common :as cmm]
[app.main.ui.inspect.styles.rows.color-properties-row :refer [color-properties-row*]]
[rumext.v2 :as mf]))
(defn- get-applied-tokens-in-shape
[shape-tokens property]
(get shape-tokens property))
(defn- get-resolved-token
"Get the resolved token for a specific property in a shape."
[shape resolved-tokens]
(let [shape-tokens (:applied-tokens shape)
applied-tokens-in-shape (get-applied-tokens-in-shape shape-tokens :fill)
token (get resolved-tokens applied-tokens-in-shape)]
token))
(mf/defc fill-panel*
[{:keys [shapes _objects resolved-tokens color-space]}]
[:div {:class (stl/css :fill-panel)}
(for [shape shapes]
[:div {:key (:id shape) :class "fill-shape"}
(for [fill (:fills shape [])]
(let [property :background
color-type (types.fills/fill->color fill) ;; can be :color, :gradient or :image
property-name (cmm/get-css-rule-humanized property)
resolved-token (get-resolved-token shape resolved-tokens)]
(if (:color color-type)
[:> color-properties-row* {:key (dm/str "fill-property-" (:id shape) (:color color-type))
:term property-name
:color color-type
:token resolved-token
:format color-space
:copiable true}]
(if (or (:gradient color-type) (:image color-type))
[:> color-properties-row* {:term property-name
:color color-type
:copiable true}]
[:span "background-image"]))))])])

View File

@ -1,9 +1,15 @@
;; 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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.inspect.styles.panels.geometry
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.main.ui.inspect.attributes.common :as cmm]
[app.main.ui.inspect.styles.properties-row :refer [properties-row*]]
[app.main.ui.inspect.styles.rows.properties-row :refer [properties-row*]]
[app.util.code-gen.style-css :as css]
[rumext.v2 :as mf]))

View File

@ -1,9 +1,15 @@
;; 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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.inspect.styles.panels.layout
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.main.ui.inspect.attributes.common :as cmm]
[app.main.ui.inspect.styles.properties-row :refer [properties-row*]]
[app.main.ui.inspect.styles.rows.properties-row :refer [properties-row*]]
[app.util.code-gen.style-css :as css]
[rumext.v2 :as mf]))

View File

@ -1,10 +1,16 @@
;; 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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.inspect.styles.panels.layout-element
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.types.shape.layout :as ctl]
[app.main.ui.inspect.attributes.common :as cmm]
[app.main.ui.inspect.styles.properties-row :refer [properties-row*]]
[app.main.ui.inspect.styles.rows.properties-row :refer [properties-row*]]
[app.util.code-gen.style-css :as css]
[rumext.v2 :as mf]))

View File

@ -1,7 +1,13 @@
;; 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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.inspect.styles.panels.tokens-panel
(:require-macros [app.main.style :as stl])
(:require
[app.main.ui.inspect.styles.properties-row :refer [properties-row*]]
[app.main.ui.inspect.styles.rows.properties-row :refer [properties-row*]]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))

View File

@ -1,10 +1,16 @@
;; 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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.inspect.styles.panels.variants-panel
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.files.variant :as cfv]
[app.common.types.component :as ctc]
[app.main.ui.inspect.styles.properties-row :refer [properties-row*]]
[app.main.ui.inspect.styles.rows.properties-row :refer [properties-row*]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))

View File

@ -1,26 +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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.inspect.styles.property-detail-copiable
(:require-macros [app.main.style :as stl])
(:require
[app.main.refs :as refs]
[app.main.ui.components.color-bullet :as bc]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.inspect.common.colors :as isc]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(def ^:private schema:property-detail-copiable
[:map
[:detail :string]
[:color {:optional true} :any] ;; color object with :color, :gradient or :image
[:token {:optional true} :any] ;; resolved token object
[:copied :boolean]
[:on-click fn?]])
(mf/defc property-detail-copiable*
{::mf/schema schema:property-detail-copiable}
[{:keys [detail token copied on-click]}]
[{:keys [detail color token copied on-click]}]
[:button {:class (stl/css-case :property-detail-copiable true
:property-detail-copied copied)
:property-detail-copied copied
:property-detail-copiable-color (some? color))
:on-click on-click}
(when color
[:> bc/color-bullet {:color color
:mini true}])
(if token
[:span {:class (stl/css :property-detail-text :property-detail-text-token)} (:name token)]
[:span {:class (stl/css :property-detail-text)} detail])
[:span {:class (stl/css :property-detail-text :property-detail-text-token)}
(:name token)]
(if (:ref-id color)
(let [colors-library (isc/use-colors-library color)
file-colors-ref (mf/deref isc/file-colors-ref)
file-colors-wokspace (mf/deref refs/workspace-file-colors)
file-colors (or file-colors-ref file-colors-wokspace)
color-library-name (get-in (or colors-library file-colors) [(:ref-id color) :name])
color (assoc color :name color-library-name)]
[:span {:class (stl/css :property-detail-text)} (:name color)])
[:span {:class (stl/css :property-detail-text)} detail]))
[:> icon* {:class (stl/css :property-detail-icon)
:icon-id (if copied i/tick i/clipboard)
:size "s"

View File

@ -37,15 +37,15 @@
cursor: pointer;
display: grid;
grid-template-columns: 1fr auto;
gap: var(--sp-s);
align-items: center;
justify-content: space-between;
width: 100%;
color: var(--detail-color);
min-block-size: var(--button-min-block-size);
min-inline-size: var(--button-min-inline-size);
padding: var(--sp-s);
border-radius: var(--button-border-radius);
border: 1px solid transparent;
border: $b-1 solid transparent;
text-align: left;
&:hover {
@ -57,9 +57,13 @@
}
}
.property-detail-copiable-color {
grid-template-columns: auto 1fr auto;
}
.property-detail-copied {
--button-border-active: var(--color-accent-tertiary);
border: 1px solid var(--button-border-active);
border: $b-1 solid var(--button-border-active);
}
.property-detail-icon {

View File

@ -0,0 +1,122 @@
;; 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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.inspect.styles.rows.color-properties-row
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.types.color :as cc]
[app.config :as cfg]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.tooltip :refer [tooltip*]]
[app.main.ui.formats :as fmt]
[app.main.ui.inspect.styles.property-detail-copiable :refer [property-detail-copiable*]]
[app.util.color :as uc]
[app.util.i18n :refer [tr]]
[app.util.timers :as tm]
[app.util.webapi :as wapi]
[rumext.v2 :as mf]))
(def ^:private schema:color-properties-row
[:map
[:term :string]
[:color :any] ;; color object with :color, :gradient or :image
[:format {:optional true} :string] ;; color format, e.g., "hex", "rgba", etc.
[:token {:optional true} :any] ;; resolved token object
[:copiable {:optional true} :boolean]])
(mf/defc color-properties-row*
{::mf/schema schema:color-properties-row}
[{:keys [class term color format token]}]
(let [copied* (mf/use-state false)
copied (deref copied*)
color-value (:color color)
color-gradient (:gradient color)
color-image (:image color)
color-image-name (:name color-image)
color-image-url (when (some? color-image)
(cfg/resolve-file-media color-image))
color-opacity (mf/use-memo
(mf/deps color)
#(dm/str (-> color
(:opacity)
(d/coalesce 1)
(* 100)
(fmt/format-number)) "%"))
formatted-color-value (mf/use-memo
(mf/deps color)
#(cond
(some? (:color color)) (case format
"hex" (dm/str color-value " " color-opacity)
"rgba" (let [[r g b a] (cc/hex->rgba color-value color-opacity)
result (cc/format-rgba [r g b a])]
result)
"hsla" (let [[h s l a] (cc/hex->hsla color-value color-opacity)
result (cc/format-hsla [h s l a])]
result)
color-value)
(some? (:gradient color)) (uc/gradient-type->string (:type color-gradient))
(some? (:image color)) (tr "media.image")
:else "none"))
copiable-value
(mf/use-memo
(mf/deps color formatted-color-value color-opacity color-image-url token)
#(if (some? token)
(:name token)
(cond
(:color color) (if (= format "hex")
(dm/str "background: " color-value "; opacity: " color-opacity ";")
(dm/str "background: " formatted-color-value ";"))
(:gradient color) (dm/str "background: " (uc/color->background color) ";")
(:image color) (dm/str "background: url(" color-image-url ") no-repeat center center / cover;")
:else "none")))
copy-attr
(mf/use-fn
(mf/deps copied formatted-color-value)
(fn []
(reset! copied* true)
(wapi/write-to-clipboard copiable-value)
(tm/schedule 1000 #(reset! copied* false))))]
[:*
[:dl {:class [(stl/css :property-row) class]}
[:dt {:class (stl/css :property-term)} term]
[:dd {:class (stl/css :property-detail)}
(if token
[:> tooltip* {:id (:name token)
:class (stl/css :tooltip-token-wrapper)
:content #(mf/html
[:div {:class (stl/css :tooltip-token)}
[:div {:class (stl/css :tooltip-token-title)}
(tr "inspect.tabs.styles.token.resolved-value")]
[:div {:class (stl/css :tooltip-token-value)}
(:value token)]])}
[:> property-detail-copiable* {:detail formatted-color-value
:color color
:token token
:copied copied
:on-click copy-attr}]]
[:> property-detail-copiable* {:detail formatted-color-value
:color color
:copied copied
:on-click copy-attr}])]]
(when (:image color)
[:div {:class (stl/css :color-image-preview)}
[:div {:class (stl/css :color-image-preview-wrapper)}
[:img {:class (stl/css :color-image)
:src color-image-url
:title color-image-name
:alt ""}]]
[:> button* {:variant "secondary"
:to color-image-url
:target "_blank"
:download color-image-name}
(tr "inspect.attributes.image.download")]])]))

View File

@ -0,0 +1,82 @@
// 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/.
//
// Copyright (c) KALEIDOS INC
@use "ds/typography.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/_borders.scss" as *;
// TOKENS ROW
.property-row {
--term-color: var(--color-foreground-secondary);
--detail-color: var(--color-foreground-primary);
--button-min-inline-size: #{$sz-154};
--button-min-block-size: #{$sz-36};
display: grid;
grid-template-columns: 30% 1fr;
align-items: center;
min-inline-size: var(--button-min-inline-size);
min-block-size: var(--button-min-block-size);
}
.property-term,
.property-detail {
@include use-typography("body-small");
}
.property-term {
color: var(--term-color);
}
.property-detail {
color: var(--detail-color);
}
// IMAGE PREVIEW
.color-image-preview {
--image-margin: var(--sp-m);
display: grid;
justify-content: center;
align-items: center;
margin: var(--image-margin);
gap: var(--sp-m);
}
.color-image-preview-wrapper {
--image-background: var(--color-background-secondary);
background: var(--image-background);
}
.color-image {
max-block-size: 10rem;
block-size: 100%;
inline-size: 100%;
object-fit: contain;
}
// TOOLTIP CONTENT
.tooltip-token {
--title-color: var(--color-foreground-secondary);
--title-value: var(--color-foreground-primary);
}
.tooltip-token-title {
@include use-typography("body-small");
color: var(--title-color);
}
.tooltip-token-value {
@include use-typography("body-small");
color: var(--title-value);
}
.tooltip-token-wrapper {
inline-size: 100%;
}

View File

@ -1,4 +1,10 @@
(ns app.main.ui.inspect.styles.properties-row
;; 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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.inspect.styles.rows.properties-row
(:require-macros [app.main.style :as stl])
(:require
[app.main.ui.ds.tooltip :refer [tooltip*]]

View File

@ -1,3 +1,9 @@
;; 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/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.inspect.styles.style-box
(:require-macros [app.main.style :as stl])
(:require

View File

@ -441,7 +441,6 @@
(defn- get-max-height
[shape objects]
(prn "max-height" shape)
(cond
(ctl/any-layout-immediate-child? objects shape)
(:layout-item-max-h shape)))