From 1bcb91aeb1b3da0b859719881011c9aefc2d77b2 Mon Sep 17 00:00:00 2001 From: Kimo Knowles Date: Fri, 29 Mar 2024 02:30:17 +0100 Subject: [PATCH] [nested-grid] Fix special keys, update docs --- src/re_com/nested_grid.cljs | 185 +++++++++++----------- src/re_com/theme/default.cljs | 2 +- src/re_demo/nested_grid.cljs | 282 +++++++++++++++++++++++----------- 3 files changed, 288 insertions(+), 181 deletions(-) diff --git a/src/re_com/nested_grid.cljs b/src/re_com/nested_grid.cljs index 57d8df08..74a1a9b2 100644 --- a/src/re_com/nested_grid.cljs +++ b/src/re_com/nested_grid.cljs @@ -51,14 +51,14 @@ [:y [:b :c]]]) [[:x] [:x :b] [:x :c] [:y] [:y :b] [:y :c]])) -(defn header-cross-span [group-path all-paths] +(defn header-cross-span [path all-paths] (->> all-paths - (filter (partial descendant? group-path)) + (filter (partial descendant? path)) count inc)) -(defn header-main-span [group-path all-paths] - (->> all-paths (map count) (apply max) (+ (- (count group-path))) inc)) +(defn header-main-span [path all-paths] + (->> all-paths (map count) (apply max) (+ (- (count path))) inc)) (defn resize-button [& {:as args}] (let [dragging? (r/atom false) @@ -80,12 +80,13 @@ :opacity (if (or @hovering? @dragging?) 1 0) :top 0 :right 0 + :cursor "col-resize" :height "100%" :width "25px" :background-color "rgba(0,0,0,0.2)"}}] (when @dragging? [:div {:on-mouse-up #(do (reset! dragging? false) - (reset! hovering? true)) + (reset! hovering? false)) :on-mouse-move #(do (.preventDefault %) (let [x (.-clientX %)] (reset! drag-x x) @@ -146,17 +147,17 @@ (theme/apply {} {:part ::column-header-wrapper} []) -(defn column-header-wrapper-part [{:keys [column-header column-path column-paths on-resize show-branch-cells? leaf? theme] :as props}] - (let [hide? (and (not leaf?) (not show-branch-cells?))] - [: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) - hide? dec)) - :grid-row-start (count column-path)}} - (theme/apply {:state {} :part ::column-header-wrapper} theme)) - [u/part column-header props column-header-part] - [resize-button {:on-resize on-resize :path column-path}]])) +(defn column-header-wrapper-part [{:keys [column-header column-path column-paths on-resize theme show?] :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 " (header-main-span column-path column-paths))}} + (theme/apply {:state {} :part ::column-header-wrapper} theme)) + [u/part column-header props column-header-part] + [resize-button {:on-resize on-resize :path column-path}]]) ;; Usage of :component-did-update @@ -166,18 +167,17 @@ (:id column) column)))) -(defn row-header-wrapper-part [{:keys [row-path row-paths row-header show-branch-cells? leaf? theme] :as props}] - (let [hide? (and (not leaf?) (not show-branch-cells?))] - [: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))) - :grid-column-start (count row-path) - :grid-column-end (str "span " (cond-> row-path - :do (header-main-span row-paths) - hide? dec))}} - (theme/apply {:state {} :part ::row-header-wrapper} theme)) - [u/part row-header props row-header-part]])) +(defn row-header-wrapper-part [{:keys [row-path row-paths row-header theme show?] :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))) + :grid-column-start (count row-path) + :grid-column-end (str "span " (cond-> row-path + :do (header-main-span row-paths) + (not show?) dec))}} + (theme/apply {:state {} :part ::row-header-wrapper} theme)) + [u/part row-header props row-header-part]]) (def level count) @@ -287,29 +287,30 @@ (get (last path) k) default)) header-prop (fn [path k dimension & [default]] - (or (some-> (case dimension :row @row-state :column @column-state) - (get path) - (get k)) - (get (meta (last path)) k) - (get (last path) k) - default)) - row-header-prop (fn [path k & [default]] - (or (some-> @row-state (get path) (get k)) - (get (meta (last path)) k) - (get (last path) k) - default)) + (let [state (-> (case dimension + :row @row-state + :column @column-state) + (get path))] + (first + (remove nil? [(get state k) + (get (meta (last path)) k) + (get (last path) k) + default])))) max-props (fn [k dimension default paths] (->> paths (group-by level) (sort-by key) (map val) - (map (fn [paths] (apply max (map #(header-prop % k :row default) paths)))))) + (map (fn [path-group] + (apply max + (map #(header-prop % k dimension default) + path-group)))))) on-resize-cell (fn [{:keys [distance path]}] (swap! column-state update-in [path :width] #(+ distance (or % (column-header-prop path :width column-width)))))] (fn [& {:keys [columns rows cell cell-wrapper column-header-wrapper column-header row-header row-header-wrapper - show-branch-cells? + show-branch-paths? max-height column-width column-header-height row-header-width row-height show-export-button? on-export on-export-success on-export-failure on-export-cell on-export-column-header on-export-row-header] @@ -318,22 +319,34 @@ row-header-width 100 row-height 30 show-export-button? true - show-branch-cells? false + show-branch-paths? false on-export-column-header pr-str}}] (let [themed (fn [part props] (theme/apply props {:part part} {})) column-paths (spec->headers* columns) column-leaf-paths (reduce (fn [paths p] (remove (partial ancestor? p) paths)) column-paths column-paths) leaf-column? (set column-leaf-paths) - column-widths (map #(column-header-prop % :width column-width) column-paths) - column-leaf-widths (map #(column-header-prop % :width column-width) column-leaf-paths) - max-column-heights (max-props :row :height column-header-height column-paths) - column-depth (count max-column-heights) row-paths (spec->headers* rows) - row-leaf-paths (reduce (fn [paths p] (remove #(descendant? % p) paths)) row-paths row-paths) - leaf-row? (set row-leaf-paths) - row-heights (map #(column-header-prop % :height row-height) row-paths) - row-leaf-heights (map #(column-header-prop % :height row-height) row-leaf-paths) - max-row-widths (max-props :row :width row-header-width row-paths) + leaf-row? (set (reduce (fn [paths p] (remove #(descendant? % p) paths)) row-paths row-paths)) + leaf? (fn [path dimension] + (case dimension + :column (leaf-column? path) + :row (leaf-row? path))) + show? (fn [path dimension] + (let [show-prop (header-prop path :show? dimension) + result (and (not (false? show-prop)) + (or (true? show-prop) + show-branch-paths? + (leaf? path dimension)))] + result)) + showing-column-paths (filter #(show? % :column) column-paths) + showing-row-paths (filter #(show? % :row) row-paths) + showing-column-widths (map #(column-header-prop % :width column-width) + showing-column-paths) + showing-row-heights (map #(column-header-prop % :height row-height) + showing-row-paths) + max-column-heights (max-props :height :column column-header-height column-paths) + max-row-widths (max-props :width :row row-header-width row-paths) + column-depth (count max-column-heights) row-depth (count max-row-widths) default-on-export-column-header (comp pr-str last) @@ -344,10 +357,10 @@ get-header-rows (fn get-header-rows [] (->> column-paths (mapcat (fn [path] - (if (leaf-column? path) [path] - (repeat - (dec (header-cross-span path column-paths)) - path)))) + (if (leaf-column? path) + [path] + (repeat (dec (header-cross-span path column-paths)) + path)))) (group-by count) (into (sorted-map)) vals @@ -362,7 +375,7 @@ (conj coll (repeat (- row-depth (count paths)) nil))) add-cell-values (fn [[paths padding]] - (->> column-leaf-paths + (->> showing-column-paths (map #((or on-export-cell default-on-export-cell) {:column-path % @@ -375,7 +388,7 @@ paths) padding cells))] - (->> row-leaf-paths + (->> showing-row-paths (map ancestors) (map vector) (map add-padding) @@ -386,27 +399,27 @@ (map u/tsv-line) str/join u/clipboard-write!)) - cell-grid-columns (mapcat (fn [path width] - (if (or show-branch-cells? (leaf-column? path)) - [path width] - [path])) - column-paths - column-widths) - cell-grid-rows (mapcat (fn [path height] - (if (or show-branch-cells? (leaf-row? path)) - [path height] - [path])) - row-paths - row-heights) + cell-grid-columns (->> column-paths + (mapcat (fn [path] + (let [width (header-prop path :width :column column-width)] + (if (show? path :column) + [path width] + [path]))))) + cell-grid-rows (->> row-paths + (mapcat (fn [path] + (let [height (header-prop path :height :row row-height)] + (if (show? path :row) + [path height] + [path]))))) control-panel [controls {:show-export-button? show-export-button? :hover? hover? :on-export (fn [_] (let [header-rows #p (->> (get-header-rows) - (map vec) - (map #(subvec % 0 2))) - main-rows (get-main-rows)] - ((or on-export default-on-export) header-rows main-rows) - (when on-export-success (on-export-success header-rows main-rows))))}] + (map vec) + (map #(subvec % 0 2))) + main-rows (get-main-rows)] + ((or on-export default-on-export) header-rows main-rows) + (when on-export-success (on-export-success header-rows main-rows))))}] grid-container [:div {:on-scroll #(do (reset! scroll-top (.-scrollTop (.-target %))) (reset! scroll-left (.-scrollLeft (.-target %)))) @@ -435,8 +448,7 @@ :column-paths column-paths :on-resize on-resize-cell :column-header column-header - :show-branch-cells? show-branch-cells? - :leaf? (leaf-column? path)}]] + :show? (show? path :column)}]] ^{:key [::column (or path (gensym))]} [u/part column-header-wrapper props column-header-wrapper-part])) row-header-cells (doall @@ -444,8 +456,7 @@ :let [props {:row-path path :row-header row-header :row-paths row-paths - :show-branch-cells? show-branch-cells? - :leaf? (leaf-row? path)}]] + :show? (show? path :row)}]] ^{:key [::row (or path (gensym))]} [u/part row-header-wrapper props row-header-wrapper-part])) header-spacer-cells (doall @@ -457,14 +468,11 @@ {:grid-column (inc x) :grid-row (inc y)}})])) cells (doall - (for [column-path column-paths - row-path row-paths - :let [leaf? (and (leaf-column? column-path) - (leaf-row? row-path)) - props {:column-path column-path + (for [column-path showing-column-paths + row-path showing-row-paths + :let [props {:column-path column-path :row-path row-path - :cell cell}] - :when leaf?] + :cell cell}]] ^{:key [::cell (or [column-path row-path] (gensym))]} [u/part cell-wrapper props cell-wrapper-part])) zebra-stripes (for [i (filter even? (range (count row-paths)))] @@ -495,10 +503,11 @@ :style {:display "grid" :grid-template-columns (grid-template [(px (apply + max-row-widths)) - (px (+ native-scrollbar-width (apply + column-leaf-widths)))]) - :grid-template-rows (grid-template ["20px" + (px (+ native-scrollbar-width + (apply + showing-column-widths)))]) + :grid-template-rows (grid-template ["20px" showing-column-widths (px (apply + max-column-heights)) - (px (apply + row-leaf-heights))])}} + (px (apply + showing-row-heights))])}} control-panel [:div {:style {:display "grid" :grid-template-columns (grid-template max-row-widths) diff --git a/src/re_com/theme/default.cljs b/src/re_com/theme/default.cljs index 77c24e67..7f8b76f9 100644 --- a/src/re_com/theme/default.cljs +++ b/src/re_com/theme/default.cljs @@ -104,7 +104,7 @@ ::nested-grid/column-header-wrapper {:style {:position "relative" - :pointer-events "none" + #_#_:pointer-events "none" :user-select "none"}}) (merge-props props))) diff --git a/src/re_demo/nested_grid.cljs b/src/re_demo/nested_grid.cljs index 88ffb7dd..843d7b37 100644 --- a/src/re_demo/nested_grid.cljs +++ b/src/re_demo/nested_grid.cljs @@ -23,13 +23,6 @@ :red "🔴" :white "⚪"}) -(nested-grid/header-spec->header-paths - [:medium [:red :yellow :blue] - :light [:red :yellow :blue] - :dark [:red :yellow :blue]]) - -(def fruit {:dimension "fruit"}) - (defn fruit-demo [] [nested-grid {:columns [{:id :fruit :hide-cells? true} [{:id :red} @@ -46,81 +39,92 @@ (map #(header->icon % (header->icon (get % :id)))) (apply str)))}]) -(def lookup-table [["🚓" "🛵" "🚲" "🛻" "🚚"] - ["🍐" "🍎" "🍌" "🥝" "🍇"] - ["🐕" "🐎" "🧸" "🐈" "🐟"]]) - -(def add {:operator + :label "Addition"}) -(def multiply {:operator * :label "Multiplication"}) -(def lookup {:operator (fn [l r] (get-in lookup-table [l r])) - :label "Lookup"}) -(def one {:left 1 :label "1"}) -(def two {:left 2 :label "2"}) -(def three {:right 3 :label "3"}) -(def four {:right 4 :label "4"}) +(defn concepts-column [] + [v-box + :children + [[title2 "Concepts"] + [p "To use " [:code "nested-grid"] + ", you’ll need to understand some key concepts - " + [:code ":column-tree"] "," + [:code ":column-spec"] "," + [:code ":column-path"] ", etc..."] + [:ul {:style {:width 400}} + [:li [p "A " [:code ":column-spec"] " describes a single column."] + [:ul + [:li "For instance, the " [:strong "Basic Demo"] " uses " + [:code ":a"] " as a " [:code ":column-spec"] "."] + [:li "Besides keywords, you can use " [:i "almost any"] " type of value."] + [:li "You " [:i "can't"] " use vectors or lists (these are reserved for the " + [:code ":column-tree"] ")."] + [:li "At Day8, we tend to use maps. For instance, " + [:pre "{:id :a + :column-label \"A\" + :special-business-logic ::xyz}"]]]] + [:br] + [:li "A " [:code ":column-tree"] "describes a nested arrangement of columns." + [:ul + [:li "There are parent columns, and they can have child columns, " + "which can have their own child columns, etc..."] + [:li "In practice, a " [:code ":column-tree"] " is a vector (or list) of " + [:code ":column-spec"] "values."] + [:li "The most basic case is a flat tree: " [:code "[:a :b :c]"] "."] + [:li "A nested tree looks like: " [:code "[:a [1 2] :b [3 4]]"] "."]]] + [:br] + [:li "A " [:code ":column-path"] " describes a distinct location within a " + [:code ":column-tree"] "." + [:ul + [:li "Given a " [:code ":column-tree"] ", " [:code ":nested-grid"] + " derives all the " [:code ":column-path"] "s it contains, mapping the " + [:code ":cell"] " function over all of them."]]] + [:br] + [:li [:code ":row-spec"] ", " [:code ":row-tree"] " and " [:code ":row-paths"] + " have all the same properties as their column equivalents."] + [:br] + [:li "A " [:i "position"] " combines one " [:code ":row-path"] + " with one " [:code ":column-path"] "." + [:ul + [:li "Rendering one cell means evaluating the " [:code ":cell"] "function, " + "by passing in keyword arguments " + [:code ":row-path"] " and " [:code ":column-path"] + " (i.e., the " [:i "position"] ")."]]]]]]) -(defn notes-column [] +(defn intro-column [] [v-box :children - [[title2 "Notes"] + [[title2 "Introduction"] [status-text "alpha" {:color "red"}] [new-in-version "v2.20.0"] - [p [:code "nested-grid"] " provides a lean abstraction for viewing multidimensional " - "tabular data, using " - [:a {:href "https://www.w3schools.com/css/css_grid.asp"} "css grid"] - " for layout."] - [title3 "Cells are Functions"] - [p "Each cell is a " [:i "function"] " of its grid position."] - - [title3 "Headers are Nested"] - [p "You can declare headers as a nested " [:i "configuration."]] - - [p "Each vertical partition you see is defined by a " [:code ":column-path"] "." - "For instance, " [:code "[:a :a1]"] " is the first " [:code ":column-path"] "."] - [p "Same goes for rows. For instance, " [:code "[:y :y2]"] " is the last " [:code ":row-path"] "."] - [title3 "Cells are Views of Header Paths"] - [p "Each cell is a function of its location."] - [p "Specifically, the " [:code ":cell"] " prop accepts a function " - "of two keyword arguments: " [:code ":column-path"] " and " [:code ":row-path"] "."] - [p "The function counts as a " - [:a {:href "https://github.com/reagent-project/reagent/blob/master/doc/CreatingReagentComponents.md"} - "reagent component"] ", returning either a string or a hiccup."] - [title3 "Header Cells are Views, Too"] - [p "Just like " [:code ":cell"] ", the " [:code ":column-header"] " and " [:code ":row-header"] " props " - "are functions of their location."] - [p "The difference is, a " [:code ":column-header"] " only has a " [:code ":column-path"] - " and a " [:code ":row-header"] " only has a " [:code ":row-path"] "."] - [title3 "Headers are Richly Declarative"] - [p "A " [:code ":column-path"] " is a vector of " [:i "header values."]] - [p "Anything can be a " [:i "header value"] ", " - [:i "except"] " a " [:code "list"] " or " [:code "vector"] " (those express " [:i "configuration"] ")."] - [p "So far, we've looked at simple " [:i "header values"] ", like " [:code ":a"] " or " [:code "\"blue\""] "."] - [p "Another common use-case is a map, like " [:code "{:id :a :label \"A\" :type :letter}"] "." - "We consider a value like this to be a " [:i "header spec"] "."] - [title3 "Nested-grid + Domain Logic = Pivot Table"] - [:i {:style {:max-width "400px"}} - "A pivot table is a table of values which are aggregations of groups of individual values from a more extensive table..." - "within one or more discrete categories. (" [:a {:href "https://en.wikipedia.org/wiki/Pivot_table"} "Wikipedia"] ")"] - [:br] - [p "The pivot table is our driving use-case. " - "By separating UI presentation from data presentation, we hope " - [:code "nested-grid"] " makes it simple to build robust and flexible pivot tables."] - [p "In " [:strong "Demo #3: Header Specifications"] ", " [:code "lookup-table"] "declares " - [:i "\"a more extensive table,\""] - " and the " [:code "lookup"] [:i "column spec"] " declares how to use that table."] - [p - "More generally:" [:br] + [p [:code "nested-grid"] + " " "provides a table with nested, hierarchical columns and rows." + " " "The archetypical use-case would be to display a " + [:a {:href "https://en.wikipedia.org/wiki/Pivot_table"} "pivot table"] "." + " " "However, " [:code "nested-grid"] " provides a lean abstraction that could" + " " "suit a variety of problems."] + [p "Essentially, each cell has a unique" " " [:i "position"] + " " "within the hierarchy." " " "The value of each cell is a" + " " [:i "function"] " " "of its" " " [:i "position."]] + [title2 "Characteristics"] + [p "Unlike" " " [:code "v-table"] ", " + [:code "nested-grid"] ":" + [:ul {:style {:width 400}} + [:li "Uses" " " [:a {:href "https://www.w3schools.com/css/css_grid.asp"} "css grid"] + " " "for layout."] + [:li "Has adjustible column & row widths."] + [:li "Is optimized for tens or hundreds of rows, not millions."] + [:li "Does not virtualize rows (" [:span {:style {:color "red"}} "...yet"] ")." + " It renders everything in a single pass."] + [:li "Does not re-render when you scroll or click. Even if that first render is expensive, " + "the UI should be snappy once it completes."]]] + [title2 "Quick Start"] + [p "To use" [:code "nested-grid"] ", at a minimum, you must declare:" [:ul - [:li "Your " [:code ":columns"] " and " [:code ":rows"] - " declare the necessary domain concepts, such as " - [:i "\"aggregations\""] " and " [:i "\"groups.\""]] - [:li "Your " [:code ":cell"] " function dispatches on each concept, " - "deriving these " [:i "\"aggregations\""] " and " [:i "\"groups\""] " from " - [:i "\"a more extensive table.\""]]]] - [p "We also envision building an interactive, configurable pivot table. " - "By changing " [:code ":columns"] " and " [:code ":rows"] ", you could reconfigure the UI presentation, and " - "your data presentation would simply follow along. " - "This could be done either programmatically or via a dedicated user interface."]]]) + [:li [:code ":column-tree"] ": a vector describing the column structure."] + [:li [:code ":row-tree"] ": a vector describing the row structure."] + [:li [:code ":cell"] ": a function which, given a " + [:code ":column-path"] " and a " [:code ":row-path"] + ", renders one cell, either as a string or a hiccup."]] + "See the " [:strong "Basic Demo"] " for examples," + " and the " [:strong "Concepts"] " section for in-depth explanations."]]]) (def color-mixer {:red {:red :red @@ -212,6 +216,19 @@ :rows [:red :yellow :blue] :cell color-shade-cell]"]]]]]) +(def lookup-table [["🚓" "🛵" "🚲" "🛻" "🚚"] + ["🍐" "🍎" "🍌" "🥝" "🍇"] + ["🐕" "🐎" "🧸" "🐈" "🐟"]]) + +(def add {:operator + :label "Addition"}) +(def multiply {:operator * :label "Multiplication"}) +(def lookup {:operator (fn [l r] (get-in lookup-table [l r])) + :label "Lookup"}) +(def one {:left 1 :label "1"}) +(def two {:left 2 :label "2"}) +(def three {:right 3 :label "3"}) +(def four {:right 4 :label "4"}) + (defn header-spec-demo [] [v-box :children @@ -268,6 +285,7 @@ :children [[nested-grid :columns [:a :b :c] + :show-branch-cells? true :rows [1 2 3] :column-width 40 :column-header-height 25 @@ -284,8 +302,8 @@ :children [[nested-grid :columns [:a :b :c] - :rows [1 [:x :y] - 2 [:x :y]] + :rows [[1 [:x :y]] + [2 [:x :y]]] :column-width 55 :column-header-height 25 :row-header-width 30 @@ -317,10 +335,14 @@ [:i {:style {:color \"grey\"}} (str column-path row-path)])]"]]]]]) +(defn header-demo [] + [:hi]) + (defn demos [] - (let [tabs [{:id :basic :label "Basic" :view basic-demo} + (let [tabs [{:id :basic :label "Basic Demo" :view basic-demo} {:id :color :label "Color" :view color-demo} {:id :shade :label "Shade" :view color-shade-demo} + {:id :header :label "Headers" :view header-demo} {:id :spec :label "Spec" :view header-spec-demo}] !tab-id (r/atom (:id (first tabs))) !tab (r/reaction (u/item-for-id @!tab-id tabs))] @@ -334,15 +356,96 @@ :tabs tabs :style {:margin-top "12px"} :on-change #(reset! !tab-id %)] - [title2 (str label " Demo")] + [title2 label] [view]]])))) +(defn args-column [] + [args-table + nested-grid-args-desc + {:total-width "550px" + :name-column-width "180px"}]) + +(defn algorithm-column [] + [v-box + :children + []]) + +(defn more-column [] + [v-box + :children + [[title2 "More"] + [title3 "Rendering Header Cells"] + [p "Just like " [:code ":cell"] ", the " + [:code ":column-header"] " and " [:code ":row-header"] " props " + "are functions of their location."] + [p "The difference is, they can only expect to be passed a single path. " + [:code ":column-header"] " only expects a " [:code ":column-path"] + ", and " + [:code ":row-header"] " only expects a " [:code ":row-path"] "."] + [p "If you don't pass any " [:code ":column-header"] " prop," + " then it's handled by this default behavior:" + [:ul + [:li "take the last " [:code ":column-spec"] " in the " [:code ":column-path"] "."] + [:li "if it's a map, get the " [:code ":label"] " key, or the " [:code ":id"] " key."] + [:li "if that doesn't work, stringify the whole item."]]] + + [title3 "Branch paths can have cells, too"] + [p "Consider this " [:code ":column-tree"] ":" + [:pre "[:plant [:fruit [:apple :banana] :vegetable [:potato]]]"]] + [p "Normally, " [:code "nested-column"] " would derive 3 " [:code ":column-path"] "s. " + "More specifically, these are the 3 " [:i "leaf paths"] ":"] + [nested-grid + :column-header-height 20 + :row-height 60 + :columns [:plant [:fruit [:apple :banana] :vegetable [:potato]]] + :cell (comp str :column-path)] + [:br] + [p "However: if you pass an optional " [:code ":show-branch-paths?"] + " key, then 6 paths will be derived - 3 " [:i "leaf paths"] " and 3 " [:i "branch paths"] ":"] + [nested-grid + :column-header-height 20 + :row-height 60 + :show-branch-paths? true + :columns [:plant [:fruit [:apple :banana] :vegetable [:potato]]] + :cell (fn [{:keys [column-path]}] + [:div + (if (= 3 (count column-path)) + [:div {:style {:font-size 10 + :color "green"}} "leaf!"] + [:div {:style {:font-size 10 + :color "brown"}} "branch!"]) + (str (last column-path))])] + [title3 "Special keys"] + [p "If your " [:code ":column-spec"] " or " [:code ":row-spec"] " is a map, you can include a few special keys. " + "These will cause " [:code "nested-grid"] " to handle your column or row with special behavior." + [:ul + [:li [:code ":width"] ": sets the initial width."] + [:li [:code ":height"] ": sets the initial height."] + [:li [:code ":show?"] ": show (" [:code "true"] ") or hide (" [:code "false"] ") cells, overriding any other context, settings, or branch/leaf position."]]] + [p "Here's the first table, but instead of the column-spec " [:code ":fruit"] ", we use a map with special keys. This lets us show a single branch path, while the others remain hidden:" + [:pre "[:plant [{:id :fruit :show? true :width 200} [:apple :banana] :vegetable [:potato]]]"]] + [nested-grid + :column-header-height 20 + :columns [:plant [{:id :fruit :show? true :width 200} [:apple :banana] :vegetable [:potato]]]] + [:br] + [p "If you prefer to separate concerns, you can instead include these keys in the metadata of your column- or row-spec."] + [title3 "Paths have state"] + [p "Within an atom, " [:code "nested-grid"] " stores a map for each " + [:code ":column-path"] " and each " [:code ":row-path"] "."] + [p "Keys in this map will override any settings, whether declared in the props, or in a column- or row-spec."] + [p "So far (" [:span {:style {:color :red}} "alpha"] + "), we only store a " [:code ":width"] " key. " + "Each column header has a draggable button, allowing you to update a column's width by hand."]]]) + ;; core holds a reference to panel, so need one level of indirection to get figwheel updates (defn panel [] - (let [tab-defs [{:id :note :label "Notes"} - {:id :parameters :label "Parameters"}] - selected-tab-id (r/atom (:id (first tab-defs)))] + (let [tabs [{:id :intro :label "Introduction" :view intro-column} + {:id :concepts :label "Concepts" :view concepts-column} + {:id :more :label "More" :view more-column} + {:id :parameters :label "Parameters" :view args-column}] + !tab-id (r/atom (:id (first tabs))) + !tab (r/reaction (u/item-for-id @!tab-id tabs))] (fn [] [v-box :src (at) @@ -361,15 +464,10 @@ :children [[rc/horizontal-tabs :src (at) - :model selected-tab-id - :tabs tab-defs + :model !tab-id + :tabs tabs :style {:margin-top "12px"} - :on-change #(reset! selected-tab-id %)] - (case @selected-tab-id - :note [notes-column] - :parameters [args-table - nested-grid-args-desc - {:total-width "550px" - :name-column-width "180px"}])]] + :on-change #(reset! !tab-id %)] + [(:view @!tab)]]] [demos]]] #_[parts-table "nested-grid" nested-grid-grid-parts-desc]]])))