Last active
October 3, 2020 01:55
-
-
Save wavejumper/ef50850eceb027072fe7d5cbe598cf72 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(require '[reagent.core :as r] | |
'[reagent.dom :as rdom] | |
'[sandbox.reagent :refer [render]] | |
'[clojure.string :as str]) | |
;; The todomvc found in https://github.com/reagent-project/reagent/tree/master/examples/todomvc | |
(inject-stylesheet "https://gist.githubusercontent.com/wavejumper/ef50850eceb027072fe7d5cbe598cf72/raw/913af47f4cbc63fea0acd382492314617edf35c7/todo.css") | |
(defonce todos (r/atom (sorted-map))) | |
(defonce counter (r/atom 0)) | |
(defn add-todo [text] | |
(let [id (swap! counter inc)] | |
(swap! todos assoc id {:id id :title text :done false}))) | |
(defn toggle [id] (swap! todos update-in [id :done] not)) | |
(defn save [id title] (swap! todos assoc-in [id :title] title)) | |
(defn delete [id] (swap! todos dissoc id)) | |
(defn mmap [m f a] (->> m (f a) (into (empty m)))) | |
(defn complete-all [v] (swap! todos mmap map #(assoc-in % [1 :done] v))) | |
(defn clear-done [] (swap! todos mmap remove #(get-in % [1 :done]))) | |
(defonce init (do | |
(add-todo "Rename Cloact to Reagent") | |
(add-todo "Add undo demo") | |
(add-todo "Make all rendering async") | |
(add-todo "Allow any arguments to component functions") | |
(complete-all true))) | |
(defn todo-input [{:keys [title on-save on-stop]}] | |
(let [val (r/atom title) | |
stop #(do (reset! val "") | |
(if on-stop (on-stop))) | |
save #(let [v (-> @val str str/trim)] | |
(if-not (empty? v) (on-save v)) | |
(stop))] | |
(fn [{:keys [id class placeholder]}] | |
[:input {:type "text" :value @val | |
:id id :class class :placeholder placeholder | |
:on-blur save | |
:on-change #(reset! val (-> % .-target .-value)) | |
:on-key-down #(case (.-which %) | |
13 (save) | |
27 (stop) | |
nil)}]))) | |
(def todo-edit (with-meta todo-input | |
{:component-did-mount #(.focus (rdom/dom-node %))})) | |
(defn todo-stats [{:keys [filt active done]}] | |
(let [props-for (fn [name] | |
{:class (if (= name @filt) "selected") | |
:on-click #(reset! filt name)})] | |
[:div | |
[:span#todo-count | |
[:strong active] " " (case active 1 "item" "items") " left"] | |
[:ul#filters | |
[:li [:a (props-for :all) "All"]] | |
[:li [:a (props-for :active) "Active"]] | |
[:li [:a (props-for :done) "Completed"]]] | |
(when (pos? done) | |
[:button#clear-completed {:on-click clear-done} | |
"Clear completed " done])])) | |
(defn todo-item [] | |
(let [editing (r/atom false)] | |
(fn [{:keys [id done title]}] | |
[:li {:class (str (if done "completed ") | |
(if @editing "editing"))} | |
[:div.view | |
[:input.toggle {:type "checkbox" :checked done | |
:on-change #(toggle id)}] | |
[:label {:on-double-click #(reset! editing true)} title] | |
[:button.destroy {:on-click #(delete id)}]] | |
(when @editing | |
[todo-edit {:class "edit" :title title | |
:on-save #(save id %) | |
:on-stop #(reset! editing false)}])]))) | |
(defn todo-app [props] | |
(let [filt (r/atom :all)] | |
(fn [] | |
(let [items (vals @todos) | |
done (->> items (filter :done) count) | |
active (- (count items) done)] | |
[:div | |
[:section#todoapp | |
[:header#header | |
[:h1 "todos"] | |
[todo-input {:id "new-todo" | |
:placeholder "What needs to be done?" | |
:on-save add-todo}]] | |
(when (-> items count pos?) | |
[:div | |
[:section#main | |
[:input#toggle-all {:type "checkbox" :checked (zero? active) | |
:on-change #(complete-all (pos? active))}] | |
[:label {:for "toggle-all"} "Mark all as complete"] | |
[:ul#todo-list | |
(for [todo (filter (case @filt | |
:active (complement :done) | |
:done :done | |
:all identity) items)] | |
^{:key (:id todo)} [todo-item todo])]] | |
[:footer#footer | |
[todo-stats {:active active :done done :filt filt}]]])] | |
[:footer#info | |
[:p "Double-click to edit a todo"]]])))) | |
(render [todo-app]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@charset "utf-8"; | |
button { | |
margin: 0; | |
padding: 0; | |
border: 0; | |
background: none; | |
font-size: 100%; | |
vertical-align: baseline; | |
font-family: inherit; | |
color: inherit; | |
-webkit-appearance: none; | |
-ms-appearance: none; | |
-o-appearance: none; | |
appearance: none; | |
} | |
button, | |
input[type="checkbox"] { | |
outline: none; | |
} | |
#todoapp { | |
background: #fff; | |
background: rgba(255, 255, 255, 0.9); | |
margin: 130px 0 40px 0; | |
border: 1px solid #ccc; | |
position: relative; | |
border-top-left-radius: 2px; | |
border-top-right-radius: 2px; | |
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), | |
0 25px 50px 0 rgba(0, 0, 0, 0.15); | |
} | |
#todoapp:before { | |
content: ''; | |
border-left: 1px solid #f5d6d6; | |
border-right: 1px solid #f5d6d6; | |
width: 2px; | |
position: absolute; | |
top: 0; | |
left: 40px; | |
height: 100%; | |
} | |
#todoapp input::-webkit-input-placeholder { | |
font-style: italic; | |
} | |
#todoapp input::-moz-placeholder { | |
font-style: italic; | |
color: #a9a9a9; | |
} | |
#todoapp h1 { | |
position: absolute; | |
top: -120px; | |
width: 100%; | |
font-size: 70px; | |
font-weight: bold; | |
text-align: center; | |
color: #b3b3b3; | |
color: rgba(255, 255, 255, 0.3); | |
text-shadow: -1px -1px rgba(0, 0, 0, 0.2); | |
-webkit-text-rendering: optimizeLegibility; | |
-moz-text-rendering: optimizeLegibility; | |
-ms-text-rendering: optimizeLegibility; | |
-o-text-rendering: optimizeLegibility; | |
text-rendering: optimizeLegibility; | |
} | |
#header { | |
padding-top: 15px; | |
border-radius: inherit; | |
} | |
#header:before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
right: 0; | |
left: 0; | |
height: 15px; | |
z-index: 2; | |
border-bottom: 1px solid #6c615c; | |
background: #8d7d77; | |
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); | |
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); | |
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); | |
/* filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); */ | |
border-top-left-radius: 1px; | |
border-top-right-radius: 1px; | |
} | |
#new-todo, | |
.edit { | |
position: relative; | |
margin: 0; | |
width: 100%; | |
font-size: 24px; | |
font-family: inherit; | |
line-height: 1.4em; | |
border: 0; | |
outline: none; | |
color: inherit; | |
padding: 6px; | |
border: 1px solid #999; | |
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); | |
-moz-box-sizing: border-box; | |
-ms-box-sizing: border-box; | |
-o-box-sizing: border-box; | |
box-sizing: border-box; | |
-webkit-font-smoothing: antialiased; | |
-moz-font-smoothing: antialiased; | |
-ms-font-smoothing: antialiased; | |
-o-font-smoothing: antialiased; | |
font-smoothing: antialiased; | |
} | |
#new-todo { | |
padding: 16px 16px 16px 60px; | |
border: none; | |
background: rgba(0, 0, 0, 0.02); | |
z-index: 2; | |
box-shadow: none; | |
} | |
#main { | |
position: relative; | |
z-index: 2; | |
border-top: 1px dotted #adadad; | |
} | |
label[for='toggle-all'] { | |
display: none; | |
} | |
#toggle-all { | |
position: absolute; | |
top: -42px; | |
left: -4px; | |
width: 40px; | |
text-align: center; | |
/* Mobile Safari */ | |
border: none; | |
} | |
#toggle-all:before { | |
content: '»'; | |
font-size: 28px; | |
color: #d9d9d9; | |
padding: 0 25px 7px; | |
} | |
#toggle-all:checked:before { | |
color: #737373; | |
} | |
#todo-list { | |
margin: 0; | |
padding: 0; | |
list-style: none; | |
} | |
#todo-list li { | |
position: relative; | |
font-size: 24px; | |
border-bottom: 1px dotted #ccc; | |
} | |
#todo-list li:last-child { | |
border-bottom: none; | |
} | |
#todo-list li.editing { | |
border-bottom: none; | |
padding: 0; | |
} | |
#todo-list li.editing .edit { | |
display: block; | |
width: 506px; | |
padding: 13px 17px 12px 17px; | |
margin: 0 0 0 43px; | |
} | |
#todo-list li.editing .view { | |
display: none; | |
} | |
#todo-list li .toggle { | |
text-align: center; | |
width: 40px; | |
/* auto, since non-WebKit browsers doesn't support input styling */ | |
height: auto; | |
position: absolute; | |
top: 0; | |
bottom: 0; | |
margin: auto 0; | |
/* Mobile Safari */ | |
border: none; | |
-webkit-appearance: none; | |
-ms-appearance: none; | |
-o-appearance: none; | |
appearance: none; | |
} | |
#todo-list li .toggle:after { | |
content: '✔'; | |
/* 40 + a couple of pixels visual adjustment */ | |
line-height: 43px; | |
font-size: 20px; | |
color: #d9d9d9; | |
text-shadow: 0 -1px 0 #bfbfbf; | |
} | |
#todo-list li .toggle:checked:after { | |
color: #85ada7; | |
text-shadow: 0 1px 0 #669991; | |
bottom: 1px; | |
position: relative; | |
} | |
#todo-list li label { | |
white-space: pre; | |
word-break: break-word; | |
padding: 15px 60px 15px 15px; | |
margin-left: 45px; | |
display: block; | |
line-height: 1.2; | |
-webkit-transition: color 0.4s; | |
transition: color 0.4s; | |
} | |
#todo-list li.completed label { | |
color: #a9a9a9; | |
text-decoration: line-through; | |
} | |
#todo-list li .destroy { | |
display: none; | |
position: absolute; | |
top: 0; | |
right: 10px; | |
bottom: 0; | |
width: 40px; | |
height: 40px; | |
margin: auto 0; | |
font-size: 22px; | |
color: #a88a8a; | |
-webkit-transition: all 0.2s; | |
transition: all 0.2s; | |
} | |
#todo-list li .destroy:hover { | |
text-shadow: 0 0 1px #000, | |
0 0 10px rgba(199, 107, 107, 0.8); | |
-webkit-transform: scale(1.3); | |
-ms-transform: scale(1.3); | |
transform: scale(1.3); | |
} | |
#todo-list li .destroy:after { | |
content: '✖'; | |
} | |
#todo-list li:hover .destroy { | |
display: block; | |
} | |
#todo-list li .edit { | |
display: none; | |
} | |
#todo-list li.editing:last-child { | |
margin-bottom: -1px; | |
} | |
#footer { | |
color: #777; | |
padding: 0 15px; | |
position: absolute; | |
right: 0; | |
bottom: -31px; | |
left: 0; | |
height: 20px; | |
z-index: 1; | |
text-align: center; | |
} | |
#footer:before { | |
content: ''; | |
position: absolute; | |
right: 0; | |
bottom: 31px; | |
left: 0; | |
height: 50px; | |
z-index: -1; | |
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), | |
0 6px 0 -3px rgba(255, 255, 255, 0.8), | |
0 7px 1px -3px rgba(0, 0, 0, 0.3), | |
0 43px 0 -6px rgba(255, 255, 255, 0.8), | |
0 44px 2px -6px rgba(0, 0, 0, 0.2); | |
} | |
#todo-count { | |
float: left; | |
text-align: left; | |
} | |
#filters { | |
margin: 0; | |
padding: 0; | |
list-style: none; | |
position: absolute; | |
right: 0; | |
left: 0; | |
} | |
#filters li { | |
display: inline; | |
} | |
#filters li a { | |
color: #83756f; | |
margin: 2px; | |
text-decoration: none; | |
} | |
#filters li a.selected { | |
font-weight: bold; | |
} | |
#clear-completed { | |
float: right; | |
position: relative; | |
line-height: 20px; | |
text-decoration: none; | |
background: rgba(0, 0, 0, 0.1); | |
font-size: 11px; | |
padding: 0 10px; | |
border-radius: 3px; | |
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); | |
} | |
#clear-completed:hover { | |
background: rgba(0, 0, 0, 0.15); | |
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); | |
} | |
#info { | |
margin: 65px auto 0; | |
color: #a6a6a6; | |
font-size: 12px; | |
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); | |
text-align: center; | |
} | |
#info a { | |
color: inherit; | |
} | |
/* | |
Hack to remove background from Mobile Safari. | |
Can't use it globally since it destroys checkboxes in Firefox and Opera | |
*/ | |
@media screen and (-webkit-min-device-pixel-ratio:0) { | |
#toggle-all, | |
#todo-list li .toggle { | |
background: none; | |
} | |
#todo-list li .toggle { | |
height: 40px; | |
} | |
#toggle-all { | |
top: -56px; | |
left: -15px; | |
width: 65px; | |
height: 41px; | |
-webkit-transform: rotate(90deg); | |
-ms-transform: rotate(90deg); | |
transform: rotate(90deg); | |
-webkit-appearance: none; | |
appearance: none; | |
} | |
} | |
.hidden { | |
display: none; | |
} | |
hr { | |
margin: 20px 0; | |
border: 0; | |
border-top: 1px dashed #C5C5C5; | |
border-bottom: 1px dashed #F7F7F7; | |
} | |
.learn a { | |
font-weight: normal; | |
text-decoration: none; | |
color: #b83f45; | |
} | |
.learn a:hover { | |
text-decoration: underline; | |
color: #787e7e; | |
} | |
.learn h3, | |
.learn h4, | |
.learn h5 { | |
margin: 10px 0; | |
font-weight: 500; | |
line-height: 1.2; | |
color: #000; | |
} | |
.learn h3 { | |
font-size: 24px; | |
} | |
.learn h4 { | |
font-size: 18px; | |
} | |
.learn h5 { | |
margin-bottom: 0; | |
font-size: 14px; | |
} | |
.learn ul { | |
padding: 0; | |
margin: 0 0 30px 25px; | |
} | |
.learn li { | |
line-height: 20px; | |
} | |
.learn p { | |
font-size: 15px; | |
font-weight: 300; | |
line-height: 1.3; | |
margin-top: 0; | |
margin-bottom: 0; | |
} | |
.quote { | |
border: none; | |
margin: 20px 0 60px 0; | |
} | |
.quote p { | |
font-style: italic; | |
} | |
.quote p:before { | |
content: '“'; | |
font-size: 50px; | |
opacity: .15; | |
position: absolute; | |
top: -20px; | |
left: 3px; | |
} | |
.quote p:after { | |
content: '”'; | |
font-size: 50px; | |
opacity: .15; | |
position: absolute; | |
bottom: -42px; | |
right: 3px; | |
} | |
.quote footer { | |
position: absolute; | |
bottom: -40px; | |
right: 0; | |
} | |
.quote footer img { | |
border-radius: 3px; | |
} | |
.quote footer a { | |
margin-left: 5px; | |
vertical-align: middle; | |
} | |
.speech-bubble { | |
position: relative; | |
padding: 10px; | |
background: rgba(0, 0, 0, .04); | |
border-radius: 5px; | |
} | |
.speech-bubble:after { | |
content: ''; | |
position: absolute; | |
top: 100%; | |
right: 30px; | |
border: 13px solid transparent; | |
border-top-color: rgba(0, 0, 0, .04); | |
} | |
.learn-bar > .learn { | |
position: absolute; | |
width: 272px; | |
top: 8px; | |
left: -300px; | |
padding: 10px; | |
border-radius: 5px; | |
background-color: rgba(255, 255, 255, .6); | |
-webkit-transition-property: left; | |
transition-property: left; | |
-webkit-transition-duration: 500ms; | |
transition-duration: 500ms; | |
} | |
@media (min-width: 899px) { | |
.learn-bar { | |
width: auto; | |
margin: 0 0 0 300px; | |
} | |
.learn-bar > .learn { | |
left: 8px; | |
} | |
.learn-bar #todoapp { | |
width: 550px; | |
margin: 130px auto 40px auto; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment