-
-
Notifications
You must be signed in to change notification settings - Fork 147
/
box.cljs
541 lines (511 loc) · 46.1 KB
/
box.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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
(ns re-com.box
(:require-macros
[re-com.core :refer [at]]
[re-com.validate :refer [validate-args-macro]])
(:require
[clojure.string :as string]
[re-com.config :refer [include-args-desc?]]
[re-com.debug :refer [->attr]]
[re-com.validate :refer [justify-style? justify-options-list align-style? align-options-list scroll-style?
scroll-options-list string-or-hiccup? css-style? css-class? html-attr?]]))
(def visualise-flow? false)
;; ------------------------------------------------------------------------------------
;; Private Helper functions
;; ------------------------------------------------------------------------------------
(defn flex-child-style
"Determines the value for the 'flex' attribute (which has grow, shrink and basis), based on the :size parameter.
IMPORTANT: The term 'size' means width of the item in the case of flex-direction 'row' OR height of the item in the case of flex-direction 'column'.
Flex property explanation:
- grow Integer ratio (used with other siblings) to determined how a flex item grows it's size if there is extra space to distribute. 0 for no growing.
- shrink Integer ratio (used with other siblings) to determined how a flex item shrinks it's size if space needs to be removed. 0 for no shrinking.
- basis Initial size (width, actually) of item before any growing or shrinking. Can be any size value, e.g. 60%, 100px, auto
Note: auto will cause the initial size to be calculated to take up as much space as possible, in conjunction with it's siblings :flex settings.
Supported values:
- initial '0 1 auto' - Use item's width/height for dimensions (or content dimensions if w/h not specified). Never grow. Shrink (to min-size) if necessary.
Good for creating boxes with fixed maximum size, but that can shrink to a fixed smaller size (min-width/height) if space becomes tight.
NOTE: When using initial, you should also set a width/height value (depending on flex-direction) to specify it's default size
and an optional min-width/height value to specify the size it can shrink to.
- auto '1 1 auto' - Use item's width/height for dimensions. Grow if necessary. Shrink (to min-size) if necessary.
Good for creating really flexible boxes that will gobble as much available space as they are allowed or shrink as much as they are forced to.
- none '0 0 auto' - Use item's width/height for dimensions (or content dimensions if not specified). Never grow. Never shrink.
Good for creating rigid boxes that stick to their width/height if specified, otherwise their content size.
- 100px '0 0 100px' - Non flexible 100px size (in the flex direction) box.
Good for fixed headers/footers and side bars of an exact size.
- 60% '60 1 0px' - Set the item's size (it's width/height depending on flex-direction) to be 60% of the parent container's width/height.
NOTE: If you use this, then all siblings with percentage values must add up to 100%.
- 60 '60 1 0px' - Same as percentage above.
- grow shrink basis 'grow shrink basis' - If none of the above common values above meet your needs, this gives you precise control.
If number of words is not 1 or 3, an exception is thrown.
Reference: http://www.w3.org/TR/css3-flexbox/#flexibility
Diagram: http://www.w3.org/TR/css3-flexbox/#flex-container
Regex101 testing: ^(initial|auto|none)|(\\d+)(px|%|em)|(\\d+)\\w(\\d+)\\w(.*) - remove double backslashes"
[size]
;; TODO: Could make initial/auto/none into keywords???
(let [split-size (string/split (string/trim size) #"\s+") ;; Split into words separated by whitespace
split-count (count split-size)
_ (assert (contains? #{1 3} split-count) "Must pass either 1 or 3 words to flex-child-style")
size-only (when (= split-count 1) (first split-size)) ;; Contains value when only one word passed (e.g. auto, 60px)
split-size-only (when size-only (string/split size-only #"(\d+)(.*)")) ;; Split into number + string
[_ num units] (when size-only split-size-only) ;; grab number and units
pass-through? (nil? num) ;; If we can't split, then we'll pass this straight through
grow-ratio? (or (= units "%") (= units "") (nil? units)) ;; Determine case for using grow ratio
grow (if grow-ratio? num "0") ;; Set grow based on percent or integer, otherwise no grow
shrink (if grow-ratio? "1" "0") ;; If grow set, then set shrink to even shrinkage as well
basis (if grow-ratio? "0px" size) ;; If grow set, then even growing, otherwise set basis size to the passed in size (e.g. 100px, 5em)
flex (if (and size-only (not pass-through?))
(str grow " " shrink " " basis)
size)]
{:-webkit-flex flex
:flex flex}))
(defn flex-flow-style
"A cross-browser helper function to output flex-flow with all it's potential browser prefixes"
[flex-flow]
{:-webkit-flex-flow flex-flow
:flex-flow flex-flow})
(defn justify-style
"Determines the value for the flex 'justify-content' attribute.
This parameter determines how children are aligned along the main axis.
The justify parameter is a keyword.
Reference: http://www.w3.org/TR/css3-flexbox/#justify-content-property"
[justify]
(let [js (case justify
:start "flex-start"
:end "flex-end"
:center "center"
:between "space-between"
:around "space-around")]
{:-webkit-justify-content js
:justify-content js}))
(defn align-style
"Determines the value for the flex align type attributes.
This parameter determines how children are aligned on the cross axis.
The justify parameter is a keyword.
Reference: http://www.w3.org/TR/css3-flexbox/#align-items-property"
[attribute align]
(let [attribute-wk (->> attribute name (str "-webkit-") keyword)
as (case align
:start "flex-start"
:end "flex-end"
:center "center"
:baseline "baseline"
:stretch "stretch")]
{attribute-wk as
attribute as}))
(defn scroll-style
"Determines the value for the 'overflow' attribute.
The scroll parameter is a keyword.
Because we're translating scroll into overflow, the keyword doesn't appear to match the attribute value"
[attribute scroll]
{attribute (case scroll
:auto "auto"
:off "hidden"
:on "scroll"
:spill "visible")})
;; ------------------------------------------------------------------------------------
;; Private Component: box-base (visualise-flow? color: lightblue)
;; ------------------------------------------------------------------------------------
(defn- box-base
"This should generally NOT be used as it is the basis for the box, scroller and border components"
[& {:keys [size scroll h-scroll v-scroll width height min-width min-height max-width max-height justify align align-self
margin padding border l-border r-border t-border b-border radius bk-color child class-name class style attr]
:as args}]
(let [s (merge
(flex-flow-style "inherit")
(flex-child-style size)
(when scroll (scroll-style :overflow scroll))
(when h-scroll (scroll-style :overflow-x h-scroll))
(when v-scroll (scroll-style :overflow-y v-scroll))
(when width {:width width})
(when height {:height height})
(when min-width {:min-width min-width})
(when min-height {:min-height min-height})
(when max-width {:max-width max-width})
(when max-height {:max-height max-height})
(when justify (justify-style justify))
(when align (align-style :align-items align))
(when align-self (align-style :align-self align-self))
(when margin {:margin margin}) ;; margin and padding: "all" OR "top&bottom right&left" OR "top right bottom left"
(when padding {:padding padding})
(when border {:border border})
(when l-border {:border-left l-border})
(when r-border {:border-right r-border})
(when t-border {:border-top t-border})
(when b-border {:border-bottom b-border})
(when radius {:border-radius radius})
(if bk-color
{:background-color bk-color}
(if visualise-flow? {:background-color "lightblue"} {}))
style)]
[:div
(merge
(->attr args)
{:class (str class-name "display-flex " class) :style s}
attr)
child]))
;; ------------------------------------------------------------------------------------
;; Component: gap (visualise-flow? color: chocolate)
;; ------------------------------------------------------------------------------------
(def gap-args-desc
(when include-args-desc?
[{:name :size :required true :type "string" :validate-fn string? :description "the length of the whitespace. Typically, an absolute CSS length like 10px or 10em, but can be a stretchy proportional amount like 2"}
{:name :width :required false :type "string" :validate-fn string? :description "a CSS width style"}
{:name :height :required false :type "string" :validate-fn string? :description "a CSS height style"}
{:name :class :required false :type "string" :validate-fn css-class? :description "CSS class names - space separated string, or a vector of strings."}
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}
{:name :src :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging. Source code coordinates map containing keys" [:code ":file"] "and" [:code ":line"] ". See 'Debugging'."]}
{:name :debug-as :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging, when one component is used implement another component, and we want the implementation component to masquerade as the original component in debug output, such as component stacks. A map optionally containing keys" [:code ":component"] "and" [:code ":args"] "."]}]))
(defn gap
"Returns a component which produces a gap between children in a v-box/h-box along the main axis"
[& {:keys [size width height class style attr]
:as args}]
(or
(validate-args-macro gap-args-desc args)
(let [s (merge
(when size (flex-child-style size))
(when width {:width width})
(when height {:height height})
(when visualise-flow? {:background-color "chocolate"})
style)]
[:div
(merge
(->attr args)
{:class (str "rc-gap " class) :style s}
attr)])))
;; ------------------------------------------------------------------------------------
;; Component: line
;; ------------------------------------------------------------------------------------
(def line-args-desc
(when include-args-desc?
[{:name :size :required false :default "1px" :type "string" :validate-fn string? :description "a CSS style for the thickness of the line. Usually px, % or em"}
{:name :color :required false :default "lightgray" :type "string" :validate-fn string? :description "a CSS color"}
{:name :class :required false :type "string" :validate-fn css-class? :description "CSS class names - space separated string, or a vector of strings."}
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}
{:name :src :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging. Source code coordinates map containing keys" [:code ":file"] "and" [:code ":line"] ". See 'Debugging'."]}
{:name :debug-as :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging, when one component is used implement another component, and we want the implementation component to masquerade as the original component in debug output, such as component stacks. A map optionally containing keys" [:code ":component"] "and" [:code ":args"] "."]}]))
(defn line
"Returns a component which produces a line between children in a v-box/h-box along the main axis.
Specify size in pixels and a stancard CSS color. Defaults to a 1px lightgray line"
[& {:keys [size color class style attr]
:or {size "1px" color "lightgray"}
:as args}]
(or
(validate-args-macro line-args-desc args)
(let [s (merge
(flex-child-style (str "0 0 " size))
{:background-color color}
style)]
[:div
(merge
(->attr args)
{:class (str "rc-line " class) :style s}
attr)])))
;; ------------------------------------------------------------------------------------
;; Component: h-box (visualise-flow? color: gold)
;; ------------------------------------------------------------------------------------
(def h-box-args-desc
(when include-args-desc?
[{:name :children :required true :type "vector" :validate-fn sequential? :description "a vector (or list) of components"}
{:name :size :required false :default "none" :type "string" :validate-fn string? :description [:span "equivalent to CSS style " [:span.bold "flex"] "." [:br] "Examples: " [:code "initial"] ", " [:code "auto"] ", " [:code "none"] ", " [:code "100px"] ", " [:code "2"] " or a generic triple of " [:code "grow shrink basis"]]}
{:name :width :required false :type "string" :validate-fn string? :description "a CSS width style"}
{:name :height :required false :type "string" :validate-fn string? :description "a CSS height style"}
{:name :min-width :required false :type "string" :validate-fn string? :description "a CSS width style. The minimum width to which the box can shrink"}
{:name :min-height :required false :type "string" :validate-fn string? :description "a CSS height style. The minimum height to which the box can shrink"}
{:name :max-width :required false :type "string" :validate-fn string? :description "a CSS width style. The maximum width to which the box can grow"}
{:name :max-height :required false :type "string" :validate-fn string? :description "a CSS height style. The maximum height to which the box can grow"}
{:name :justify :required false :default :start :type "keyword" :validate-fn justify-style? :description [:span "equivalent to CSS style " [:span.bold "justify-content"] "." [:br] "One of " justify-options-list]}
{:name :align :required false :default :stretch :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-items"] "." [:br] " One of " align-options-list]}
{:name :align-self :required false :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-self"] "." [:br] "Used when a child must override the parent's align-items setting."]}
{:name :margin :required false :type "string" :validate-fn string? :description "a CSS margin style"}
{:name :padding :required false :type "string" :validate-fn string? :description "a CSS padding style"}
{:name :gap :required false :type "string" :validate-fn string? :description "the amount of whitespace to put between each child. Typically, an absolute CSS length like 10px or 10em, but can be a stretchy proportional amount like 2"}
{:name :class :required false :type "string" :validate-fn css-class? :description "CSS class names - space separated string, or a vector of strings."}
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}
{:name :src :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging. Source code coordinates map containing keys" [:code ":file"] "and" [:code ":line"] ". See 'Debugging'."]}
{:name :debug-as :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging, when one component is used implement another component, and we want the implementation component to masquerade as the original component in debug output, such as component stacks. A map optionally containing keys" [:code ":component"] "and" [:code ":args"] "."]}]))
(defn h-box
"Returns hiccup which produces a horizontal box.
It's primary role is to act as a container for components and lays it's children from left to right.
By default, it also acts as a child under it's parent"
[& {:keys [size width height min-width min-height max-width max-height justify align align-self margin padding gap children class style attr]
:or {size "none" justify :start align :stretch}
:as args}]
(or
(validate-args-macro h-box-args-desc args)
(let [s (merge
(flex-flow-style "row nowrap")
(flex-child-style size)
(when width {:width width})
(when height {:height height})
(when min-width {:min-width min-width})
(when min-height {:min-height min-height})
(when max-width {:max-width max-width})
(when max-height {:max-height max-height})
(justify-style justify)
(align-style :align-items align)
(when align-self (align-style :align-self align-self))
(when margin {:margin margin}) ;; margin and padding: "all" OR "top&bottom right&left" OR "top right bottom left"
(when padding {:padding padding})
(when visualise-flow? {:background-color "gold"})
style)
gap-form (when gap [re-com.box/gap
:src (at)
:size gap
:width gap]) ;; TODO: required to get around a Chrome bug: https://code.google.com/p/chromium/issues/detail?id=423112. Remove once fixed.
children (if gap
(interpose gap-form (filter identity children)) ;; filter is to remove possible nils so we don't add unwanted gaps
children)]
(into [:div
(merge
(->attr args)
{:class (str "rc-h-box display-flex " class) :style s}
attr)]
children))))
;; ------------------------------------------------------------------------------------
;; Component: v-box (visualise-flow? color: antiquewhite)
;; ------------------------------------------------------------------------------------
(def v-box-args-desc
(when include-args-desc?
[{:name :children :required true :type "vector" :validate-fn sequential? :description "a vector (or list) of components"}
{:name :size :required false :default "none" :type "string" :validate-fn string? :description [:span "equivalent to CSS style " [:span.bold "flex"] "." [:br] "Examples: " [:code "initial"] ", " [:code "auto"] ", " [:code "none"] ", " [:code "100px"] ", " [:code "2"] " or a generic triple of " [:code "grow shrink basis"]]}
{:name :width :required false :type "string" :validate-fn string? :description "a CSS width style"}
{:name :height :required false :type "string" :validate-fn string? :description "a CSS height style"}
{:name :min-width :required false :type "string" :validate-fn string? :description "a CSS width style. The minimum width to which the box can shrink"}
{:name :min-height :required false :type "string" :validate-fn string? :description "a CSS height style. The minimum height to which the box can shrink"}
{:name :max-width :required false :type "string" :validate-fn string? :description "a CSS width style. The maximum width to which the box can grow"}
{:name :max-height :required false :type "string" :validate-fn string? :description "a CSS height style. The maximum height to which the box can grow"}
{:name :justify :required false :default :start :type "keyword" :validate-fn justify-style? :description [:span "equivalent to CSS style " [:span.bold "justify-content"] "." [:br] "One of " justify-options-list]}
{:name :align :required false :default :stretch :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-items"] "." [:br] " One of " align-options-list]}
{:name :align-self :required false :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-self"] "." [:br] "Used when a child must override the parent's align-items setting."]}
{:name :margin :required false :type "string" :validate-fn string? :description "a CSS margin style"}
{:name :padding :required false :type "string" :validate-fn string? :description "a CSS padding style"}
{:name :gap :required false :type "string" :validate-fn string? :description "the amount of whitespace to put between each child. Typically, an absolute CSS length like 10px or 10em, but can be a stretchy proportional amount like 2"}
{:name :class :required false :type "string" :validate-fn css-class? :description "CSS class names - space separated string, or a vector of strings."}
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}
{:name :src :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging. Source code coordinates map containing keys" [:code ":file"] "and" [:code ":line"] ". See 'Debugging'."]}
{:name :debug-as :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging, when one component is used implement another component, and we want the implementation component to masquerade as the original component in debug output, such as component stacks. A map optionally containing keys" [:code ":component"] "and" [:code ":args"] "."]}]))
(defn v-box
"Returns hiccup which produces a vertical box.
It's primary role is to act as a container for components and lays it's children from top to bottom.
By default, it also acts as a child under it's parent"
[& {:keys [size width height min-width min-height max-width max-height justify align align-self margin padding gap children class style attr]
:or {size "none" justify :start align :stretch}
:as args}]
(or
(validate-args-macro v-box-args-desc args)
(let [s (merge
(flex-flow-style "column nowrap")
(flex-child-style size)
(when width {:width width})
(when height {:height height})
(when min-width {:min-width min-width})
(when min-height {:min-height min-height})
(when max-width {:max-width max-width})
(when max-height {:max-height max-height})
(justify-style justify)
(align-style :align-items align)
(when align-self (align-style :align-self align-self))
(when margin {:margin margin}) ;; margin and padding: "all" OR "top&bottom right&left" OR "top right bottom left"
(when padding {:padding padding})
(when visualise-flow? {:background-color "antiquewhite"})
style)
gap-form (when gap [re-com.box/gap
:src (at)
:size gap
:height gap]) ;; TODO: required to get around a Chrome bug: https://code.google.com/p/chromium/issues/detail?id=423112. Remove once fixed.
children (if gap
(interpose gap-form (filter identity children)) ;; filter is to remove possible nils so we don't add unwanted gaps
children)]
(into [:div
(merge
(->attr args)
{:class (str "rc-v-box display-flex " class) :style s}
attr)]
children))))
;; ------------------------------------------------------------------------------------
;; Component: box
;; ------------------------------------------------------------------------------------
(def box-args-desc
(when include-args-desc?
[{:name :child :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "a component (or string)"}
{:name :size :required false :default "none" :type "string" :validate-fn string? :description [:span "equivalent to CSS style " [:span.bold "flex"] "." [:br] "Examples: " [:code "initial"] ", " [:code "auto"] ", " [:code "none"] ", " [:code "100px"] ", " [:code "2"] " or a generic triple of " [:code "grow shrink basis"]]}
{:name :width :required false :type "string" :validate-fn string? :description "a CSS width style"}
{:name :height :required false :type "string" :validate-fn string? :description "a CSS height style"}
{:name :min-width :required false :type "string" :validate-fn string? :description "a CSS width style. The minimum width to which the box can shrink"}
{:name :min-height :required false :type "string" :validate-fn string? :description "a CSS height style. The minimum height to which the box can shrink"}
{:name :max-width :required false :type "string" :validate-fn string? :description "a CSS width style. The maximum width to which the box can grow"}
{:name :max-height :required false :type "string" :validate-fn string? :description "a CSS height style. The maximum height to which the box can grow"}
{:name :justify :required false :default :start :type "keyword" :validate-fn justify-style? :description [:span "equivalent to CSS style " [:span.bold "justify-content"] "." [:br] "One of " justify-options-list]}
{:name :align :required false :default :stretch :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-items"] "." [:br] " One of " align-options-list]}
{:name :align-self :required false :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-self"] "." [:br] "Used when a child must override the parent's align-items setting."]}
{:name :margin :required false :type "string" :validate-fn string? :description "a CSS margin style"}
{:name :padding :required false :type "string" :validate-fn string? :description "a CSS padding style"}
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}
{:name :src :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging. Source code coordinates map containing keys" [:code ":file"] "and" [:code ":line"] ". See 'Debugging'."]}
{:name :debug-as :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging, when one component is used implement another component, and we want the implementation component to masquerade as the original component in debug output, such as component stacks. A map optionally containing keys" [:code ":component"] "and" [:code ":args"] "."]}]))
(defn box
"Returns hiccup which produces a box, which is generally used as a child of a v-box or an h-box.
By default, it also acts as a container for further child compenents, or another h-box or v-box"
[& {:keys [size width height min-width min-height max-width max-height justify align align-self margin padding child class style attr src debug-as]
:or {size "none"}
:as args}]
(or
(validate-args-macro box-args-desc args)
(box-base :size size
:width width
:height height
:min-width min-width
:min-height min-height
:max-width max-width
:max-height max-height
:justify justify
:align align
:align-self align-self
:margin margin
:padding padding
:child child
:class-name "rc-box "
:class class
:style style
:attr attr
:src src
:debug-as debug-as)))
;; ------------------------------------------------------------------------------------
;; Component: scroller
;; ------------------------------------------------------------------------------------
(def scroller-args-desc
(when include-args-desc?
[{:name :child :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "a component (or string)"}
{:name :size :required false :default "auto" :type "string" :validate-fn string? :description [:span "equivalent to CSS style " [:span.bold "flex"] "." [:br] "Examples: " [:code "initial"] ", " [:code "auto"] ", " [:code "none"] ", " [:code "100px"] ", " [:code "2"] " or a generic triple of " [:code "grow shrink basis"]]}
{:name :scroll :required false :default "auto" :type "keyword" :validate-fn scroll-style? :description [:span "Sets both h-scroll and v-scroll at once: " [:br]
[:code ":auto"] ": only show scroll bar(s) if the content is larger than the scroller" [:br]
[:code ":on"] ": always show scroll bars" [:br]
[:code ":off"] ": never show scroll bar(s). Content which is not in the bounds of the scroller can not be seen" [:br]
[:code ":spill"] ": never show scroll bar(s). Content which is not in the bounds of the scroller spills all over the place"]}
{:name :h-scroll :required false :type "keyword" :validate-fn scroll-style? :description [:span "see " [:code ":scroll"] ". Overrides that setting"]}
{:name :v-scroll :required false :type "keyword" :validate-fn scroll-style? :description [:span "see " [:code ":scroll"] ". Overrides that setting"]}
{:name :width :required false :type "string" :validate-fn string? :description "initial width"}
{:name :height :required false :type "string" :validate-fn string? :description "initial height"}
{:name :min-width :required false :type "string" :validate-fn string? :description "a CSS width style. The minimum width to which the box can shrink"}
{:name :min-height :required false :type "string" :validate-fn string? :description "a CSS height style. The minimum height to which the box can shrink"}
{:name :max-width :required false :type "string" :validate-fn string? :description "a CSS width style. The maximum width to which the box can grow"}
{:name :max-height :required false :type "string" :validate-fn string? :description "a CSS height style. The maximum height to which the box can grow"}
{:name :justify :required false :default :start :type "keyword" :validate-fn justify-style? :description [:span "equivalent to CSS style " [:span.bold "justify-content"] "." [:br] "One of " justify-options-list]}
{:name :align :required false :default :stretch :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-items"] "." [:br] " One of " align-options-list]}
{:name :align-self :required false :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-self"] "." [:br] "Used when a child must override the parent's align-items setting."]}
{:name :margin :required false :type "string" :validate-fn string? :description "a CSS margin style"}
{:name :padding :required false :type "string" :validate-fn string? :description "a CSS padding style"}
{:name :class :required false :type "string" :validate-fn css-class? :description "CSS class names - space separated string, or a vector of strings."}
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}
{:name :src :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging. Source code coordinates map containing keys" [:code ":file"] "and" [:code ":line"] ". See 'Debugging'."]}
{:name :debug-as :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging, when one component is used implement another component, and we want the implementation component to masquerade as the original component in debug output, such as component stacks. A map optionally containing keys" [:code ":component"] "and" [:code ":args"] "."]}]))
(defn scroller
"Returns hiccup which produces a scoller component.
This is the way scroll bars are added to boxes, in favour of adding the scroll attributes directly to the boxes themselves.
IMPORTANT: Because this component becomes the flex child in place of the component it is wrapping, you must copy the size attibutes to this componenet.
There are three scroll types:
- h-scroll Determines how the horizontal scroll bar will be displayed.
- v-scroll Determines how the vertical scroll bar will be displayed.
- scroll Sets both h-scroll and v-scroll at once.
Syntax: :auto [DEFAULT] Only show scroll bar(s) if the content is larger than the scroller.
:on Always show scroll bar(s).
:off Never show scroll bar(s). Content which is not in the bounds of the scroller can not be seen.
:spill Never show scroll bar(s). Content which is not in the bounds of the scroller spills all over the place.
Note: If scroll is set, then setting h-scroll or v-scroll overrides the scroll value"
[& {:keys [size scroll h-scroll v-scroll width height min-width min-height max-width max-height justify align align-self margin padding child class style attr src debug-as]
:or {size "auto"}
:as args}]
(or
(validate-args-macro scroller-args-desc args)
(let [not-v-or-h (and (nil? v-scroll) (nil? h-scroll))
scroll (if (and (nil? scroll) not-v-or-h) :auto scroll)]
(box-base :size size
:scroll scroll
:h-scroll h-scroll
:v-scroll v-scroll
:width width
:height height
:min-width min-width
:min-height min-height
:max-width max-width
:max-height max-height
:justify justify
:align align
:align-self align-self
:margin margin
:padding padding
:child child
:class-name "rc-scroller "
:class class
:style style
:attr attr
:src src
:debug-as debug-as))))
;; ------------------------------------------------------------------------------------
;; Component: border
;; ------------------------------------------------------------------------------------
(def border-args-desc
(when include-args-desc?
[{:name :child :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "a component (or string)"}
{:name :border :required false :default "1px solid lightgrey" :type "string" :validate-fn string? :description "a CSS border style. A convenience to describe all borders in one parameter"}
{:name :l-border :required false :type "string" :validate-fn string? :description [:span "a CSS border style for the left border. Overrides " [:code ":border"]]}
{:name :r-border :required false :type "string" :validate-fn string? :description [:span "a CSS border style for the right border. Overrides " [:code ":border"]]}
{:name :t-border :required false :type "string" :validate-fn string? :description [:span "a CSS border style for the top border. Overrides " [:code ":border"]]}
{:name :b-border :required false :type "string" :validate-fn string? :description [:span "a CSS border style for the bottom. Overrides " [:code ":border"]]}
{:name :radius :required false :type "string" :validate-fn string? :description "a CSS radius style eg.\"2px\""}
{:name :size :required false :default "none" :type "string" :validate-fn string? :description [:span "equivalent to CSS style " [:span.bold "flex"] "." [:br] "Examples: " [:code "initial"] ", " [:code "auto"] ", " [:code "none"] ", " [:code "100px"] ", " [:code "2"] " or a generic triple of " [:code "grow shrink basis"]]}
{:name :width :required false :type "string" :validate-fn string? :description "a CSS style describing the initial width"}
{:name :height :required false :type "string" :validate-fn string? :description "a CSS style describing the initial height"}
{:name :min-width :required false :type "string" :validate-fn string? :description "a CSS width style. The minimum width to which the box can shrink"}
{:name :min-height :required false :type "string" :validate-fn string? :description "a CSS height style. The minimum height to which the box can shrink"}
{:name :max-width :required false :type "string" :validate-fn string? :description "a CSS width style. The maximum width to which the box can grow"}
{:name :max-height :required false :type "string" :validate-fn string? :description "a CSS height style. The maximum height to which the box can grow"}
{:name :margin :required false :type "string" :validate-fn string? :description "a CSS margin style"}
{:name :padding :required false :type "string" :validate-fn string? :description "a CSS padding style"}
{:name :class :required false :type "string" :validate-fn css-class? :description "CSS class names - space separated string, or a vector of strings."}
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}
{:name :src :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging. Source code coordinates map containing keys" [:code ":file"] "and" [:code ":line"] ". See 'Debugging'."]}
{:name :debug-as :required false :type "map" :validate-fn map? :description [:span "Used in dev builds to assist with debugging, when one component is used implement another component, and we want the implementation component to masquerade as the original component in debug output, such as component stacks. A map optionally containing keys" [:code ":component"] "and" [:code ":args"] "."]}]))
(defn border
"Returns hiccup which produces a border component.
This is the way borders are added to boxes, in favour of adding the border attributes directly to the boxes themselves.
border property syntax: '<border-width> || <border-style> || <color>'
- border-width: thin, medium, thick or standard CSS size (e.g. 2px, 0.5em)
- border-style: none, hidden, dotted, dashed, solid, double, groove, ridge, inset, outset
- color: standard CSS color (e.g. grey #88ffee)"
[& {:keys [size width height min-width min-height max-width max-height margin padding border l-border r-border t-border b-border radius child class style attr src debug-as]
:or {size "none"}
:as args}]
(or
(validate-args-macro border-args-desc args)
(let [no-border (every? nil? [border l-border r-border t-border b-border])
default-border "1px solid lightgrey"]
(box-base :size size
:width width
:height height
:min-width min-width
:min-height min-height
:max-width max-width
:max-height max-height
:margin margin
:padding padding
:border (if no-border default-border border)
:l-border l-border
:r-border r-border
:t-border t-border
:b-border b-border
:radius radius
:child child
:class-name "rc-border "
:class class
:style style
:attr attr
:src src
:debug-as debug-as))))