---
title: "Using shinyglass with shinyreact"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Using shinyglass with shinyreact}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
```

shinyreact apps do not render Shiny's tag UI. The client is `www/ui.js`, and
`shinyreact::page_react()` drops Bootstrap unless you pass `theme`.
`glass_page_react()` does that, and it also loads the Liquid Glass React
components so the client does not need its own glass CSS.

```{r eval = FALSE}
library(shiny)
library(shinyreact)
library(shinyglass)

ui <- glass_page_react(
  theme = glass_theme(preset = "auto", scene = "tahoe"),
  title = "Liquid Glass"
)

server <- function(input, output, session) {
  output$dist <- renderPlot({
    pal <- glass_plot_colors(input = input)
    hist(faithful$waiting, col = pal$fill, border = NA)
  }, bg = "transparent")
}

shinyApp(ui, server)
```

The matching `www/ui.js` reads the components from the same global shinyreact
already uses:

```js
const { ReactDOM, useShinyInitialized, ShinyOutput } = window.shinyreact;
const {
  useGlassTheme,
  GlassPage,
  GlassSidebar,
  GlassSurface,
  GlassButton
} = window.shinyglass;

const h = React.createElement;

function App() {
  const ready = useShinyInitialized();
  const theme = useGlassTheme();
  if (!ready) return null;
  return h(
    GlassPage,
    null,
    h(
      GlassSidebar,
      null,
      h(
        GlassButton,
        {
          variant: theme.mode === "dark" ? "primary" : "secondary",
          pressed: theme.mode === "dark",
          onClick: () => theme.setMode(theme.mode === "dark" ? "light" : "dark")
        },
        theme.mode
      )
    ),
    h(GlassSurface, null, h(ShinyOutput, { id: "dist", className: "shiny-plot-output glass-plot" }))
  );
}

ReactDOM.createRoot(document.body.appendChild(document.createElement("div"))).render(h(App));
```

A full app ships at
`system.file("examples/shinyreact-glass", package = "shinyglass")`.
It has no `www/ui.css`.

# Components

| Name | Element | Class |
|------|---------|-------|
| `GlassPage` | `main` | `.glass-page` |
| `GlassMain` | `div` | `.glass-main` |
| `GlassSidebar` | `aside` | `.glass-sidebar` |
| `GlassSurface` | `section` | `.glass-surface` |
| `GlassCard` | `section` | `.glass-surface.glass-card` |
| `GlassStack` | `div` | `.glass-stack` |
| `GlassButton` | `button` | `.glass-button-primary` or `.glass-button-secondary` |
| `GlassTitle` | `h1` | `.glass-title` |
| `GlassMuted` | `p` | `.glass-muted` |
| `GlassRange` | `input` | `.glass-range` |
| `GlassIntensitySlider` | `div` | `.glass-intensity-slider` (Ultra Clear to Tinted, default `0.5`) |

`useGlassTheme()` also reports `refraction`, `morph`, `press`, `scrollEdge`,
`minimizeTabs`, `iconStyle`, `floatingSidebar`, and `reducedMotion`. Setters
such as `theme.setRefraction(false)` and `theme.setIconStyle("color")` call
the same client API as `update_glass_theme()`. `GlassSidebar` adds
`.glass-sidebar-floating` when `floating` is true or the theme turned
`floating_sidebar` on.

`GlassButton` takes `variant` (`"primary"` or `"secondary"`) and `pressed`.
Primary fill follows `--glass-accent` / `--glass-on-primary`. Secondary label
ink follows `--glass-body-color`. Sidebar, card, and page chrome use the same
surface tokens as bslib cards (blur, border, radius, shadow). Light and dark
packs swap those variables, so a button or panel written this way tracks
`setPreset` without a client stylesheet.

Plot and table hosts inside `.glass-surface`, `.glass-card`, and
`.glass-sidebar` stay transparent, same as a plot inside `.card`. Ink for a
base or ggplot graphic is still drawn on the server. Use
`glass_plot_colors(input = input)` or `glass_resolved_preset(input)` so the
R graphic follows the resolved pack. CSS variables are not available to the
graphics device.

# useGlassTheme()

The hook reads `document.documentElement` and subscribes with a
`MutationObserver`, so it sees the head script, `setPreset`, and any other
writer of the dataset.

| Field | Meaning |
|-------|---------|
| `mode` | `"light"`, `"dark"`, or `"auto"` |
| `preset` | Resolved pack, `"light"` or `"dark"` |
| `scene` | Wallpaper scene |
| `material` | `"regular"` or `"clear"` (`"clear"` is deprecated) |
| `intensity` | Number from 0 (ultra clear) to 1 (tinted). Default `0.5`. |
| `reducedTransparency` | `true` when Reduce Transparency is on |
| `increasedContrast` | `true` when Increase Contrast is on |
| `forcedColors` | `true` when forced colors are active |

`setMode`, `setScene`, `setMaterial`, and `setIntensity` call the existing
`window.shinyglass` mutators. `setMode("dark")` is `setPreset("dark")`.
`setMaterial("clear")` warns and maps to intensity `0.12`. A later
`setIntensity()` wins. `GlassIntensitySlider` calls `setIntensity` and
labels the ends Ultra Clear and Tinted.

```js
const { GlassIntensitySlider, useGlassTheme } = window.shinyglass;

function Controls() {
  const theme = useGlassTheme();
  return React.createElement(GlassIntensitySlider, {
    id: "glass-intensity",
    value: theme.intensity,
  });
}
```

# Where the module comes from

`inst/js/shinyglass-react.js` is an ES module attached as the html dependency
`shinyglass-react` (see `glass_react_dependency()`). `glass_page_react()`
bundles it into the theme. `bootstrapPage()` emits theme dependencies before
`www/ui.js`, so the app module can destructure `window.shinyglass` at top
level. The file does not touch `window.shinyreact` until a component renders,
because the theme module is earlier in the document than shinyreact's deferred
script.

`glass_theme_dependencies()` appends the same dependency for
`page_react_html(extra_deps = )`. A plain `glass_theme()` does not load it, so
classic Shiny pages do not download the React file. `page_react(theme =
glass_theme())` still gets the CSS and `shiny-glass.js`. Use
`glass_page_react()`, or pass `glass_theme_dependencies()`, when you want the
components.

This is not published as an npm package. shinyreact's default app has no build,
and hooks must use the single React already on `window.shinyreact`. A package
that imported `react` would load a second copy unless every app externalized
it. htmltools also serves the file at a versioned path, so a hardcoded import
in `www/ui.js` would break on each shinyglass release. Named `export`s are
there for a bundler that can resolve the file itself. The supported no-build
path is the global, same as `window.shinyreact`.

# Shadow roots

Document class rules do not cross a shadow root. `--glass-*` custom properties
do. Call `window.shinyglass.adoptShadow(shadowRoot)` from the component that
created the root. The example does this for a note inside the page.
