From 14906072e9f3d45a518d6ca634c8cd45a137f2c0 Mon Sep 17 00:00:00 2001 From: Kimo Knowles Date: Mon, 19 Aug 2024 00:27:21 +0200 Subject: [PATCH] [nested-grid] Polish :parts, support :align- keys in column specs --- CHANGELOG.md | 4 + src/re_com/nested_grid.cljs | 226 ++++++++++++++++++---------------- src/re_com/theme.cljs | 19 ++- src/re_com/theme/default.cljs | 85 +++++++------ src/re_demo/nested_grid.cljs | 80 +++++++++++- 5 files changed, 263 insertions(+), 151 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b481c4ea..13594a0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,11 @@ > Committed but unreleased changes are put here, at the top. Older releases are detailed chronologically below. #### Added + +## 2.21.19 (2024-08-19) + - `single-dropdown` - `:drop-direction` prop. Overrides any behavior which would position the body dynamically. `:up` or `:above` positions the body above the anchor, `:down`, `:dn` or `:below` positions it below. +- `nested-grid` - optional `:align-column` & `:align-column-header` keys for column-specs. ## 2.21.18 (2024-08-09) diff --git a/src/re_com/nested_grid.cljs b/src/re_com/nested_grid.cljs index 24500671..6d70b7e1 100644 --- a/src/re_com/nested_grid.cljs +++ b/src/re_com/nested_grid.cljs @@ -9,7 +9,15 @@ [re-com.box :as box] [re-com.buttons :as buttons])) -(def nested-grid-parts-desc {}) +(def nested-grid-parts-desc + (when include-args-desc? + [{:name :wrapper :level 1 :impl "[:div]"} + {:name :cell-grid-container :level 2 :impl "[:div]"} + {:name :cell-wrapper :level 3 :impl "[:div]"} + {:name :column-header-wrapper :level 2 :impl "[:div]"} + {:name :row-header-wrapper :level 2 :impl "[:div]"} + {:name :header-spacer :level 2 :impl "[:div]"} + {:name :zebra-stripe :level 2 :impl "[:div]"}])) (def nested-grid-parts (when include-args-desc? @@ -404,16 +412,13 @@ (defn cell-part [{:keys [column-path row-path]}] nil) -(defn cell-wrapper-part [{:keys [column-path row-path cell theme cell-value edge] +(defn cell-wrapper-part [{:keys [column-path row-path cell theme cell-value value] :as props}] - (let [props (cond-> props - :do (dissoc :cell) - cell-value (assoc :value (cell-value props)))] - [:div - (-> {:style {:grid-column (path->grid-line-name column-path) - :grid-row (path->grid-line-name row-path)}} - (theme/apply {:state {:edge edge} :part ::cell-wrapper} theme)) - [u/part cell props :default cell-part]])) + [:div + (-> {:style {:grid-column (path->grid-line-name column-path) + :grid-row (path->grid-line-name row-path)}} + (theme/apply {:part ::cell-wrapper} theme)) + [u/part cell (dissoc props :cell) :default cell-part]]) (defn header-label [{:keys [path]}] (let [header (last path)] @@ -421,30 +426,13 @@ (:id header) header)))) -(defn column-header-part [{:keys [column-path]}] - (header-label {:path column-path})) - -(theme/apply {} {:part ::column-header-wrapper} []) +(defn column-header-part [{:keys [path]}] + (header-label {:path path})) (defn column-header-wrapper-part - [{:keys [column-header column-path column-paths theme show? edge resize-columns?] - :as props}] - [:div - {:style {:grid-column-start (path->grid-line-name column-path) - :grid-column-end (str "span " (cond-> column-path - :do (header-cross-span column-paths) - (not show?) dec)) - :grid-row-start (count column-path) - :grid-row-end (str "span " (cond-> column-path - :do (header-main-span column-paths) - (not show?) dec)) - :position "relative"}} - [:div (theme/apply {} {:state {:edge edge} :part ::column-header-wrapper} - theme) - [u/part column-header props :default column-header-part]] - (when (and resize-columns? show?) - [resize-button (merge props {:dimension :column - :path column-path})])]) + [{:keys [theme children]}] + (into [:div (theme/apply {} {:part ::column-header-wrapper} theme)] + children)) (defn row-header-part [{:keys [row-path]}] (header-label {:path row-path})) @@ -452,19 +440,19 @@ (defn row-header-wrapper-part [{:keys [row-path row-paths row-header theme show? edge resize-rows?] :as props}] - [:div - {:style {:grid-row-start (path->grid-line-name row-path) - :grid-row-end (str "span " (cond-> row-path - :do (header-cross-span row-paths) - (not show?) dec)) - :grid-column-start (count row-path) - :grid-column-end (str "span " (cond-> row-path - :do (header-main-span row-paths) - (not show?) dec)) - :position "relative"}} - [:div (theme/apply {} - {:state {:edge edge} :part ::row-header-wrapper} - theme) + [:div (theme/apply {:style {:grid-row-start (path->grid-line-name row-path) + :grid-row-end (str "span " (cond-> row-path + :do (header-cross-span row-paths) + (not show?) dec)) + :grid-column-start (count row-path) + :grid-column-end (str "span " (cond-> row-path + :do (header-main-span row-paths) + (not show?) dec)) + :position "relative"}} + {:state {:edge edge} :part ::row-header-wrapper} + theme) + + [:div [u/part row-header props :default row-header-part]] (when (and resize-rows? show?) [resize-button (merge props {:dimension :row @@ -624,31 +612,37 @@ (header-prop path :height :row row-height)) (+ y-distance) (max 0)))) - resize-handler (r/atom #()) - theme {:user [theme (theme/parts parts)]}] - (fn [& {:keys [column-tree row-tree - cell cell-value column-header row-header header-spacer - cell-wrapper column-header-wrapper row-header-wrapper header-spacer-wrapper - show-branch-paths? - max-height max-width - column-width column-header-height row-header-width row-height - show-export-button? on-export - on-export-cell on-export-column-header on-export-row-header - show-zebra-stripes? - show-selection-box? resize-columns? resize-rows?] - :or {column-header-height 25 - column-width 55 - row-header-width 80 - row-height 25 - show-export-button? true - show-branch-paths? false - show-selection-box? false - show-zebra-stripes? true - on-export-column-header header-label - on-export-row-header header-label - resize-columns? true - resize-rows? false}}] - (let [themed (fn [part props] (theme/apply props {:part part} theme)) + resize-handler (r/atom #())] + (fn [& {:as passed-in-props}] + (let [{:as props + :keys [column-tree row-tree + cell cell-value column-header row-header header-spacer + cell-wrapper column-header-wrapper row-header-wrapper header-spacer-wrapper + show-branch-paths? + max-height max-width + column-width column-header-height row-header-width row-height + show-export-button? on-export + on-export-cell on-export-column-header on-export-row-header + show-zebra-stripes? + show-selection-box? resize-columns? resize-rows?] + :or {column-header-height 25 + column-width 55 + row-header-width 80 + row-height 25 + show-export-button? true + show-branch-paths? false + show-selection-box? false + show-zebra-stripes? true + on-export-column-header header-label + on-export-row-header header-label + resize-columns? true + resize-rows? false}} + (theme/top-level-part passed-in-props ::nested-grid) + theme (theme/defaults + props + {:user [(theme/<-props props {:part ::wrapper + :include [:style :class]})]}) + themed (fn [part props] (theme/apply props {:part part} theme)) column-paths (spec->headers* column-tree) column-leaf-paths (leaf-paths column-paths) leaf-column? (set column-leaf-paths) @@ -825,28 +819,47 @@ :grid-template-rows (grid-template cell-grid-rows)}})] column-header-cells (doall (for [path column-paths - :let [props {:column-path path - :path path - :column-header column-header - :column-paths column-paths - :show? (show? path :column) - :on-resize resize-column! - :mouse-down-x mouse-down-x - :last-mouse-x last-mouse-x - :mouse-x mouse-x - :resize-handler resize-handler - :resize-columns? resize-columns? - :drag drag - :selection? selection? - :edge (cond-> #{} - (start-branch? path column-paths) (conj :left) - (end-branch? path column-paths) (conj :right) - (root-level? path column-paths) (conj :top) - (leaf-level? path column-paths) (conj :bottom) - (section-left? path) (conj :column-section-left) - (section-right? path) (conj :column-section-right))}]] + :let [edge (cond-> #{} + (start-branch? path column-paths) (conj :left) + (end-branch? path column-paths) (conj :right) + (root-level? path column-paths) (conj :top) + (leaf-level? path column-paths) (conj :bottom) + (section-left? path) (conj :column-section-left) + (section-right? path) (conj :column-section-right)) + show? (show? path :column) + state {:edge edge + :column-path path + :path path + :header-spec (last path) + :show? show?} + props (merge {:theme (update theme :user-variables + conj (theme/with-state state)) + :selection? selection? + :edge edge} + state)]] ^{:key [::column (or path (gensym))]} - [u/part column-header-wrapper props :default column-header-wrapper-part])) + [:div {:style {:grid-column-start (path->grid-line-name path) + :grid-column-end (str "span " (cond-> path + :do (header-cross-span column-paths) + (not show?) dec)) + :grid-row-start (count path) + :grid-row-end (str "span " (cond-> path + :do (header-main-span column-paths) + (not show?) dec)) + :position "relative"}} + [u/part column-header-wrapper + (merge props {:children [[u/part column-header props :default column-header-part]]}) + :default column-header-wrapper-part] + (when (and resize-columns? show?) + [resize-button (merge props {:mouse-down-x mouse-down-x + :last-mouse-x last-mouse-x + :mouse-x mouse-x + :resize-handler resize-handler + :resize-columns? resize-columns? + :on-resize resize-column! + :drag drag + :dimension :column + :path path})])])) row-header-cells (doall (for [path row-paths :let [props {:row-path path @@ -886,19 +899,25 @@ (for [row-path showing-row-paths] (doall (for [column-path showing-column-paths - :let [props (merge {:column-path column-path - :row-path row-path - :cell cell - :theme theme - :edge (cond-> #{} - (= column-path (first showing-column-paths)) (conj :left) - (= column-path (last showing-column-paths)) (conj :right) - (= row-path (first showing-row-paths)) (conj :top) - (= row-path (last showing-row-paths)) (conj :bottom) - (cell-section-left? column-path) (conj :column-section-left) - (cell-section-right? column-path) (conj :column-section-right))} + :let [edge (cond-> #{} + (= column-path (first showing-column-paths)) (conj :left) + (= column-path (last showing-column-paths)) (conj :right) + (= row-path (first showing-row-paths)) (conj :top) + (= row-path (last showing-row-paths)) (conj :bottom) + (cell-section-left? column-path) (conj :column-section-left) + (cell-section-right? column-path) (conj :column-section-right)) + value (when cell-value (cell-value {:column-path column-path + :row-path row-path})) + state {:edge edge + :column-path column-path + :row-path row-path + :value value} + props (merge {:cell cell + :theme (update theme :user-variables + conj (theme/with-state state))} (when cell-value - {:cell-value cell-value}))]] + {:cell-value cell-value}) + state)]] ^{:key [::cell-wrapper (or [column-path row-path] (gensym))]} [u/part cell-wrapper props :default cell-wrapper-part])))) zebra-stripes (for [i (filter even? (range 1 (inc (count row-paths))))] @@ -933,6 +952,7 @@ native-scrollbar-width showing-row-heights)] [:div + (themed ::wrapper {}) [:div {:on-mouse-enter #(reset! hover? true) :on-mouse-leave #(reset! hover? false) :style diff --git a/src/re_com/theme.cljs b/src/re_com/theme.cljs index 8ff1f8dc..e9c74f2a 100644 --- a/src/re_com/theme.cljs +++ b/src/re_com/theme.cljs @@ -40,9 +40,9 @@ (defn apply ([props ctx themes] (->> - (if-not (map? themes) - (update @registry :user conj themes) - (merge @registry themes)) + (cond + (map? themes) (re-com.theme/merge @registry themes) + :else (update @registry :user conj themes)) named->vec flatten (remove nil?) @@ -50,6 +50,14 @@ first (#(dissoc % :re-com/system))))) +(defn with-ctx [new-ctx] + (fn with-ctx [props ctx] + [props (clojure.core/merge ctx new-ctx)])) + +(defn with-state [new-state] + (fn with-state [props ctx] + [props (update ctx :state clojure.core/merge new-state)])) + (defn props [ctx themes] (apply {} ctx themes)) @@ -77,6 +85,11 @@ (update :attr clojure.core/merge (select-keys outer-props outer-attr-keys))))))) +(defn top-level-part [{:keys [theme] :as props} part] + (cond-> props + theme (re-com.theme/apply {:part part} theme) + theme (merge props))) + (defn add-parts-path [path] (fn parts-pather [props {:keys [part] :as ctx}] [(update props :theme conj (add-parts-path (conj path part))) diff --git a/src/re_com/theme/default.cljs b/src/re_com/theme/default.cljs index cefabaf8..fcd52e52 100644 --- a/src/re_com/theme/default.cljs +++ b/src/re_com/theme/default.cljs @@ -211,42 +211,49 @@ :background-color light-background}} ::nested-grid/cell-wrapper - {:style {:font-size "12px" - :background-color "white" - :color "#777" - :padding-top sm-3 - :padding-right sm-3 - :text-align "right" - :border-right (condp #(get %2 %1) (:edge state) - :column-section-right - (str "thin" " solid " border-dark) - :right - (str "thin" " solid " border-dark) - (str "thin" " solid " border)) - :border-bottom (if ((:edge state) :bottom) - (str "thin" " solid " border-dark) - (str "thin" " solid " border))}} + (let [{:keys [value]} state + {:keys [align-column]} (into {} (filter map?) (:column-path state))] + {:style {:font-size "12px" + :background-color "white" + :color "#777" + :padding-top sm-3 + :padding-right sm-3 + :padding-left sm-3 + :text-align (or align-column + (cond (string? value) :left + (number? value) :right) + :right) + :border-right (condp #(get %2 %1) (:edge state) + :column-section-right + (str "thin" " solid " border-dark) + :right + (str "thin" " solid " border-dark) + (str "thin" " solid " border)) + :border-bottom (if ((:edge state) :bottom) + (str "thin" " solid " border-dark) + (str "thin" " solid " border))}}) ::nested-grid/column-header-wrapper - {:style {:padding-top sm-3 - :padding-right sm-4 - :padding-left sm-4 - :border-bottom (str "thin" " solid" border) - :background-color light-background - :color "#666" - :text-align "center" - :font-size "13px" - :border-top (when (get (:edge state) :top) (str "thin solid " border-dark)) - :border-right (condp #(get %2 %1) (:edge state) - :column-section-right - (str "thin" " solid " border-dark) - :right - (str "thin" " solid " border-dark) - (str "thin" " solid " border)) - #_#_:font-weight "bold" - :overflow "hidden" - :white-space "nowrap" - :text-overflow "ellipsis"}} + (let [{:keys [align-column align-column-header]} (:header-spec state)] + {:style {:padding-top sm-3 + :padding-right sm-4 + :padding-left sm-4 + :border-bottom (str "thin" " solid " border) + :background-color light-background + :color "#666" + :text-align (or align-column-header align-column :center) + :font-size "13px" + :border-top (when (get (:edge state) :top) (str "thin solid " border-dark)) + :border-right (condp #(get %2 %1) (:edge state) + :column-section-right + (str "thin" " solid " border-dark) + :right + (str "thin" " solid " border-dark) + (str "thin" " solid " border)) + #_#_:font-weight "bold" + :overflow "hidden" + :white-space "nowrap" + :text-overflow "ellipsis"}}) ::nested-grid/row-header-wrapper {:style {:padding-top sm-3 @@ -292,7 +299,7 @@ ::error-modal/modal {:wrap-nicely? false - :style {:z-index 50}} + :style {:z-index 50}} ::error-modal/inner-wrapper {:style {:background-color (:white $) @@ -316,10 +323,10 @@ :height (px 50)}) ::error-modal/title - {:style {:font-size 25 - :color (:white $) - :padding 0 - :margin "0px"}} + {:style {:font-size 25 + :color (:white $) + :padding 0 + :margin "0px"}} ::error-modal/triangle (let [{:keys [severity]} state] diff --git a/src/re_demo/nested_grid.cljs b/src/re_demo/nested_grid.cljs index 66cdf7f9..05045ee2 100644 --- a/src/re_demo/nested_grid.cljs +++ b/src/re_demo/nested_grid.cljs @@ -3,8 +3,9 @@ [clojure.string :as str] [re-com.core :as rc :refer [at h-box v-box box gap line label p p-span hyperlink-href]] [re-com.util :as u] + [re-com.theme :as theme] [reagent.core :as r] - [re-com.nested-grid :as nested-grid :refer [nested-grid nested-grid-args-desc nested-grid-parts-desc]] + [re-com.nested-grid :as grid :refer [nested-grid nested-grid-args-desc nested-grid-parts-desc]] [re-demo.utils :refer [source-reference panel-title title2 title3 args-table parts-table github-hyperlink status-text new-in-version]])) (def arg-style {:style {:display "inline-block" @@ -293,6 +294,72 @@ (defn rand-color [] (str "rgb(" (* 255 (rand)) "," (* 255 (rand)) "," (* 255 (rand)) ")")) +(defn rf8-grid-theme [props {:keys [part state] $ :variables}] + (let [$ (merge $ {:border-light "#ccc" + :dark "#768895"})] + (->> + nil + (case part + :re-com.nested-grid/nested-grid + {:row-height "20px" + :column-header-height "20px" + :row-header-width 0 + :show-zebra-stripes? false} + + :re-com.nested-grid/cell-wrapper + {:style {:border-left "none" + :border-right "none"}} + + :re-com.nested-grid/column-header-wrapper + {:style {:border-right "none" + :padding-left "10px" + :padding-top 1 + :background-color ($ :dark) + :color ($ :white)}}) + (theme/merge-props props)))) + +(def rf8-grid-parts + {:re-com.nested-grid/nested-grid + {:row-height "20px" + :column-header-height "20px" + :row-header-width 0 + :show-zebra-stripes? false} + + :re-com.nested-grid/cell-wrapper + {:style {:border-left "none" + :border-right "none"}} + + :re-com.nested-grid/column-header-wrapper + {:style {:border-right "none" + :padding-left "10px" + :padding-top 1 + :background-color "#768895" + :color :white}}}) + +(defn style-demo [] + [v-box + :gap "12px" + :children + [[nested-grid + {#_#_:theme rf8-grid-theme + :parts rf8-grid-parts + :row-header-width 0 + :column-tree (->> [{:id "Align Column" :width 120 :align-column :left} + {:id "Default Alignment" :width 120} + {:id "Align Column Header" :width 150 :align-column-header :right}] + (map-indexed (fn [i item] (assoc item :index i))) + vec) + :cell-value (fn [{[{:keys [cell-values]}] :row-path + [{:keys [index]}] :column-path}] + (get cell-values index)) + :row-tree (->> [{:label "Text" :cell-values ["Lorem" "ipsum" "dolor"]} + {:label "Text" :cell-values ["sit" "amet" " consectetur"]} + {:label "Text" :cell-values ["adipiscing" "elit" " sed"]} + {:label "Number" :cell-values (vec (range 1000 1003))}] + (map-indexed (fn [i item] (assoc item :index i))) + vec) + :cell (fn [{:keys [value]}] value)}]]]) + (defn internals-demo [] [v-box :gap "12px" @@ -311,9 +378,9 @@ :cell (fn [{:keys [column-path] [{:keys [tree]}] :row-path}] (case (:id (last column-path)) "Tree" (str tree) - "Leaf Paths" (str (vec (nested-grid/leaf-paths - (nested-grid/header-spec->header-paths tree)))) - "All Paths" (str (nested-grid/header-spec->header-paths tree))))] + "Leaf Paths" (str (vec (grid/leaf-paths + (grid/header-spec->header-paths tree)))) + "All Paths" (str (grid/header-spec->header-paths tree))))] [p "This table demonstrates how " [:code "nested-grid"] " derives a vector of " [:code ":column-path"] "s from a " [:code ":column-tree"] "."] [line] [h-box @@ -614,7 +681,8 @@ (let [tabs [{:id :basic :label "Basic Demo" :view basic-demo} {:id :internals :label "Internals" :view internals-demo} {:id :multimodal :label "Multimodal" :view multimodal-demo} - {:id :app :label "Applications" :view app-demo}] + {:id :app :label "Applications" :view app-demo} + {:id :style :label "Style" :view style-demo}] !tab-id (r/atom (:id (first tabs))) !tab (r/reaction (u/item-for-id @!tab-id tabs))] (fn [] @@ -741,4 +809,4 @@ :on-change #(reset! !tab-id %)] [(:view @!tab)]]] [demos]]] - #_[parts-table "nested-grid" nested-grid-grid-parts-desc]]]))) + [parts-table "nested-grid" nested-grid-parts-desc]]])))