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}{
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
530717webr_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>\U 0001F9EA 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
590777webr_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 ' \U 0001F310 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
623812webr_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 ' \U 0001F680 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 ' \U 0001F310 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 ' \U 0001F519 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+ ' \U 00026F6 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 ' \U 0001F310 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