🎉 Add prototype tab UI tweaks (#7832)

* 🎉 Add prototype tab UI tweaks

* 📎 PR changes
This commit is contained in:
Luis de Dios 2025-12-02 10:44:16 +01:00 committed by GitHub
parent 4ed49cdc5d
commit ef68081d1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 916 additions and 725 deletions

View File

@ -119,21 +119,6 @@
(let [page (dsh/lookup-page state)] (let [page (dsh/lookup-page state)]
(rx/of (update-flow (:id page) flow-id #(assoc % :name name))))))) (rx/of (update-flow (:id page) flow-id #(assoc % :name name)))))))
(defn start-rename-flow
[id]
(dm/assert! (uuid? id))
(ptk/reify ::start-rename-flow
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :flow-for-rename] id))))
(defn end-rename-flow
[]
(ptk/reify ::end-rename-flow
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local dissoc :flow-for-rename))))
;; --- Interactions ;; --- Interactions
(defn- connected-frame? (defn- connected-frame?

View File

@ -9,6 +9,7 @@
[app.config :as cf] [app.config :as cf]
[app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.checkbox :refer [checkbox*]]
[app.main.ui.ds.controls.combobox :refer [combobox*]] [app.main.ui.ds.controls.combobox :refer [combobox*]]
[app.main.ui.ds.controls.input :refer [input*]] [app.main.ui.ds.controls.input :refer [input*]]
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]] [app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
@ -62,6 +63,7 @@
:RawSvg raw-svg* :RawSvg raw-svg*
:Select select* :Select select*
:Switch switch* :Switch switch*
:Checkbox checkbox*
:Combobox combobox* :Combobox combobox*
:Text text* :Text text*
:TabSwitcher tab-switcher* :TabSwitcher tab-switcher*

View File

@ -11,12 +11,18 @@
%base-button { %base-button {
--button-bg-color: initial; --button-bg-color: initial;
--button-fg-color: initial; --button-fg-color: initial;
--button-hover-bg-color: initial; --button-hover-bg-color: initial;
--button-hover-fg-color: initial; --button-hover-fg-color: initial;
--button-active-bg-color: initial; --button-active-bg-color: initial;
--button-active-fg-color: initial;
--button-disabled-bg-color: initial; --button-disabled-bg-color: initial;
--button-disabled-fg-color: initial; --button-disabled-fg-color: initial;
--button-border-color: var(--button-bg-color); --button-border-color: var(--button-bg-color);
--button-focus-inner-ring-color: initial; --button-focus-inner-ring-color: initial;
--button-focus-outer-ring-color: initial; --button-focus-outer-ring-color: initial;
@ -38,8 +44,10 @@
--button-fg-color: var(--button-hover-fg-color); --button-fg-color: var(--button-hover-fg-color);
} }
&:active { &:active,
&[aria-pressed="true"] {
--button-bg-color: var(--button-active-bg-color); --button-bg-color: var(--button-active-bg-color);
--button-fg-color: var(--button-active-fg-color);
} }
&:focus-visible { &:focus-visible {
@ -63,6 +71,7 @@
--button-hover-fg-color: var(--color-background-secondary); --button-hover-fg-color: var(--color-background-secondary);
--button-active-bg-color: var(--color-accent-tertiary); --button-active-bg-color: var(--color-accent-tertiary);
--button-active-fg-color: var(--color-background-secondary);
--button-disabled-bg-color: var(--color-accent-primary-muted); --button-disabled-bg-color: var(--color-accent-primary-muted);
--button-disabled-fg-color: var(--color-background-secondary); --button-disabled-fg-color: var(--color-background-secondary);
@ -72,7 +81,8 @@
--button-focus-inner-ring-color: var(--color-background-secondary); --button-focus-inner-ring-color: var(--color-background-secondary);
--button-focus-outer-ring-color: var(--color-accent-primary); --button-focus-outer-ring-color: var(--color-accent-primary);
&:active { &:active,
&[aria-pressed="true"] {
box-shadow: inset 0 0 #{px2rem(10)} #{px2rem(2)} rgba(0, 0, 0, 0.2); box-shadow: inset 0 0 #{px2rem(10)} #{px2rem(2)} rgba(0, 0, 0, 0.2);
} }
} }
@ -85,6 +95,7 @@
--button-hover-fg-color: var(--color-accent-primary); --button-hover-fg-color: var(--color-accent-primary);
--button-active-bg-color: var(--color-background-quaternary); --button-active-bg-color: var(--color-background-quaternary);
--button-active-fg-color: var(--color-accent-primary);
--button-disabled-bg-color: transparent; --button-disabled-bg-color: transparent;
--button-disabled-fg-color: var(--color-foreground-secondary); --button-disabled-fg-color: var(--color-foreground-secondary);
@ -103,6 +114,7 @@
--button-hover-fg-color: var(--color-accent-primary); --button-hover-fg-color: var(--color-accent-primary);
--button-active-bg-color: var(--color-background-quaternary); --button-active-bg-color: var(--color-background-quaternary);
--button-active-fg-color: var(--color-accent-primary);
--button-disabled-bg-color: transparent; --button-disabled-bg-color: transparent;
--button-disabled-fg-color: var(--color-accent-primary-muted); --button-disabled-fg-color: var(--color-accent-primary-muted);
@ -121,6 +133,7 @@
--button-hover-fg-color: var(--color-foreground-primary); --button-hover-fg-color: var(--color-foreground-primary);
--button-active-bg-color: var(--color-accent-error); --button-active-bg-color: var(--color-accent-error);
--button-active-fg-color: var(--color-foreground-primary);
--button-disabled-bg-color: var(--color-background-error); --button-disabled-bg-color: var(--color-background-error);
--button-disabled-fg-color: var(--color-accent-error); --button-disabled-fg-color: var(--color-accent-error);
@ -130,7 +143,8 @@
--button-focus-inner-ring-color: var(--color-background-primary); --button-focus-inner-ring-color: var(--color-background-primary);
--button-focus-outer-ring-color: var(--color-accent-primary); --button-focus-outer-ring-color: var(--color-accent-primary);
&:active { &:active,
&[aria-pressed="true"] {
box-shadow: inset 0 0 #{px2rem(10)} #{px2rem(2)} rgba(0, 0, 0, 0.2); box-shadow: inset 0 0 #{px2rem(10)} #{px2rem(2)} rgba(0, 0, 0, 0.2);
} }
} }

View File

@ -0,0 +1,45 @@
;; 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.ds.controls.checkbox
(:require-macros
[app.main.style :as stl])
(:require
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[rumext.v2 :as mf]))
(def ^:private schema:checkbox
[:map
[:id {:optional true} :string]
[:label {:optional true} :string]
[:checked {:optional true} :boolean]
[:on-change {:optional true} fn?]
[:disabled {:optional true} :boolean]])
(mf/defc checkbox*
{::mf/schema schema:checkbox}
[{:keys [id class label checked on-change disabled] :rest props}]
(let [props
(mf/spread-props props {:type "checkbox"
:class (stl/css :checkbox-input)
:id id
:checked checked
:on-change on-change
:disabled disabled})]
[:div {:class [class (stl/css :checkbox)]}
[:label {:for id
:class (stl/css :checkbox-label)}
[:div {:class (stl/css-case :checkbox-box true
:checked checked
:disabled disabled)}
(when checked
[:> icon* {:icon-id i/tick
:size "s"}])]
[:div {:class (stl/css :checkbox-text)} label]
[:> :input props]]]))

View File

@ -0,0 +1,40 @@
{ /* 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 */ }
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import * as Checkbox from "./checkbox.stories";
<Meta title="Controls/Checkbox" />
# Checkbox
The `checkbox*` component is a toggle control. It allows users to switch between boolean states (`false` or `true`).
<Canvas of={Checkbox.Default} />
<Canvas of={Checkbox.Checked} />
## Anatomy
The checkbox component consists of three main parts:
- **Label** (optional): the text that describes what the checkbox controls. Clicking on this text also works for toggling.
- **Box**: the box which shows the current state. Contains a check if the state is `true`.
- **Native element**: the native HTML element which holds the state. It remains hidden to the user.
## Usage Guidelines
### When to Use
- For boolean settings that take effect immediately.
- In preference panels and configuration screens.
### When Not to Use
- For actions that require confirmation (use buttons instead).
- For multiple choice selections (use radio buttons or select).
- For temporary states that need explicit "Apply" action.
- For ternary states.

View File

@ -0,0 +1,86 @@
// 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/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/typography.scss" as *;
.checkbox {
--input-checkbox-border-color: var(--color-foreground-secondary);
--input-checkbox-border-color-focus: var(--color-accent-primary);
--input-checkbox-border-color-hover: var(--color-accent-primary-muted);
--input-checkbox-foreground-color: var(--color-foreground-primary);
--input-checkbox-background-color: var(--color-background-quaternary);
--input-checkbox-border-color-checked: var(--color-background-quaternary);
--input-checkbox-foreground-color-checked: var(--color-background-primary);
--input-checkbox-background-color-checked: var(--color-accent-primary);
--input-checkbox-foreground-color-disabled: var(--color-background-primary);
--input-checkbox-background-color-disabled: var(--color-foreground-secondary);
--input-checkbox-text-color: var(--color-foreground-secondary);
}
.checkbox-label {
display: grid;
grid-template-columns: var(--sp-l) 1fr 0;
align-items: center;
&:hover {
.checkbox-box {
border-color: var(--input-checkbox-border-color-hover);
}
}
&:focus,
&:focus-within {
.checkbox-box {
border-color: var(--input-checkbox-border-color-focus);
}
}
}
.checkbox-box {
display: flex;
align-items: center;
justify-content: center;
inline-size: $sz-16;
block-size: $sz-16;
border-radius: $br-4;
border: $b-1 solid var(--input-checkbox-border-color);
color: var(--input-checkbox-foreground-color);
background-color: var(--input-checkbox-background-color);
&.disabled {
border: 0;
}
&.checked {
--input-checkbox-border-color: var(--input-checkbox-border-color-checked);
--input-checkbox-foreground-color: var(--input-checkbox-foreground-color-checked);
--input-checkbox-background-color: var(--input-checkbox-background-color-checked);
&.disabled {
--input-checkbox-foreground-color: var(--input-checkbox-foreground-color-disabled);
--input-checkbox-background-color: var(--input-checkbox-background-color-disabled);
}
}
}
.checkbox-text {
@include use-typography("body-small");
padding-inline-start: var(--sp-s);
color: var(--input-checkbox-text-color);
}
.checkbox-input {
&:focus {
outline: 0;
inline-size: 0;
block-size: 0;
}
}

View File

@ -0,0 +1,76 @@
// 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
import * as React from "react";
import Components from "@target/components";
const { Checkbox } = Components;
export default {
title: "Controls/Checkbox",
component: Checkbox,
argTypes: {
label: {
control: { type: "text" },
description: "Label text displayed next to the checkbox",
},
checked: {
control: { type: "boolean" },
description: "Whether the checkbox is checked",
},
disabled: {
control: { type: "boolean" },
description: "Whether the checkbox is disabled",
},
},
args: {
checked: false,
disabled: false,
},
parameters: {
controls: {
exclude: ["id", "on-change"],
},
},
render: ({ ...args }) => <Checkbox {...args} />,
};
export const Default = {
args: {
label: "Toggle something",
disabled: false,
},
render: ({ ...args }) => <Checkbox {...args} />,
};
export const Checked = {
args: {
label: "Toggle something",
checked: true,
disabled: false,
},
render: ({ ...args }) => <Checkbox {...args} />,
};
export const WithoutLabel = {
args: {
disabled: false,
},
render: ({ ...args }) => <Checkbox {...args} />,
};
export const WithLongLabel = {
args: {
label:
"This is a very long label that demonstrates how the checkbox component handles text wrapping and layout when the label content is extensive",
disabled: false,
},
render: ({ ...args }) => (
<div style={{ maxWidth: "300px" }}>
<Checkbox {...args} />
</div>
),
};

View File

@ -300,6 +300,7 @@
"A collection of all icons" "A collection of all icons"
(collect-icons)) (collect-icons))
(def ^:private ^:const icon-size-l 32)
(def ^:private ^:const icon-size-m 16) (def ^:private ^:const icon-size-m 16)
(def ^:private ^:const icon-size-s 12) (def ^:private ^:const icon-size-s 12)
@ -308,7 +309,7 @@
[:class {:optional true} [:maybe :string]] [:class {:optional true} [:maybe :string]]
[:icon-id [:and :string [:fn #(contains? icon-list %)]]] [:icon-id [:and :string [:fn #(contains? icon-list %)]]]
[:size {:optional true} [:size {:optional true}
[:maybe [:enum "s" "m"]]]]) [:maybe [:enum "s" "m" "l"]]]])
(mf/defc icon* (mf/defc icon*
{::mf/schema schema:icon} {::mf/schema schema:icon}
@ -317,10 +318,14 @@
{:class [class (stl/css :icon)] {:class [class (stl/css :icon)]
:width icon-size-m :width icon-size-m
:height icon-size-m}) :height icon-size-m})
size-px (if (= size "s")
icon-size-s size-px (cond (= size "l") icon-size-l
icon-size-m) (= size "s") icon-size-s
offset (/ (- icon-size-m size-px) 2)] :else icon-size-m)
offset (if (or (= size "s") (= size "m"))
(/ (- icon-size-m size-px) 2)
0)]
[:> :svg props [:> :svg props
[:use {:href (dm/str "#icon-" icon-id) [:use {:href (dm/str "#icon-" icon-id)

View File

@ -25,7 +25,7 @@
[app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options*]] [app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options*]]
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu*]] [app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu*]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]] [app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu*]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :as layout-container] [app.main.ui.workspace.sidebar.options.menus.layout-container :as layout-container]
[app.main.ui.workspace.sidebar.options.page :as page] [app.main.ui.workspace.sidebar.options.page :as page]
[app.main.ui.workspace.sidebar.options.shapes.bool :as bool] [app.main.ui.workspace.sidebar.options.shapes.bool :as bool]
@ -215,7 +215,7 @@
(case options-mode (case options-mode
:prototype :prototype
[:div {:class (stl/css :element-options :interaction-options)} [:div {:class (stl/css :element-options :interaction-options)}
[:& interactions-menu {:shape (first shapes)}]] [:> interactions-menu* {:shape (first shapes)}]]
:inspect :inspect
[:div {:class (stl/css :element-options :inspect-options)} [:div {:class (stl/css :element-options :inspect-options)}

View File

@ -8,28 +8,27 @@
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.types.page :as ctp] [app.common.types.page :as ctp]
[app.common.types.shape-tree :as ctt] [app.common.types.shape-tree :as ctt]
[app.common.types.shape.interactions :as ctsi] [app.common.types.shape.interactions :as ctsi]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace :as dw] [app.main.data.common :as dcm]
[app.main.data.workspace.interactions :as dwi] [app.main.data.workspace.interactions :as dwi]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]] [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
[app.main.ui.components.select :refer [select]] [app.main.ui.components.select :refer [select]]
[app.main.ui.components.title-bar :refer [title-bar*]] [app.main.ui.components.title-bar :refer [title-bar*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.ds.controls.checkbox :refer [checkbox*]]
[app.main.ui.icons :as deprecated-icon] [app.main.ui.ds.controls.input :refer [input*]]
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
[cuerdas.core :as str] [cuerdas.core :as str]
[okulary.core :as l]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(defn- event-type-names (defn- event-type-names
@ -67,149 +66,117 @@
(filter #(and (not= (:id %) (:id shape)) ; A frame cannot navigate to itself (filter #(and (not= (:id %) (:id shape)) ; A frame cannot navigate to itself
(not= (:id %) (:frame-id shape)))) ; nor a shape to its container frame (not= (:id %) (:frame-id shape)))) ; nor a shape to its container frame
(map (fn [frame] (map (fn [frame]
{:value (str (:id frame)) :label (:name frame)})))) {:value (str (:id frame))
:label (:name frame)}))))
(defn- get-shared-frames-options (defn- get-shared-frames-options
[shared-frames] [shared-frames]
(map (fn [frame] (map (fn [frame]
{:value (str (:id frame)) :label (:name frame)}) shared-frames)) {:value (str (:id frame))
:label (:name frame)}) shared-frames))
(def flow-for-rename-ref (mf/defc prototype-pill*
(l/derived (l/in [:workspace-local :flow-for-rename]) st/state)) [{:keys [title description on-change is-editable
left-button-icon-id left-button-tooltip on-left-button-click is-left-button-active
right-button-icon-id right-button-tooltip on-right-button-click is-right-button-active]} external-ref]
(let [local-ref (mf/use-ref)
ref (or external-ref local-ref)
(mf/defc flow-item handle-focus
(mf/use-fn
(fn []
(let [input-node (mf/ref-val ref)]
(dom/select-text! input-node))))
handle-key-down
(mf/use-fn
(fn [event]
(let [input-node (mf/ref-val ref)
target (dom/get-target event)
value (-> target dom/get-value str/trim)]
(when (or (kbd/esc? event) (kbd/enter? event))
(dom/blur! input-node)
(on-change value)))))
handle-blur
(mf/use-fn
(fn [event]
(let [target (dom/get-target event)
value (-> target dom/get-value str/trim)]
(on-change value))))]
[:div {:class (stl/css-case :prototype-pill true
:double (some? description))}
[:> icon-button* {:variant "secondary"
:class (stl/css :prototype-pill-button :left)
:aria-pressed is-left-button-active
:icon left-button-icon-id
:aria-label left-button-tooltip
:on-click on-left-button-click}]
[:div {:class (stl/css :prototype-pill-main)}
(if is-editable
[:input {:type "text"
:class (stl/css :prototype-pill-input)
:ref ref
:default-value title
:on-focus handle-focus
:on-key-down handle-key-down
:on-blur handle-blur}]
[:div {:class (stl/css :prototype-pill-center)}
[:div {:class (stl/css :prototype-pill-info)}
[:div {:class (stl/css :prototype-pill-name)} title]
[:div {:class (stl/css :prototype-pill-description)} description]]])
[:> icon-button* {:variant "secondary"
:class (stl/css :prototype-pill-button :right)
:aria-pressed is-right-button-active
:icon right-button-icon-id
:aria-label right-button-tooltip
:on-click on-right-button-click}]]]))
(mf/defc flow-item*
[{:keys [flow]}] [{:keys [flow]}]
(let [editing? (mf/use-state false) (let [start-flow
flow-for-rename (mf/deref flow-for-rename-ref)
name-ref (mf/use-ref)
start-edit (fn []
(reset! editing? true))
accept-edit (fn []
(let [name-input (mf/ref-val name-ref)
name (str/trim (dom/get-value name-input))]
(reset! editing? false)
(st/emit! (dwi/end-rename-flow)
(when-not (str/empty? name)
(dwi/rename-flow (:id flow) name)))))
cancel-edit (fn []
(reset! editing? false)
(st/emit! (dwi/end-rename-flow)))
on-key-down (fn [event]
(when (kbd/enter? event) (accept-edit))
(when (kbd/esc? event) (cancel-edit)))
start-flow
(mf/use-fn (mf/use-fn
(mf/deps flow) (mf/deps flow)
#(st/emit! (dw/select-shape (:starting-frame flow)))) #(st/emit! (dcm/go-to-viewer {:section "interactions"
:frame-id (:starting-frame flow)})))
;; rename-flow rename-flow
;; (mf/use-fn (mf/use-fn
;; (mf/deps flow) (mf/deps flow)
;; #(st/emit! (dwi/start-rename-flow (:id flow)))) (fn [value]
(when-not (str/empty? value)
(st/emit! (dwi/rename-flow (:id flow) value)))))
remove-flow remove-flow
(mf/use-fn (mf/use-fn
(mf/deps flow) (mf/deps flow)
#(st/emit! (dwi/remove-flow (:id flow))))] #(st/emit! (dwi/remove-flow (:id flow))))]
(mf/use-effect [:> prototype-pill* {:title (:name flow "")
(fn [] :is-editable true
#(when editing? :on-change rename-flow
(cancel-edit)))) :left-button-icon-id i/play
:left-button-tooltip (tr "workspace.options.flows.flow-start")
:on-left-button-click start-flow
:right-button-icon-id i/remove
:right-button-tooltip (tr "labels.remove")
:on-right-button-click remove-flow}]))
(mf/use-effect (mf/defc interaction-item*
(mf/deps flow-for-rename)
#(when (and (= flow-for-rename (:id flow))
(not @editing?))
(start-edit)))
(mf/use-effect
(mf/deps @editing?)
#(when @editing?
(let [name-input (mf/ref-val name-ref)]
(dom/select-text! name-input))
nil))
[:div {:class (stl/css :flow-element)}
[:span {:class (stl/css :flow-info)}
[:span {:class (stl/css :flow-name-wrapper)}
[:button {:class (stl/css :start-flow-btn)
:on-click start-flow}
[:span {:class (stl/css :button-icon)}
deprecated-icon/play]]
[:span {:class (stl/css :flow-input-wrapper)}
[:input
{:class (stl/css :flow-input)
:type "text"
:ref name-ref
:on-blur accept-edit
:on-key-down on-key-down
:default-value (:name flow "")}]]]]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.flows.remove-flow")
:on-click remove-flow
:icon i/remove}]]))
(mf/defc page-flows
{::mf/props :obj}
[{:keys [flows]}]
(when flows
[:div {:class (stl/css :interaction-options)}
[:> title-bar* {:collapsable false
:title (tr "workspace.options.flows.flow-starts")
:class (stl/css :title-spacing-layout-flow)}]
(for [[id flow] flows]
[:& flow-item {:flow flow :key (dm/str id)}])]))
(mf/defc shape-flows
{::mf/props :obj}
[{:keys [flows shape]}]
(when (cfh/frame-shape? shape)
(let [flow (ctp/get-frame-flow flows (:id shape))
add-flow (mf/use-fn #(st/emit! (dwi/add-flow-selected-frame)))]
[:div {:class (stl/css :element-set)}
[:> title-bar* {:collapsable false
:title (tr "workspace.options.flows.flow")
:class (stl/css :title-spacing-layout-flow)}
(when (nil? flow)
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.flows.add-flow-start")
:on-click add-flow
:icon i/add}])]
(when (some? flow)
[:& flow-item {:flow flow :key (dm/str (:id flow))}])])))
(def ^:private corner-center-icon
(deprecated-icon/icon-xref :corner-center (stl/css :corner-icon)))
(def ^:private corner-bottom-icon
(deprecated-icon/icon-xref :corner-bottom (stl/css :corner-icon)))
(def ^:private corner-bottomleft-icon
(deprecated-icon/icon-xref :corner-bottom-left (stl/css :corner-icon)))
(def ^:private corner-bottomright-icon
(deprecated-icon/icon-xref :corner-bottom-right (stl/css :corner-icon)))
(def ^:private corner-top-icon
(deprecated-icon/icon-xref :corner-top (stl/css :corner-icon)))
(def ^:private corner-topleft-icon
(deprecated-icon/icon-xref :corner-top-left (stl/css :corner-icon)))
(def ^:private corner-topright-icon
(deprecated-icon/icon-xref :corner-top-right (stl/css :corner-icon)))
(mf/defc interaction-entry
[{:keys [index shape interaction update-interaction remove-interaction]}] [{:keys [index shape interaction update-interaction remove-interaction]}]
(let [objects (deref refs/workspace-page-objects) (let [objects (deref refs/workspace-page-objects)
destination (get objects (:destination interaction)) destination (get objects (:destination interaction))
frames (mf/with-memo [objects] (ctt/get-viewer-frames objects {:all-frames? true})) frames (mf/with-memo [objects]
shape-parent-ids (mf/with-memo [objects] (cfh/get-parent-ids objects (:id shape))) (ctt/get-viewer-frames objects {:all-frames? true}))
shape-parents (mf/with-memo [frames shape] (filter (comp (set shape-parent-ids) :id) frames)) shape-parent-ids (mf/with-memo [objects]
(cfh/get-parent-ids objects (:id shape)))
shape-parents (mf/with-memo [frames shape]
(filter (comp (set shape-parent-ids) :id) frames))
overlay-pos-type (:overlay-pos-type interaction) overlay-pos-type (:overlay-pos-type interaction)
close-click-outside? (:close-click-outside interaction false) close-click-outside? (:close-click-outside interaction false)
@ -219,14 +186,16 @@
way (-> interaction :animation :way) way (-> interaction :animation :way)
direction (-> interaction :animation :direction) direction (-> interaction :animation :direction)
state* (mf/use-state false) open-extended* (mf/use-state false)
extended-open? (deref state*) open-extended? (deref open-extended*)
toggle-extended (mf/use-fn #(swap! state* not))
ext-delay-ref (mf/use-ref nil) ext-delay-ref (mf/use-ref nil)
ext-duration-ref (mf/use-ref nil) ext-duration-ref (mf/use-ref nil)
toggle-extended
(mf/use-fn
#(swap! open-extended* not))
change-event-type change-event-type
(mf/use-fn (mf/use-fn
(mf/deps index update-interaction) (mf/deps index update-interaction)
@ -425,201 +394,188 @@
{:icon :easing-ease-out :value :ease-out :label (tr "workspace.options.interaction-easing-ease-out")} {:icon :easing-ease-out :value :ease-out :label (tr "workspace.options.interaction-easing-ease-out")}
{:icon :easing-ease-in-out :value :ease-in-out :label (tr "workspace.options.interaction-easing-ease-in-out")}]] {:icon :easing-ease-in-out :value :ease-in-out :label (tr "workspace.options.interaction-easing-ease-in-out")}]]
[:div {:class (stl/css :interaction-item)}
[:> prototype-pill* {:title (event-type-name interaction)
:description (action-summary interaction destination)
:left-button-icon-id i/hsva
:left-button-tooltip (tr "labels.options")
:is-left-button-active open-extended?
:on-left-button-click toggle-extended
:right-button-icon-id i/remove
:right-button-tooltip (tr "labels.remove")
:on-right-button-click #(remove-interaction index)}]
[:div {:class (stl/css-case :element-set-options-group true (when open-extended?
:element-set-options-group-open extended-open?)} [:*
; Summary
[:div {:class (stl/css :interactions-summary)}
[:button {:class (stl/css-case :extend-btn true
:extended extended-open?)
:on-click toggle-extended}
deprecated-icon/menu]
[:div {:class (stl/css :interactions-info)
:on-click toggle-extended}
[:div {:class (stl/css :trigger-name)} (event-type-name interaction)]
[:div {:class (stl/css :action-summary)} (action-summary interaction destination)]]
[:button {:class (stl/css :remove-btn)
:data-value index
:on-click #(remove-interaction index)}
deprecated-icon/remove-icon]]
(when extended-open?
[:div {:class (stl/css :extended-options)}
;; Trigger select ;; Trigger select
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:span {:class (stl/css :interaction-name)} [:label {:class (stl/css :interaction-row-label)}
(tr "workspace.options.interaction-trigger")] [:div {:class (stl/css :interaction-row-name)}
[:div {:class (stl/css :select-wrapper)} (tr "workspace.options.interaction-trigger")]]
[:& select {:class (stl/css :interaction-type-select) [:div {:class (stl/css :interaction-row-select)}
:default-value (:event-type interaction) [:& select {:default-value (:event-type interaction)
:options event-type-options :options event-type-options
:on-change change-event-type}]]] :on-change change-event-type}]]]
;; Delay ;; Delay
(when (ctsi/has-delay interaction) (when (ctsi/has-delay interaction)
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:span {:class (stl/css :interaction-name)} [:div {:class (stl/css :interaction-row-label)}
(tr "workspace.options.interaction-delay")] [:div {:class (stl/css :interaction-row-name)}
[:div {:class (stl/css :input-element-wrapper) (tr "workspace.options.interaction-delay")]]
:title (tr "workspace.options.interaction-ms")} [:div {:class (stl/css :interaction-row-input)}
[:span.after (tr "workspace.options.interaction-ms")]
[:> numeric-input* {:ref ext-delay-ref [:> numeric-input* {:ref ext-delay-ref
:className (stl/css :numeric-input) :icon i/character-m
:property (tr "workspace.options.interaction-ms")
:on-change change-delay :on-change change-delay
:value (:delay interaction) :value (:delay interaction)}]]])
:title (tr "workspace.options.interaction-ms")}]]])
;; Action select ;; Action select
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:span {:class (stl/css :interaction-name)} (tr "workspace.options.interaction-action")] [:div {:class (stl/css :interaction-row-label)}
[:div {:class (stl/css :select-wrapper)} [:div {:class (stl/css :interaction-row-name)}
[:& select {:class (stl/css :interaction-type-select) (tr "workspace.options.interaction-action")]]
:default-value (:action-type interaction) [:div {:class (stl/css :interaction-row-select)}
[:& select {:default-value (:action-type interaction)
:options action-type-options :options action-type-options
:on-change change-action-type}]]] :on-change change-action-type}]]]
;; Destination ;; Destination
(when (ctsi/has-destination interaction) (when (ctsi/has-destination interaction)
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:span {:class (stl/css :interaction-name)} (tr "workspace.options.interaction-destination")] [:div {:class (stl/css :interaction-row-label)}
[:div {:class (stl/css :select-wrapper)} [:div {:class (stl/css :interaction-row-name)}
[:& select {:class (stl/css :interaction-type-select) (tr "workspace.options.interaction-destination")]]
:default-value (str (:destination interaction)) [:div {:class (stl/css :interaction-row-select)}
[:& select {:default-value (str (:destination interaction))
:options destination-options :options destination-options
:on-change change-destination}]]]) :on-change change-destination}]]])
;; Preserve scroll ;; Preserve scroll
(when (ctsi/has-preserve-scroll interaction) (when (ctsi/has-preserve-scroll interaction)
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:div {:class (stl/css :checkbox-option)} [:div {:class (stl/css :interaction-row-checkbox)}
[:label {:for (str "preserve-" index) [:> checkbox* {:id (str "preserve-" index)
:class (stl/css-case :global/checked preserve-scroll?)} :label (tr "workspace.options.interaction-preserve-scroll")
[:span {:class (stl/css-case :global/checked preserve-scroll?)}
(when preserve-scroll?
deprecated-icon/status-tick)]
(tr "workspace.options.interaction-preserve-scroll")
[:input {:type "checkbox"
:id (str "preserve-" index)
:checked preserve-scroll? :checked preserve-scroll?
:on-change change-preserve-scroll}]]]]) :on-change change-preserve-scroll}]]])
;; URL ;; URL
(when (ctsi/has-url interaction) (when (ctsi/has-url interaction)
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:span {:class (stl/css :interaction-name)} (tr "workspace.options.interaction-url")] [:div {:class (stl/css :interaction-row-label)}
[:div {:class (stl/css :input-element-wrapper)} [:div {:class (stl/css :interaction-row-name)}
[:input {:class (stl/css :input-text) (tr "workspace.options.interaction-url")]]
:type "url" [:div {:class (stl/css :interaction-row-input)}
[:> input* {:type "url"
:placeholder "http://example.com" :placeholder "http://example.com"
:default-value (str (:url interaction)) :default-value (:url interaction)
:on-blur change-url}]]]) :on-blur change-url}]]])
(when (ctsi/has-overlay-opts interaction) (when (ctsi/has-overlay-opts interaction)
[:* [:*
;; Overlay position relative-to (select) ;; Overlay position relative-to (select)
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:span {:class (stl/css :interaction-name)} (tr "workspace.options.interaction-relative-to")] [:div {:class (stl/css :interaction-row-label)}
[:div {:class (stl/css :select-wrapper)} [:div {:class (stl/css :interaction-row-name)}
[:& select {:class (stl/css :interaction-type-select) (tr "workspace.options.interaction-relative-to")]]
:default-value (str (:position-relative-to interaction)) [:div {:class (stl/css :interaction-row-select)}
[:& select {:default-value (str (:position-relative-to interaction))
:options relative-to-opts :options relative-to-opts
:on-change change-position-relative-to}]]] :on-change change-position-relative-to}]]]
;; Overlay position (select) ;; Overlay position (select)
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:span {:class (stl/css :interaction-name)} (tr "workspace.options.interaction-position")] [:div {:class (stl/css :interaction-row-label)}
[:div {:class (stl/css :select-wrapper)} [:div {:class (stl/css :interaction-row-name)}
[:& select {:class (stl/css :interaction-type-select) (tr "workspace.options.interaction-position")]]
:default-value (:overlay-pos-type interaction) [:div {:class (stl/css :interaction-row-select)}
[:& select {:default-value (:overlay-pos-type interaction)
:options overlay-position-opts :options overlay-position-opts
:on-change change-overlay-pos-type}]]] :on-change change-overlay-pos-type}]]]
;; Overlay position (buttons) ;; Overlay position (buttons)
[:div {:class (stl/css-case :property-row true [:div {:class (stl/css :interaction-row)}
:big-row true)} [:div {:class (stl/css :interaction-row-position)}
[:div {:class (stl/css :position-btns-wrapper)} [:div {:class (stl/css :center)}
[:button {:class (stl/css-case :direction-btn true [:> icon-button* {:variant "secondary"
:center-btn true :aria-pressed (= overlay-pos-type :center)
:active (= overlay-pos-type :center))
:data-value "center" :data-value "center"
:on-click toggle-overlay-pos-type} :icon i/corner-center
corner-center-icon] :aria-label (tr "workspace.options.interaction-pos-center")
[:button {:class (stl/css-case :direction-btn true :on-click toggle-overlay-pos-type}]]
:top-left-btn true
:active (= overlay-pos-type :top-left)) [:div {:class (stl/css :top-left)}
[:> icon-button* {:variant "secondary"
:aria-pressed (= overlay-pos-type :top-left)
:data-value "top-left" :data-value "top-left"
:on-click toggle-overlay-pos-type} :icon i/corner-top-left
corner-topleft-icon] :aria-label (tr "workspace.options.interaction-pos-top-left")
[:button {:class (stl/css-case :direction-btn true :on-click toggle-overlay-pos-type}]]
:top-right-btn true
:active (= overlay-pos-type :top-right)) [:div {:class (stl/css :top-right)}
[:> icon-button* {:variant "secondary"
:aria-pressed (= overlay-pos-type :top-right)
:data-value "top-right" :data-value "top-right"
:on-click toggle-overlay-pos-type} :icon i/corner-top-right
corner-topright-icon] :aria-label (tr "workspace.options.interaction-pos-top-right")
:on-click toggle-overlay-pos-type}]]
[:button {:class (stl/css-case :direction-btn true [:div {:class (stl/css :top-center)}
:top-center-btn true [:> icon-button* {:variant "secondary"
:active (= overlay-pos-type :top-center)) :aria-pressed (= overlay-pos-type :top-center)
:data-value "top-center" :data-value "top-center"
:on-click toggle-overlay-pos-type} :icon i/corner-top
corner-top-icon] :aria-label (tr "workspace.options.interaction-pos-top-center")
:on-click toggle-overlay-pos-type}]]
[:button {:class (stl/css-case :direction-btn true [:div {:class (stl/css :bottom-left)}
:bottom-left-btn true [:> icon-button* {:variant "secondary"
:active (= overlay-pos-type :bottom-left)) :aria-pressed (= overlay-pos-type :bottom-left)
:data-value "bottom-left" :data-value "bottom-left"
:on-click toggle-overlay-pos-type} :icon i/corner-bottom-left
corner-bottomleft-icon] :aria-label (tr "workspace.options.interaction-pos-bottom-left")
[:button {:class (stl/css-case :direction-btn true :on-click toggle-overlay-pos-type}]]
:bottom-right-btn true
:active (= overlay-pos-type :bottom-right)) [:div {:class (stl/css :bottom-right)}
[:> icon-button* {:variant "secondary"
:aria-pressed (= overlay-pos-type :bottom-right)
:data-value "bottom-right" :data-value "bottom-right"
:on-click toggle-overlay-pos-type} :icon i/corner-bottom-right
corner-bottomright-icon] :aria-label (tr "workspace.options.interaction-pos-bottom-right")
[:button {:class (stl/css-case :direction-btn true :on-click toggle-overlay-pos-type}]]
:bottom-center-btn true
:active (= overlay-pos-type :bottom-center)) [:div {:class (stl/css :bottom-center)}
[:> icon-button* {:variant "secondary"
:aria-pressed (= overlay-pos-type :bottom-center)
:data-value "bottom-center" :data-value "bottom-center"
:on-click toggle-overlay-pos-type} :icon i/corner-bottom
corner-bottom-icon]]] :aria-label (tr "workspace.options.interaction-pos-bottom-center")
:on-click toggle-overlay-pos-type}]]]]
;; Overlay click outside ;; Overlay click outside
[:div {:class (stl/css :interaction-row)}
[:ul {:class (stl/css :property-list)} [:div {:class (stl/css :interaction-row-checkbox)}
[:li {:class (stl/css :property-row)} [:> checkbox* {:id (str "close-" index)
[:div {:class (stl/css :checkbox-option)} :label (tr "workspace.options.interaction-close-outside")
[:label {:for (str "close-" index)
:class (stl/css-case :global/checked close-click-outside?)}
[:span {:class (stl/css-case :global/checked close-click-outside?)}
(when close-click-outside?
deprecated-icon/status-tick)]
(tr "workspace.options.interaction-close-outside")
[:input {:type "checkbox"
:id (str "close-" index)
:checked close-click-outside? :checked close-click-outside?
:on-change change-close-click-outside}]]]] :on-change change-close-click-outside}]]]
;; Overlay background [:div {:class (stl/css :interaction-row)}
[:li {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row-checkbox)}
[:div {:class (stl/css :checkbox-option)} [:> checkbox* {:id (str "background-" index)
[:label {:for (str "background-" index) :label (tr "workspace.options.interaction-background")
:class (stl/css-case :global/checked background-overlay?)}
[:span {:class (stl/css-case :global/checked background-overlay?)}
(when background-overlay?
deprecated-icon/status-tick)]
(tr "workspace.options.interaction-background")
[:input {:type "checkbox"
:id (str "background-" index)
:checked background-overlay? :checked background-overlay?
:on-change change-background-overlay}]]]]]]) :on-change change-background-overlay}]]]])
(when (ctsi/has-animation? interaction) (when (ctsi/has-animation? interaction)
[:* [:*
;; Animation select ;; Animation select
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:span {:class (stl/css :interaction-name)} (tr "workspace.options.interaction-animation")] [:div {:class (stl/css :interaction-row-label)}
[:div {:class (stl/css :select-wrapper)} [:div {:class (stl/css :interaction-row-name)}
(tr "workspace.options.interaction-animation")]]
[:div {:class (stl/css :interaction-row-select)}
[:& select {:class (stl/css :animation-select) [:& select {:class (stl/css :animation-select)
:default-value (or (-> interaction :animation :animation-type) "") :default-value (or (-> interaction :animation :animation-type) "")
:options animation-opts :options animation-opts
@ -627,9 +583,8 @@
;; Direction ;; Direction
(when (ctsi/has-way? interaction) (when (ctsi/has-way? interaction)
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:div {:class (stl/css :inputs-wrapper)} [:div {:class (stl/css :interaction-row-radio)}
[:& radio-buttons {:selected (d/name way) [:& radio-buttons {:selected (d/name way)
:on-change change-way :on-change change-way
:name "animation-way"} :name "animation-way"}
@ -640,16 +595,16 @@
;; Direction ;; Direction
(when (ctsi/has-direction? interaction) (when (ctsi/has-direction? interaction)
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:div {:class (stl/css :buttons-wrapper)} [:div {:class (stl/css :interaction-row-radio)}
[:& radio-buttons {:selected (d/name direction) [:& radio-buttons {:selected (d/name direction)
:on-change change-direction :on-change change-direction
:name "animation-direction"} :name "animation-direction"}
[:& radio-button {:icon i/column [:& radio-button {:icon i/row
:icon-class (stl/css :right) :icon-class (stl/css :right)
:value "right" :value "right"
:id "animation-right"}] :id "animation-right"}]
[:& radio-button {:icon i/column [:& radio-button {:icon i/row-reverse
:icon-class (stl/css :left) :icon-class (stl/css :left)
:id "animation-left" :id "animation-left"
:value "left"}] :value "left"}]
@ -657,29 +612,31 @@
:icon-class (stl/css :down) :icon-class (stl/css :down)
:id "animation-down" :id "animation-down"
:value "down"}] :value "down"}]
[:& radio-button {:icon i/column [:& radio-button {:icon i/column-reverse
:icon-class (stl/css :up) :icon-class (stl/css :up)
:id "animation-up" :id "animation-up"
:value "up"}]]]]) :value "up"}]]]])
;; Duration ;; Duration
(when (ctsi/has-duration? interaction) (when (ctsi/has-duration? interaction)
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:span {:class (stl/css :interaction-name)} (tr "workspace.options.interaction-duration")] [:div {:class (stl/css :interaction-row-label)}
[:div {:class (stl/css :input-element-wrapper) [:div {:class (stl/css :interaction-row-name)}
:title (tr "workspace.options.interaction-ms")} (tr "workspace.options.interaction-duration")]]
[:span {:class (stl/css :after)} [:div {:class (stl/css :interaction-row-input)}
(tr "workspace.options.interaction-ms")]
[:> numeric-input* {:ref ext-duration-ref [:> numeric-input* {:ref ext-duration-ref
:icon i/character-m
:property (tr "workspace.options.interaction-ms")
:on-change change-duration :on-change change-duration
:value (-> interaction :animation :duration) :value (-> interaction :animation :duration)}]]])
:title (tr "workspace.options.interaction-ms")}]]])
;; Easing ;; Easing
(when (ctsi/has-easing? interaction) (when (ctsi/has-easing? interaction)
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:span {:class (stl/css :interaction-name)} (tr "workspace.options.interaction-easing")] [:div {:class (stl/css :interaction-row-label)}
[:div {:class (stl/css :select-wrapper)} [:div {:class (stl/css :interaction-row-name)}
(tr "workspace.options.interaction-easing")]]
[:div {:class (stl/css :interaction-row-select)}
[:& select {:class (stl/css :easing-select) [:& select {:class (stl/css :easing-select)
:dropdown-class (stl/css :dropdown-upwards) :dropdown-class (stl/css :dropdown-upwards)
:default-value (-> interaction :animation :easing) :default-value (-> interaction :animation :easing)
@ -688,76 +645,159 @@
;; Offset effect ;; Offset effect
(when (ctsi/has-offset-effect? interaction) (when (ctsi/has-offset-effect? interaction)
[:div {:class (stl/css :property-row)} [:div {:class (stl/css :interaction-row)}
[:div {:class (stl/css :checkbox-option)} [:div {:class (stl/css :interaction-row-checkbox)}
[:label {:for (str "offset-effect-" index) [:> checkbox* {:id (str "offset-effect-" index)
:class (stl/css-case :global/checked (-> interaction :animation :offset-effect))} :label (tr "workspace.options.interaction-offset-effect")
[:span {:class (stl/css-case :global/checked (-> interaction :animation :offset-effect))}
(when (-> interaction :animation :offset-effect)
deprecated-icon/status-tick)]
(tr "workspace.options.interaction-offset-effect")
[:input {:type "checkbox"
:id (str "offset-effect-" index)
:checked (-> interaction :animation :offset-effect) :checked (-> interaction :animation :offset-effect)
:on-change change-offset-effect}]]]])])])])) :on-change change-offset-effect}]]])])])]))
(mf/defc interactions-menu (mf/defc page-flows*
{::mf/props :obj} [{:keys [flows]}]
[{:keys [shape]}] (let [show-content* (mf/use-state true)
(let [interactions show-content? (deref show-content*)
(get shape :interactions [])
flows (mf/deref refs/workspace-page-flows) toggle-content
(mf/use-fn
#(swap! show-content* not))]
[:div {:class (stl/css :section)}
[:div {:class (stl/css :title)}
[:> title-bar* {:collapsable (> (count flows) 0)
:collapsed (not show-content?)
:on-collapsed toggle-content
:title (tr "workspace.options.flows.flow-starts")
:class (stl/css :title-bar)}]]
(when show-content?
[:div {:class (stl/css :content)}
(for [[id flow] flows]
[:> flow-item* {:key id
:flow flow}])])]))
(mf/defc shape-flows*
[{:keys [flows shape]}]
(let [show-content* (mf/use-state true)
show-content? (deref show-content*)
flow (ctp/get-frame-flow flows (:id shape))
toggle-content
(mf/use-fn
#(swap! show-content* not))
add-flow
(mf/use-fn
#(st/emit! (dwi/add-flow-selected-frame)))]
[:div {:class (stl/css :section)}
[:div {:class (stl/css :title)}
[:> title-bar* {:collapsable (some? flow)
:collapsed (not show-content?)
:on-collapsed toggle-content
:title (tr "workspace.options.flows.flow")
:class (stl/css :title-bar)}
(when (nil? flow)
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.flows.add-flow-start")
:on-click add-flow
:icon i/add}])]]
(when (and show-content? (some? flow))
[:div {:class (stl/css :content)}
[:> flow-item* {:key (:id flow)
:flow flow}]])]))
(mf/defc interactions*
[{:keys [interactions shape]}]
(let [show-content* (mf/use-state true)
show-content? (deref show-content*)
toggle-content
(mf/use-fn
#(swap! show-content* not))
add-interaction add-interaction
(fn [] (mf/use-fn
(st/emit! (dwi/add-new-interaction shape))) (mf/deps shape)
#(st/emit! (dwi/add-new-interaction shape)))
remove-interaction remove-interaction
(mf/use-fn
(mf/deps shape)
(fn [index] (fn [index]
(st/emit! (dwi/remove-interaction shape index))) (st/emit! (dwi/remove-interaction shape index))))
update-interaction update-interaction
(mf/use-fn
(mf/deps shape)
(fn [index update-fn] (fn [index update-fn]
(st/emit! (dwi/update-interaction shape index update-fn)))] (st/emit! (dwi/update-interaction shape index update-fn))))]
[:div {:class (stl/css :interactions-content)}
(if shape
[:& shape-flows {:flows flows
:shape shape}]
[:& page-flows {:flows flows}])
[:div {:class (stl/css :interaction-options)}
(when (and shape (not (cfh/unframed-shape? shape)))
[:div {:class (stl/css :element-title)}
[:> title-bar* {:collapsable false
:title (tr "workspace.options.interactions")
:class (stl/css :title-spacing-layout-interactions)}
[:div {:class (stl/css :section)}
[:div {:class (stl/css :title)}
[:> title-bar* {:collapsable (> (count interactions) 0)
:collapsed (not show-content?)
:on-collapsed toggle-content
:title (tr "workspace.options.interactions")
:class (stl/css :title-bar)}
[:> icon-button* {:variant "ghost" [:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.interactions.add-interaction") :aria-label (tr "workspace.options.interactions.add-interaction")
:on-click add-interaction :on-click add-interaction
:icon i/add}]]]) :icon i/add}]]]
(when (= (count interactions) 0) (when show-content?
[:div {:class (stl/css :help-content)} [:div {:class (stl/css :content :content-interactions)}
(when (and shape (not (cfh/unframed-shape? shape)))
[:div {:class (stl/css :help-group)}
[:div {:class (stl/css :interactions-help-icon)} deprecated-icon/add]
[:div {:class (stl/css :interactions-help)}
(tr "workspace.options.add-interaction")]])
[:div {:class (stl/css :help-group)}
[:div {:class (stl/css :interactions-help-icon)} deprecated-icon/interaction]
[:div {:class (stl/css :interactions-help)}
(tr "workspace.options.select-a-shape")]]
[:div {:class (stl/css :help-group)}
[:div {:class (stl/css :interactions-help-icon)} deprecated-icon/play]
[:div {:class (stl/css :interactions-help)}
(tr "workspace.options.use-play-button")]]])
[:div {:class (stl/css :groups)}
(for [[index interaction] (d/enumerate interactions)] (for [[index interaction] (d/enumerate interactions)]
[:& interaction-entry {:key (dm/str (:id shape) "-" index) [:> interaction-item* {:key (str (:id shape) "-" index)
:index index :index index
:shape shape :shape shape
:interaction interaction :interaction interaction
:update-interaction update-interaction :update-interaction update-interaction
:remove-interaction remove-interaction}])]]])) :remove-interaction remove-interaction}])])]))
(mf/defc interactions-menu*
[{:keys [shape]}]
(let [interactions (get shape :interactions [])
flows (mf/deref refs/workspace-page-flows)
framed-shape? (and shape (not (cfh/unframed-shape? shape)))]
[:div {:class (stl/css :wrapper)}
(if shape
(when (cfh/frame-shape? shape)
[:> shape-flows* {:flows flows
:shape shape}])
(when flows
[:> page-flows* {:flows flows}]))
(when framed-shape?
[:> interactions* {:interactions interactions
:shape shape}])
(when (= (count interactions) 0)
[:div {:class (stl/css :section)}
[:div {:class (stl/css :content)}
[:div {:class (stl/css :help)}
(when framed-shape?
[:div {:class (stl/css :help-group)}
[:div {:class (stl/css :help-icon)}
[:> icon* {:icon-id i/add
:size "l"
:class (stl/css :help-icon-inner)}]]
[:div {:class (stl/css :help-text)}
(tr "workspace.options.add-interaction")]])
[:div {:class (stl/css :help-group)}
[:div {:class (stl/css :help-icon)}
[:> icon* {:icon-id i/interaction
:size "l"
:class (stl/css :help-icon-inner)}]]
[:div {:class (stl/css :help-text)}
(tr "workspace.options.select-a-shape")]]
[:div {:class (stl/css :help-group)}
[:div {:class (stl/css :help-icon)}
[:> icon* {:icon-id i/play
:size "l"
:class (stl/css :help-icon-inner)}]]
[:div {:class (stl/css :help-text)}
(tr "workspace.options.use-play-button")]]]]])]))

View File

@ -4,22 +4,51 @@
// //
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/_utils.scss" as *;
@use "ds/mixins.scss" as *;
@use "ds/typography.scss" as t;
@use "../../../sidebar/common/sidebar.scss" as sidebar;
.interactions-content { .wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: deprecated.$s-8; gap: var(--sp-s);
padding-left: var(--sp-m); padding-inline-start: var(--sp-m);
} }
.interaction-options { .section {
@include deprecated.flexColumn; @include sidebar.option-grid-structure;
} }
.help-content { .title {
padding: deprecated.$s-32 0; grid-column: span 8;
width: deprecated.$s-200; }
.title-bar {
padding-inline-start: var(--sp-xxs);
}
.content {
grid-column: span 8;
display: flex;
flex-direction: column;
gap: var(--sp-xs);
margin-block-start: var(--sp-xs);
}
.content-interactions {
gap: var(--sp-l);
}
.help {
display: flex;
flex-direction: column;
gap: var(--sp-xxxl);
inline-size: $sz-200;
padding: var(--sp-xxxl) 0;
margin: 0 auto; margin: 0 auto;
} }
@ -27,130 +56,168 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin-bottom: deprecated.$s-40; gap: var(--sp-m);
gap: deprecated.$s-12;
} }
.interactions-help-icon { .help-text {
@include deprecated.flexCenter; @include t.use-typography("body-small");
width: deprecated.$s-48;
height: deprecated.$s-48;
border-radius: deprecated.$br-circle;
background-color: var(--pill-background-color);
svg {
@extend .button-icon;
stroke: var(--icon-foreground);
height: deprecated.$s-32;
width: deprecated.$s-32;
}
}
.after {
@include deprecated.bodySmallTypography;
margin-top: deprecated.$s-1;
}
.interactions-help {
@include deprecated.bodySmallTypography;
text-align: center; text-align: center;
color: var(--title-foreground-color); color: var(--color-foreground-secondary);
} }
.element-set { .help-icon {
@include deprecated.flexColumn; display: flex;
justify-content: center;
align-items: center;
inline-size: $sz-48;
block-size: $sz-48;
} }
.interactions-info { .help-icon-inner {
color: var(--color-foreground-secondary);
inline-size: $sz-32;
block-size: $sz-32;
}
.interaction-item {
display: flex;
flex-direction: column;
gap: var(--sp-xs);
}
.prototype-pill {
display: flex;
align-items: center;
gap: px2rem(1);
border-radius: $br-8;
padding: var(--sp-s) var(--sp-m);
block-size: $sz-32;
padding: 0;
&.double {
block-size: $sz-48;
.prototype-pill-button {
block-size: $sz-48;
}
}
&:has(.prototype-pill-input:focus) {
outline: $b-1 solid var(--color-accent-primary);
}
}
.prototype-pill-button {
&.left {
border-end-end-radius: 0;
border-start-end-radius: 0;
}
&.right {
border-start-start-radius: 0;
border-end-start-radius: 0;
}
}
.prototype-pill-main {
display: flex;
flex-grow: 1; flex-grow: 1;
display: grid; block-size: 100%;
inline-size: 100%;
} }
.trigger-name { .prototype-pill-center {
flex-grow: 1;
display: flex;
align-items: center;
block-size: 100%;
padding: 0 var(--sp-s);
background-color: var(--color-background-tertiary);
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
} }
.action-summary { .prototype-pill-info {
color: var(--color-foreground-secondary);
@include deprecated.textEllipsis;
}
.groups {
@include deprecated.flexColumn(deprecated.$s-12);
}
.element-set-options-group-open {
@include deprecated.flexColumn;
}
.extended-options {
@include deprecated.flexColumn;
}
.property-list {
list-style: none;
margin: 0;
display: grid; display: grid;
row-gap: deprecated.$s-16; inline-size: 100%;
margin-block: calc(#{deprecated.$s-16} - #{deprecated.$s-4});
} }
.property-row { .prototype-pill-input {
@extend .attr-row; @include t.use-typography("body-small");
height: auto; border: none;
&.big-row { background: none;
height: 100%; outline: none;
block-size: 100%;
inline-size: 100%;
flex-grow: 1;
margin: var(--sp-xxs) 0;
padding: 0 0 0 var(--sp-s);
margin: 0;
background-color: var(--color-background-tertiary);
color: var(--color-foreground-primary);
display: grid;
inline-size: 100%;
&:hover {
background-color: var(--color-background-quaternary);
&:active {
background-color: var(--color-background-quaternary);
} }
.interaction-name {
@include deprecated.twoLineTextEllipsis;
@include deprecated.bodySmallTypography;
padding-left: deprecated.$s-4;
width: deprecated.$s-92;
margin: auto 0;
grid-area: name;
color: var(--title-foreground-color);
} }
.select-wrapper {
&:focus {
background-color: var(--color-background-tertiary);
}
}
.prototype-pill-name {
@include t.use-typography("body-small");
@include textEllipsis;
color: var(--color-foreground-primary);
}
.prototype-pill-description {
@include t.use-typography("body-small");
@include textEllipsis;
color: var(--color-foreground-secondary);
}
.interaction-row {
@include sidebar.option-grid-structure;
}
.interaction-row-label {
grid-column: span 3;
display: flex; display: flex;
align-items: center; align-items: center;
grid-area: content; color: var(--color-foreground-secondary);
.easing-select {
width: deprecated.$s-156;
padding: 0 deprecated.$s-8;
.dropdown-upwards {
bottom: deprecated.$s-36;
width: deprecated.$s-156;
top: unset;
}
}
}
.input-element-wrapper {
@extend .input-element;
@include deprecated.bodySmallTypography;
grid-area: content;
}
.buttons-wrapper {
grid-area: content;
.right svg {
transform: rotate(-90deg);
}
.left svg {
transform: rotate(90deg);
}
.up svg {
transform: rotate(180deg);
}
}
.inputs-wrapper {
grid-area: content;
@include deprecated.flexRow;
.radio-btn {
@extend .input-checkbox;
}
}
} }
.position-btns-wrapper { .interaction-row-name {
grid-area: content; @include twoLineTextEllipsis;
@include t.use-typography("body-small");
color: var(--color-foreground-secondary);
}
.interaction-row-select {
grid-column: span 5;
}
.interaction-row-checkbox {
grid-column: 4 / span 5;
min-block-size: $sz-32;
display: flex;
align-items: center;
}
.interaction-row-input {
grid-column: span 5;
}
.interaction-row-radio {
grid-column: 4 / span 5;
}
.interaction-row-position {
grid-column: 4 / span 5;
display: grid; display: grid;
grid-template-areas: grid-template-areas:
"topleft top topright" "topleft top topright"
@ -158,191 +225,30 @@
"bottomleft bottom bottomright"; "bottomleft bottom bottomright";
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr);
width: deprecated.$s-84; inline-size: calc($sz-32 * 3);
height: deprecated.$s-84; block-size: calc($sz-32 * 3);
border-radius: deprecated.$br-8; border-radius: $br-8;
background-color: var(--color-background-tertiary); background-color: var(--color-background-tertiary);
.center-btn {
.center {
grid-area: center; grid-area: center;
} }
.top-left-btn { .top-left {
grid-area: topleft; grid-area: topleft;
} }
.top-right-btn { .top-center {
grid-area: topright;
}
.top-center-btn {
grid-area: top; grid-area: top;
} }
.bottom-left-btn { .top-right {
grid-area: topright;
}
.bottom-left {
grid-area: bottomleft; grid-area: bottomleft;
} }
.bottom-right-btn { .bottom-center {
grid-area: bottomright;
}
.bottom-center-btn {
grid-area: bottom; grid-area: bottom;
} }
} .bottom-right {
grid-area: bottomright;
.direction-btn {
@extend .button-tertiary;
height: deprecated.$s-28;
width: deprecated.$s-28;
&.active {
@extend .button-icon-selected;
}
}
.checkbox-option {
@extend .input-checkbox;
grid-area: content;
line-height: 1.2;
label {
align-items: start;
}
}
.interactions-summary {
@extend .asset-element;
height: deprecated.$s-44;
padding: 0;
gap: deprecated.$s-8;
.remove-btn {
@extend .button-tertiary;
height: deprecated.$s-32;
width: deprecated.$s-28;
svg {
@extend .button-icon-small;
}
}
}
.extend-btn {
@extend .button-tertiary;
--button-tertiary-border-width: var(--expand-button-icon-border-width);
height: 100%;
width: deprecated.$s-28;
border-end-end-radius: 0;
border-start-end-radius: 0;
padding: 0;
svg {
@extend .button-icon;
}
position: relative;
&:after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-inline-end: deprecated.$s-1 solid var(--panel-background-color);
}
&.extended {
@extend .button-icon-selected;
--button-tertiary-border-width: var(--expand-button-icon-border-width-selected);
}
}
.corner-icon {
fill: none;
stroke: currentColor;
width: deprecated.$s-12;
height: deprecated.$s-12;
}
.flow-element {
@include deprecated.flexRow;
}
.flow-info {
display: flex;
align-items: center;
gap: deprecated.$s-2;
border-radius: deprecated.$s-8;
background-color: var(--input-details-color);
height: deprecated.$s-32;
width: 100%;
flex-grow: 1;
}
.flow-name-wrapper {
@include deprecated.bodySmallTypography;
@include deprecated.focusInput;
display: flex;
align-items: center;
gap: deprecated.$s-4;
flex-grow: 1;
height: deprecated.$s-32;
width: 100%;
border-radius: deprecated.$br-8;
padding: 0;
margin-right: 0;
background-color: var(--input-background-color);
border: deprecated.$s-1 solid var(--input-border-color);
color: var(--input-foreground-color);
.start-flow-btn {
@include deprecated.buttonStyle;
height: deprecated.$s-32;
width: deprecated.$s-28;
padding: 0 deprecated.$s-2 0 deprecated.$s-8;
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
background-color: transparent;
svg {
@extend .button-icon;
stroke: var(--icon-foreground);
&:hover {
stroke: var(--input-foreground-color-active);
}
}
}
.flow-input {
@extend .input-base;
@include deprecated.bodySmallTypography;
background-color: transparent;
height: deprecated.$s-28;
}
.flow-input-wrapper {
@include deprecated.bodySmallTypography;
display: flex;
align-items: center;
height: deprecated.$s-28;
padding: 0;
width: 100%;
margin: 0;
flex-grow: 1;
background-color: transparent;
color: var(--input-foreground-color);
border-radius: deprecated.$br-8;
}
&:hover {
background-color: var(--input-background-color-hover);
border: deprecated.$s-1 solid var(--input-border-color-hover);
&:active {
background-color: var(--input-background-color-hover);
.flow-input-wrapper {
background-color: var(--input-background-color-hover);
}
}
}
&:focus,
&:focus-within {
background-color: var(--input-background-color-focus);
border: deprecated.$s-1 solid var(--input-border-color-focus);
&:hover {
border: deprecated.$s-1 solid var(--input-border-color-focus);
}
}
&.editing {
background-color: var(--input-background-color-active);
border: deprecated.$s-1 solid var(--input-border-color-active);
} }
} }

View File

@ -100,8 +100,8 @@
[{:keys [x y stroke action-type arrow-dir zoom] :as props}] [{:keys [x y stroke action-type arrow-dir zoom] :as props}]
(let [icon-pdata (case action-type (let [icon-pdata (case action-type
:navigate (case arrow-dir :navigate (case arrow-dir
:right "M -6.5 0 l 12 0 l -6 -6 m 6 6 l -6 6" :right "M -6.5 0 L 5.5 0 M 6.715 0.715 L -0.5 -6.5 M 6.715 -0.715 L -0.365 6.635"
:left "M 6.5 0 l -12 0 l 6 -6 m -6 6 l 6 6" :left "M 6.5 0 l -12 0 m -0.715 0.715 l 6.5 -6.9 m -6 6 l 6 6.35"
nil) nil)
:open-overlay "M-5 -5 h7 v7 h-7 z M2 -2 h3.5 v7 h-7 v-2.5" :open-overlay "M-5 -5 h7 v7 h-7 z M2 -2 h3.5 v7 h-7 v-2.5"

View File

@ -17,7 +17,6 @@
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.common :as dcm] [app.main.data.common :as dcm]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.interactions :as dwi]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.streams :as ms] [app.main.streams :as ms]
@ -277,7 +276,6 @@
pos (gpt/point x (- y (/ 35 zoom))) pos (gpt/point x (- y (/ 35 zoom)))
frame-id (:id frame) frame-id (:id frame)
flow-id (:id flow)
flow-name (:name flow) flow-name (:name flow)
on-pointer-down on-pointer-down
@ -291,11 +289,6 @@
(dom/stop-propagation event) (dom/stop-propagation event)
(st/emit! (dcm/go-to-viewer params)))))) (st/emit! (dcm/go-to-viewer params))))))
on-double-click
(mf/use-fn
(mf/deps flow-id)
#(st/emit! (dwi/start-rename-flow flow-id)))
on-pointer-enter on-pointer-enter
(mf/use-fn (mf/use-fn
(mf/deps frame-id on-frame-enter) (mf/deps frame-id on-frame-enter)
@ -319,7 +312,6 @@
[:div {:class (stl/css-case :frame-flow-badge-content true [:div {:class (stl/css-case :frame-flow-badge-content true
:selected is-selected) :selected is-selected)
:on-pointer-down on-pointer-down :on-pointer-down on-pointer-down
:on-double-click on-double-click
:on-pointer-enter on-pointer-enter :on-pointer-enter on-pointer-enter
:on-pointer-leave on-pointer-leave} :on-pointer-leave on-pointer-leave}
[:> icon* {:icon-id i/play [:> icon* {:icon-id i/play