-
Notifications
You must be signed in to change notification settings - Fork 412
/
Copy pathcore.cljs
218 lines (190 loc) · 7.52 KB
/
core.cljs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
(ns example.core
(:require [reagent.core :as r]
[reagent.dom :as rdom]
["react" :as react]
["@mui/material" :as mui]
["@mui/material/styles" :refer [ThemeProvider createTheme]]
["@mui/styles" :refer [withStyles]]
["@mui/material/colors/red$default" :as mui-red]
["@mui/icons-material" :as mui-icons]
; ["@mui/lab/Autocomplete" :as Autocomplete]
[goog.object :as gobj]
;; FIXME: Internal impl namespace should not be used
[reagent.impl.template :as rtpl]))
(set! *warn-on-infer* true)
(defn event-value
[^js/Event e]
(let [^js/HTMLInputElement el (.-target e)]
(.-value el)))
;; TextField cursor fix:
;; For some reason the new MUI doesn't pass ref in the props,
;; but we can get it using forwardRef?
;; This is someone incovenient as we need to convert props to Cljs
;; but reactify-component would also do that.
(def ^:private input-component
(react/forwardRef
(fn [props ref]
(r/as-element
[:input (-> (js->clj props :keywordize-keys true)
(assoc :ref ref))]))))
(def ^:private textarea-component
(react/forwardRef
(fn [props ref]
(r/as-element
[:textarea (-> (js->clj props :keywordize-keys true)
(assoc :ref ref))]))))
;; To fix cursor jumping when controlled input value is changed,
;; use wrapper input element created by Reagent instead of
;; letting Material-UI to create input element directly using React.
;; Create-element + convert-props-value is the same as what adapt-react-class does.
(defn text-field [props & children]
(let [props (-> props
(assoc-in [:InputProps :inputComponent] (cond
(and (:multiline props) (:rows props) (not (:maxRows props)))
textarea-component
;; FIXME: Autosize multiline field is broken.
(:multiline props)
nil
;; Select doesn't require cursor fix so default can be used.
(:select props)
nil
:else
input-component))
;; FIXME: Internal fn should not be used
;; clj->js is not enough as prop on-change -> onChange, class -> classNames etc should be handled
rtpl/convert-prop-value)]
(apply r/create-element mui/TextField props (map r/as-element children))))
;; Example
(def custom-theme
(createTheme
#js {:palette #js {:primary #js {:main (gobj/get mui-red 100)}}}))
(defn custom-styles [^js/Mui.Theme theme]
#js {:button #js {:margin (.spacing theme 1)}
:textField #js {:width 200
:marginLeft (.spacing theme 1)
:marginRight (.spacing theme 1)}})
(def with-custom-styles (withStyles custom-styles))
(defonce text-state (r/atom "foobar"))
(defonce select-state (r/atom ""))
(defn autocomplete-example []
[:> mui/Grid
{:item true}
#_
[:> Autocomplete {:options ["foo" "bar" "foobar"]
:style {:width 300}
;; Note that the function parameter is a JS Object!
;; Autocomplete expects the renderInput value to be function
;; returning React elements, not a component!
;; So reactify-component won't work here.
:render-input (fn [^js params]
;; Don't call js->clj because that would recursively
;; convert all JS objects (e.g. React ref objects)
;; to Cljs maps, which breaks them, even when converted back to JS.
;; Best thing is to use r/create-element and
;; pass the JS params to it.
;; If necessary, use JS interop to modify params.
(set! (.-variant params) "outlined")
(set! (.-label params) "Autocomplete")
(r/create-element mui/TextField params))}]])
;; Props in cljs but classes in JS object
(defn form [{:keys [classes] :as props}]
[:> mui/Grid
{:container true
:direction "column"
:spacing 2}
[:> mui/Grid {:item true}
[:> mui/Toolbar
{:disable-gutters true}
[:> mui/Button
{:variant "contained"
:color "primary"
:class (.-button classes)
:on-click #(swap! text-state str " foo")}
"Update value property"
[:> mui-icons/AddBox]]
[:> mui/Button
{:variant "outlined"
:color "secondary"
:class (.-button classes)
:on-click #(reset! text-state "")}
"Reset"
[:> mui-icons/Clear]]]]
[:> mui/Grid {:item true}
[text-field
{:value @text-state
:label "Text input"
:placeholder "Placeholder"
:helper-text "Helper text"
:class (.-textField classes)
:on-change (fn [e]
(reset! text-state (event-value e)))
:inputRef (fn [e]
(js/console.log "input-ref" e))}]]
[:> mui/Grid {:item true}
[text-field
{:value @text-state
:label "Textarea"
:placeholder "Placeholder"
:helper-text "Helper text"
:class (.-textField classes)
:on-change (fn [e]
(reset! text-state (event-value e)))
:multiline true
;; TODO: Autosize textarea is broken.
:rows 10}]]
[:> mui/Grid {:item true}
[text-field
{:value @select-state
:label "Select"
:placeholder "Placeholder"
:helper-text "Helper text"
:class (.-textField classes)
:on-change (fn [e]
(reset! select-state (event-value e)))
:select true}
[:> mui/MenuItem
{:value 1}
"Item 1"]
;; Same as previous, alternative to adapt-react-class
[:> mui/MenuItem
{:value 2}
"Item 2"]]]
[:> mui/Grid {:item true}
[:> mui/Grid
{:container true
:direction "row"
:spacing 4}
;; For properties that require React Node as parameter,
;; either use r/as-element to convert Reagent hiccup forms into React elements,
;; or use r/create-element to directly instantiate element from React class (i.e. non-adapted React component).
[:> mui/Grid {:item true}
[:> mui/Chip
{:icon (r/as-element [:> mui-icons/Face])
:label "Icon element example, r/as-element"}]]
[:> mui/Grid {:item true}
[:> mui/Chip
{:icon (r/create-element mui-icons/Face)
:label "Icon element example, r/create-element"}]]]]
[:> mui/Grid {:item true}
[:> mui/Grid
{:container true
:direction "row"
:spacing 4}
[autocomplete-example]]]])
(defn main []
;; fragment
[:<>
[:> mui/CssBaseline]
[:> ThemeProvider
{:theme custom-theme}
[:> mui/Grid
{:container true
:direction "row"
:justify "center"}
[:> mui/Grid
{:item true
:xs 6}
[:> (with-custom-styles (r/reactify-component form))]]]]])
(defn start []
(rdom/render [main] (js/document.getElementById "app")))
(start)