Skip to content

Commit

Permalink
Merge pull request #199 from PawelStroinski/tabs
Browse files Browse the repository at this point in the history
Add tooltip to horizontal-bar-tabs & :validate?
  • Loading branch information
superstructor authored Dec 10, 2020
2 parents a8fd6f9 + 84a148e commit eb52b6c
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 43 deletions.
106 changes: 65 additions & 41 deletions src/re_com/tabs.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@
(:require-macros [re-com.core :refer [handler-fn]])
(:require [re-com.util :refer [deref-or-value]]
[re-com.box :refer [flex-child-style]]
[re-com.validate :refer [css-style? html-attr? vector-of-maps?] :refer-macros [validate-args-macro]]))
[re-com.validate :refer [css-style? html-attr? vector-of-maps?
position? position-options-list] :refer-macros [validate-args-macro]]
[re-com.popover :refer [popover-tooltip]]
[reagent.core :as reagent]))


;;--------------------------------------------------------------------------------------------------
;; Component: horizontal-tabs
;;--------------------------------------------------------------------------------------------------

(def tabs-args-desc
[{:name :model :required true :type "unique-id | atom" :description "the unique identifier of the currently selected tab"}
{:name :tabs :required true :type "vector of tabs | atom" :validate-fn vector-of-maps? :description "one element in the vector for each tab. Typically, each element is a map with :id and :label keys"}
{:name :on-change :required true :type "unique-id -> nil" :validate-fn fn? :description "called when user alters the selection. Passed the unique identifier of the selection"}
{:name :id-fn :required false :default :id :type "tab -> anything" :validate-fn ifn? :description [:span "given an element of " [:code ":tabs"] ", returns its unique identifier (aka id)"]}
{:name :label-fn :required false :default :label :type "tab -> string | hiccup" :validate-fn ifn? :description [:span "given an element of " [:code ":tabs"] ", returns its displayable label"]}
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated (applies to the outer container)"}
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description [:span "CSS styles to add or override (aplies to " [:span.bold "each individual tab"] " rather than the container)"]}
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed (applies to the outer container)"]}])
[{:name :model :required true :type "unique-id | atom" :description "the unique identifier of the currently selected tab"}
{:name :tabs :required true :type "vector of tabs | atom" :validate-fn vector-of-maps? :description "one element in the vector for each tab. Typically, each element is a map with :id and :label keys"}
{:name :on-change :required true :type "unique-id -> nil" :validate-fn fn? :description "called when user alters the selection. Passed the unique identifier of the selection"}
{:name :id-fn :required false :default :id :type "tab -> anything" :validate-fn ifn? :description [:span "given an element of " [:code ":tabs"] ", returns its unique identifier (aka id)"]}
{:name :label-fn :required false :default :label :type "tab -> string | hiccup" :validate-fn ifn? :description [:span "given an element of " [:code ":tabs"] ", returns its displayable label"]}
{:name :tooltip-fn :required false :default :tooltip :type "tab -> string | hiccup" :validate-fn ifn? :description [:span "[horizontal-bar-tabs only] given an element of " [:code ":tabs"] ", returns its tooltip"]}
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "[horizontal-bar-tabs only] relative to this anchor. One of " position-options-list]}
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated (applies to the outer container)"}
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description [:span "CSS styles to add or override (aplies to " [:span.bold "each individual tab"] " rather than the container)"]}
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed (applies to the outer container)"]}
{:name :validate? :required false :default true :type "boolean" :description [:span "[horizontal-bar-tabs & vertical-bar-tabs only] validate " [:code ":model"] " against " [:code ":tabs"]]}])

(defn horizontal-tabs
[& {:keys [model tabs on-change id-fn label-fn class style attr]
Expand Down Expand Up @@ -50,45 +56,62 @@
;;--------------------------------------------------------------------------------------------------

(defn- bar-tabs
[& {:keys [model tabs on-change id-fn label-fn vertical? class style attr]}]
(let [current (deref-or-value model)
tabs (deref-or-value tabs)
_ (assert (not-empty (filter #(= current (id-fn %)) tabs)) "model not found in tabs vector")]
[:div
(merge {:class (str "rc-tabs noselect btn-group" (if vertical? "-vertical") " " class)
:style (flex-child-style "none")}
attr)
(for [t tabs]
(let [id (id-fn t)
label (label-fn t)
selected? (= id current)] ;; must use current instead of @model to avoid reagent warnings
[:button
{:type "button"
:key (str id)
:class (str "btn btn-default " (if selected? "active"))
:style style
:on-click (when on-change (handler-fn (on-change id)))}
label]))]))
[& {:keys [model tabs on-change id-fn label-fn tooltip-fn tooltip-position vertical? class style attr validate?]}]
(let [showing (reagent/atom nil)]
(fn [& {:keys [model tabs]}]
(let [current (deref-or-value model)
tabs (deref-or-value tabs)
_ (assert (or (not validate?) (not-empty (filter #(= current (id-fn %)) tabs))) "model not found in tabs vector")]
(into [:div
(merge {:class (str "rc-tabs noselect btn-group" (if vertical? "-vertical") " " class)
:style (flex-child-style "none")}
attr)]
(for [t tabs]
(let [id (id-fn t)
label (label-fn t)
tooltip (when tooltip-fn (tooltip-fn t))
selected? (= id current)
the-button [:button
(merge
{:type "button"
:key (str id)
:class (str "btn btn-default " (if selected? "active"))
:style style
:on-click (when on-change (handler-fn (on-change id)))}
(when tooltip
{:on-mouse-over (handler-fn (reset! showing id))
:on-mouse-out (handler-fn (swap! showing #(when-not (= id %) %)))}))
label]]
(if tooltip
[popover-tooltip
:label tooltip
:position (or tooltip-position :below-center)
:showing? (reagent/track #(= id @showing))
:anchor the-button]
the-button))))))))


(defn horizontal-bar-tabs
[& {:keys [model tabs on-change id-fn label-fn class style attr]
:or {id-fn :id label-fn :label}
[& {:keys [model tabs on-change id-fn label-fn tooltip-fn tooltip-position class style attr validate?]
:or {id-fn :id label-fn :label tooltip-fn :tooltip}
:as args}]
{:pre [(validate-args-macro tabs-args-desc args "tabs")]}
(bar-tabs
:model model
:tabs tabs
:on-change on-change
:id-fn id-fn
:label-fn label-fn
:vertical? false
:class class
:style style
:attr attr))
:model model
:tabs tabs
:on-change on-change
:id-fn id-fn
:label-fn label-fn
:tooltip-fn tooltip-fn
:tooltip-position tooltip-position
:vertical? false
:class class
:style style
:attr attr
:validate? validate?))

(defn vertical-bar-tabs
[& {:keys [model tabs on-change id-fn label-fn class style attr]
[& {:keys [model tabs on-change id-fn label-fn class style attr validate?]
:or {id-fn :id label-fn :label}
:as args}]
{:pre [(validate-args-macro tabs-args-desc args "tabs")]}
Expand All @@ -101,7 +124,8 @@
:vertical? true
:class class
:style style
:attr attr))
:attr attr
:validate? validate?))


;;--------------------------------------------------------------------------------------------------
Expand Down
22 changes: 20 additions & 2 deletions src/re_demo/tabs.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

(def demos [{:id 1 :label "Tab Styles"}
{:id 2 :label "Persistent Tab Selection"}
{:id 3 :label "Dynamic Tabs"}])
{:id 3 :label "Dynamic Tabs"}
{:id 4 :label "Tooltips"}])


;; Define some tabs.
Expand Down Expand Up @@ -144,6 +145,22 @@
new-tab {:id (keyword c) :label c}]
(swap! tab-defs conj new-tab)))]]]]])))


(defn tooltips-demo
[]
(let [tab-defs [{:id ::1 :label "Left Tab" :tooltip "This is the first tooltip..."}
{:id ::2 :label "Middle Tab" :tooltip "This is the second tooltip..."}
{:id ::3 :label "Right Tab" :tooltip "This is the third and the final tooltip!"}]
selected-tab-id (reagent/atom (:id (first tab-defs)))]
(fn []
[v-box
:gap "20px"
:children [[p "Hover over a tab to see a tooltip."]
[horizontal-bar-tabs
:model selected-tab-id
:tabs tab-defs
:on-change #(reset! selected-tab-id %)]]])))

(defn panel2
[]
(let [selected-demo-id (reagent/atom 1)]
Expand Down Expand Up @@ -186,7 +203,8 @@
(case @selected-demo-id
1 [tab-styles-demo]
2 [remembers-demo]
3 [adding-tabs-demo])]]]]]]]])))
3 [adding-tabs-demo]
4 [tooltips-demo])]]]]]]]])))


;; core holds a reference to panel, so need one level of indirection to get figwheel updates
Expand Down

0 comments on commit eb52b6c

Please sign in to comment.