parent
b9890a9d44
commit
3b8f66d2ec
|
@ -0,0 +1,64 @@
|
|||
(ns quo2.components.calendar.calendar.component-spec
|
||||
(:require [quo2.components.calendar.calendar.view :as calendar]
|
||||
[test-helpers.component :as h]
|
||||
[cljs-time.core :as time]))
|
||||
|
||||
(def start-date (time/date-time (time/year (time/now)) (time/month (time/now)) 5))
|
||||
(def end-date (time/date-time (time/plus start-date (time/days 2))))
|
||||
|
||||
(h/describe "calendar component"
|
||||
(h/test "default render of calendar component"
|
||||
(h/render
|
||||
[calendar/view
|
||||
{:start-date start-date
|
||||
:end-date end-date}])
|
||||
(-> (h/expect (h/query-by-translation-text "Mo"))
|
||||
(h/is-truthy)))
|
||||
|
||||
(h/test "should call on-change with selected date on first click"
|
||||
(let [on-change (h/mock-fn)]
|
||||
(h/render
|
||||
[calendar/view
|
||||
{:start-date nil
|
||||
:end-date nil
|
||||
:on-change on-change}])
|
||||
(h/fire-event :press (h/query-by-text (str (time/day start-date))))
|
||||
(h/was-called-with on-change {:start-date start-date :end-date nil})))
|
||||
|
||||
(h/test "should call on-change with start and end date on second click"
|
||||
(let [on-change (h/mock-fn)]
|
||||
(h/render
|
||||
[calendar/view
|
||||
{:start-date start-date :end-date nil :on-change on-change}])
|
||||
(h/fire-event :press (h/query-by-text (str (time/day end-date))))
|
||||
(h/was-called-with on-change {:start-date start-date :end-date end-date})))
|
||||
|
||||
(h/test "should reset the dates on third click"
|
||||
(let [on-change (h/mock-fn)]
|
||||
(h/render
|
||||
[calendar/view
|
||||
{:start-date start-date
|
||||
:end-date end-date
|
||||
:on-change on-change}])
|
||||
(h/fire-event :press (h/query-by-text (str (time/day start-date))))
|
||||
(h/was-called-with on-change {:start-date start-date :end-date nil})))
|
||||
|
||||
(h/test "should reset dates when start date is clicked again"
|
||||
(let [on-change (h/mock-fn)]
|
||||
(h/render
|
||||
[calendar/view
|
||||
{:start-date start-date
|
||||
:end-date nil
|
||||
:on-change on-change}])
|
||||
(h/fire-event :press (h/query-by-text (str (time/day start-date))))
|
||||
(h/was-called-with on-change {:start-date nil :end-date nil})))
|
||||
|
||||
(h/test "should assign start and end date correctly when upper range selected first"
|
||||
(let [on-change (h/mock-fn)]
|
||||
(h/render
|
||||
[calendar/view
|
||||
{:start-date end-date
|
||||
:end-date nil
|
||||
:on-change on-change}])
|
||||
(h/fire-event :press (h/query-by-text (str (time/day start-date))))
|
||||
(h/was-called-with on-change {:start-date start-date :end-date end-date}))))
|
|
@ -0,0 +1,7 @@
|
|||
(ns quo2.components.calendar.calendar.days-grid.style)
|
||||
|
||||
(def container-days
|
||||
{:flex-grow 1
|
||||
:margin-top 4
|
||||
:margin-horizontal 8
|
||||
:overflow :hidden})
|
|
@ -0,0 +1,65 @@
|
|||
(ns quo2.components.calendar.calendar.days-grid.utils
|
||||
(:require
|
||||
[utils.number :as utils.number]
|
||||
[cljs-time.core :as time]))
|
||||
|
||||
(defn- day-of-week
|
||||
[date]
|
||||
(let [day (time/day-of-week date)]
|
||||
(mod day 7)))
|
||||
|
||||
(defn- add-days
|
||||
[date days]
|
||||
(time/plus date (time/days days)))
|
||||
|
||||
(defn day-grid
|
||||
[year month]
|
||||
(let [year (utils.number/parse-int year)
|
||||
month (utils.number/parse-int month)
|
||||
first-day (time/date-time year month 1)
|
||||
start-day (add-days first-day (- 0 (day-of-week first-day)))
|
||||
end-day (add-days start-day 34)]
|
||||
(loop [days []
|
||||
current-day start-day]
|
||||
(if (time/after? current-day end-day)
|
||||
days
|
||||
(recur (conj days current-day) (add-days current-day 1))))))
|
||||
|
||||
(defn get-day-state
|
||||
[day today year month start-date end-date]
|
||||
(cond
|
||||
(and start-date (time/equal? day start-date)) :selected
|
||||
(and end-date (time/equal? day end-date)) :selected
|
||||
(and (= (time/year day) (time/year today))
|
||||
(= (time/month day) (time/month today))
|
||||
(= (time/day day) (time/day today))) :today
|
||||
(and (= (time/year day) year) (= (time/month day) month)) :default
|
||||
:else :disabled))
|
||||
|
||||
(defn update-range
|
||||
[day start-date end-date]
|
||||
(let [new-state (cond
|
||||
(and start-date end-date) {:start-date day :end-date nil}
|
||||
(and start-date (time/equal? day start-date)) {:start-date nil :end-date nil}
|
||||
(and end-date (time/equal? day end-date)) {:start-date nil :end-date nil}
|
||||
(nil? start-date) {:start-date day :end-date nil}
|
||||
(nil? end-date) {:start-date start-date :end-date day}
|
||||
:else {:start-date start-date
|
||||
:end-date end-date})]
|
||||
(if (and (:start-date new-state)
|
||||
(:end-date new-state)
|
||||
(time/after? (:start-date new-state) (:end-date new-state)))
|
||||
{:start-date (:end-date new-state) :end-date (:start-date new-state)}
|
||||
new-state)))
|
||||
|
||||
(defn in-range?
|
||||
[day start-date end-date]
|
||||
(and start-date end-date (time/after? day start-date) (time/before? day end-date)))
|
||||
|
||||
(defn get-in-range-pos
|
||||
[day start-date end-date]
|
||||
(cond
|
||||
(or (nil? start-date) (nil? end-date)) nil
|
||||
(and start-date (time/equal? day start-date)) :start
|
||||
(and end-date (time/equal? day end-date)) :end
|
||||
(in-range? day start-date end-date) :middle))
|
|
@ -0,0 +1,51 @@
|
|||
(ns quo2.components.calendar.calendar.days-grid.utils-test
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[quo2.components.calendar.calendar.days-grid.utils :as utils]
|
||||
[cljs-time.core :as time]))
|
||||
|
||||
(deftest day-grid-test
|
||||
(let [day-grid-result (utils/day-grid "2023" "7")]
|
||||
(testing "it returns correct days grid"
|
||||
(is (= 35 (count day-grid-result)))
|
||||
(is (time/equal? (time/date-time 2023 6 25) (first day-grid-result)))
|
||||
(is (time/equal? (time/date-time 2023 7 29) (last day-grid-result))))))
|
||||
|
||||
(deftest get-day-state-test
|
||||
(let [today (time/date-time 2023 7 27)
|
||||
year 2023
|
||||
month 7
|
||||
start-date (time/date-time 2023 7 20)
|
||||
end-date (time/date-time 2023 7 30)]
|
||||
(testing "it returns :today when day equals today"
|
||||
(is (= :today (utils/get-day-state today today year month start-date end-date))))
|
||||
(testing "it returns :selected when day equals start-date and not today"
|
||||
(is
|
||||
(= :selected (utils/get-day-state start-date today year month start-date end-date))))
|
||||
(testing "it returns :selected when day equals end-date and not today"
|
||||
(is
|
||||
(= :selected (utils/get-day-state end-date today year month start-date end-date))))))
|
||||
|
||||
(deftest update-range-test
|
||||
(let [start-date (time/date-time 2023 7 20)
|
||||
end-date (time/date-time 2023 7 30)
|
||||
day (time/date-time 2023 7 27)]
|
||||
(testing "it returns updated range"
|
||||
(is
|
||||
(= {:start-date day :end-date nil} (utils/update-range day start-date end-date))))))
|
||||
|
||||
(deftest in-range-test
|
||||
(let [start-date (time/date-time 2023 7 20)
|
||||
end-date (time/date-time 2023 7 30)
|
||||
day (time/date-time 2023 7 27)]
|
||||
(testing "it returns true when day is within range"
|
||||
(is (utils/in-range? day start-date end-date))
|
||||
(is (not (utils/in-range? (time/date-time 2023 7 19) start-date end-date))))))
|
||||
|
||||
(deftest get-in-range-pos-test
|
||||
(let [start-date (time/date-time 2023 7 20)
|
||||
end-date (time/date-time 2023 7 30)
|
||||
day (time/date-time 2023 7 27)]
|
||||
(testing "it returns correct position within range"
|
||||
(is (= :start (utils/get-in-range-pos start-date start-date end-date)))
|
||||
(is (= :end (utils/get-in-range-pos end-date start-date end-date)))
|
||||
(is (= :middle (utils/get-in-range-pos day start-date end-date))))))
|
|
@ -0,0 +1,41 @@
|
|||
(ns quo2.components.calendar.calendar.days-grid.view
|
||||
(:require [react-native.core :as rn]
|
||||
[cljs-time.core :as time]
|
||||
[quo2.components.calendar.calendar.days-grid.utils :as utils]
|
||||
[quo2.components.calendar.calendar-day.view :as calendar-day]
|
||||
[quo2.components.calendar.calendar.days-grid.style :as style]))
|
||||
|
||||
(defn- day-view
|
||||
[day _ _ {:keys [year month selection-range on-press customization-color]}]
|
||||
(let [today (time/now)
|
||||
start-date (:start-date selection-range)
|
||||
end-date (:end-date selection-range)
|
||||
state (utils/get-day-state day today year month start-date end-date)
|
||||
in-range (utils/get-in-range-pos day start-date end-date)
|
||||
on-press #(on-press (time/date-time day))]
|
||||
[calendar-day/view
|
||||
{:customization-color customization-color
|
||||
:state state
|
||||
:in-range in-range
|
||||
:on-press on-press}
|
||||
(str (time/day day))]))
|
||||
|
||||
(defn view
|
||||
[{:keys [year month on-change start-date end-date customization-color]}]
|
||||
(let [on-day-press (fn [day]
|
||||
(let [new-selection (utils/update-range day start-date end-date)]
|
||||
(on-change new-selection)))]
|
||||
[rn/view
|
||||
{:style style/container-days}
|
||||
[rn/flat-list
|
||||
{:data (utils/day-grid year month)
|
||||
:key-fn str
|
||||
:num-columns 7
|
||||
:content-container-style {:margin-horizontal -2}
|
||||
:render-fn day-view
|
||||
:render-data {:customization-color customization-color
|
||||
:year year
|
||||
:month month
|
||||
:on-press on-day-press
|
||||
:selection-range {:start-date start-date
|
||||
:end-date end-date}}}]]))
|
|
@ -0,0 +1,25 @@
|
|||
(ns quo2.components.calendar.calendar.month-picker.component-spec
|
||||
(:require [quo2.components.calendar.calendar.month-picker.view :as month-picker]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "month-picker component"
|
||||
(h/test "default render of month-picker component"
|
||||
(h/render
|
||||
[month-picker/view
|
||||
{:year "2023" :month "7"}])
|
||||
(-> (h/expect (h/query-by-translation-text "July 2023"))
|
||||
(h/is-truthy)))
|
||||
|
||||
(h/test "should call on-change with next month when right button pressed"
|
||||
(let [on-change (h/mock-fn)]
|
||||
(h/render
|
||||
[month-picker/view {:year "2023" :month "7" :on-change on-change}])
|
||||
(h/fire-event :press (h/query-by-label-text :next-month-button))
|
||||
(h/was-called on-change)))
|
||||
|
||||
(h/test "should call on-change with previous month when left button pressed"
|
||||
(let [on-change (h/mock-fn)]
|
||||
(h/render
|
||||
[month-picker/view {:year "2023" :month "1" :on-change on-change}])
|
||||
(h/fire-event :press (h/query-by-label-text :previous-month-button))
|
||||
(h/was-called on-change))))
|
|
@ -0,0 +1,14 @@
|
|||
(ns quo2.components.calendar.calendar.month-picker.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def container
|
||||
{:align-items :center
|
||||
:flex-direction :row
|
||||
:flex-grow 1
|
||||
:padding-horizontal 12
|
||||
:padding-vertical 9
|
||||
:justify-content :space-between})
|
||||
|
||||
(defn text
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/neutral-100 colors/white theme)})
|
|
@ -0,0 +1,22 @@
|
|||
(ns quo2.components.calendar.calendar.month-picker.utils
|
||||
(:require [utils.datetime :as datetime]))
|
||||
|
||||
(defn format-month-year
|
||||
[year month]
|
||||
(let [month (cond
|
||||
(or (nil? month) (zero? month)) 1
|
||||
(> month 12) 12
|
||||
:else month)]
|
||||
(str (datetime/format-long-month month) " " year)))
|
||||
|
||||
(defn next-month
|
||||
[year month]
|
||||
(let [new-month (if (= month 12) 1 (inc month))
|
||||
new-year (if (= month 12) (inc year) year)]
|
||||
{:year (str new-year) :month (str new-month)}))
|
||||
|
||||
(defn previous-month
|
||||
[year month]
|
||||
(let [new-month (if (= month 1) 12 (dec month))
|
||||
new-year (if (= month 1) (dec year) year)]
|
||||
{:year (str new-year) :month (str new-month)}))
|
|
@ -0,0 +1,20 @@
|
|||
(ns quo2.components.calendar.calendar.month-picker.utils-test
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[quo2.components.calendar.calendar.month-picker.utils :as utils]))
|
||||
|
||||
(deftest format-month-year-test
|
||||
(testing "returns correct format for given year and month"
|
||||
(is (= (utils/format-month-year 2023 1) "January 2023"))
|
||||
(is (= (utils/format-month-year 2023 12) "December 2023"))
|
||||
(is (= (utils/format-month-year 2023 0) "January 2023"))
|
||||
(is (= (utils/format-month-year 2023 13) "December 2023"))))
|
||||
|
||||
(deftest next-month-test
|
||||
(testing "returns the next month and year"
|
||||
(is (= (utils/next-month 2023 1) {:year "2023" :month "2"}))
|
||||
(is (= (utils/next-month 2023 12) {:year "2024" :month "1"}))))
|
||||
|
||||
(deftest previous-month-test
|
||||
(testing "returns the previous month and year"
|
||||
(is (= (utils/previous-month 2023 1) {:year "2022" :month "12"}))
|
||||
(is (= (utils/previous-month 2023 12) {:year "2023" :month "11"}))))
|
|
@ -0,0 +1,36 @@
|
|||
(ns quo2.components.calendar.calendar.month-picker.view
|
||||
(:require [react-native.core :as rn]
|
||||
[utils.number :as utils.number]
|
||||
[quo2.theme :as theme]
|
||||
[quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.calendar.calendar.month-picker.style :as style]
|
||||
[quo2.components.calendar.calendar.month-picker.utils :as utils]))
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [year month on-change theme]}]
|
||||
(let [year (utils.number/parse-int year)
|
||||
month (utils.number/parse-int month)]
|
||||
[rn/view
|
||||
{:style style/container}
|
||||
[button/button
|
||||
{:icon true
|
||||
:type :outline
|
||||
:accessibility-label :previous-month-button
|
||||
:size 24
|
||||
:on-press #(on-change (utils/previous-month year month))}
|
||||
:i/chevron-left]
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-1
|
||||
:style (style/text theme)}
|
||||
(utils/format-month-year year month)]
|
||||
[button/button
|
||||
{:icon true
|
||||
:accessibility-label :next-month-button
|
||||
:size 24
|
||||
:type :outline
|
||||
:on-press #(on-change (utils/next-month year month))}
|
||||
:i/chevron-right]]))
|
||||
|
||||
(def view (theme/with-theme view-internal))
|
|
@ -0,0 +1,14 @@
|
|||
(ns quo2.components.calendar.calendar.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(defn container
|
||||
[theme]
|
||||
{:flex-direction :row
|
||||
:height 270
|
||||
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-80 theme)
|
||||
:border-radius 12
|
||||
:border-width 1
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-80-opa-40 theme)})
|
||||
|
||||
(def container-main
|
||||
{:flex-grow 1})
|
|
@ -0,0 +1,25 @@
|
|||
(ns quo2.components.calendar.calendar.utils
|
||||
(:require [utils.datetime :as datetime]
|
||||
[utils.number :as utils.number]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(defn generate-years
|
||||
[current-year]
|
||||
(let [current-year current-year]
|
||||
(reverse (vec (range (- current-year 100) (+ current-year 1))))))
|
||||
|
||||
(defn current-year
|
||||
[]
|
||||
(-> (datetime/now)
|
||||
datetime/timestamp->year-month-day-date
|
||||
(string/split #"-")
|
||||
first
|
||||
utils.number/parse-int))
|
||||
|
||||
(defn current-month
|
||||
[]
|
||||
(-> (datetime/now)
|
||||
datetime/timestamp->year-month-day-date
|
||||
(string/split #"-")
|
||||
second
|
||||
utils.number/parse-int))
|
|
@ -0,0 +1,30 @@
|
|||
(ns quo2.components.calendar.calendar.utils-test
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[quo2.components.calendar.calendar.utils :as utils]
|
||||
[utils.datetime :as datetime]
|
||||
[clojure.string :as string]
|
||||
[utils.number :as utils.number]))
|
||||
|
||||
(deftest generate-years-test
|
||||
(testing "returns correct years range"
|
||||
(let [current-year (utils/current-year)]
|
||||
(is (= (last (utils/generate-years current-year)) (- current-year 100)))
|
||||
(is (= (first (utils/generate-years current-year)) current-year)))))
|
||||
|
||||
(deftest current-year-test
|
||||
(testing "returns the current year"
|
||||
(let [current-year (-> (datetime/now)
|
||||
datetime/timestamp->year-month-day-date
|
||||
(string/split #"-")
|
||||
first
|
||||
utils.number/parse-int)]
|
||||
(is (= (utils/current-year) current-year)))))
|
||||
|
||||
(deftest current-month-test
|
||||
(testing "returns the current month"
|
||||
(let [current-month (-> (datetime/now)
|
||||
datetime/timestamp->year-month-day-date
|
||||
(string/split #"-")
|
||||
second
|
||||
utils.number/parse-int)]
|
||||
(is (= (utils/current-month) current-month)))))
|
|
@ -0,0 +1,42 @@
|
|||
(ns quo2.components.calendar.calendar.view
|
||||
(:require [react-native.core :as rn]
|
||||
[quo2.theme :as theme]
|
||||
[reagent.core :as reagent]
|
||||
[utils.number :as utils.number]
|
||||
[quo2.components.calendar.calendar.utils :as utils]
|
||||
[quo2.components.calendar.calendar.style :as style]
|
||||
[quo2.components.calendar.calendar.years-list.view :as years-list]
|
||||
[quo2.components.calendar.calendar.days-grid.view :as days-grid]
|
||||
[quo2.components.calendar.calendar.weekdays-header.view :as weekdays-header]
|
||||
[quo2.components.calendar.calendar.month-picker.view :as month-picker]))
|
||||
|
||||
(defn- view-internal
|
||||
[]
|
||||
(let [selected-year (reagent/atom (utils/current-year))
|
||||
selected-month (reagent/atom (utils/current-month))
|
||||
on-change-year #(reset! selected-year %)
|
||||
on-change-month (fn [new-date]
|
||||
(reset! selected-year (utils.number/parse-int (:year new-date)))
|
||||
(reset! selected-month (utils.number/parse-int (:month new-date))))]
|
||||
(fn [{:keys [on-change start-date end-date theme]}]
|
||||
[rn/view
|
||||
{:style (style/container theme)}
|
||||
[years-list/view
|
||||
{:on-change-year on-change-year
|
||||
:year @selected-year}]
|
||||
[rn/view
|
||||
{:style style/container-main}
|
||||
[month-picker/view
|
||||
{:year @selected-year
|
||||
:month @selected-month
|
||||
:on-change on-change-month}]
|
||||
[weekdays-header/view]
|
||||
[days-grid/view
|
||||
{:year @selected-year
|
||||
:month @selected-month
|
||||
:start-date start-date
|
||||
:end-date end-date
|
||||
:on-change on-change
|
||||
:customization-color :blue}]]])))
|
||||
|
||||
(def view (theme/with-theme view-internal))
|
|
@ -0,0 +1,18 @@
|
|||
(ns quo2.components.calendar.calendar.weekdays-header.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def container-weekday-row
|
||||
{:flex-direction :row
|
||||
:justify-content :space-between
|
||||
:padding-horizontal 8})
|
||||
|
||||
(def container-weekday
|
||||
{:width 32
|
||||
:height 30
|
||||
:padding-top 2
|
||||
:justify-content :center
|
||||
:align-items :center})
|
||||
|
||||
(defn text-weekdays
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)})
|
|
@ -0,0 +1,23 @@
|
|||
(ns quo2.components.calendar.calendar.weekdays-header.view
|
||||
(:require [react-native.core :as rn]
|
||||
[quo2.theme :as theme]
|
||||
[utils.datetime :as datetime]
|
||||
[utils.i18n :as i18n]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.calendar.calendar.weekdays-header.style :as style]))
|
||||
|
||||
(defn- view-internal
|
||||
[theme]
|
||||
[rn/view
|
||||
{:style style/container-weekday-row}
|
||||
(for [weekday datetime/weekday-names]
|
||||
[rn/view
|
||||
{:style style/container-weekday
|
||||
:key weekday}
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2
|
||||
:style (style/text-weekdays theme)}
|
||||
(str (i18n/label weekday))]])])
|
||||
|
||||
(def view (theme/with-theme view-internal))
|
|
@ -0,0 +1,33 @@
|
|||
(ns quo2.components.calendar.calendar.years-list.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(defn gradient-start-color
|
||||
[theme]
|
||||
(colors/theme-colors colors/white colors/neutral-90 theme))
|
||||
|
||||
(defn gradient-end-color
|
||||
[theme]
|
||||
(colors/theme-colors colors/white-opa-0 colors/neutral-100-opa-0 theme))
|
||||
|
||||
(def gradient-view
|
||||
{:position :absolute
|
||||
:height 50
|
||||
:border-top-left-radius 12
|
||||
:top 0
|
||||
:left 0
|
||||
:right 0})
|
||||
|
||||
(defn container-years
|
||||
[theme]
|
||||
{:border-width 1
|
||||
:overflow :hidden
|
||||
:padding-left 8
|
||||
:padding-right 7
|
||||
:padding-vertical 8
|
||||
:margin-left -1
|
||||
:margin-top -1
|
||||
:margin-bottom -1
|
||||
:border-style :dashed
|
||||
:border-top-left-radius 12
|
||||
:border-bottom-left-radius 12
|
||||
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-80 theme)})
|
|
@ -0,0 +1,49 @@
|
|||
(ns quo2.components.calendar.calendar.years-list.view
|
||||
(:require [react-native.core :as rn]
|
||||
[quo2.theme :as theme]
|
||||
[react-native.linear-gradient :as linear-gradient]
|
||||
[quo2.components.calendar.calendar.utils :as utils]
|
||||
[quo2.components.calendar.calendar-year.view :as calendar-year]
|
||||
[quo2.components.calendar.calendar.years-list.style :as style]))
|
||||
|
||||
(defn- year-view
|
||||
[year _ _ {:keys [selected-year on-press]}]
|
||||
[calendar-year/view
|
||||
{:selected? (= year selected-year)
|
||||
:on-press #(on-press year)}
|
||||
(str year)])
|
||||
|
||||
(defn- separator
|
||||
[]
|
||||
[rn/view {:style {:height 4}}])
|
||||
|
||||
(defn- footer
|
||||
[]
|
||||
[rn/view {:style {:height 32}}])
|
||||
|
||||
(defn- gradiant-overview
|
||||
[theme]
|
||||
[linear-gradient/linear-gradient
|
||||
{:colors [(style/gradient-start-color theme) (style/gradient-end-color theme)]
|
||||
:style style/gradient-view
|
||||
:start {:x 0 :y 0}
|
||||
:end {:x 0 :y 1}}])
|
||||
|
||||
(defn view-internal
|
||||
[{:keys [on-change-year year theme]}]
|
||||
[rn/view
|
||||
{:style (style/container-years theme)}
|
||||
[rn/flat-list
|
||||
{:data (utils/generate-years (utils/current-year))
|
||||
:key-fn str
|
||||
:list-key :years-list
|
||||
:inverted true
|
||||
:shows-vertical-scroll-indicator false
|
||||
:footer [footer]
|
||||
:separator [separator]
|
||||
:render-fn year-view
|
||||
:render-data {:selected-year year
|
||||
:on-press #(on-change-year %)}}]
|
||||
[gradiant-overview theme]])
|
||||
|
||||
(def view (theme/with-theme view-internal))
|
|
@ -0,0 +1,14 @@
|
|||
(ns quo2.components.calendar.calendar-day.component-spec
|
||||
(:require [quo2.components.calendar.calendar-day.view :as calendar-day]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "calendar-day component"
|
||||
(h/test "default render of calendar-day component"
|
||||
(h/render [calendar-day/view {} "25"])
|
||||
(h/is-truthy (h/query-by-text "25")))
|
||||
|
||||
(h/test "should not call on-press when state is :disabled"
|
||||
(let [on-press (h/mock-fn)]
|
||||
(h/render [calendar-day/view {:on-press on-press :state :disabled} "25"])
|
||||
(h/fire-event :press (h/query-by-text "25"))
|
||||
(h/was-not-called on-press))))
|
|
@ -0,0 +1,69 @@
|
|||
(ns quo2.components.calendar.calendar-day.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def wrapper
|
||||
{:flex 1
|
||||
:margin-vertical 2
|
||||
:justify-content :center
|
||||
:align-items :center})
|
||||
|
||||
(def container-base
|
||||
{:align-items :center
|
||||
:justify-content :center
|
||||
:border-radius 10
|
||||
:height 32
|
||||
:width 32})
|
||||
|
||||
(defn text-base
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/neutral-100 colors/white theme)
|
||||
:text-align :center})
|
||||
|
||||
(defn in-range-background
|
||||
[{:keys [in-range theme]}]
|
||||
(cond-> {:position :absolute
|
||||
:top 0
|
||||
:right 0
|
||||
:left 0
|
||||
:bottom 0}
|
||||
(= in-range :start)
|
||||
(assoc :background-color
|
||||
(colors/theme-colors colors/neutral-5 colors/neutral-80 theme)
|
||||
:left 20)
|
||||
|
||||
(= in-range :middle)
|
||||
(assoc :background-color
|
||||
(colors/theme-colors colors/neutral-5 colors/neutral-80 theme))
|
||||
|
||||
(= in-range :end)
|
||||
(assoc :background-color
|
||||
(colors/theme-colors colors/neutral-5 colors/neutral-80 theme)
|
||||
:right 20)))
|
||||
|
||||
(defn container
|
||||
[{:keys [state theme customization-color]}]
|
||||
(cond-> container-base
|
||||
(= state :default)
|
||||
(assoc :background-color colors/neutral-100-opa-0)
|
||||
|
||||
(= state :disabled)
|
||||
(assoc :opacity 0.3)
|
||||
|
||||
(= state :selected)
|
||||
(assoc :background-color (colors/custom-color-by-theme customization-color 50 60 nil nil theme))))
|
||||
|
||||
(defn text
|
||||
[{:keys [state theme]}]
|
||||
(cond-> (text-base theme)
|
||||
(= state :selected) (assoc :color colors/white)))
|
||||
|
||||
(defn indicator
|
||||
[{:keys [state theme customization-color]}]
|
||||
{:width 4
|
||||
:position :absolute
|
||||
:bottom 3
|
||||
:height 2
|
||||
:border-radius 8
|
||||
:background-color (if (= state :today)
|
||||
(colors/custom-color-by-theme customization-color 50 60 nil nil theme)
|
||||
colors/neutral-100-opa-0)})
|
|
@ -0,0 +1,31 @@
|
|||
(ns quo2.components.calendar.calendar-day.view
|
||||
(:require [react-native.core :as rn]
|
||||
[quo2.theme :as theme]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.calendar.calendar-day.style :as style]))
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [state in-range on-press customization-color theme]
|
||||
:or {state :default}}
|
||||
day]
|
||||
[rn/view {:style style/wrapper}
|
||||
[rn/view {:style (style/in-range-background {:in-range in-range :theme theme})}]
|
||||
[rn/touchable-opacity
|
||||
{:on-press on-press
|
||||
:style (style/container
|
||||
{:state state
|
||||
:theme theme
|
||||
:customization-color customization-color})
|
||||
:disabled (= state :disabled)}
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2
|
||||
:style (style/text {:state state :theme theme})}
|
||||
day]
|
||||
[rn/view
|
||||
{:style (style/indicator
|
||||
{:state state
|
||||
:theme theme
|
||||
:customization-color customization-color})}]]])
|
||||
|
||||
(def view (theme/with-theme view-internal))
|
|
@ -0,0 +1,14 @@
|
|||
(ns quo2.components.calendar.calendar-year.component-spec
|
||||
(:require [quo2.components.calendar.calendar-year.view :as calendar-year]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "calendar-year component"
|
||||
(h/test "default render of calendar-year component"
|
||||
(h/render [calendar-year/view {} "2023"])
|
||||
(h/is-truthy (h/query-by-text "2023")))
|
||||
|
||||
(h/test "should not call on-press when disabled"
|
||||
(let [on-press (h/mock-fn)]
|
||||
(h/render [calendar-year/view {:on-press on-press :disabled? true} "2023"])
|
||||
(h/fire-event :press (h/query-by-text "2023"))
|
||||
(h/was-not-called on-press))))
|
|
@ -0,0 +1,25 @@
|
|||
(ns quo2.components.calendar.calendar-year.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def container-base
|
||||
{:align-items :center
|
||||
:justify-content :center
|
||||
:border-radius 10
|
||||
:height 32
|
||||
:width 48})
|
||||
|
||||
(defn text-base
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)})
|
||||
|
||||
(defn container
|
||||
[{:keys [selected? disabled? theme]}]
|
||||
(cond-> container-base
|
||||
disabled? (assoc :opacity 0.3)
|
||||
selected? (assoc :background-color
|
||||
(colors/theme-colors colors/neutral-10 colors/neutral-70 theme))))
|
||||
|
||||
(defn text
|
||||
[{:keys [selected? theme]}]
|
||||
(cond-> (text-base theme)
|
||||
selected? (assoc :color (colors/theme-colors colors/neutral-100 colors/white theme))))
|
|
@ -0,0 +1,24 @@
|
|||
(ns quo2.components.calendar.calendar-year.view
|
||||
(:require [react-native.core :as rn]
|
||||
[quo2.theme :as theme]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.calendar.calendar-year.style :as style]))
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [selected? disabled? on-press theme]} year]
|
||||
[rn/touchable-opacity
|
||||
{:on-press on-press
|
||||
:style (style/container
|
||||
{:selected? selected?
|
||||
:disabled? disabled?
|
||||
:theme theme})
|
||||
:disabled disabled?}
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2
|
||||
:style (style/text
|
||||
{:selected? selected?
|
||||
:theme theme})}
|
||||
year]])
|
||||
|
||||
(def view (theme/with-theme view-internal))
|
|
@ -13,6 +13,9 @@
|
|||
quo2.components.buttons.predictive-keyboard.view
|
||||
quo2.components.buttons.slide-button.view
|
||||
quo2.components.browser.browser-input.view
|
||||
quo2.components.calendar.calendar.view
|
||||
quo2.components.calendar.calendar-day.view
|
||||
quo2.components.calendar.calendar-year.view
|
||||
quo2.components.code.snippet
|
||||
quo2.components.colors.color-picker.view
|
||||
quo2.components.common.separator.view
|
||||
|
@ -120,6 +123,11 @@
|
|||
;;;; BROWSER
|
||||
(def browser-input quo2.components.browser.browser-input.view/browser-input)
|
||||
|
||||
;;;; CALENDAR
|
||||
(def calendar quo2.components.calendar.calendar.view/view)
|
||||
(def calendar-day quo2.components.calendar.calendar-day.view/view)
|
||||
(def calendar-year quo2.components.calendar.calendar-year.view/view)
|
||||
|
||||
;;;; CODE
|
||||
(def snippet quo2.components.code.snippet/snippet)
|
||||
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
[quo2.components.buttons.button.component-spec]
|
||||
[quo2.components.buttons.predictive-keyboard.component-spec]
|
||||
[quo2.components.buttons.slide-button.component-spec]
|
||||
[quo2.components.calendar.calendar.component-spec]
|
||||
[quo2.components.calendar.calendar-day.component-spec]
|
||||
[quo2.components.calendar.calendar.month-picker.component-spec]
|
||||
[quo2.components.calendar.calendar-year.component-spec]
|
||||
[quo2.components.browser.browser-input.component-spec]
|
||||
[quo2.components.colors.color-picker.component-spec]
|
||||
[quo2.components.counter.component-spec]
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
(ns status-im2.contexts.quo-preview.calendar.calendar
|
||||
(:require [status-im2.contexts.quo-preview.preview :as preview]
|
||||
[react-native.core :as rn]
|
||||
[utils.datetime :as dt]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[reagent.core :as reagent]
|
||||
[quo2.core :as quo]))
|
||||
|
||||
(def descriptor
|
||||
[{:label "Start Date"
|
||||
:key :start-date
|
||||
:type :text}
|
||||
{:label "End Date"
|
||||
:key :end-date
|
||||
:type :text}])
|
||||
|
||||
(defn cool-preview
|
||||
[]
|
||||
(let [state (reagent/atom {:start-date nil :end-date nil})
|
||||
range (reagent/atom {:start-date nil :end-date nil})]
|
||||
(fn
|
||||
[]
|
||||
[rn/touchable-without-feedback
|
||||
{:on-press rn/dismiss-keyboard!}
|
||||
[rn/view {:style {:flex 1}}
|
||||
[preview/customizer state descriptor]
|
||||
[rn/view {:style {:padding 19 :flex-grow 2}}
|
||||
[quo/calendar
|
||||
{:start-date (:start-date @range)
|
||||
:end-date (:end-date @range)
|
||||
:on-change (fn [new-range]
|
||||
(reset! state
|
||||
{:start-date (dt/format-date (:start-date new-range))
|
||||
:end-date (dt/format-date (:end-date new-range))})
|
||||
(reset! range new-range))}]]]])))
|
||||
|
||||
(defn preview-calendar
|
||||
[]
|
||||
[rn/view
|
||||
{:style {:background-color (colors/theme-colors colors/white colors/neutral-95)
|
||||
:flex 1}}
|
||||
[cool-preview]])
|
|
@ -0,0 +1,45 @@
|
|||
(ns status-im2.contexts.quo-preview.calendar.calendar-day
|
||||
(:require [status-im2.contexts.quo-preview.preview :as preview]
|
||||
[react-native.core :as rn]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[reagent.core :as reagent]
|
||||
[quo2.core :as quo]))
|
||||
|
||||
(def descriptor
|
||||
[{:label "State:"
|
||||
:key :state
|
||||
:type :select
|
||||
:options [{:key :default
|
||||
:value "Default"}
|
||||
{:key :selected
|
||||
:value "Selected"}
|
||||
{:key :disabled
|
||||
:value "Disabled"}
|
||||
{:key :today
|
||||
:value "Today"}]}])
|
||||
|
||||
(defn cool-preview
|
||||
[]
|
||||
(let [state (reagent/atom
|
||||
{:state :default})]
|
||||
(fn
|
||||
[]
|
||||
[rn/touchable-without-feedback
|
||||
{:on-press rn/dismiss-keyboard!}
|
||||
[rn/view
|
||||
[preview/customizer state descriptor]
|
||||
[rn/view
|
||||
{:padding-vertical 60
|
||||
:align-items :center}
|
||||
[quo/calendar-day (assoc @state :customization-color :blue) 12]]]])))
|
||||
|
||||
(defn preview-calendar-day
|
||||
[]
|
||||
[rn/view
|
||||
{:style {:background-color (colors/theme-colors colors/white colors/neutral-95)
|
||||
:flex 1}}
|
||||
[rn/flat-list
|
||||
{:style {:flex 1}
|
||||
:keyboard-should-persist-taps :always
|
||||
:header [cool-preview]
|
||||
:key-fn str}]])
|
|
@ -0,0 +1,39 @@
|
|||
(ns status-im2.contexts.quo-preview.calendar.calendar-year
|
||||
(:require [status-im2.contexts.quo-preview.preview :as preview]
|
||||
[react-native.core :as rn]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[reagent.core :as reagent]
|
||||
[quo2.core :as quo]))
|
||||
|
||||
(def descriptor
|
||||
[{:label "Selected?"
|
||||
:key :selected?
|
||||
:type :boolean}
|
||||
{:label "Disabled?"
|
||||
:key :disabled?
|
||||
:type :boolean}])
|
||||
|
||||
(defn cool-preview
|
||||
[]
|
||||
(let [state (reagent/atom {:selected? false :disabled? false})]
|
||||
(fn
|
||||
[]
|
||||
[rn/touchable-without-feedback
|
||||
{:on-press rn/dismiss-keyboard!}
|
||||
[rn/view
|
||||
[preview/customizer state descriptor]
|
||||
[rn/view
|
||||
{:padding-vertical 60
|
||||
:align-items :center}
|
||||
[quo/calendar-year @state "2023"]]]])))
|
||||
|
||||
(defn preview-calendar-year
|
||||
[]
|
||||
[rn/view
|
||||
{:style {:background-color (colors/theme-colors colors/white colors/neutral-95)
|
||||
:flex 1}}
|
||||
[rn/flat-list
|
||||
{:style {:flex 1}
|
||||
:keyboard-should-persist-taps :always
|
||||
:header [cool-preview]
|
||||
:key-fn str}]])
|
|
@ -19,6 +19,9 @@
|
|||
[status-im2.contexts.quo-preview.buttons.slide-button :as slide-button]
|
||||
[status-im2.contexts.quo-preview.buttons.dynamic-button :as dynamic-button]
|
||||
[status-im2.contexts.quo-preview.buttons.predictive-keyboard :as predictive-keyboard]
|
||||
[status-im2.contexts.quo-preview.calendar.calendar :as calendar]
|
||||
[status-im2.contexts.quo-preview.calendar.calendar-day :as calendar-day]
|
||||
[status-im2.contexts.quo-preview.calendar.calendar-year :as calendar-year]
|
||||
[status-im2.contexts.quo-preview.browser.browser-input :as browser-input]
|
||||
[status-im2.contexts.quo-preview.code.snippet :as code-snippet]
|
||||
[status-im2.contexts.quo-preview.colors.color-picker :as color-picker]
|
||||
|
@ -145,6 +148,15 @@
|
|||
:browser [{:name :browser-input
|
||||
:options {:topBar {:visible false}}
|
||||
:component browser-input/preview-browser-input}]
|
||||
:calendar [{:name :calendar
|
||||
:options {:topBar {:visible true}}
|
||||
:component calendar/preview-calendar}
|
||||
{:name :calendar-day
|
||||
:options {:topBar {:visible true}}
|
||||
:component calendar-day/preview-calendar-day}
|
||||
{:name :calendar-year
|
||||
:options {:topBar {:visible true}}
|
||||
:component calendar-year/preview-calendar-year}]
|
||||
:code [{:name :snippet
|
||||
:options {:topBar {:visible true}}
|
||||
:component code-snippet/preview-code-snippet}]
|
||||
|
|
|
@ -204,6 +204,10 @@
|
|||
[mock]
|
||||
(.toHaveBeenCalled (js/expect mock)))
|
||||
|
||||
(defn was-called-with
|
||||
[mock expected-arg]
|
||||
(.toHaveBeenCalledWith (js/expect mock) expected-arg))
|
||||
|
||||
(defn was-called-times
|
||||
[^js mock number-of-times]
|
||||
(.toHaveBeenCalledTimes (js/expect mock) number-of-times))
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
(defn now [] (t/now))
|
||||
|
||||
(def weekday-names ["su" "mo" "tu" "we" "th" "fr" "sa"])
|
||||
|
||||
(def ^:const int->weekday
|
||||
"Maps the corresponding string representation of a weekday
|
||||
By it's numeric index as in cljs-time"
|
||||
|
@ -93,6 +95,12 @@
|
|||
[^js locsym]
|
||||
(nth (.-DATEFORMATS locsym) 2))
|
||||
|
||||
(defn format-date
|
||||
[date]
|
||||
(if date
|
||||
(t.format/unparse (t.format/formatter "dd/MM/yyyy") date)
|
||||
""))
|
||||
|
||||
;;;; Datetime formats
|
||||
(defn- medium-date-time-format
|
||||
[locsym]
|
||||
|
@ -113,6 +121,10 @@
|
|||
(def short-date-with-time-fmt (get-formatter-fn short-date-format-with-time))
|
||||
(def datetime-within-one-week-fmt (get-formatter-fn datetime-within-one-week-format))
|
||||
|
||||
(def format-long-month
|
||||
(memoize (fn [month]
|
||||
(.format ^js ((get-formatter-fn (constantly "MMMM")))
|
||||
(t/date-time 1970 month)))))
|
||||
;;;; Utilities
|
||||
(defn previous-years?
|
||||
[datetime]
|
||||
|
|
|
@ -190,3 +190,11 @@
|
|||
"it"
|
||||
#'utils.datetime/medium-date-time-format)]
|
||||
(is (= (datetime/day-relative epoch) "01 gen 1970, 12:00:00 AM")))))
|
||||
|
||||
(deftest format-long-month-test
|
||||
(testing "returns correct month string"
|
||||
(is (= "January" (datetime/format-long-month 1)))
|
||||
(is (= "February" (datetime/format-long-month 2)))
|
||||
(is (= "March" (datetime/format-long-month 3)))
|
||||
(is (= "April" (datetime/format-long-month 4)))
|
||||
(is (= "December" (datetime/format-long-month 12)))))
|
||||
|
|
|
@ -2221,6 +2221,13 @@
|
|||
"all-messages": "All messages",
|
||||
"muted-until": "Muted until {{duration}}",
|
||||
"until-you-turn-it-back-on": "you turn it back on",
|
||||
"mo": "Mo",
|
||||
"tu": "Tu",
|
||||
"we": "We",
|
||||
"th": "Th",
|
||||
"fr": "Fr",
|
||||
"sa": "Sa",
|
||||
"su": "Su",
|
||||
"mon": "Mon",
|
||||
"tue": "Tue",
|
||||
"wed": "Wed",
|
||||
|
|
Loading…
Reference in New Issue