1
- {-# LANGUAGE DerivingVia #-}
2
1
{-# LANGUAGE LambdaCase #-}
3
2
{-# LANGUAGE UndecidableInstances #-}
4
3
5
4
module Example.Page.Todos.TodoCSS (page ) where
6
5
7
6
import Control.Monad (forM_ )
7
+ import Data.Bool (bool )
8
8
import Data.Text qualified as T
9
- import Effectful
10
- import Example.Colors (AppColor (.. ))
9
+
11
10
import Example.Effects.Todos (Todo , TodoId , Todos )
12
11
import Example.Effects.Todos qualified as Todos
13
- import Example.Page.Todos.Shared
14
- import Example.Style qualified as Style
15
- import Example.View.Icon qualified as Icon
16
- import Example.View.Inputs (toggleCheckbox )
12
+ import Example.Page.Todos.Shared (FilterTodo (.. ), TodoForm (.. ), pluralize )
17
13
import Web.Hyperbole as Hyperbole
14
+ import Web.Hyperbole.View.Forms (Input (Input ))
15
+ import Web.View.Style (extClass )
16
+ import Web.View.Types (AttValue )
17
+
18
+ {-
19
+
20
+ To make the CSS version work and overcome the default CSS reset, we tweaked the output slightly via a few style tags here and there:
21
+
22
+ only need to add one manual rule to the footer, to override the CSS reset
23
+
24
+ - main title
25
+ - override its absolute positioning
26
+ - footer
27
+ - add bottom padding
28
+
29
+ -}
18
30
19
31
page :: (Todos :> es ) => Eff es (Page '[TodosView , TodoView ])
20
32
page = do
21
33
todos <- Todos. loadAll
22
- pure $ tag " div" id $ do
23
- tag " h1" id $ text " Todos CSS"
24
- tag " p" id $
25
- text (" Count:" <> T. pack (show $ length todos))
26
- hyper MkTodosView $ todosView FilterAll todos
34
+ pure $ do
35
+ div' (style' " _margin-top: 50px" ) $ do
36
+ stylesheet
" https://cdn.jsdelivr.net/npm/[email protected] /index.min.css"
37
+ section (extClass " todoapp" ) $ do
38
+ header (extClass " header" ) $ do
39
+ h1 (style' " top:-80px" ) $ text " todos"
40
+ hyper MkTodosView $ todosView FilterAll todos
27
41
28
42
--- TodosView ----------------------------------------------------------------------------
29
43
@@ -73,41 +87,77 @@ instance (Todos :> es) => HyperView TodosView es where
73
87
74
88
todosView :: FilterTodo -> [Todo ] -> View TodosView ()
75
89
todosView filt todos = do
76
- todoForm filt
77
- col id $ do
90
+ todoForm
91
+ main' (extClass " main" ) $ do
92
+ div' (extClass " toggle-all-container" ) $ do
93
+ input'
94
+ ( extClass " toggle-all"
95
+ . att " id" " toggle-all"
96
+ . att " type" " checkbox"
97
+ )
98
+ label'
99
+ ( extClass " toggle-all-label"
100
+ . att " for" " toggle-all"
101
+ . onClick (ToggleAll filt)
102
+ )
103
+ (text " Mark all as complete" )
104
+ ul' (extClass " todo-list" ) $ do
78
105
forM_ todos $ \ todo -> do
79
106
hyper (MkTodoView todo. id ) $ todoView filt todo
80
107
statusBar filt todos
81
108
82
- todoForm :: FilterTodo -> View TodosView ()
83
- todoForm filt = do
109
+ todoForm :: View TodosView ()
110
+ todoForm = do
84
111
let f :: TodoForm FieldName = fieldNames
85
- tag " div" id $ do
86
- tag " span" id $ do
87
- button (ToggleAll filt) (width 32 . hover (color Primary )) Icon. chevronDown
88
- form SubmitTodo grow $ do
89
- field f. task id $ do
90
- input TextInput (pad 12 . placeholder " What needs to be done?" . value " " )
112
+ form SubmitTodo grow $ do
113
+ field f. task id $ do
114
+ Input (FieldName nm) <- context
115
+ input' -- we use a custom input field, because the Hyperbole one overrides autocomplete
116
+ ( extClass " new-todo"
117
+ {-
118
+ -- . autofocus
119
+ FIXME: turning off autofocus, that "steals" the focus on item click.
120
+ FIXME: to solve this, we could either store the "initially focused" state and track that boolean, or use buttons
121
+ FIXME: but since this example is meant to match as close as possible to the original CSS version
122
+ FIXME: and not diverge too much from the other todo example, I'm leaving as-is.
123
+ -}
124
+ . att " autocomplete" " off"
125
+ . placeholder " What needs to be done?"
126
+ . value " "
127
+ . name nm -- because we use a custom field, we must provide this param for the library
128
+ )
91
129
92
130
statusBar :: FilterTodo -> [Todo ] -> View TodosView ()
93
131
statusBar filt todos = do
94
- tag " div " id $ do
132
+ footer (extClass " footer " . style' " padding-bottom: 30px " ) $ do
95
133
let numLeft = length $ filter (\ t -> not t. completed) todos
96
- el_ $ do
97
- text $ T. pack (show numLeft)
98
- text " items left!"
134
+ span' (extClass " todo-count" ) $ do
135
+ text $
136
+ mconcat
137
+ [ T. pack $ show numLeft
138
+ , " "
139
+ , pluralize numLeft " item" " items"
140
+ , " "
141
+ , " left!"
142
+ ]
99
143
space
100
- tag " div " id $ do
101
- filterButton FilterAll " All"
102
- filterButton Active " Active"
103
- filterButton Completed " Completed"
144
+ ul' (extClass " filters " ) $ do
145
+ filterLi FilterAll " All"
146
+ filterLi Active " Active"
147
+ filterLi Completed " Completed"
104
148
space
105
- button ClearCompleted (hover (color Primary ) ) " Clear completed"
149
+ button ClearCompleted (extClass " clear-completed " ) " Clear completed"
106
150
where
107
- filterButton f =
108
- button (Filter f) (selectedFilter f . pad (XY 4 0 ) . rounded 2 )
151
+ filterLi f str =
152
+ li' (extClass " filter" . selectedFilter f) $ do
153
+ tag
154
+ " a"
155
+ ( onClick (Filter f)
156
+ . att " href" " " -- harmless empty href is for the CSS
157
+ )
158
+ (text str)
109
159
selectedFilter f =
110
- if f == filt then border 1 else id
160
+ if f == filt then extClass " selected " else id
111
161
112
162
--- TodoView ----------------------------------------------------------------------------
113
163
@@ -131,17 +181,64 @@ instance (Todos :> es) => HyperView TodoView es where
131
181
132
182
todoView :: FilterTodo -> Todo -> View TodoView ()
133
183
todoView filt todo = do
134
- tag " div" id $ do
135
- target MkTodosView $ do
136
- toggleCheckbox (SetCompleted filt todo) todo. completed
137
- tag " span" (completed . pad (XY 18 4 ) . onDblClick (Edit filt todo)) $ text todo. task
138
- where
139
- completed = if todo. completed then Style. strikethrough else id
184
+ li'
185
+ ( onDblClick (Edit filt todo)
186
+ . bool id (extClass " completed" ) todo. completed
187
+ )
188
+ $ do
189
+ div' (extClass " view" ) $ do
190
+ target MkTodosView $ do
191
+ input'
192
+ ( extClass " toggle"
193
+ . att " type" " checkbox"
194
+ . onClick (SetCompleted filt todo $ not todo. completed)
195
+ . checked todo. completed
196
+ )
197
+ label' (extClass " label" ) $ do
198
+ text todo. task
140
199
141
200
todoEditView :: FilterTodo -> Todo -> View TodoView ()
142
201
todoEditView filt todo = do
143
202
let f = fieldNames @ TodoForm
144
- tag " div" id $ do
203
+ div' id $ do
145
204
form (SubmitEdit filt todo) (pad (TRBL 0 0 0 46 )) $ do
146
205
field f. task id $ do
147
206
input TextInput (pad 4 . value todo. task . autofocus)
207
+
208
+ --- Helpers ----------------------------------------------------------------------------
209
+
210
+ div' :: Mod c -> View c () -> View c ()
211
+ div' = tag " div"
212
+
213
+ span' :: Mod c -> View c () -> View c ()
214
+ span' = tag " span"
215
+
216
+ section :: Mod c -> View c () -> View c ()
217
+ section = tag " section"
218
+
219
+ header :: Mod c -> View c () -> View c ()
220
+ header = tag " header"
221
+
222
+ main' :: Mod c -> View c () -> View c ()
223
+ main' = tag " main"
224
+
225
+ h1 :: Mod c -> View c () -> View c ()
226
+ h1 = tag " h1"
227
+
228
+ label' :: Mod c -> View c () -> View c ()
229
+ label' = tag " label"
230
+
231
+ input' :: Mod c -> View c ()
232
+ input' m = tag " input" m " "
233
+
234
+ ul' :: Mod c -> View c () -> View c ()
235
+ ul' = tag " ul"
236
+
237
+ li' :: Mod c -> View c () -> View c ()
238
+ li' = tag " li"
239
+
240
+ footer :: Mod c -> View c () -> View c ()
241
+ footer = tag " footer"
242
+
243
+ style' :: AttValue -> Mod c
244
+ style' = att " style"
0 commit comments