Skip to content

Commit

Permalink
WIP: badges field.
Browse files Browse the repository at this point in the history
- see new-badge for next todos
  • Loading branch information
mhuebert committed Jan 4, 2024
1 parent 5fc5c31 commit 5c97b2d
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 69 deletions.
5 changes: 5 additions & 0 deletions src/sb/app.cljc
Expand Up @@ -31,6 +31,11 @@
{:string {:view field.ui/text-field}
:http/url {:view field.ui/text-field}
:boolean {:view field.ui/checkbox-field}
:project/badges {:view field.ui/badges-field
:make-field (fn [init _props]
(io/field :many {:badge/label ?label
:badge/color ?color}
:init init))}
:prose/as-map {:view field.ui/prose-field
:make-field (fn [init _props]
(io/form {:prose/format (prose/?format :init :prose.format/markdown)
Expand Down
7 changes: 5 additions & 2 deletions src/sb/app/content/data.cljc
Expand Up @@ -11,5 +11,8 @@
:prose/format
:prose/string]}}

{:content/badge {s- [:map {:closed true} :badge/label]}
:badge/label {s- :string}}))
{:content/badge {s- [:map {:closed true}
:badge/label
:badge/color]}
:badge/label {s- :string}
:badge/color {s- :html/color}}))
6 changes: 1 addition & 5 deletions src/sb/app/field/admin_ui.cljc
Expand Up @@ -37,11 +37,7 @@
:style {:background-color @?color
:color (color/contrasting-text-color @?color)}}]
[:div.relative.w-10.focus-within-ring.rounded.overflow-hidden.self-stretch
[field.ui/color-field ?color {:style {:top -10
:left -10
:width 100
:height 100
:position "absolute"}}]]
[field.ui/color-field ?color nil]]
[radix/dropdown-menu {:id :field-option
:trigger [:button.p-1.relative.icon-gray.cursor-default.rounded.hover:bg-gray-200.self-stretch
[icons/ellipsis-horizontal "w-4 h-4"]]
Expand Down
145 changes: 108 additions & 37 deletions src/sb/app/field/ui.cljc
@@ -1,5 +1,6 @@
(ns sb.app.field.ui
(:require [applied-science.js-interop :as j]
(:require #?(:cljs ["@radix-ui/react-popover" :as Pop])
[applied-science.js-interop :as j]
[clojure.set :as set]
[clojure.string :as str]
[inside-out.forms :as io]
Expand All @@ -17,7 +18,10 @@
[sb.schema :as sch]
[sb.util :as u]
[yawn.hooks :as h]
[yawn.view :as v]))
[yawn.view :as v]
[sb.color :as color]
[sb.i18n :refer [t]]
[promesa.core :as p]))

#?(:cljs
(defn parse-video-url [url]
Expand Down Expand Up @@ -109,7 +113,8 @@
wrap
unwrap
can-edit?
unstyled?]
unstyled?
keybindings]
:or {wrap identity
unwrap identity}} (merge props (:props (meta ?field)))
blur! (fn [e] (j/call-in e [:target :blur]))
Expand All @@ -135,37 +140,39 @@
data-props {:data-touched (:touched ?field)
:data-invalid (not (io/valid? ?field))
:data-focused (:focused ?field)}]
[:div.field-wrapper
(merge data-props {:class (:wrapper classes)})
(form.ui/show-label ?field (:field/label props) (:label classes))
[:div.flex-v.relative
(with-messages-popover ?field
[auto-size (-> (form.ui/pass-props props)
(v/merge-props
data-props
{:disabled (not can-edit?)
:class ["w-full" (:input classes)]
:placeholder (:placeholder props)
:on-key-down (let [save #(when (io/ancestor-by ?field :field/persisted?)
(j/call % :preventDefault)
(entity.data/maybe-save-field ?field))]
(ui/keydown-handler (merge {:Meta-Enter save
:Escape cancel!
:Meta-. cancel!}
(when-not multi-line?
{:Enter save}))))}))])
(when-let [postfix (or (:field/postfix props)
(:field/postfix (meta ?field))
(and (some-> (entity.data/persisted-value ?field)
(not= (:value props)))
[icons/pencil-outline "w-4 h-4 text-txt/40"]))]
[:div.pointer-events-none.absolute.inset-y-0.right-0.top-0.bottom-0.flex.items-center.p-2 postfix])

(when (:loading? ?field)
[:div.loading-bar.absolute.bottom-0.left-0.right-0 {:class "h-[3px]"}])]
(when-let [hint (and (:focused ?field)
(:field/hint props))]
[:div.text-gray-500.text-sm hint])]))
(when (or can-edit? (u/some-str (:value props)))
[:div.field-wrapper
(merge data-props {:class (:wrapper classes)})
(form.ui/show-label ?field (:field/label props) (:label classes))
[:div.flex-v.relative
(with-messages-popover ?field
[auto-size (-> (form.ui/pass-props props)
(v/merge-props
data-props
{:disabled (not can-edit?)
:class ["w-full" (:input classes)]
:placeholder (:placeholder props)
:on-key-down (let [save #(when (io/ancestor-by ?field :field/persisted?)
(j/call % :preventDefault)
(entity.data/maybe-save-field ?field))]
(ui/keydown-handler (merge {:Meta-Enter save
:Escape cancel!
:Meta-. cancel!}
(when-not multi-line?
{:Enter save})
keybindings)))}))])
(when-let [postfix (or (:field/postfix props)
(:field/postfix (meta ?field))
(and (some-> (entity.data/persisted-value ?field)
(not= (:value props)))
[icons/pencil-outline "w-4 h-4 text-txt/40"]))]
[:div.pointer-events-none.absolute.inset-y-0.right-0.top-0.bottom-0.flex.items-center.p-2 postfix])

(when (:loading? ?field)
[:div.loading-bar.absolute.bottom-0.left-0.right-0 {:class "h-[3px]"}])]
(when-let [hint (and (:focused ?field)
(:field/hint props))]
[:div.text-gray-500.text-sm hint])])))

(defn wrap-prose [value]
(when-not (str/blank? value)
Expand Down Expand Up @@ -200,7 +207,7 @@
[icons/play-circle "icon-xl w-20 h-20 text-white drop-shadow-2xl transition-all hover:scale-110 "]])

(ui/defview video-field
{:key (fn [?field] #?(:cljs (goog/getUid ?field)))}
{:key (fn [?field _] #?(:cljs (goog/getUid ?field)))}
[?field {:as props :keys [field/can-edit?]}]
(let [!editing? (h/use-state (nil? @?field))]
[:div.field-wrapper
Expand Down Expand Up @@ -237,16 +244,80 @@
(when (:loading? ?field)
[:div.loading-bar.absolute.bottom-0.left-0.right-0 {:class "h-[3px]"}])])

(ui/defview color-field [?field props]
(ui/defview color-field
;; color field must be contained within a relative.overflow-hidden element, which it expands to fill.
[?field props]
[:input.default-ring.default-ring-hover.rounded
(-> (form.ui/?field-props ?field
(merge props {:field/event->value (j/get-in [:target :value])
:save-on-change? true}))
(v/merge-props props)
(v/merge-props {:style {:top -10
:left -10
:width 100
:height 100
:position "absolute"}} props)
(assoc :type "color")
(update :value #(or % "#ffffff"))
(form.ui/pass-props))])


(ui/defview new-badge [?badges close!]
;; TODO
;; - Enable the "add badge" in project ellipsis menu
;; - color picker should show colors already used in the board?
;; - right-click on a badge for a context menu: [edit, remove]
(let [!form (h/use-ref)]
(io/with-form [?badge {:badge/label ?label
:badge/color (?color :init "#dddddd")}
:required [?label ?color]]
(let [submit! (fn []
(io/add-many! ?badges @?badge)
(io/clear! ?badge)
(p/let [res (entity.data/maybe-save-field ?badges)]
(when-not (:error res)
(close!))))]
[:el.relative Pop/Root {:open true :on-open-change #(close!)}
[:el Pop/Anchor]
[:el Pop/Content {:as-child true}
[:div.p-2.z-10 {:class radix/float-small}
[:form.outline-none.flex.gap-2.items-stretch
{:ref !form
:on-submit (fn [e]
(.preventDefault e)
(submit!))}
[text-field ?label {:placeholder (t :tr/label)
:field/keybindings {:Enter submit!}
:field/multi-line? false
:field/can-edit? true
:field/label false}]
[:div.relative.w-10.h-10.overflow-hidden.rounded.outline.outline-black.outline-1 [color-field ?color {:field/can-edit? true}]]
[:button.flex.items-center {:type "submit"} [icons/checkmark "w-5 h-5 icon-gray"]]]
(form.ui/show-field-messages ?badges)]]]))))

(ui/defview badges-field [?badges {:field/keys [can-edit?]}]
[:div.flex.gap-1
(for [{:as ?badge
:syms [?label ?color]} ?badges
:let [bg (or (u/some-str @?color) "#ffffff")
color (color/contrasting-text-color bg)]]
[radix/context-menu {:trigger [:div.rounded.bg-badge.text-badge-txt.py-1.px-2.text-sm.inline-flex
{:key @?label
:style {:background-color bg :color color}} @?label]
:items [[radix/context-menu-item
{:on-select (fn []
(io/remove-many! ?badge)
(entity.data/maybe-save-field ?badges))}
(t :tr/remove)]]}])
(let [!open (h/use-state false)]
(when can-edit?
[:div.inline-flex.text-sm.gap-1.items-center
{:on-click #(reset! !open true)}
(when-not (seq ?badges) "Add Badge")
[icons/plus "w-4 h-4 icon-gray"]
(when @!open
[new-badge ?badges #(reset! !open false)])]))])


(ui/defview image-field [?field props]
(let [src (asset.ui/asset-src @?field :card)
loading? (:loading? ?field)
Expand Down
2 changes: 2 additions & 0 deletions src/sb/app/project/data.cljc
Expand Up @@ -83,6 +83,8 @@
[{:keys [project-id member/roles]}]
(merge {:member/roles roles}
(q/pull `[~@entity.data/fields
{:project/badges [:badge/label
:badge/color]}
:entity/field-entries
{:entity/parent
[~@entity.data/fields
Expand Down
51 changes: 31 additions & 20 deletions src/sb/app/project/ui.cljc
Expand Up @@ -90,25 +90,33 @@

#?(:cljs
(defn use-dev-panel [entity]
(let [!dev-edit? (h/use-state nil)
can-edit? (if-some [edit? @!dev-edit?]
edit?
(validate/editing-role? (:member/roles entity)))]
[can-edit? (when (ui/dev?)
[radix/select-menu {:value @!dev-edit?
:on-value-change (partial reset! !dev-edit?)
:field/classes {:trigger "flex items-center px-2 icon-gray text-sm self-start"
:content (str radix/menu-content-classes " text-sm")}
:field/can-edit? true
:field/options [{:value nil :text "Current User"}
{:value true :text "Editor"}
{:value false :text "Viewer"}]}])])))
(let [m {"Current" (:member/roles entity)
"Editor" #{:role/editor}
"Viewer" #{}
"Admin" #{:role/admin}}
!roles (h/use-state "Current")
roles (m @!roles)]
[(validate/editing-role? roles)
roles
(when (ui/dev?)
[radix/select-menu {:value @!roles
:on-value-change #(reset! !roles %)
:field/classes {:trigger "flex items-center px-2 icon-gray text-sm self-start"
:content (str radix/menu-content-classes " text-sm")}
:field/can-edit? true
:field/options [{:value "Current" :text "Current"}
{:value "Editor" :text "Editor"}
{:value "Viewer" :text "Viewer"}
{:value "Admin" :text "Admin"}]}])
])))

(def title-icon-classes "px-1 py-2 icon-light-gray")

(def modal-close [radix/dialog-close
[:div {:class title-icon-classes} [icons/close]]])



(ui/defview show
{:route "/p/:project-id"
:view/router :router/modal}
Expand All @@ -120,7 +128,7 @@
field-entries]
:keys [project/badges
member/roles]} (data/show params)
[can-edit? dev-panel] (use-dev-panel project)
[can-edit? roles dev-panel] (use-dev-panel project)
field-params {:member/roles roles
:field/can-edit? can-edit?}]
[:<>
Expand All @@ -137,6 +145,11 @@
dev-panel
[:div.flex.self-start.ml-auto.px-1.rounded-bl-lg.border-b.border-l.relative

(when (:role/admin roles)
[radix/dropdown-menu
{:trigger [:div.flex.items-center [icons/ellipsis-horizontal "rotate-90 icon-gray"]]
:children [[{:on-click #()} "Add Badge"]]}])

[radix/tooltip "Back to board"
[:a {:class title-icon-classes
:href (routing/entity-path (:entity/parent project) 'ui/show)}
Expand All @@ -149,12 +162,10 @@
modal-close]]

[:div.px-body.flex-v.gap-6
(entity.ui/use-persisted-attr project :entity/description (merge field-params {:field/label false}))
(when badges
[:section
(into [:ul]
(map (fn [bdg] [:li.rounded.bg-badge.text-badge-txt.py-1.px-2.text-sm.inline-flex (:badge/label bdg)]))
badges)])
(entity.ui/use-persisted-attr project :entity/description (merge field-params
{:field/label false
:placeholder "Description"}))
(entity.ui/use-persisted-attr project :project/badges field-params)
(entity.ui/use-persisted-attr project
:entity/field-entries
{:entity/fields (->> project :entity/parent :board/project-fields)
Expand Down
14 changes: 9 additions & 5 deletions src/sb/app/views/radix.cljc
Expand Up @@ -223,19 +223,23 @@
(def context-menu-item (v/from-element :el.text-sm.flex.items-center.outline-none.user-select-none.rounded.px-2.py-1.cursor-default ContextMenu/Item
{:class "data-[highlighted]:bg-gray-100"}))

(def float-small "bg-white rounded overflow-hidden shadow-md")

(v/defview context-menu [{:keys [trigger
items]}]
[:el ContextMenu/Root
[:el ContextMenu/Trigger (v/x trigger)]
[:el.bg-white.rounded.overflow-hidden.p-1.shadow-md.min-w-32 ContextMenu/Content {:style {:z-index 20}}
[:el ContextMenu/Content {:style {:z-index 20}
:class [float-small
"p-1 min-w-32"]}
(into [:<>] items)]])

(v/defview persistent-popover [{:keys [content classes props]} anchor]
(v/x [:el.foo popover/Root (v/props (merge (:root props)
{:open (boolean content)
:style {:z-index 1}}))
(v/x [:el popover/Root (v/props (merge (:root props)
{:open (boolean content)
:style {:z-index 1}}))
[:el.hidden popover/Trigger]
[:el popover/Anchor anchor]
[:el popover/Content {:class (:content classes)}
[:el.outline-none popover/Content {:class (:content classes)}
[:el popover/Arrow {:class (:arrow classes)}]
content]]))
3 changes: 3 additions & 0 deletions src/sb/i18n.cljc
Expand Up @@ -342,6 +342,9 @@ See https://iso639-3.sil.org/code_tables/639/data/all for list of codes"
:tr/label {:en "Label"
:fr "Étiquette"
:es "Etiqueta"}
:tr/color {:en "Color"
:fr "Couleur"
:es "Color"}
:tr/hint {:en "Hint"
:fr "Indice"
:es "Pista"}
Expand Down

0 comments on commit 5c97b2d

Please sign in to comment.