Skip to content

Commit 8a8a7ab

Browse files
authored
Full-Width and Separate CSS (#4)
* Separates out the inline CSS on elements and attempts to re-use existing bootstrap styling set via pkgdown * Allow full-width (full screen)
1 parent 7580cf2 commit 8a8a7ab

File tree

2 files changed

+289
-27
lines changed

2 files changed

+289
-27
lines changed

R/examplesWebR.R

Lines changed: 259 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
#' \section{WebR}{
3131
#' \ifelse{html}{
3232
#' \out{
33-
#' <a href="webR_URL">\U0001F310 View in webR REPL</a>
33+
#' <div class="rocleteer-webr-container">...</div>
3434
#' }
3535
#' }{
3636
#' \ifelse{latex}{
@@ -56,6 +56,32 @@
5656
#' - **LaTeX/PDF**: Plain URL link to webR session
5757
#' - **Other formats**: Informational message about limited support
5858
#'
59+
#' @section Styling and Theming:
60+
#'
61+
#' The webR Section HTML output uses Bootstrap-compatible CSS classes and custom
62+
#' properties to link themeing to pkgdown's existing CSS themes. The styles
63+
#' follow the CSS custom properties pattern:
64+
#'
65+
#' - Private variables (`--_*`): Used internally
66+
#' - Public variables (`--rocleteer-webr-*`): For user customization
67+
#' - Bootstrap variables (`--bs-*`): Used as fallbacks
68+
#'
69+
#' Main CSS classes are:
70+
#'
71+
#' - `.rocleteer-webr-container`: Main container
72+
#' - `.rocleteer-webr-warning`: Experimental feature warning
73+
#' - `.rocleteer-webr-btn`, `.rocleteer-webr-btn-primary`, etc.: Button styling
74+
#'
75+
#' To customize appearance in pkgdown sites, override CSS custom properties by
76+
#' including into a file:
77+
#'
78+
#' ```css
79+
#' .rocleteer-webr-container {
80+
#' --rocleteer-webr-container-bg: #f0f8ff;
81+
#' --rocleteer-webr-btn-primary-bg: #1976d2;
82+
#' }
83+
#' ```
84+
#'
5985
#' @section Parameters:
6086
#'
6187
#' The tag supports optional parameters (these override global `DESCRIPTION` config):
@@ -326,7 +352,7 @@ encode_webr_code <- function(code, filename = "example.R", autorun = FALSE) {
326352
if (!requireNamespace("base64enc", quietly = TRUE)) {
327353
stop("Package 'base64enc' is required for @examplesWebR tag")
328354
}
329-
355+
330356
# Create the share item structure exactly as webR expects
331357
share_item <- list(
332358
list(
@@ -519,6 +545,167 @@ parse_webr_params <- function(tag_line, global_config = list()) {
519545
return(params)
520546
}
521547

548+
#' Generate Rocleteer webR CSS styles
549+
#'
550+
#' Create the complete CSS for webR components as an inline style block
551+
#'
552+
#' @return
553+
#' HTML string with complete CSS styles
554+
#'
555+
#' @noRd
556+
webr_inline_css <- function() {
557+
paste0(
558+
'<style id="rocleteer-webr-styles">',
559+
'/* WebR Interactive Examples CSS - rocleteer package */',
560+
'.rocleteer-webr-container {',
561+
' --_container-bg: var(--rocleteer-webr-container-bg, var(--bs-light, #f8f9fa));',
562+
' --_container-border: var(--rocleteer-webr-container-border, var(--bs-border-color, #dee2e6));',
563+
' --_container-border-radius: var(--rocleteer-webr-container-border-radius, var(--bs-border-radius, 8px));',
564+
' --_warning-bg: var(--rocleteer-webr-warning-bg, var(--bs-info-bg-subtle, #e7f3ff));',
565+
' --_warning-border: var(--rocleteer-webr-warning-border, var(--bs-info-border-subtle, #b3d9ff));',
566+
' --_warning-text: var(--rocleteer-webr-warning-text, var(--bs-info-text-emphasis, #0c5aa6));',
567+
' --_btn-primary-bg: var(--rocleteer-webr-btn-primary-bg, var(--bs-primary, #007bff));',
568+
' --_btn-primary-hover: var(--rocleteer-webr-btn-primary-hover, var(--bs-primary-hover, #0056b3));',
569+
' --_btn-success-bg: var(--rocleteer-webr-btn-success-bg, var(--bs-success, #28a745));',
570+
' --_btn-success-hover: var(--rocleteer-webr-btn-success-hover, var(--bs-success-hover, #1e7e34));',
571+
' --_btn-secondary-bg: var(--rocleteer-webr-btn-secondary-bg, var(--bs-secondary, #6c757d));',
572+
' --_btn-secondary-hover: var(--rocleteer-webr-btn-secondary-hover, var(--bs-secondary-hover, #545b62));',
573+
' --_text-color: var(--rocleteer-webr-text-color, var(--bs-body-color, #333));',
574+
' --_text-emphasis: var(--rocleteer-webr-text-emphasis, var(--bs-emphasis-color, #000));',
575+
' --_container-padding: var(--rocleteer-webr-container-padding, 16px);',
576+
' --_container-margin: var(--rocleteer-webr-container-margin, 16px 0);',
577+
' --_btn-padding: var(--rocleteer-webr-btn-padding, 12px 20px);',
578+
' --_btn-margin: var(--rocleteer-webr-btn-margin, 0 8px 0 0);',
579+
' --_font-size: var(--rocleteer-webr-font-size, var(--bs-body-font-size, 14px));',
580+
' --_font-weight-bold: var(--rocleteer-webr-font-weight-bold, var(--bs-font-weight-bold, 700));',
581+
' border: 1px solid var(--_container-border);',
582+
' border-radius: var(--_container-border-radius);',
583+
' padding: var(--_container-padding);',
584+
' margin: var(--_container-margin);',
585+
' background-color: var(--_container-bg);',
586+
' color: var(--_text-color);',
587+
' font-size: var(--_font-size);',
588+
'}',
589+
'.rocleteer-webr-warning {',
590+
' background-color: var(--_warning-bg);',
591+
' border: 1px solid var(--_warning-border);',
592+
' border-radius: var(--bs-border-radius, 4px);',
593+
' padding: 12px;',
594+
' margin-bottom: 16px;',
595+
' color: var(--_warning-text);',
596+
' font-size: var(--_font-size);',
597+
'}',
598+
'.rocleteer-webr-warning strong {',
599+
' font-weight: var(--_font-weight-bold);',
600+
'}',
601+
'.rocleteer-webr-btn {',
602+
' display: inline-block;',
603+
' padding: var(--_btn-padding);',
604+
' margin: var(--_btn-margin);',
605+
' border: none;',
606+
' border-radius: var(--bs-border-radius, 4px);',
607+
' cursor: pointer;',
608+
' text-decoration: none;',
609+
' font-size: var(--_font-size);',
610+
' color: white;',
611+
' transition: background-color 0.15s ease-in-out;',
612+
'}',
613+
'.rocleteer-webr-btn-primary {',
614+
' background-color: var(--_btn-primary-bg);',
615+
'}',
616+
'.rocleteer-webr-btn-primary:hover {',
617+
' background-color: var(--_btn-primary-hover);',
618+
' color: white;',
619+
' text-decoration: none;',
620+
'}',
621+
'.rocleteer-webr-btn-success {',
622+
' background-color: var(--_btn-success-bg);',
623+
'}',
624+
'.rocleteer-webr-btn-success:hover {',
625+
' background-color: var(--_btn-success-hover);',
626+
'}',
627+
'.rocleteer-webr-btn-secondary {',
628+
' background-color: var(--_btn-secondary-bg);',
629+
'}',
630+
'.rocleteer-webr-btn-secondary:hover {',
631+
' background-color: var(--_btn-secondary-hover);',
632+
'}',
633+
'.rocleteer-webr-initial-text {',
634+
' margin: 8px 0;',
635+
' font-weight: var(--_font-weight-bold);',
636+
' color: var(--_text-emphasis);',
637+
'}',
638+
'.rocleteer-webr-iframe-container {',
639+
' display: none;',
640+
'}',
641+
'.rocleteer-webr-container.rocleteer-webr-fullwidth {',
642+
' position: fixed;',
643+
' top: 0;',
644+
' left: 0;',
645+
' width: 100vw;',
646+
' height: 100vh;',
647+
' z-index: var(--rocleteer-webr-fullwidth-z-index, 1050);',
648+
' margin: 0;',
649+
' border-radius: 0;',
650+
' border: none;',
651+
' overflow: auto;',
652+
' background-color: var(--_container-bg);',
653+
' transition: all 0.3s ease-in-out;',
654+
'}',
655+
'.rocleteer-webr-container.rocleteer-webr-fullwidth .rocleteer-webr-iframe {',
656+
' height: calc(100vh - 180px) !important;',
657+
' border: none;',
658+
'}',
659+
'.rocleteer-webr-fullwidth-backdrop {',
660+
' position: fixed;',
661+
' top: 0;',
662+
' left: 0;',
663+
' width: 100vw;',
664+
' height: 100vh;',
665+
' background-color: rgba(0, 0, 0, 0.5);',
666+
' z-index: var(--rocleteer-webr-backdrop-z-index, 1040);',
667+
' display: none;',
668+
'}',
669+
'.rocleteer-webr-iframe-controls {',
670+
' margin-bottom: 12px;',
671+
'}',
672+
'.rocleteer-webr-iframe-controls .rocleteer-webr-btn {',
673+
' padding: 8px 16px;',
674+
' font-size: var(--_font-size);',
675+
'}',
676+
'.rocleteer-webr-iframe {',
677+
' width: 100\\%;',
678+
' border: 1px solid var(--_container-border);',
679+
' border-radius: var(--bs-border-radius, 4px);',
680+
'}',
681+
'@media (max-width: 576px) {',
682+
' .rocleteer-webr-container {',
683+
' --_container-padding: 12px;',
684+
' --_btn-padding: 10px 16px;',
685+
' --_font-size: 13px;',
686+
' }',
687+
' .rocleteer-webr-btn {',
688+
' display: block;',
689+
' margin: 4px 0;',
690+
' text-align: center;',
691+
' }',
692+
' .rocleteer-webr-iframe-controls .rocleteer-webr-btn {',
693+
' display: inline-block;',
694+
' margin: 0 4px 0 0;',
695+
' font-size: 12px;',
696+
' padding: 6px 12px;',
697+
' }',
698+
' .rocleteer-webr-container.rocleteer-webr-fullwidth {',
699+
' padding: 8px;',
700+
' }',
701+
' .rocleteer-webr-container.rocleteer-webr-fullwidth .rocleteer-webr-iframe {',
702+
' height: calc(100vh - 120px) !important;',
703+
' }',
704+
'}',
705+
'</style>'
706+
)
707+
}
708+
522709
#' Generate webR warning HTML
523710
#'
524711
#' Create standardized warning message for webR examples
@@ -529,7 +716,7 @@ parse_webr_params <- function(tag_line, global_config = list()) {
529716
#' @noRd
530717
webr_experimental_warning <- function() {
531718
paste0(
532-
'<div class="webr-warning" style="background-color: #e7f3ff; border: 1px solid #b3d9ff; border-radius: 4px; padding: 12px; margin-bottom: 16px; font-size: 14px; color: #0c5aa6;">',
719+
'<div class="rocleteer-webr-warning">',
533720
'<strong>\U0001F9EA Experimental:</strong> Interactive webR examples are a new feature. ',
534721
'Loading may take a moment, and the package version might differ from this documentation.',
535722
'</div>'
@@ -590,15 +777,17 @@ webr_repl_href <- function(encoded_code, version = "latest", mode = "", channel
590777
webr_repl_link <- function(encoded_code, version = "latest", mode = "", channel = "") {
591778
url <- webr_repl_href(encoded_code, version, mode, channel)
592779
html <- paste0(
593-
'<div class="webr-container" style="border: 1px solid #ddd; border-radius: 8px; padding: 16px; margin: 16px 0; background-color: #f8f9fa;">',
780+
# Include CSS
781+
# TODO: Figure out how to include it once? Post parser re-write?
782+
webr_inline_css(),
783+
784+
'<div class="rocleteer-webr-container">',
594785

595786
# Warning message
596787
webr_experimental_warning(),
597788

598789
# Link button
599-
'<p><a href="', url, '" target="_blank" ',
600-
'style="background-color: #007bff; color: white; padding: 12px 20px; ',
601-
'text-decoration: none; border-radius: 4px; font-size: 14px; display: inline-block;">',
790+
'<p><a href="', url, '" target="_blank" class="rocleteer-webr-btn rocleteer-webr-btn-primary">',
602791
'\U0001F310 View in webR REPL</a></p>',
603792

604793
'</div>'
@@ -622,61 +811,105 @@ webr_repl_link <- function(encoded_code, version = "latest", mode = "", channel
622811
#' @noRd
623812
webr_repl_iframe <- function(encoded_code, version = "latest", height = 300, mode = "", channel = "") {
624813
url <- webr_repl_href(encoded_code, version, mode, channel)
625-
814+
626815
# Create an ID for this iframe (based on the encoded code)
627816
hashed_id <- abs(sum(utf8ToInt(substr(encoded_code, 1, 10))))
628817
iframe_id <- paste0("webr_iframe_", hashed_id)
629818

630819
html <- paste0(
631-
'<div class="webr-container" style="border: 1px solid #ddd; border-radius: 8px; padding: 16px; margin: 16px 0; background-color: #f8f9fa;">',
820+
# Include CSS once
821+
webr_inline_css(),
822+
823+
'<div class="rocleteer-webr-container">',
632824

633825
# Warning message
634826
webr_experimental_warning(),
635827

636828
# Initial buttons (before iframe loads)
637-
'<div id="', iframe_id, '_initial" class="webr-initial">',
638-
'<p style="margin: 8px 0; font-weight: bold; color: #333;">Interactive Example Available</p>',
639-
'<button onclick="loadWebRIframe(\'', iframe_id, '\', \'', url, '\', ', height, ')" ',
640-
'style="background-color: #28a745; color: white; padding: 12px 20px; border: none; border-radius: 4px; ',
641-
'cursor: pointer; font-size: 14px; margin-right: 8px;">',
829+
'<div id="', iframe_id, '_initial" class="rocleteer-webr-initial">',
830+
'<p class="rocleteer-webr-initial-text">Interactive Example Available</p>',
831+
'<button onclick="loadWebRIframe(\'', iframe_id, '\', \'', url, '\', ', height, ', false)" ',
832+
'class="rocleteer-webr-btn rocleteer-webr-btn-success rocleteer-webr-btn-try">',
642833
'\U0001F680 Try it in your browser</button>',
643834
'<button onclick="window.open(\'', url, '\', \'_blank\')" ',
644-
'style="background-color: #007bff; color: white; padding: 12px 20px; border: none; border-radius: 4px; ',
645-
'cursor: pointer; font-size: 14px;">',
835+
'class="rocleteer-webr-btn rocleteer-webr-btn-primary rocleteer-webr-btn-open">',
646836
'\U0001F310 Open in Tab</button>',
647837
'</div>',
648838

649839
# Iframe container (hidden initially)
650-
'<div id="', iframe_id, '_container" class="webr-iframe-container" style="display: none;">',
651-
'<div style="margin-bottom: 12px;">',
840+
'<div id="', iframe_id, '_container" class="rocleteer-webr-iframe-container">',
841+
'<div class="rocleteer-webr-iframe-controls">',
652842
'<button onclick="hideWebRIframe(\'', iframe_id, '\')" ',
653-
'style="background-color: #6c757d; color: white; padding: 8px 16px; border: none; border-radius: 4px; ',
654-
'cursor: pointer; font-size: 14px; margin-right: 8px;">',
843+
'class="rocleteer-webr-btn rocleteer-webr-btn-secondary rocleteer-webr-btn-back">',
655844
'\U0001F519 Go back</button>',
845+
'<button onclick="toggleWebRFullWidth(\'', iframe_id, '\')" ',
846+
'class="rocleteer-webr-btn rocleteer-webr-btn-primary rocleteer-webr-btn-toggle" ',
847+
'id="', iframe_id, '_toggle">',
848+
'\U00026F6 Full Screen</button>',
656849
'<button onclick="window.open(\'', url, '\', \'_blank\')" ',
657-
'style="background-color: #007bff; color: white; padding: 8px 16px; border: none; border-radius: 4px; ',
658-
'cursor: pointer; font-size: 14px;">',
850+
'class="rocleteer-webr-btn rocleteer-webr-btn-primary rocleteer-webr-btn-open">',
659851
'\U0001F310 Open in Tab</button>',
660852
'</div>',
661-
'<div id="', iframe_id, '_content"></div>',
853+
'<div id="', iframe_id, '_content" class="rocleteer-webr-iframe-content"></div>',
662854
'</div>',
663855

664856
'</div>',
665857

666858
# JavaScript for iframe management
667859
'<script>',
668-
'function loadWebRIframe(id, url, height) {',
860+
'function loadWebRIframe(id, url, height, fullwidth) {',
861+
' fullwidth = fullwidth || false;',
669862
' document.getElementById(id + "_initial").style.display = "none";',
670863
' document.getElementById(id + "_container").style.display = "block";',
671864
' document.getElementById(id + "_content").innerHTML = ',
672-
' \'<iframe src="\' + url + \'" width="100\\%" height="\' + height + \'px" \' +',
673-
' \'style="border: 1px solid #ddd; border-radius: 4px;" title="webR REPL"></iframe>\';',
865+
' \'<iframe src="\' + url + \'" class="rocleteer-webr-iframe" height="\' + height + \'px" \' +',
866+
' \'title="webR REPL"></iframe>\';',
867+
' if (fullwidth) {',
868+
' enterWebRFullWidth(id);',
869+
' }',
674870
'}',
675871
'function hideWebRIframe(id) {',
872+
' exitWebRFullWidth(id);',
676873
' document.getElementById(id + "_initial").style.display = "block";',
677874
' document.getElementById(id + "_container").style.display = "none";',
678875
' document.getElementById(id + "_content").innerHTML = "";',
679876
'}',
877+
'function toggleWebRFullWidth(id) {',
878+
' var container = document.getElementById(id + "_container").closest(".rocleteer-webr-container");',
879+
' if (container.classList.contains("rocleteer-webr-fullwidth")) {',
880+
' exitWebRFullWidth(id);',
881+
' } else {',
882+
' enterWebRFullWidth(id);',
883+
' }',
884+
'}',
885+
'function enterWebRFullWidth(id) {',
886+
' var container = document.getElementById(id + "_container").closest(".rocleteer-webr-container");',
887+
' var backdrop = document.createElement("div");',
888+
' backdrop.className = "rocleteer-webr-fullwidth-backdrop";',
889+
' backdrop.id = id + "_backdrop";',
890+
' backdrop.onclick = function() { exitWebRFullWidth(id); };',
891+
' document.body.appendChild(backdrop);',
892+
' backdrop.style.display = "block";',
893+
' container.classList.add("rocleteer-webr-fullwidth");',
894+
' document.body.style.overflow = "hidden";',
895+
' var toggleBtn = document.getElementById(id + "_toggle");',
896+
' if (toggleBtn) {',
897+
' toggleBtn.innerHTML = "\\u274C Exit Full Screen";',
898+
' }',
899+
'}',
900+
'function exitWebRFullWidth(id) {',
901+
' var container = document.getElementById(id + "_container").closest(".rocleteer-webr-container");',
902+
' var backdrop = document.getElementById(id + "_backdrop");',
903+
' if (backdrop) {',
904+
' backdrop.remove();',
905+
' }',
906+
' container.classList.remove("rocleteer-webr-fullwidth");',
907+
' document.body.style.overflow = "";',
908+
' var toggleBtn = document.getElementById(id + "_toggle");',
909+
' if (toggleBtn) {',
910+
' toggleBtn.innerHTML = "\\u26F6 Full width";',
911+
' }',
912+
'}',
680913
'</script>'
681914
)
682915

0 commit comments

Comments
 (0)