-
Notifications
You must be signed in to change notification settings - Fork 57
/
beautify-sass.Rmd
963 lines (809 loc) · 27.1 KB
/
beautify-sass.Rmd
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
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
# Tidy your CSS with Sass {#beautify-sass}
One of the main problem with __CSS__ is to handle the growing number of files and the __code repetition__. Nowadays, web developers mainly use __CSS pre-processors__ like [Sass](https://sass-lang.com/guide), which stands for "Syntactically Awesome Style Sheets ", providing access to variables, mathematical operators, functions and loops, thereby reducing the code complexity and extending the possibilities. [RStudio](https://rstudio.com) developed the [{sass}](https://rstudio.github.io/sass/articles/sass.html) package [@R-sass], which makes it possible to use __Sass__ in Shiny apps or Rmarkdown documents.
```{r, echo=FALSE}
# disable devmode.
# For some reason it may turn on without notice.
shiny::devmode(FALSE)
```
## Getting started with Sass
While we could use Sass from the terminal, we leverage the `{sass}` package features to work from R.
To install `{sass}`, we run:
```{r, eval=FALSE}
# CRAN
install.packages("sass")
# development version
remotes::install_github("rstudio/sass")
```
The main function is `sass()`, whose __input__ parameter accepts:
- A R string like `a { color: pink}`.
- A named `list()`.
- A file passed with `sass_file()`; imports with `sass_import()`.
- More complex structures.
```{r, eval=FALSE}
library(sass)
# with a string
sass(input = ".element-class { color: pink;}")
# with named list
sass(list(color = "pink", ".element-class { color: $color;}"))
```
```{r, echo=FALSE}
library(sass)
print(sass(list(color = "pink", ".element-class { color: $color;}")))
```
Note how the R code objects are seamlessly converted to Sass variables. For convenience, we will be using named lists in the remainder of the book.
### Variables
Let us consider the following example, where two different classes have the same color:
```{r, echo=FALSE, results='asis'}
css_code <- ".class-1{
color: #33BEFF;
}
.class-2{
background-color: #33BEFF;
}"
code_chunk_custom(css_code, "css")
```
Shouldn't this be easier? Imagine if we had hundreds of elements with the same color.
What happens in case the color changes? Do we have to update all properties by hand?
If we let the Sass variable `$color`:
```{r, echo=FALSE, results='asis'}
css_code <- "$color: purple;
.class-1{
color: $color;
}
.class-2{
background-color: $color;
}"
code_chunk_custom(css_code, "css")
```
we can quickly solve that problem. With `{sass}`, we define one variable holding the color as well as
our two rules, so that we obtain:
```{r, eval=FALSE}
rule1 <- ".class-1{ color: $color; }"
rule2 <- ".class-2{ background-color: $color; }"
sass(input = list(color = "purple", rule1, rule2))
```
```{r, echo=FALSE}
rule1 <- ".class-1{ color: $color; }"
rule2 <- ".class-2{ background-color: $color; }"
print(sass(input = list(color = "purple", rule1, rule2)))
```
::: {.noteblock data-latex=""}
Add the __default!__ tag after the variable definition, if you want
to let others modify it, that is `"$color: purple !default;"`.
:::
### Partials and Modules
It is best practice to save useful code snippets in one place, and reuse them anytime and anywhere. Sass allows to define __partials__, like `_partial.css`, with the leading
underscore, which avoids its conversion into CSS. Partials are subsequently called
with `@import <PARTIAL_NAME>` (you may also find `@use`, the latter not being handled
by `LibSass`, which fuels`{sass}`), thereby significantly reducing code duplication.
Modules are pieces of Sass files that are later converted into CSS, reducing file size to a minimum.
Below is an example of the `bootstrap.scss` [file](https://github.com/twbs/bootstrap/blob/main/scss/bootstrap.scss):
```{r, echo=FALSE, results='asis'}
css_code <- '/*!
* Bootstrap v5.0.0-beta1 (https://getbootstrap.com/)
...
*/
// scss-docs-start import-stack
// Configuration
@import "functions";
@import "variables";
@import "mixins";
@import "utilities";
// Layout & components
@import "root";
@import "reboot";
...
// Helpers
@import "helpers";
// Utilities
@import "utilities/api";
// scss-docs-end import-stack'
code_chunk_custom(css_code, "css")
```
which is easier to read and maintain than the original `bootstrap.css` with 10717 lines of code!
In practice, we often end up with a main Sass file and compile it as follows:
```{r, eval=FALSE}
sass(sass_file("main.scss"))
```
### Mixins and Functions
Another great advantage of Sass is the ability to generate reusable units of code, also known
as __mixins__ or __functions__.
#### Mixins
To make a 90-degree rotation in CSS, we have to write:
```{r, echo=FALSE, results='asis'}
css_code <- ".element {
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}"
code_chunk_custom(css_code, "css")
```
which is already tedious. Mixins allow us to encapsulate the logic into a __reusable__ unit:
```{r, echo=FALSE, results='asis'}
css_code <- "@mixin transform($property, ...) {
-webkit-transform: $property;
-ms-transform: $property;
transform: $property;
}
.element1 {
@include transform(rotate(90deg));
}"
code_chunk_custom(css_code, "css")
```
The mixin starts with a `@mixin` keyword followed by its name and parameters.
It is called with `@include <MIXIN_NAME(PARMS)>`, very similar to a function declaration, except that
it must return a CSS rule.
```{r, eval=FALSE}
mixin <- "@mixin transform($property) {
-webkit-transform: $property;
-ms-transform: $property;
transform: $property;
}"
rule <- ".element1 { @include transform($prop); }"
sass(input = list(prop = "rotate(90deg)", mixin, rule))
```
```{r, echo=FALSE}
mixin <- "@mixin transform($property) {
-webkit-transform: $property;
-ms-transform: $property;
transform: $property;
}"
rule <- ".element1 { @include transform($prop); }"
print(sass(input = list(prop = "rotate(90deg)", mixin, rule)))
```
#### Functions
Sass offers many built-in [modules](https://sass-lang.com/documentation/modules) containing
ready-to-use functions for colors, numbers, strings, lists, maps, ... Some functions like
`rgb` are global, so that we don't have to import the corresponding module.
```{r, eval=FALSE}
sass(".pouet { color: rgb(0, 255, 0); }")
```
```{r, echo=FALSE}
print(sass(".pouet { color: rgb(0, 255, 0); }"))
```
Besides, it is also possible to design custom functions with `@function`, the syntax being similar to that of the mixins:
```{r, echo=FALSE, results='asis'}
css_code <- "@function name($parm1, $parm2) {
/* logic */
@return value;
}"
code_chunk_custom(css_code, "css")
```
While debugging functions, it might be useful to capture intermediate elements.
`@debug` allows this:
```{r, echo=FALSE, results='asis'}
css_code <- "$test: 1;
@debug test;"
code_chunk_custom(css_code, "css")
```
```{r, eval=FALSE}
sass(
list(
a = 2,
b = 4,
"@function multiply($parm1, $parm2) {
@debug 'parm1 is #{$parm1}';
@debug 'parm2 is #{$parm2}';
@return $parm1 * $parm2;
}",
".my-class {
width: multiply($a, $b) * 1px;
}"
)
)
```
```{r, echo=FALSE, warning=TRUE, message=TRUE}
print(
sass(
list(
a = 2,
b = 4,
"@function multiply($parm1, $parm2) {
@debug 'parm1 is #{$parm1}';
@debug 'parm2 is #{$parm2}';
@return $parm1 * $parm2;
}",
".my-class {
width: multiply($a, $b) * 1px;
}"
)
)
)
```
Notice the use of __mathematical operators__ like `*`, `+`, `-`, `/` and `%`, which is not possible with CSS.
### Extend/Inheritance
We consider two alerts with the color as the only difference. As we can't capture multiple properties
inside one single Sass variable, we introduce the __extend__ concept, which permits us to import
CSS properties inside multiple rules. We first define a generic `alerts-common` rule,
prefixed by the `%` symbol. It contains several rules and variables:
```{r, echo=FALSE, results='asis'}
css_code <- "%alerts-common {
position: relative;
padding: $alert-padding-y $alert-padding-x;
margin-bottom: $alert-margin-bottom;
}
.alert-red {
@extend %alerts-common;
color: red;
}
.alert-green {
@extend %alerts-common;
color: green;
}"
code_chunk_custom(css_code, "css")
```
Let's translate this into R:
```{r, eval=FALSE}
common <- "%alerts-common {
position: relative;
padding: $alert-padding-y $alert-padding-x;
margin-bottom: $alert-margin-bottom;
}"
alert_red <- ".alert-red {
@extend %alerts-common;
color: red;
}
"
alert_green <- ".alert-green {
@extend %alerts-common;
color: green;
}
"
sass(input = list(
"alert-padding-y" = "5px",
"alert-padding-x" = "10px",
"alert-margin-bottom" = "2px",
common,
alert_red,
alert_green
))
```
```{r, echo=FALSE}
common <- "%alerts-common {
position: relative;
padding: $alert-padding-y $alert-padding-x;
margin-bottom: $alert-margin-bottom;
}"
alert_red <- ".alert-red {
@extend %alerts-common;
color: red;
}
"
alert_green <- ".alert-green {
@extend %alerts-common;
color: green;
}
"
print(sass(input = list(
"alert-padding-y" = "5px",
"alert-padding-x" = "10px",
"alert-margin-bottom" = "2px",
common,
alert_red,
alert_green
)))
```
This method avoids to overload elements with unnecessary classes such as `.alert-common .alert-red ...`.
Yet, we could have programmatically generated the two alert
classes with a loop to avoid duplication.
### Flow controls
These are elements aimed at fine-tuning mixins and functions behavior.
#### if and else
As in every programming language, `if` and `else` control the execution of a code block, depending
on some conditions.
Below, we only want to conditionally control a shadow property, depending on the alert color:
```{r, echo=FALSE, results='asis'}
css_code <- "@mixin add-shadow($color) {
@if $color == red {
box-shadow:
0 4px 10px 0 rgb(255, 0, 0),
0 4px 20px 0 rgb(255, 0, 0);
} @else if $color == green {
box-shadow:
0 4px 10px 0 rgb(0, 255, 0),
0 4px 20px 0 rgb(0, 255, 0);
}
}
.alert-red {
@extend %alerts-common;
color: red;
@include add-shadow($color: red);
}
.alert-green {
@extend %alerts-common;
color: green;
@include add-shadow($color: green);
}"
code_chunk_custom(css_code, "css")
```
```{r, eval=FALSE}
add_shadow <- "@mixin add-shadow($color) {
@if $color == red {
box-shadow:
0 4px 10px 0 rgb(255, 0, 0),
0 4px 20px 0 rgb(255, 0, 0);
} @else if $color == green {
box-shadow:
0 4px 10px 0 rgb(0, 255, 0),
0 4px 20px 0 rgb(0, 255, 0);
}
}
"
common <- "%alerts-common {
position: relative;
padding: $alert-padding-y $alert-padding-x;
margin-bottom: $alert-margin-bottom;
}"
alert_red <- ".alert-red {
@extend %alerts-common;
color: red;
@include add-shadow($color: red);
}
"
alert_green <- ".alert-green {
@extend %alerts-common;
color: green;
@include add-shadow($color: green);
}
"
sass(input = list(
"alert-padding-y" = "5px",
"alert-padding-x" = "10px",
"alert-margin-bottom" = "2px",
common,
add_shadow,
alert_red,
alert_green
))
```
```{r, echo=FALSE}
add_shadow <- "@mixin add-shadow($color) {
@if $color == red {
box-shadow:
0 4px 10px 0 rgb(255, 0, 0),
0 4px 20px 0 rgb(255, 0, 0);
} @else if $color == green {
box-shadow:
0 4px 10px 0 rgb(0, 255, 0),
0 4px 20px 0 rgb(0, 255, 0);
}
}
"
common <- "%alerts-common {
position: relative;
padding: $alert-padding-y $alert-padding-x;
margin-bottom: $alert-margin-bottom;
}"
alert_red <- ".alert-red {
@extend %alerts-common;
color: red;
@include add-shadow($color: red);
}
"
alert_green <- ".alert-green {
@extend %alerts-common;
color: green;
@include add-shadow($color: green);
}
"
print(sass(input = list(
"alert-padding-y" = "5px",
"alert-padding-x" = "10px",
"alert-margin-bottom" = "2px",
common,
add_shadow,
alert_red,
alert_green
)))
```
#### Loops
##### Each
We would like to create the alert class with only one rule. We first define a list of colors in Sass and
call `@each`:
```{r, echo=FALSE, results='asis'}
css_code <- "$colors: red, green;
@each $color in $colors {
.alert-#{$color} {
color: green;
@include add-shadow($box-color: $color);
}
}"
code_chunk_custom(css_code, "css")
```
The structure is the same as the JavaScript loop. You'll also notice the `#{...}`, which
is called [interpolation](https://sass-lang.com/documentation/interpolation) and allows us to
insert any Sass expression in a string. As another example, if we want to create a `background-image` property
within a mixin, we could do `background-image: url("/some_path/#{$name}.svg")`, where `#{$name}` holds the file name.
```{r, eval=FALSE}
add_shadow <- "@mixin add-shadow($color) {
@if $color == red {
box-shadow:
0 4px 10px 0 rgb(255, 0, 0),
0 4px 20px 0 rgb(255, 0, 0);
} @else if $color == green {
box-shadow:
0 4px 10px 0 rgb(0, 255, 0),
0 4px 20px 0 rgb(0, 255, 0);
}
}
"
common <- "%alerts-common {
position: relative;
padding: $alert-padding-y $alert-padding-x;
margin-bottom: $alert-margin-bottom;
}"
alerts_rule <- "@each $color in $colors {
.alert-#{$color} {
@extend %alerts-common;
color: green;
@include add-shadow($color: $color);
}
}
"
sass(input = list(
colors = c("green", "red"),
"alert-padding-y" = "5px",
"alert-padding-x" = "10px",
"alert-margin-bottom" = "2px",
common,
add_shadow,
alerts_rule
))
```
```{r, echo=FALSE}
add_shadow <- "@mixin add-shadow($color) {
@if $color == red {
box-shadow:
0 4px 10px 0 rgb(255, 0, 0),
0 4px 20px 0 rgb(255, 0, 0);
} @else if $color == green {
box-shadow:
0 4px 10px 0 rgb(0, 255, 0),
0 4px 20px 0 rgb(0, 255, 0);
}
}
"
common <- "%alerts-common {
position: relative;
padding: $alert-padding-y $alert-padding-x;
margin-bottom: $alert-margin-bottom;
}"
alerts_rule <- "@each $color in $colors {
.alert-#{$color} {
@extend %alerts-common;
color: green;
@include add-shadow($color: $color);
}
}
"
print(sass(input = list(
colors = c("green", "red"),
"alert-padding-y" = "5px",
"alert-padding-x" = "10px",
"alert-margin-bottom" = "2px",
common,
add_shadow,
alerts_rule
)))
```
It becomes even more powerful while working with __maps__ like `$font-weights: ("regular": 400, "medium": 500, "bold": 700);`, i.e, by key/value [pairs](https://sass-lang.com/documentation/at-rules/control/each#with-maps).
`@each` is as convenient as `lapply` or `map` functions to chain repetitive rules creation.
##### For
However, it is not straightforward to count up or down with `@each`. This is precisely where
`@for` fills the gap. The generic scheme is:
```{r, echo=FALSE, results='asis'}
css_code <- "@for <variable> from <expression> to <expression> { ... }
@for <variable> from <expression> through <expression> { ... }"
code_chunk_custom(css_code, "css")
```
__to__ excludes the last number while __through__ includes it.
### Nesting code
To make your code more readable, `Sass` allows you to __nest__ rules by hierarchy, thereby aligning with the underlying HTML structure. For instance, looking at the previous example from section \@ref(css-pseudoelements):
```{r, echo=FALSE, results='asis'}
css_code <- ".navbar-nav > li:first-child > a {
font-size: 20px;
font-weight: bold;
}
.navbar-nav > li:first-child > a::before {
content: '✌️'
}"
code_chunk_custom(css_code, "css")
```
Transforming to Sass code, we combine the two previous rules into a single one:
```{r, echo=FALSE, results='asis'}
css_code <- ".navbar-nav {
> li:first-child {
> a {
font-size: 20px;
font-weight: bold;
&:before {
content: '✌️'
}
}
}
}"
code_chunk_custom(css_code, "css")
```
The following is another example taken from the AdminLTE3 library powering `{bs4Dash}`. This code shows how the card CSS is structured, leveraging all the above Sass capabilities like __mixins__, nesting, functions, ... This chunk may be read from top to bottom and gives a rather good idea of how the card will look, contrary to the classic CSS approach. It first applies a shadow with a mixin, then sets the bottom margin with a function. All listed variables are defined in a separate file. Besides, the card body has text printed in white color when the dark mode is active. Finally, when the card is collapsed, none of the body and footer elements are displayed.
```{r, echo=FALSE, results='asis'}
css_code <- ".card {
@include box-shadow($card-shadow);
margin-bottom: map-get($spacers, 3);
&.bg-dark {
.card-header {
border-color: $card-dark-border-color;
}
&,
.card-body {
color: $white;
}
}
/* other rules */
// collapsed mode
&.collapsed-card {
.card-body,
.card-footer {
display: none;
}
}
/* other rules */
}"
code_chunk_custom(css_code, "css")
```
## `{sass}` best practices
As it's best practice, especially for debugging purposes, to include assets as HTML
dependencies, it is a good idea to organize the Sass variable definition, the function/mixins in __layers__, leveraging the `sass_layer()` function:
```{r, warning=FALSE}
rule1 <- ".class-1{ color: $color; }"
rule2 <- ".class-2{ background-color: $color; }"
layer1 <- sass_layer(
defaults = list(color = "purple"),
rules = list(rule1, rule2)
)
```
```{r, echo=FALSE}
print(layer1)
```
Besides, `sass_layer()` provides options like:
- __declarations__ containing any function, mixin elements, in a `sass_file` for instance.
- __html_deps__ that attaches a single or a list of HTML dependencies to the provided Sass code, as shown below.
```{r, eval = FALSE}
sass_layer(
html_deps = htmltools::htmlDependency(
name = "my-dep",
version = "1.0.0",
package = "mypkg",
src = "path",
...
)
)
```
Ultimately, multiple layers may be bundled with `sass_bundle()`:
```{r, warning=FALSE}
layer2 <- sass_layer(
defaults = list(color = "blue"),
rules = list(rule1, rule2)
)
my_bundle <- sass_bundle(layer1 = layer1, layer2 = layer2)
my_bundle
```
```{r, echo=FALSE}
print(sass(my_bundle))
```
`sass_bundle_remove()` removes a given layer from the bundle, provided that you passed a named list to `sass_bundle()`.
This allows other developers to reuse and modify predefined layers:
```{r}
my_bundle <- sass_bundle_remove(my_bundle, "layer2")
my_bundle
sass(my_bundle)
```
```{r, echo=FALSE}
print(sass(my_bundle))
```
## From Sass to CSS
`sass()` can generate CSS from Sass by passing an __output__ parameter pointing to the path where
to generate the CSS file. Best practice consists in enabling __compression__ and __source maps__. We discuss
this later in the book in Chapter \@ref(workflow-charpente). Overall those steps makes the code faster to load and easier to debug:
```{r, eval=FALSE}
sass(
list(
color = "pink",
".a { color: $color; }"
),
options = sass_options(
output_style = "compressed",
source_map_embed = TRUE
)
)
```
<!-- manual copy paste for pdf output formating -->
```{r, echo=FALSE, results='asis'}
css_code <- "/* CSS */
.a{color:pink}
/*# sourceMappingURL=data:application/json;base64,ewoJInZlcn
Npb24iOiAzLAoJImZpbGUiOiAic3RkaW4uY3NzIiwKCSJzb3VyY2VzIjogWwo
JCSJzdGRpbiIKCV0sCgkibmFtZXMiOiBbXSwKCSJtYXBwaW5ncyI6ICJBQUNB
LEFBQUEsRUFBRSxBQUFDLENBQUUsS0FBSyxDQURGLElBQUksQ0FDVSIK
fQ== */"
code_chunk_custom(css_code, "css")
```
```{r, eval=FALSE}
sass(
sass_file("main.scss"),
"<OUTPUT PATH>",
options = sass_options(
output_style = "compressed",
source_map_embed = TRUE
)
)
```
## Caching
By default, `sass()` will try to do the minimum work, that is, on the first compilation, the resulting code is cached so that further compilation is not required. However, this behavior can lead to issues during development, especially for previewing a new theme change, since by definition, `sass()` will go directly in the cache instead of picking up the new elements. This is also what happens in a web browser when CSS is cached, preventing seeing any style change unless the cache is emptied. To avoid this, we encourage developers to set `options(sass.cache=FALSE)` during the theme development phase.
## Sass and Shiny
Now let's go back to Shiny! How do we include Sass code in a Shiny app? There are multiple situations:
1. You simply want to style a Shiny app.
2. You developed a template with custom JS and Sass/CSS to be reused by other developers.
The first option is rather simple since the Sass code is compiled with `sass()` before the Shiny app is launched. The resulting code may be either a string or a CSS file (within the `www` folder), to be included in the `head`. We assume it is located at the app folder level:
```{r, eval=FALSE}
sass(
list(
color = "pink",
size = "30px",
".awesome-link {
color: $color;
font-size: $size;
&:hover{
color: green;
}
}"
),
output = "www/main.min.css",
options = sass_options(
output_style = "compressed",
source_map_embed = TRUE
)
)
```
Source maps allow us to see the original Sass code, as shown Figure \@ref(fig:sass-inspect).
`sass_options()` gives the flexibility to fine-tune the CSS output and source map configuration.
::: {.importantblock data-latex=""}
For complex projects where the CSS compilation may take time, we strongly advise processing the CSS independently from the app startup.
:::
```{r sass-inspect, echo=FALSE, fig.cap='Inspect Sass code in the web browser.', out.width='100%', fig.align='center'}
knitr::include_graphics("images/beautify/sass-inspect.png")
```
The second option requires running `sass()` passing an __output__ file within the package. Then, the generated CSS
is included in an HTML dependency, ready to be shipped with the template:
```{r, eval=FALSE}
sass(
sass_file("main.scss"),
"<OUTPUT PATH>/main.css",
options = sass_options(
output_style = "compressed",
source_map_embed = TRUE
)
)
my_css_deps <- htmltools::htmlDependency(
name = "my-style",
version = "1.0.0",
package = "mypkg",
src = "<OUTPUT PATH>",
stylesheet = "main.css"
)
```
## Examples
### Customize `{bs4Dash}` colors {#sass-customize-bs4Dash}
`{bs4Dash}` is a Bootstrap 4 dashboard template built on top of the [AdminLTE3](https://adminlte.io/themes/v3/) HTML template. `{shinydashboard}` is powered by the previous version, that is [AdminLTE2](https://adminlte.io/themes/AdminLTE/index2.html), which makes it somehow `{bs4Dash}`'s big brother!
AdminLTE3 relies on Sass, and all files are stored [here](https://github.com/ColorlibHQ/AdminLTE/tree/4fc0f9e59fff2a96b2fecae66def58664af57e9d/build/scss). Particularly, all variables are located in the `_variables.scss` partial. Since we can decompose our
Sass code in multiple layers, we seamlessly customize the theme color
variables listed below:
```{r, echo=FALSE, results='asis'}
css_code <- "$blue: #0073b7 !default;
$lightblue: #3c8dbc !default;
$navy: #001f3f !default;
$teal: #39cccc !default;
$olive: #3d9970 !default;
$lime: #01ff70 !default;
$orange: #ff851b !default;
$fuchsia: #f012be !default;
$purple: #605ca8 !default;
$maroon: #d81b60 !default;
$black: #111 !default;
$gray-x-light: #d2d6de !default;"
code_chunk_custom(css_code, "css")
```
Let's provide our own defaults with some custom [colors](https://colorswall.com/palette/66354/):
```{r, echo=FALSE, results='asis'}
css_code <- "$blue: #136377 !default;
$olive: #d8bc66 !default;
$lime: #fcec0c !default;
$orange: #978d01 !default;
$maroon: #58482c !default;
$gray-x-light: #d1c5c0 !default;"
code_chunk_custom(css_code, "css")
```
Now we would have to recompile the whole AdminLTE3 Sass code to account for these changes.
It means all scss assets must be accessible somewhere: this is what the `{fresh}` package is doing under the [hood](https://github.com/dreamRs/fresh/blob/bf1e00a9d9f2c3388fd88a6a0ca63869b3bf8a81/R/create_theme.R#L69).
No worries, we'll come back to `{fresh}` in the next chapter. For now, we rely on the `{OSUICode}` Sass code stored at
`system.file("sass/adminlte/adminlte.scss", package = "OSUICode")` (since `{fresh}` may change in the future, `{OSUICode}` will be frozen to make sure the code always works):
```{r, eval=FALSE}
css <- sass(
sass_layer(
defaults = list(
lightblue = "#136377",
olive = "#d8bc66",
lime = "#fcec0c",
orange = "#978d01",
maroon = "#58482c",
"gray-x-light" = "#d1c5c0"
),
rules = sass_file(
input = system.file(
"sass/adminlte/adminlte.scss",
package = "OSUICode"
)
)
)
)
# This CSS is injected inside the app head (see example)
```
The corresponding app may be tested below. If you want to see the whole code, run `OSUICode::get_example("sass/examples/bs4Dash-custom")`.
```{r, echo=FALSE, results='asis'}
code_chunk(OSUICode::get_example("sass/examples/bs4Dash-custom", view_code = FALSE), "r")
```
Output is shown in Figure \@ref(fig:sass-adminlte-colors).
```{r sass-adminlte-colors, echo=FALSE, fig.cap='Custom AdminLTE colors.', out.width='100%'}
knitr::include_graphics("images/beautify/sass-adminlte-colors.png")
```
You probably noticed a potential issue. We indeed have to rely on a specific AdminLTE version,
namely `3.1.0`, that is not necessarily the one we want. Therefore, an alternative would
be to download the Sass files from AdminLTE3, store them in a package, ideally the `/inst` folder, and recompile the code from that folder with new variables. As AdminLTE3 depends on Bootstrap 4, we would have to recover those dependencies in a separate folder, making sure it is accessible to the AdminLTE Sass code.
### Customize `{shinybulma}`
For convenience, the Sass code is already included in the book side [package](https://github.com/DivadNojnarg/OSUICode/tree/43911d32885e960d6f42c7bd7d92748109f29f00/inst/sass/bulma).
The goal is to change the main color palette that comprises:
- primary
- info
- success
- warning
- danger
In total, bulma exposes 419 Sass [variables](https://bulma.io/documentation/customize/concepts/)!
Among all files, we locate the main variables file and select the relevant variables we want to modify.
Notice we can retrieve all the information on [initial variables](https://bulma.io/documentation/customize/variables/#initial-variables) and [derived variables](https://bulma.io/documentation/customize/variables/#variables).
We assign them new [values](https://blog.depositphotos.com/neon-color-palettes.html):
```{r, echo=FALSE, results='asis'}
css_code <- "$turquoise: #03a4ff;
$cyan: #e705be;
$green: #f3d6e9;
$yellow: #fdaf2c;
$red: #ff483e;
$scheme-main: hsl(0, 0%, 4%);"
code_chunk_custom(css_code, "css")
```
Particularly, we target the main body color stored in `$scheme-main`. Instead of pointing
to `$white`, we change its value to the default `$black`. We then Compile the new CSS with `sass()`:
```{r, warning=FALSE}
css <- sass(
sass_layer(
defaults = list(
turquoise = "#03a4ff",
cyan = "#e705be",
green = "#f3d6e9",
yellow = "#fdaf2c",
red = "#ff483e",
"scheme-main" = "hsl(0, 0%, 10%)"
),
rules = sass_file(input = system.file(
"sass/bulma/bulma.sass",
package = "OSUICode"
))
)
)
```
Finally, we try the new theme in the following app, shown in Figure \@ref(fig:sass-bulma-tiles).
If you want to see the whole code, run `OSUICode::get_example("sass/examples/shinybulma-custom")`.
```{r, echo=FALSE, results='asis'}
code_chunk(OSUICode::get_example("sass/examples/shinybulma-custom", view_code = FALSE), "r")
```
```{r sass-bulma-tiles, echo=FALSE, fig.cap='Custom bulma theme.', out.width='100%'}
knitr::include_graphics("images/beautify/sass-bulma-tiles.png")
```