Merge pull request #236 from pablodip/sort-and-filter

Sorting and filtering for open bounties
This commit is contained in:
P 2018-02-01 10:53:52 +01:00 committed by GitHub
commit 34db7df1fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 742 additions and 51 deletions

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<g fill="none" fill-rule="evenodd">
<rect width="20" height="20" fill="#000" opacity=".5" rx="6"/>
<path fill="#57A7ED" d="M10 8.812L6.436 5.247a.839.839 0 0 0-1.19 0 .839.839 0 0 0 .001 1.189L8.812 10l-3.565 3.564a.839.839 0 0 0 0 1.19c.33.33.86.327 1.189-.001L10 11.188l3.564 3.565c.33.33.861.328 1.19 0a.839.839 0 0 0-.001-1.189L11.188 10l3.565-3.564a.839.839 0 0 0 0-1.19.839.839 0 0 0-1.189.001L10 8.812z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 533 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="7" viewBox="0 0 12 7">
<path fill="#8D99A4" fill-rule="evenodd" d="M6 3.828L2.462.291A1.003 1.003 0 0 0 1.05.293a.996.996 0 0 0-.002 1.412l4.247 4.247c.192.192.447.289.702.29a.981.981 0 0 0 .708-.29l4.247-4.247A1.003 1.003 0 0 0 10.95.293.996.996 0 0 0 9.538.29L6 3.828z"/>
</svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@ -422,6 +422,7 @@ SELECT
i.payout_hash AS payout_hash,
i.payout_receipt AS payout_receipt,
i.updated AS updated,
i.created_at AS created_at,
r.owner AS repo_owner,
r.owner_avatar_url AS repo_owner_avatar_url,
r.repo AS repo_name,

View File

@ -155,7 +155,8 @@
:value_usd :value-usd
:claim_count :claim-count
:balance_eth :balance-eth
:user_has_address :user-has-address}]
:user_has_address :user-has-address
:created_at :created-at}]
(map #(-> %
(rename-keys renames)
(update :value-usd usd-decimal->str)

View File

@ -1,26 +1,30 @@
(ns commiteth.bounties
(:require [re-frame.core :as rf]
[reagent.core :as r]
(:require [reagent.core :as r]
[re-frame.core :as rf]
[commiteth.common :refer [moment-timestamp
display-data-page
items-per-page
issue-url]]))
issue-url]]
[commiteth.handlers :as handlers]
[commiteth.db :as db]
[commiteth.ui-model :as ui-model]
[commiteth.subscriptions :as subs]))
(defn bounty-item [bounty]
(let [{avatar-url :repo_owner_avatar_url
owner :repo-owner
repo-name :repo-name
issue-title :issue-title
(let [{avatar-url :repo_owner_avatar_url
owner :repo-owner
repo-name :repo-name
issue-title :issue-title
issue-number :issue-number
updated :updated
tokens :tokens
balance-eth :balance-eth
value-usd :value-usd
claim-count :claim-count} bounty
full-repo (str owner "/" repo-name)
repo-url (str "https://github.com/" full-repo)
repo-link [:a {:href repo-url} full-repo]
updated :updated
tokens :tokens
balance-eth :balance-eth
value-usd :value-usd
claim-count :claim-count} bounty
full-repo (str owner "/" repo-name)
repo-url (str "https://github.com/" full-repo)
repo-link [:a {:href repo-url} full-repo]
issue-link [:a
{:href (issue-url owner repo-name issue-number)}
issue-title]]
@ -40,35 +44,185 @@
[:span.usd-value-label "Value "] [:span.usd-balance-label (str "$" value-usd)]
(when (> claim-count 0)
[:span.open-claims-label (str claim-count " open claim"
(when (> claim-count 1) "s"))]) ]]
(when (> claim-count 1) "s"))])]]
[:div.open-bounty-item-icon
[:div.ui.tiny.circular.image
[:img {:src avatar-url}]]]]))
(defn bounties-list [{:keys [items item-count page-number total-count]
:as bounty-page-data}
(defn bounties-filter-tooltip-value-input-view [label tooltip-open? opts]
[:div.open-bounties-filter-element-tooltip-value-input-container
[:div.:input.open-bounties-filter-element-tooltip-value-input-label
label]
[:input.open-bounties-filter-element-tooltip-value-input
{:type "range"
:min (:min opts)
:max (:max opts)
:step (:step opts)
:value (:current-val opts)
:on-change (when-let [f (:on-change-val opts)]
#(-> % .-target .-value int f))
:on-focus #(reset! tooltip-open? true)}]])
(defmulti bounties-filter-tooltip-view #(-> %2 ::ui-model/bounty-filter-type.category))
(defmethod bounties-filter-tooltip-view ::ui-model/bounty-filter-type-category|range
[filter-type filter-type-def current-filter-value tooltip-open?]
(let [default-min (::ui-model/bounty-filter-type.min-val filter-type-def)
default-max (::ui-model/bounty-filter-type.max-val filter-type-def)
common-range-opts {:min default-min :max default-max}
current-min (or (first current-filter-value) default-min)
current-max (or (second current-filter-value) default-max)
on-change-fn (fn [min-val max-val]
(rf/dispatch [::handlers/set-open-bounty-filter-type
filter-type
[(min min-val default-max)
(max max-val default-min)]]))
on-min-change-fn (fn [new-min]
(let [new-max (max current-max (min default-max new-min))]
(on-change-fn new-min new-max)))
on-max-change-fn (fn [new-max]
(let [new-min (min current-min (max default-min new-max))]
(on-change-fn new-min new-max)))]
[:div
"$0 - $1000+"
[bounties-filter-tooltip-value-input-view "Min" tooltip-open? (merge common-range-opts
{:current-val current-min
:on-change-val on-min-change-fn})]
[bounties-filter-tooltip-value-input-view "Max" tooltip-open? (merge common-range-opts
{:current-val current-max
:on-change-val on-max-change-fn})]]))
(defmethod bounties-filter-tooltip-view ::ui-model/bounty-filter-type-category|single-static-option
[filter-type filter-type-def current-filter-value tooltip-open?]
[:div.open-bounties-filter-list
(for [[option-type option-text] (::ui-model/bounty-filter-type.options filter-type-def)]
^{:key (str option-type)}
[:div.open-bounties-filter-list-option
(merge {:on-click #(do (rf/dispatch [::handlers/set-open-bounty-filter-type
filter-type
option-type])
(reset! tooltip-open? false))}
(when (= option-type current-filter-value)
{:class "active"}))
option-text])])
(defmethod bounties-filter-tooltip-view ::ui-model/bounty-filter-type-category|multiple-dynamic-options
[filter-type filter-type-def current-filter-value tooltip-open?]
(let [options (rf/subscribe [(::ui-model/bounty-filter-type.re-frame-subs-key-for-options filter-type-def)])]
[:div.open-bounties-filter-list
(for [option @options]
(let [active? (boolean (and current-filter-value (current-filter-value option)))]
^{:key (str option)}
[:div.open-bounties-filter-list-option-checkbox
[:label
{:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type
filter-type
(cond
(and active? (= #{option} current-filter-value)) nil
active? (disj current-filter-value option)
:else (into #{option} current-filter-value))])
:tab-index 0
:on-focus #(do (.stopPropagation %) (reset! tooltip-open? true))}
[:input
{:type "checkbox"
:checked active?
:on-focus #(reset! tooltip-open? true)}]
[:div.text option]]]))]))
(defn bounty-filter-view [filter-type current-filter-value]
(let [tooltip-open? (r/atom false)]
(fn [filter-type current-filter-value]
[:div.open-bounties-filter-element-container
{:tab-index 0
:on-focus #(reset! tooltip-open? true)
:on-blur #(reset! tooltip-open? false)}
[:div.open-bounties-filter-element
{:on-mouse-down #(swap! tooltip-open? not)
:class (when (or current-filter-value @tooltip-open?)
"open-bounties-filter-element-active")}
[:div.text
(if current-filter-value
(ui-model/bounty-filter-value->short-text filter-type current-filter-value)
(ui-model/bounty-filter-type->name filter-type))]
(when current-filter-value
[:div.remove-container
{:tab-index 0
:on-focus #(.stopPropagation %)}
[:img.remove
{:src "bounty-filter-remove.svg"
:on-mouse-down (fn [e]
(.stopPropagation e)
(rf/dispatch [::handlers/set-open-bounty-filter-type filter-type nil])
(reset! tooltip-open? false))}]])]
(when @tooltip-open?
[:div.open-bounties-filter-element-tooltip
[bounties-filter-tooltip-view
filter-type
(ui-model/bounty-filter-types-def filter-type)
current-filter-value
tooltip-open?]])])))
(defn bounty-filters-view []
(let [current-filters (rf/subscribe [::subs/open-bounties-filters])]
[:div.open-bounties-filter
; doall because derefs are not supported in lazy seqs: https://github.com/reagent-project/reagent/issues/18
(doall
(for [filter-type ui-model/bounty-filter-types]
^{:key (str filter-type)}
[bounty-filter-view
filter-type
(get @current-filters filter-type)]))]))
(defn bounties-sort-view []
(let [open? (r/atom false)]
(fn []
(let [current-sorting (rf/subscribe [::subs/open-bounties-sorting-type])]
[:div.open-bounties-sort
{:tab-index 0
:on-blur #(reset! open? false)}
[:div.open-bounties-sort-element
{:on-click #(swap! open? not)}
(ui-model/bounty-sorting-type->name @current-sorting)
[:div.icon-forward-white-box
[:img
{:src "icon-forward-white.svg"}]]]
(when @open?
[:div.open-bounties-sort-element-tooltip
(for [sorting-type (keys ui-model/bounty-sorting-types-def)]
^{:key (str sorting-type)}
[:div.open-bounties-sort-type
{:on-click #(do
(reset! open? false)
(rf/dispatch [::handlers/set-open-bounties-sorting-type sorting-type]))}
(ui-model/bounty-sorting-type->name sorting-type)])])]))))
(defn bounties-list [{:keys [items item-count page-number total-count]
:as bounty-page-data}
container-element]
(if (empty? items)
[:div.view-no-data-container
[:p "No recent activity yet"]]
[:p "No matching bounties found."]]
[:div
(let [left (inc (* (dec page-number) items-per-page))
(let [left (inc (* (dec page-number) items-per-page))
right (dec (+ left item-count))]
[:div.item-counts-label
[:span (str "Showing " left "-" right " of " total-count)]])
(display-data-page bounty-page-data bounty-item container-element)]))
(defn bounties-page []
(let [bounty-page-data (rf/subscribe [:open-bounties-page])
(let [bounty-page-data (rf/subscribe [:open-bounties-page])
open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]])
container-element (atom nil)]
container-element (atom nil)]
(fn []
(if @open-bounties-loading?
[:div.view-loading-container
[:div.ui.active.inverted.dimmer
[:div.ui.text.loader.view-loading-label "Loading"]]]
[:div.ui.container.open-bounties-container
{:ref #(reset! container-element %1)}
{:ref #(reset! container-element %1)}
[:div.open-bounties-header "Bounties"]
[:div.open-bounties-filter-and-sort
[bounty-filters-view]
[bounties-sort-view]]
[bounties-list @bounty-page-data container-element]]))
))

View File

@ -1,14 +1,22 @@
(ns commiteth.db)
(ns commiteth.db
(:require [commiteth.ui-model :as ui-model]))
(def default-db
{:page :bounties
:user nil
:repos-loading? false
:repos {}
:activity-feed-loading? false
:open-bounties-loading? false
:open-bounties []
:page-number 1
:owner-bounties {}
:top-hunters []
:activity-feed []})
{:page :bounties
:user nil
:repos-loading? false
:repos {}
:activity-feed-loading? false
:open-bounties-loading? false
:open-bounties []
:page-number 1
:bounty-page-number 1
:activity-page-number 1
::open-bounties-sorting-type ::ui-model/bounty-sorting-type|most-recent
::open-bounties-filters {::ui-model/bounty-filter-type|value nil
::ui-model/bounty-filter-type|currency nil
::ui-model/bounty-filter-type|date nil
::ui-model/bounty-filter-type|owner nil}
:owner-bounties {}
:top-hunters []
:activity-feed []})

View File

@ -11,7 +11,8 @@
[cljs-web3.eth :as web3-eth]
[akiroz.re-frame.storage
:as rf-storage
:refer [reg-co-fx!]]))
:refer [reg-co-fx!]]
[commiteth.ui-model :as ui-model]))
(rf-storage/reg-co-fx! :commiteth-sob {:fx :store
@ -63,10 +64,12 @@
(reg-event-db
:set-active-page
(fn [db [_ page]]
(assoc db :page page
:page-number 1)))
:set-active-page
(fn [db [_ page]]
(assoc db :page page
:page-number 1
::db/open-bounties-filters {}
::db/open-bounties-sorting-type ::ui-model/bounty-sorting-type|most-recent)))
(reg-event-db
:set-page-number
@ -457,3 +460,16 @@
(fn [db [_]]
(.removeEventListener js/window "click" close-dropdown)
(assoc db :user-dropdown-open? false)))
(reg-event-db
::set-open-bounties-sorting-type
(fn [db [_ sorting-type]]
(merge db {::db/open-bounties-sorting-type sorting-type
:bounty-page-number 1})))
(reg-event-db
::set-open-bounty-filter-type
(fn [db [_ filter-type filter-value]]
(-> db
(assoc-in [::db/open-bounties-filters filter-type] filter-value)
(assoc :bounty-page-number 1))))

View File

@ -1,10 +1,12 @@
(ns commiteth.subscriptions
(:require [re-frame.core :refer [reg-sub]]
[commiteth.db :as db]
[commiteth.ui-model :as ui-model]
[commiteth.common :refer [items-per-page]]))
(reg-sub
:db
(fn [db _] db))
:db
(fn [db _] db))
(reg-sub
:page
@ -43,7 +45,7 @@
(reg-sub
:open-bounties-page
:<- [:open-bounties]
:<- [::filtered-and-sorted-open-bounties]
:<- [:page-number]
(fn [[open-bounties page-number] _]
(let [total-count (count open-bounties)
@ -105,16 +107,56 @@
(get-in db path)))
(reg-sub
:usage-metrics
(fn [db _]
(:usage-metrics db)))
:usage-metrics
(fn [db _]
(:usage-metrics db)))
(reg-sub
:metrics-loading?
(fn [db _]
(:metrics-loading? db)))
:metrics-loading?
(fn [db _]
(:metrics-loading? db)))
(reg-sub
:user-dropdown-open?
:user-dropdown-open?
(fn [db _]
(:user-dropdown-open? db)))
(reg-sub
::open-bounties-sorting-type
(fn [db _]
(::db/open-bounties-sorting-type db)))
(reg-sub
::open-bounties-filters
(fn [db _]
(::db/open-bounties-filters db)))
(reg-sub
::open-bounties-owners
:<- [:open-bounties]
(fn [open-bounties _]
(->> open-bounties
(map :repo-owner)
set)))
(reg-sub
::open-bounties-currencies
:<- [:open-bounties]
(fn [open-bounties _]
(let [token-ids (->> open-bounties
(map :tokens)
(mapcat keys)
(filter identity)
set)]
(into #{"ETH"} token-ids))))
(reg-sub
::filtered-and-sorted-open-bounties
:<- [:open-bounties]
:<- [::open-bounties-filters]
:<- [::open-bounties-sorting-type]
(fn [[open-bounties filters sorting-type] _]
(cond->> open-bounties
true (ui-model/filter-bounties filters)
sorting-type (ui-model/sort-bounties-by-sorting-type sorting-type)
true vec)))

View File

@ -0,0 +1,164 @@
(ns commiteth.ui-model
(:require [clojure.set :as set]
[cljs-time.core :as t]
[cljs-time.coerce :as t-coerce]
[cljs-time.format :as t-format]))
;;;; bounty sorting types
(def bounty-sorting-types-def
{::bounty-sorting-type|most-recent {::bounty-sorting-type.name "Most recent"
::bounty-sorting-type.sort-key-fn (fn [bounty]
(:created-at bounty))
::bounty-sorting-type.sort-comparator-fn compare}
::bounty-sorting-type|lowest-value {::bounty-sorting-type.name "Lowest value"
::bounty-sorting-type.sort-key-fn (fn [bounty]
(js/parseFloat (:value-usd bounty)))
::bounty-sorting-type.sort-comparator-fn compare}
::bounty-sorting-type|highest-value {::bounty-sorting-type.name "Highest value"
::bounty-sorting-type.sort-key-fn (fn [bounty]
(js/parseFloat (:value-usd bounty)))
::bounty-sorting-type.sort-comparator-fn (comp - compare)}
::bounty-sorting-type|owner {::bounty-sorting-type.name "Owner"
::bounty-sorting-type.sort-key-fn (fn [bounty]
(:repo-owner bounty))
::bounty-sorting-type.sort-comparator-fn compare}})
(defn bounty-sorting-type->name [sorting-type]
(-> bounty-sorting-types-def (get sorting-type) ::bounty-sorting-type.name))
(defn sort-bounties-by-sorting-type [sorting-type bounties]
(let [keyfn (-> bounty-sorting-types-def
sorting-type
::bounty-sorting-type.sort-key-fn)
comparator (-> bounty-sorting-types-def
sorting-type
::bounty-sorting-type.sort-comparator-fn)]
(sort-by keyfn comparator bounties)))
;;;; bounty filter types
(def bounty-filter-type-date-options-def {::bounty-filter-type-date-option|last-week "Last week"
::bounty-filter-type-date-option|last-month "Last month"
::bounty-filter-type-date-option|last-3-months "Last 3 months"})
(def bounty-filter-type-date-options (keys bounty-filter-type-date-options-def))
(defn bounty-filter-type-date-option->name [option]
(bounty-filter-type-date-options-def option))
(def bounty-filter-type-date-pre-predicate-value-processor
"It converts an option of the filter type date to a cljs-time interval in which
that option is valid, so that you can check `cljs-time.core.within?` against that
interval and know if a `cljs-time` date is valid for that filter type date option."
(fn [filter-value]
(let [filter-from (condp = filter-value
::bounty-filter-type-date-option|last-week (t/minus (t/now) (t/weeks 1))
::bounty-filter-type-date-option|last-month (t/minus (t/now) (t/months 1))
::bounty-filter-type-date-option|last-3-months (t/minus (t/now) (t/months 3)))]
(t/interval filter-from (t/now)))))
(def bounty-filter-type-date-predicate
(fn [filter-value-interval bounty]
(when-let [created-at-inst (:created-at bounty)]
(let [created-at-date (-> created-at-inst inst-ms t-coerce/from-long)]
(t/within? filter-value-interval created-at-date)))))
(def bounty-filter-type-claims-options-def {::bounty-filter-type-claims-option|no-claims "With no claims"})
(def bounty-filter-type-claims-options (keys bounty-filter-type-claims-options-def))
(defn bounty-filter-type-claims-option->name [option]
(bounty-filter-type-claims-options-def option))
(def bounty-filter-types-def
{::bounty-filter-type|value
{::bounty-filter-type.name "Value"
::bounty-filter-type.category ::bounty-filter-type-category|range
::bounty-filter-type.min-val 0
::bounty-filter-type.max-val 1000
::bounty-filter-type.predicate (fn [filter-value bounty]
(let [min-val (first filter-value)
max-val (second filter-value)]
(<= min-val (:value-usd bounty) max-val)))}
::bounty-filter-type|currency
{::bounty-filter-type.name "Currency"
::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options
::bounty-filter-type.re-frame-subs-key-for-options :commiteth.subscriptions/open-bounties-currencies
::bounty-filter-type.predicate (fn [filter-value bounty]
(and (or (not-any? #{"ETH"} filter-value)
(< 0 (:balance-eth bounty)))
(set/subset? (->> filter-value (remove #{"ETH"}) set)
(-> bounty :tokens keys set))))}
::bounty-filter-type|date
{::bounty-filter-type.name "Date"
::bounty-filter-type.category ::bounty-filter-type-category|single-static-option
::bounty-filter-type.options bounty-filter-type-date-options-def
::bounty-filter-type.pre-predicate-value-processor bounty-filter-type-date-pre-predicate-value-processor
::bounty-filter-type.predicate bounty-filter-type-date-predicate}
::bounty-filter-type|owner
{::bounty-filter-type.name "Owner"
::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options
::bounty-filter-type.re-frame-subs-key-for-options :commiteth.subscriptions/open-bounties-owners
::bounty-filter-type.predicate (fn [filter-value bounty]
(->> filter-value
(some #{(:repo-owner bounty)})
boolean))}
::bounty-filter-type|claims
{::bounty-filter-type.name "Claims"
::bounty-filter-type.category ::bounty-filter-type-category|single-static-option
::bounty-filter-type.options bounty-filter-type-claims-options-def
::bounty-filter-type.predicate (fn [filter-value bounty]
(condp = filter-value
::bounty-filter-type-claims-option|no-claims
(= 0 (:claim-count bounty))))}})
(def bounty-filter-types (keys bounty-filter-types-def))
(defn bounty-filter-type->name [filter-type]
(-> bounty-filter-types-def (get filter-type) ::bounty-filter-type.name))
(defn bounty-filter-value->short-text [filter-type filter-value]
(cond
(= filter-type ::bounty-filter-type|date)
(bounty-filter-type-date-option->name filter-value)
(#{::bounty-filter-type|owner
::bounty-filter-type|currency} filter-type)
(str (bounty-filter-type->name filter-type) " (" (count filter-value) ")")
(= filter-type ::bounty-filter-type|value)
(str "$" (first filter-value) "-$" (second filter-value))
(= filter-type ::bounty-filter-type|claims)
(bounty-filter-type-claims-option->name filter-value)
:else
(str filter-type " with val " filter-value)))
(defn- bounty-filter-values-by-type->predicates [filters-by-type]
"It receives a map with filter types as keys and filter values as values and
returns a lazy seq of predicates, one for each pair of filter type and value.
Those predicate can receive a bounty and tell whether that bounty passes
the filter type with that filter value. It removes filter types with a `nil`
filter value."
(->> filters-by-type
; used `nil?` because a valid filter value can be `false`
(remove #(nil? (val %)))
(map (fn [[filter-type filter-value]]
(let [filter-type-def (bounty-filter-types-def filter-type)
pred (::bounty-filter-type.predicate filter-type-def)
pre-pred-processor (::bounty-filter-type.pre-predicate-value-processor filter-type-def)
filter-value (cond-> filter-value
pre-pred-processor pre-pred-processor)]
(partial pred filter-value))))))
(defn filter-bounties [filters-by-type bounties]
(let [filter-preds (bounty-filter-values-by-type->predicates filters-by-type)
filters-pred (fn [bounty]
(every? #(% bounty) filter-preds))]
(cond->> bounties
(not-empty filter-preds) (filter filters-pred))))

View File

@ -413,6 +413,302 @@
font-weight: 500;
color: #42505c;
}
.open-bounties-filter-and-sort {
margin-top: 24px;
display: flex;
justify-content: space-between;
}
@media (max-width: 767px) {
.open-bounties-filter-and-sort {
display: none;
}
}
.open-bounties-filter {
display: flex;
}
.open-bounties-filter-element {
font-family: "PostGrotesk-Book";
font-size: 15px;
font-weight: 500;
line-height: 1.0;
color: #8d99a4;
//padding: 8px 12px;
border-radius: 8px;
border: solid 1px rgba(151, 151, 151, 0.2);
margin-right: 10px;
position: relative;
display: flex;
.text {
margin: 8px 12px;
}
.remove-container {
display: flex;
}
.remove {
margin-left: -6px;
margin-right: 5px;
}
}
.open-bounties-filter-element:hover {
cursor: pointer;
}
.open-bounties-filter-element-active
{
background-color: #57a7ed;
color: #ffffff;
}
.open-bounties-filter-element-container:focus {
outline: none;
}
.open-bounties-filter-element-tooltip {
position: absolute;
min-width: 227px;
margin-top: 12px;
padding: 24px 16px;
border-radius: 10px;
background-color: #ffffff;
box-shadow: 0 15px 12px 0 rgba(161, 174, 182, 0.53), 0 0 38px 0 rgba(0, 0, 0, 0.05);
font-family: "PostGrotesk-Book";
font-size: 16px;
line-height: 1.5;
z-index: 999;
.open-bounties-filter-element-tooltip-value-input-container {
display: flex;
margin-top: 10px;
}
.open-bounties-filter-element-tooltip-value-input-label {
width: 60px;
}
.open-bounties-filter-element-tooltip-value-input {
margin-top: 24px;
width: 288.5px;
height: 4px;
background-color: #55a5ea;
}
// generated with http://danielstern.ca/range.css/#/
input[type=range] {
-webkit-appearance: none;
//width: 100%;
margin: 14.5px 0;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
background: #55a5ea;
border-radius: 0px;
border: 0px solid #010101;
}
input[type=range]::-webkit-slider-thumb {
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
border: 0px solid #000000;
height: 33px;
width: 33px;
border-radius: 50px;
background: #55a5ea;
cursor: pointer;
-webkit-appearance: none;
margin-top: -14.5px;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #5aa7eb;
}
input[type=range]::-moz-range-track {
width: 100%;
height: 4px;
cursor: pointer;
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
background: #55a5ea;
border-radius: 0px;
border: 0px solid #010101;
}
input[type=range]::-moz-range-thumb {
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
border: 0px solid #000000;
height: 33px;
width: 33px;
border-radius: 50px;
background: #55a5ea;
cursor: pointer;
}
input[type=range]::-ms-track {
width: 100%;
height: 4px;
cursor: pointer;
background: transparent;
border-color: transparent;
color: transparent;
}
input[type=range]::-ms-fill-lower {
background: #50a3e9;
border: 0px solid #010101;
border-radius: 0px;
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
}
input[type=range]::-ms-fill-upper {
background: #55a5ea;
border: 0px solid #010101;
border-radius: 0px;
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
}
input[type=range]::-ms-thumb {
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
border: 0px solid #000000;
height: 33px;
width: 33px;
border-radius: 50px;
background: #55a5ea;
cursor: pointer;
height: 4px;
}
input[type=range]:focus::-ms-fill-lower {
background: #55a5ea;
}
input[type=range]:focus::-ms-fill-upper {
background: #5aa7eb;
}
.open-bounties-filter-list {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.open-bounties-filter-list-option {
font-family: "PostGrotesk-Book";
font-size: 15px;
font-weight: 500;
line-height: 1.0;
color: #42505c;
padding: 8px 12px;
border-radius: 8px;
border: solid 1px rgba(151, 151, 151, 0.2);
}
.open-bounties-filter-list-option.active {
background-color: #57a7ed;
color: #ffffff;
}
.open-bounties-filter-list-option:not(:first-child) {
margin-top: 8px;
}
.open-bounties-filter-list-option:hover {
cursor: pointer;
background-color: #57a7ed;
color: #ffffff;
}
.open-bounties-filter-list-option-checkbox {
label {
display: flex;
align-items: baseline;
}
label:hover {
cursor: pointer;
}
label:focus {
outline: none;
}
input {
background: green;
transform: scale(1.3);
}
input:hover {
cursor: pointer;
}
.text {
font-size: 15px;
font-weight: 500;
line-height: 1.0;
margin-left: 12px;
}
}
.open-bounties-filter-list-option-checkbox:not(:first-child) {
margin-top: 12px;
}
}
.open-bounties-sort {
position: relative;
}
.open-bounties-sort:focus {
outline: none;
}
.open-bounties-sort-element {
display: flex;
font-size: 15px;
font-weight: 500;
line-height: 1.0;
color: #8d99a4;
padding: 8px 12px;
display: flex;
align-items: center;
}
.open-bounties-sort-element:hover {
cursor: pointer;
}
.open-bounties-sort-element-tooltip {
position: absolute;
right: 0;
//padding: 16px 0;
min-width: 200px;
border-radius: 8px;
background-color: #1e3751;
font-family: "PostGrotesk-Book";
font-size: 15px;
line-height: 1.0;
text-align: left;
color: #ffffff;
z-index: 999;
}
.open-bounties-sort-type {
padding: 19px 24px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.open-bounties-sort-type:hover {
cursor: pointer;
}
.icon-forward-white-box {
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-content: center;
}
}
.open-bounty-item {