(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}]])))