diff --git a/DESCRIPTION b/DESCRIPTION
index 5f16934..6d0ca2a 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,6 +1,6 @@
Package: css2r
Title: An Amazing Shiny App
-Version: 0.1.0
+Version: 0.2.0
Authors@R:
person("Arthur", "Bréant", , "arthur@thinkr.fr", role = c("aut", "cre"))
Description: What the package does (one paragraph).
diff --git a/NEWS.md b/NEWS.md
index 25f14f9..d6845ff 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,5 @@
+# css2r 0.2.0
+
# css2r 0.1.0
* Update UI with new features
diff --git a/R/app_server.R b/R/app_server.R
index ff2aed2..afabaf1 100644
--- a/R/app_server.R
+++ b/R/app_server.R
@@ -5,7 +5,7 @@
#' @import shiny
#' @noRd
app_server <- function(input, output, session) {
- rv <- reactiveValues(site = NULL)
+ rv <- reactiveValues(site = NULL, preview_primary = NULL, preview_secondary = NULL)
observeEvent(input$analyze_btn, {
rv$result <- NULL
@@ -66,6 +66,12 @@ app_server <- function(input, output, session) {
observeEvent(rv$continue, {
req(rv$site)
+ session$setCurrentTheme(rv$site$shiny_theme)
+
+ top_colors_df_init <- rv$site$top_colors$top_colors
+ rv$preview_primary <- top_colors_df_init$Color[1]
+ rv$preview_secondary <- if (nrow(top_colors_df_init) > 1) top_colors_df_init$Color[2] else top_colors_df_init$Color[1]
+
session$sendCustomMessage(
type = "apply_gradient",
message = list(
@@ -107,7 +113,22 @@ app_server <- function(input, output, session) {
tags$p(
class = "color-section-total",
paste0(nrow(rv$site$all_colors), " unique colors in total")
- )
+ ),
+ if (!is.null(rv$site$top_colors$white_black) && nrow(rv$site$top_colors$white_black) > 0) {
+ wb <- rv$site$top_colors$white_black
+ div(
+ class = "color-section-neutrals",
+ tags$span(class = "color-section-neutrals-label", "Neutrals"),
+ lapply(seq_len(nrow(wb)), function(j) {
+ hex_wb <- wb$Color[j]
+ div(
+ class = "color-neutral-chip",
+ div(class = "color-neutral-swatch", style = paste0("background:", hex_wb, ";")),
+ tags$span(class = "color-neutral-hex", hex_wb)
+ )
+ })
+ )
+ }
),
# Right: backing gradient + fan cards
div(
@@ -191,7 +212,12 @@ app_server <- function(input, output, session) {
tags$ol(
class = "result-link-list",
lapply(rv$site$fonts, function(font) {
- tags$li(font[1])
+ family_raw <- font[["family"]]
+ font_name <- strsplit(as.character(family_raw), ":")[[1]][1]
+ font_url <- paste0("https://fonts.google.com/specimen/", gsub(" ", "+", font_name))
+ tags$li(
+ tags$a(href = font_url, target = "_blank", font_name)
+ )
})
)
)
@@ -226,7 +252,7 @@ app_server <- function(input, output, session) {
# 5. All colors section
div(
- class = "result-section result-section-last",
+ class = "result-section",
div(
class = "result-section-label",
tags$span(class = "result-section-tag", "All colors"),
@@ -235,8 +261,107 @@ app_server <- function(input, output, session) {
),
div(
class = "result-section-card p-0",
+ div(
+ class = "all-colors-toolbar",
+ tags$span(class = "all-colors-sort-label", "Sort"),
+ radioButtons(
+ inputId = "color_sort",
+ label = NULL,
+ choices = c("Frequency" = "freq", "Hue" = "hue"),
+ selected = "freq",
+ inline = TRUE
+ )
+ ),
uiOutput("all_colors")
)
+ ),
+
+ # 6. Live preview section
+ div(
+ class = "result-section result-section-last",
+ div(
+ class = "result-section-label",
+ tags$span(class = "result-section-tag", "Preview"),
+ tags$h3(class = "result-section-title", "Live mockup"),
+ tags$p(
+ class = "result-section-desc",
+ HTML("Bootstrap components rendered with the extracted theme.
What your app could look like.")
+ )
+ ),
+ div(
+ class = "result-section-card p-0 overflow-hidden",
+ uiOutput("preview_controls"),
+ div(
+ class = "theme-preview-wrap",
+ # Navbar
+ tags$nav(
+ class = "navbar bg-primary px-3 py-2",
+ `data-bs-theme` = "dark",
+ div(
+ class = "container-fluid p-0",
+ tags$a(class = "navbar-brand text-white fw-semibold me-4", rv$site$domain),
+ div(
+ class = "navbar-nav flex-row gap-3",
+ tags$a(class = "nav-link active text-white", href = "#", "Home"),
+ tags$a(class = "nav-link text-white opacity-75", href = "#", "Plots"),
+ tags$a(class = "nav-link text-white opacity-75", href = "#", "Data")
+ )
+ )
+ ),
+ # Body
+ div(
+ class = "theme-preview-body container-fluid py-3 px-3",
+ div(
+ class = "row g-3",
+ # Card 1
+ div(
+ class = "col-md-6",
+ div(
+ class = "card",
+ div(class = "card-header fw-semibold", "Dashboard"),
+ div(
+ class = "card-body",
+ tags$p(class = "card-text text-muted small mb-3",
+ "Your Shiny app, styled with the extracted palette."),
+ div(
+ class = "d-flex flex-wrap gap-2",
+ tags$button(class = "btn btn-primary btn-sm", "Primary"),
+ tags$button(class = "btn btn-secondary btn-sm", "Secondary"),
+ tags$button(class = "btn btn-outline-primary btn-sm", "Outline")
+ )
+ )
+ )
+ ),
+ # Card 2
+ div(
+ class = "col-md-6",
+ div(
+ class = "card",
+ div(class = "card-header fw-semibold", "Controls"),
+ div(
+ class = "card-body",
+ div(
+ class = "mb-3",
+ tags$label(class = "form-label small text-muted", "Select a variable"),
+ tags$select(
+ class = "form-select form-select-sm",
+ tags$option("Option A"),
+ tags$option("Option B"),
+ tags$option("Option C")
+ )
+ ),
+ div(
+ class = "input-group input-group-sm",
+ tags$input(type = "number", class = "form-control", value = "42"),
+ tags$button(class = "btn btn-primary", "Run")
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
)
)
})
@@ -247,9 +372,86 @@ app_server <- function(input, output, session) {
rv$result
})
+ output$preview_controls <- renderUI({
+ req(rv$site, rv$preview_primary, rv$preview_secondary)
+ colors <- rv$site$top_colors$top_colors$Color
+
+ make_chips <- function(role, current) {
+ div(
+ class = "theme-preview-editor-row",
+ tags$span(class = "preview-role-label", role),
+ div(
+ class = "preview-palette",
+ lapply(colors, function(hex) {
+ div(
+ class = paste0("preview-chip", if (hex == current) " preview-chip-active" else ""),
+ style = paste0("background:", hex, ";"),
+ onclick = paste0(
+ 'Shiny.setInputValue("preview_',
+ tolower(role),
+ '", "', hex, '", {priority: "event"})'
+ )
+ )
+ })
+ )
+ )
+ }
+
+ div(
+ class = "theme-preview-editor",
+ make_chips("Primary", rv$preview_primary),
+ make_chips("Secondary", rv$preview_secondary)
+ )
+ })
+
+ observeEvent(input$preview_primary, {
+ req(rv$site)
+ rv$preview_primary <- input$preview_primary
+ session$setCurrentTheme(
+ bslib::bs_theme_update(
+ rv$site$shiny_theme,
+ primary = rv$preview_primary,
+ secondary = rv$preview_secondary
+ )
+ )
+ })
+
+ observeEvent(input$preview_secondary, {
+ req(rv$site)
+ rv$preview_secondary <- input$preview_secondary
+ session$setCurrentTheme(
+ bslib::bs_theme_update(
+ rv$site$shiny_theme,
+ primary = rv$preview_primary,
+ secondary = rv$preview_secondary
+ )
+ )
+ })
+
output$all_colors <- renderUI({
req(rv$site$all_colors)
- df <- rv$site$all_colors
+ df <- rv$site$all_colors
+
+ if (!is.null(input$color_sort) && input$color_sort == "hue") {
+ hex_to_hue <- function(hex) {
+ hex <- sub("^#", "", hex)
+ r <- strtoi(substr(hex, 1, 2), 16L) / 255
+ g <- strtoi(substr(hex, 3, 4), 16L) / 255
+ b <- strtoi(substr(hex, 5, 6), 16L) / 255
+ max_c <- max(r, g, b)
+ min_c <- min(r, g, b)
+ if (max_c == min_c) return(0)
+ delta <- max_c - min_c
+ if (max_c == r) h <- (g - b) / delta
+ else if (max_c == g) h <- 2 + (b - r) / delta
+ else h <- 4 + (r - g) / delta
+ h <- h * 60
+ if (h < 0) h <- h + 360
+ h
+ }
+ df <- df[order(sapply(df$Color, hex_to_hue)), ]
+ }
+
max_count <- max(df$Count)
div(
@@ -269,6 +471,11 @@ app_server <- function(input, output, session) {
div(class = "color-swatch-bar", style = paste0("width:", bar, "%;"))
),
tags$span(class = "color-swatch-count", paste0(cnt, "\u00d7"))
+ ),
+ tags$button(
+ class = "swatch-copy-btn",
+ onclick = paste0('copySwatchHex("', hex, '", this)'),
+ "Copy"
)
)
})
diff --git a/R/css2r.R b/R/css2r.R
index cd98a27..aa6762b 100644
--- a/R/css2r.R
+++ b/R/css2r.R
@@ -255,6 +255,19 @@ css2r <- R6Class(
)
)
+ # Also scan downloaded CSS for @import Google Fonts URLs
+ if (!is.null(self$css_content)) {
+ css_import_matches <- regmatches(
+ self$css_content,
+ gregexpr(
+ pattern = "https://fonts\\.googleapis\\.com[^\"')\\s]+",
+ text = self$css_content,
+ perl = TRUE
+ )
+ )[[1]]
+ google_font_links <- unique(c(google_font_links, css_import_matches))
+ }
+
if (length(google_font_links) > 0) {
google_fonts_params <- private$extract_google_font_params(
links = google_font_links
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 0000000..a1dc34e
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,32 @@
+# ROADMAP
+
+## Limitations moteur (`R/css2r.R`)
+
+- Regex hex 6 chiffres uniquement → manque `rgb()`, `hsl()`, `#ABC`, variables CSS (`--my-color`)
+- Top 4 couleurs hardcodé (line 229 : `head(other_colors, 4)`) → rendre configurable
+- CSS même domaine uniquement → CDN externes exclus (Bootstrap, Tailwind…)
+- Google Fonts uniquement (`fonts.googleapis.com`) → pas de `@font-face`, Adobe Fonts
+- Balises `` uniquement → pas de `