405 lines
28 KiB
Clojure
405 lines
28 KiB
Clojure
(ns re-com.buttons
|
|
(:require-macros [re-com.core :refer [handler-fn]])
|
|
(:require [re-com.util :refer [deref-or-value px]]
|
|
[re-com.validate :refer [position? position-options-list button-size? button-sizes-list
|
|
string-or-hiccup? css-style? html-attr? string-or-atom?] :refer-macros [validate-args-macro]]
|
|
[re-com.popover :refer [popover-tooltip]]
|
|
[re-com.box :refer [h-box v-box box gap line flex-child-style]]
|
|
[reagent.core :as reagent]))
|
|
|
|
;; ------------------------------------------------------------------------------------
|
|
;; Component: button
|
|
;; ------------------------------------------------------------------------------------
|
|
|
|
(def button-args-desc
|
|
[{:name :label :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "label for the button"}
|
|
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the button is clicked"}
|
|
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
|
|
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
|
{:name :disabled? :required false :default false :type "boolean | atom" :description "if true, the user can't click the button"}
|
|
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
|
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles"}
|
|
{: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"]}])
|
|
|
|
(defn button
|
|
"Returns the markup for a basic button"
|
|
[]
|
|
(let [showing? (reagent/atom false)]
|
|
(fn
|
|
[& {:keys [label on-click tooltip tooltip-position disabled? class style attr]
|
|
:or {class "btn-default"}
|
|
:as args}]
|
|
{:pre [(validate-args-macro button-args-desc args "button")]}
|
|
(when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
|
|
(let [disabled? (deref-or-value disabled?)
|
|
the-button [:button
|
|
(merge
|
|
{:class (str "rc-button btn " class)
|
|
:style (merge
|
|
(flex-child-style "none")
|
|
style)
|
|
:disabled disabled?
|
|
:on-click (handler-fn
|
|
(when (and on-click (not disabled?))
|
|
(on-click event)))}
|
|
(when tooltip
|
|
{:on-mouse-over (handler-fn (reset! showing? true))
|
|
:on-mouse-out (handler-fn (reset! showing? false))})
|
|
attr)
|
|
label]]
|
|
(when disabled?
|
|
(reset! showing? false))
|
|
[box
|
|
:class "display-inline-flex"
|
|
:align :start
|
|
:child (if tooltip
|
|
[popover-tooltip
|
|
:label tooltip
|
|
:position (if tooltip-position tooltip-position :below-center)
|
|
:showing? showing?
|
|
:anchor the-button]
|
|
the-button)]))))
|
|
|
|
|
|
;;--------------------------------------------------------------------------------------------------
|
|
;; Component: md-circle-icon-button
|
|
;;--------------------------------------------------------------------------------------------------
|
|
|
|
(def md-circle-icon-button-args-desc
|
|
[{:name :md-icon-name :required true :default "zmdi-plus" :type "string" :validate-fn string? :description [:span "the name of the icon." [:br] "For example, " [:code "\"zmdi-plus\""] " or " [:code "\"zmdi-undo\""]] }
|
|
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the button is clicked"}
|
|
{:name :size :required false :default :regular :type "keyword" :validate-fn button-size? :description [:span "one of " button-sizes-list]}
|
|
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
|
|
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
|
{:name :emphasise? :required false :default false :type "boolean" :description "if true, use emphasised styling so the button really stands out"}
|
|
{:name :disabled? :required false :default false :type "boolean" :description "if true, the user can't click the button"}
|
|
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
|
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
|
{: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"]}])
|
|
|
|
(defn md-circle-icon-button
|
|
"a circular button containing a material design icon"
|
|
[]
|
|
(let [showing? (reagent/atom false)]
|
|
(fn
|
|
[& {:keys [md-icon-name on-click size tooltip tooltip-position emphasise? disabled? class style attr]
|
|
:or {md-icon-name "zmdi-plus"}
|
|
:as args}]
|
|
{:pre [(validate-args-macro md-circle-icon-button-args-desc args "md-circle-icon-button")]}
|
|
(when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
|
|
(let [the-button [:div
|
|
(merge
|
|
{:class (str
|
|
"rc-md-circle-icon-button noselect "
|
|
(case size
|
|
:smaller "rc-circle-smaller "
|
|
:larger "rc-circle-larger "
|
|
" ")
|
|
(when emphasise? "rc-circle-emphasis ")
|
|
(when disabled? "rc-circle-disabled ")
|
|
class)
|
|
:style (merge
|
|
{:cursor (when-not disabled? "pointer")}
|
|
style)
|
|
:on-click (handler-fn
|
|
(when (and on-click (not disabled?))
|
|
(on-click event)))}
|
|
(when tooltip
|
|
{:on-mouse-over (handler-fn (reset! showing? true))
|
|
:on-mouse-out (handler-fn (reset! showing? false))})
|
|
attr)
|
|
[:i {:class (str "zmdi zmdi-hc-fw-rc " md-icon-name)}]]]
|
|
(if tooltip
|
|
[popover-tooltip
|
|
:label tooltip
|
|
:position (if tooltip-position tooltip-position :below-center)
|
|
:showing? showing?
|
|
:anchor the-button]
|
|
the-button)))))
|
|
|
|
|
|
;;--------------------------------------------------------------------------------------------------
|
|
;; Component: md-icon-button
|
|
;;--------------------------------------------------------------------------------------------------
|
|
|
|
(def md-icon-button-args-desc
|
|
[{:name :md-icon-name :required true :default "zmdi-plus" :type "string" :validate-fn string? :description [:span "the name of the icon." [:br] "For example, " [:code "\"zmdi-plus\""] " or " [:code "\"zmdi-undo\""]]}
|
|
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the button is clicked"}
|
|
{:name :size :required false :default :regular :type "keyword" :validate-fn button-size? :description [:span "one of " button-sizes-list]}
|
|
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
|
|
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
|
{:name :emphasise? :required false :default false :type "boolean" :description "if true, use emphasised styling so the button really stands out"}
|
|
{:name :disabled? :required false :default false :type "boolean" :description "if true, the user can't click the button"}
|
|
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
|
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
|
{: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"]}])
|
|
|
|
(defn md-icon-button
|
|
"a square button containing a material design icon"
|
|
[]
|
|
(let [showing? (reagent/atom false)]
|
|
(fn
|
|
[& {:keys [md-icon-name on-click size tooltip tooltip-position emphasise? disabled? class style attr]
|
|
:or {md-icon-name "zmdi-plus"}
|
|
:as args}]
|
|
{:pre [(validate-args-macro md-icon-button-args-desc args "md-icon-button")]}
|
|
(when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
|
|
(let [the-button [:div
|
|
(merge
|
|
{:class (str
|
|
"rc-md-icon-button noselect "
|
|
(case size
|
|
:smaller "rc-icon-smaller "
|
|
:larger "rc-icon-larger "
|
|
" ")
|
|
(when emphasise? "rc-icon-emphasis ")
|
|
(when disabled? "rc-icon-disabled ")
|
|
class)
|
|
:style (merge
|
|
{:cursor (when-not disabled? "pointer")}
|
|
style)
|
|
:on-click (handler-fn
|
|
(when (and on-click (not disabled?))
|
|
(on-click event)))}
|
|
(when tooltip
|
|
{:on-mouse-over (handler-fn (reset! showing? true))
|
|
:on-mouse-out (handler-fn (reset! showing? false))})
|
|
attr)
|
|
[:i {:class (str "zmdi zmdi-hc-fw-rc " md-icon-name)}]]]
|
|
(if tooltip
|
|
[popover-tooltip
|
|
:label tooltip
|
|
:position (if tooltip-position tooltip-position :below-center)
|
|
:showing? showing?
|
|
:anchor the-button]
|
|
the-button)))))
|
|
|
|
|
|
;;--------------------------------------------------------------------------------------------------
|
|
;; Component: info-button
|
|
;;--------------------------------------------------------------------------------------------------
|
|
|
|
(def info-button-args-desc
|
|
[{:name :info :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "what's shown in the popover"}
|
|
{:name :position :required false :default :right-below :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
|
{:name :width :required false :default "250px" :type "string" :validate-fn string? :description "width in px"}
|
|
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
|
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
|
{: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"]}])
|
|
|
|
(defn info-button
|
|
"A tiny light grey button, with an 'i' in it. Meant to be unobrusive.
|
|
When pressed, displays a popup assumidly containing helpful information.
|
|
Primarily designed to be nestled against the label of an input field, explaining the purpose of that field.
|
|
Create a very small \"i\" icon via SVG"
|
|
[]
|
|
(let [showing? (reagent/atom false)]
|
|
(fn
|
|
[& {:keys [info position width class style attr] :as args}]
|
|
{:pre [(validate-args-macro info-button-args-desc args "info-button")]}
|
|
[popover-tooltip
|
|
:label info
|
|
:status :info
|
|
:position (if position position :right-below)
|
|
:width (if width width "250px")
|
|
:showing? showing?
|
|
:on-cancel #(swap! showing? not)
|
|
:anchor [:div
|
|
(merge
|
|
{:class (str "rc-info-button noselect " class)
|
|
:style (merge {:cursor "pointer"} style)
|
|
:on-click (handler-fn (swap! showing? not))}
|
|
attr)
|
|
[:svg {:width "11" :height "11"}
|
|
[:circle {:cx "5.5" :cy "5.5" :r "5.5"}]
|
|
[:circle {:cx "5.5" :cy "2.5" :r "1.4" :fill "white"}]
|
|
[:line {:x1 "5.5" :y1 "5.2" :x2 "5.5" :y2 "9.7" :stroke "white" :stroke-width "2.5"}]]]])))
|
|
|
|
|
|
;;--------------------------------------------------------------------------------------------------
|
|
;; Component: row-button
|
|
;;--------------------------------------------------------------------------------------------------
|
|
|
|
(def row-button-args-desc
|
|
[{:name :md-icon-name :required true :default "zmdi-plus" :type "string" :validate-fn string? :description [:span "the name of the icon." [:br] "For example, " [:code "\"zmdi-plus\""] " or " [:code "\"zmdi-undo\""]]}
|
|
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the button is clicked"}
|
|
{:name :mouse-over-row? :required false :default false :type "boolean" :description "true if the mouse is hovering over the row"}
|
|
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
|
|
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
|
{:name :disabled? :required false :default false :type "boolean" :description "if true, the user can't click the button"}
|
|
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
|
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
|
{: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"]}])
|
|
|
|
(defn row-button
|
|
"a small button containing a material design icon"
|
|
[]
|
|
(let [showing? (reagent/atom false)]
|
|
(fn
|
|
[& {:keys [md-icon-name on-click mouse-over-row? tooltip tooltip-position disabled? class style attr]
|
|
:or {md-icon-name "zmdi-plus"}
|
|
:as args}]
|
|
{:pre [(validate-args-macro row-button-args-desc args "row-button")]}
|
|
(when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
|
|
(let [the-button [:div
|
|
(merge
|
|
{:class (str
|
|
"rc-row-button noselect "
|
|
(when mouse-over-row? "rc-row-mouse-over-row ")
|
|
(when disabled? "rc-row-disabled ")
|
|
class)
|
|
:style style
|
|
:on-click (handler-fn
|
|
(when (and on-click (not disabled?))
|
|
(on-click event)))}
|
|
(when tooltip
|
|
{:on-mouse-over (handler-fn (reset! showing? true))
|
|
:on-mouse-out (handler-fn (reset! showing? false))}) ;; Need to return true to ALLOW default events to be performed
|
|
attr)
|
|
[:i {:class (str "zmdi zmdi-hc-fw-rc " md-icon-name)}]]]
|
|
(if tooltip
|
|
[popover-tooltip
|
|
:label tooltip
|
|
:position (if tooltip-position tooltip-position :below-center)
|
|
:showing? showing?
|
|
:anchor the-button]
|
|
the-button)))))
|
|
|
|
|
|
;;--------------------------------------------------------------------------------------------------
|
|
;; Component: hyperlink
|
|
;;--------------------------------------------------------------------------------------------------
|
|
|
|
(def hyperlink-args-desc
|
|
[{:name :label :required true :type "string | hiccup | atom" :validate-fn string-or-hiccup? :description "label/hiccup for the button"}
|
|
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the button is clicked"}
|
|
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
|
|
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
|
{:name :disabled? :required false :default false :type "boolean | atom" :description "if true, the user can't click the button"}
|
|
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
|
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
|
{: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"]}])
|
|
|
|
(defn hyperlink
|
|
"Renders an underlined text hyperlink component.
|
|
This is very similar to the button component above but styled to looks like a hyperlink.
|
|
Useful for providing button functionality for less important functions, e.g. Cancel"
|
|
[]
|
|
(let [showing? (reagent/atom false)]
|
|
(fn
|
|
[& {:keys [label on-click tooltip tooltip-position disabled? class style attr] :as args}]
|
|
{:pre [(validate-args-macro hyperlink-args-desc args "hyperlink")]}
|
|
(when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
|
|
(let [label (deref-or-value label)
|
|
disabled? (deref-or-value disabled?)
|
|
the-button [box
|
|
:align :start
|
|
:child [:a
|
|
(merge
|
|
{:class (str "rc-hyperlink noselect " class)
|
|
:style (merge
|
|
(flex-child-style "none")
|
|
{:cursor (if disabled? "not-allowed" "pointer")
|
|
:color (when disabled? "grey")}
|
|
style)
|
|
:on-click (handler-fn
|
|
(when (and on-click (not disabled?))
|
|
(on-click event)))}
|
|
(when tooltip
|
|
{:on-mouse-over (handler-fn (reset! showing? true))
|
|
:on-mouse-out (handler-fn (reset! showing? false))})
|
|
attr)
|
|
label]]]
|
|
(if tooltip
|
|
[popover-tooltip
|
|
:label tooltip
|
|
:position (if tooltip-position tooltip-position :below-center)
|
|
:showing? showing?
|
|
:anchor the-button]
|
|
the-button)))))
|
|
|
|
|
|
;;--------------------------------------------------------------------------------------------------
|
|
;; Component: hyperlink-href
|
|
;;--------------------------------------------------------------------------------------------------
|
|
|
|
(def hyperlink-href-args-desc
|
|
[{:name :label :required true :type "string | hiccup | atom" :validate-fn string-or-hiccup? :description "label/hiccup for the button"}
|
|
{:name :href :required true :type "string | atom" :validate-fn string-or-atom? :description "if specified, the link target URL"}
|
|
{:name :target :required false :default "_self" :type "string | atom" :validate-fn string-or-atom? :description "one of \"_self\" or \"_blank\""}
|
|
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
|
|
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
|
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
|
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
|
{: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"]}])
|
|
|
|
(defn hyperlink-href
|
|
"Renders an underlined text hyperlink component.
|
|
This is very similar to the button component above but styled to looks like a hyperlink.
|
|
Useful for providing button functionality for less important functions, e.g. Cancel"
|
|
[]
|
|
(let [showing? (reagent/atom false)]
|
|
(fn
|
|
[& {:keys [label href target tooltip tooltip-position class style attr] :as args}]
|
|
{:pre [(validate-args-macro hyperlink-href-args-desc args "hyperlink-href")]}
|
|
(when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
|
|
(let [label (deref-or-value label)
|
|
href (deref-or-value href)
|
|
target (deref-or-value target)
|
|
the-button [:a
|
|
(merge {:class (str "rc-hyperlink-href noselect " class)
|
|
:style (merge (flex-child-style "none")
|
|
style)
|
|
:href href
|
|
:target target}
|
|
(when tooltip
|
|
{:on-mouse-over (handler-fn (reset! showing? true))
|
|
:on-mouse-out (handler-fn (reset! showing? false))})
|
|
attr)
|
|
label]]
|
|
|
|
(if tooltip
|
|
[popover-tooltip
|
|
:label tooltip
|
|
:position (if tooltip-position tooltip-position :below-center)
|
|
:showing? showing?
|
|
:anchor the-button]
|
|
the-button)))))
|
|
|
|
|
|
;; TODO: Eventually remove
|
|
;;----------------------------------------------------------------------
|
|
;; Round button with no dependencies - for use in re-frame demo
|
|
;;----------------------------------------------------------------------
|
|
|
|
#_(defn round-button
|
|
"a circular button containing a material design icon"
|
|
[]
|
|
(let [mouse-over? (reagent/atom false)]
|
|
(fn
|
|
[& {:keys [md-icon-name on-click disabled? style attr]
|
|
:or {md-icon-name "md-add"}}]
|
|
[:div
|
|
(merge
|
|
{:style (merge
|
|
{:cursor (when-not disabled? "pointer")
|
|
:font-size "24px"
|
|
:width "40px"
|
|
:height "40px"
|
|
:line-height "44px"
|
|
:text-align "center"
|
|
:-webkit-user-select "none"}
|
|
(if disabled?
|
|
{:color "lightgrey"}
|
|
{:border (str "1px solid " (if @mouse-over? "#428bca" "lightgrey"))
|
|
:color (when @mouse-over? "#428bca")
|
|
:border-radius "50%"})
|
|
style)
|
|
:on-mouse-over #(do (reset! mouse-over? true) nil)
|
|
:on-mouse-out #(do (reset! mouse-over? false) nil)
|
|
:on-click #(when (and on-click (not disabled?))
|
|
(on-click %)
|
|
nil)}
|
|
attr)
|
|
[:i {:class md-icon-name}]])))
|