Theme
The app's stylesheet, its colours in light and in dark, the stage's colours, and the switch between the two.
Components
AppStyle
const AppStyle: React.FC;
Mounts the app's tokens and styles, as global stylesheets that live as long as it is mounted.
The tokens are the colours of the current theme (ThemeColors), each a custom property on :root,
--rcad-<kebab-case> (--rcad-ink, --rcad-accent-fill), with the app's metrics (--rcad-row-h, …) and its face,
JetBrains Mono, which the library carries. The styles name only those properties, so they follow the theme.
Drawing renders it. A host that composes the pieces itself (a ModelTree here, a ToolBar
there) renders one <AppStyle /> beside them, and puts them inside an element with the class rcad, which sets
the app's type.
Example
import type { DrawingID } from '@buerli.io/core'
import { AppStyle, ModelTree, ToolBar } from '@buerli.io/react-cad'
// two pieces in a page of the app's own: one stylesheet, and the class that sets the app's type
const Pieces = ({ drawingId }: { drawingId: DrawingID }) => (
<div className="rcad">
<AppStyle />
<ToolBar drawingId={drawingId} />
<div style={{ height: 400 }}>
<ModelTree drawingId={drawingId} />
</div>
<p style={{ color: 'var(--rcad-mute)' }}>The tokens are custom properties too.</p>
</div>
)
ThemeToggle
const ThemeToggle: React.FC;
The theme switch: a button with a disc, half inked, that switches between the light and the dark theme.
It sets the mode with setRCadThemeMode, which remembers the reader's pick for the next visit. The disc turns
to the other side in the dark. Drawing shows it at the end of its bar (unless themeSwitch is false).
Example
import type { DrawingID } from '@buerli.io/core'
import { AppStyle, ThemeToggle, ToolBar } from '@buerli.io/react-cad'
// a bar of the app's own: its tools, and the theme switch at its end
const Bar = ({ drawingId }: { drawingId: DrawingID }) => (
<header className="rcad" style={{ display: 'flex', justifyContent: 'space-between' }}>
<AppStyle />
<ToolBar drawingId={drawingId} />
<ThemeToggle />
</header>
)
Functions
setRCadThemeMode()
function setRCadThemeMode(mode, options?): void;
Sets the app's theme to light or dark.
The pick is remembered for the next visit (in localStorage); picking what the system has forgets it, so that the
app follows the system again. A page that has a theme of its own passes { remember: false } and calls this
whenever its theme changes: the app then follows the page (and Drawing is given themeSwitch={false}).
Parameters
| Parameter | Type | Description |
|---|---|---|
mode | ThemeMode | The theme to show. |
options? | { remember?: boolean; } | remember: false leaves the reader's remembered pick alone. |
options.remember? | boolean | - |
Returns
void
Example
import { useEffect } from 'react'
import type { DrawingID } from '@buerli.io/core'
import { Drawing, setRCadThemeMode } from '@buerli.io/react-cad'
// a page with a theme switch of its own: the app follows it, and shows no switch of its own
function EditorPage({ dark, drawingId }: { dark: boolean; drawingId: DrawingID }) {
useEffect(() => setRCadThemeMode(dark ? 'dark' : 'light', { remember: false }), [dark])
return <Drawing drawingId={drawingId} themeSwitch={false} />
}
getStage()
function getStage(): StageColors;
The stage's colours in the current theme, outside of a component (as useStage, without rendering again).
Returns
Example
import { init, WASMClient } from '@buerli.io/classcad'
import { elements, getStage } from '@buerli.io/react-cad'
// buerli's highlight colours from the stage's, as Buerligons sets them
const stage = getStage()
init((drawingId) => new WASMClient(drawingId, { token: 'ccpk_…' }), {
elements,
theme: { primary: stage.pick, secondary: stage.hover, dark: stage.datum, highlightedGeom: stage.select, hoveredGeom: stage.hover },
})
Hooks
useRCadTheme()
function useRCadTheme(): ThemeColors;
Returns the colours of the current theme, and renders again when the theme changes.
Returns
lightTheme or darkTheme.
Example
import { useRCadTheme, useRCadThemeMode } from '@buerli.io/react-cad'
// a panel of the app's own in the app's colours, in either theme
function Note({ children }: { children: React.ReactNode }) {
const colors = useRCadTheme()
const mode = useRCadThemeMode()
return (
<div style={{ background: colors.panel, color: colors.ink, border: `1px solid ${colors.line}`, boxShadow: colors.shadow }}>
{children} <small style={{ color: colors.dim }}>({mode})</small>
</div>
)
}
useStage()
function useStage(): StageColors;
Returns the stage's colours in the current theme, and renders again when the theme changes.
Returns
Example
import { Line } from '@react-three/drei'
import { useStage } from '@buerli.io/react-cad'
// a guide of the app's own on the stage, in the stage's colours: it follows the theme
function Guide({ from, to }: { from: [number, number, number]; to: [number, number, number] }) {
const stage = useStage()
return <Line points={[from, to]} color={stage.datum} lineWidth={1.5} dashed dashSize={2} gapSize={2} />
}
useRCadThemeMode()
function useRCadThemeMode(): ThemeMode;
Returns the current theme's mode, 'light' or 'dark', and renders again when it changes.
The example of useRCadTheme uses it beside the colours.
Returns
Types
StageColors
type StageColors = {
alt: string;
axisX: string;
axisY: string;
axisZ: string;
badgeDisc: string;
badgeInk: string;
body: string;
datum: string;
dimExpr: string;
dimHalo: string;
dimLine: string;
dimText: string;
edge: string;
gridMajor: string;
gridMinor: string;
hover: string;
pick: string;
plane: string;
select: string;
sketchConstruction: string;
sketchFree: string;
sketchHover: string;
sketchHoverSoft: string;
sketchIdle: string;
sketchIdlePoint: string;
sketchMark: string;
sketchOver: string;
sketchRedundant: string;
sketchSelect: string;
sketchSolved: string;
};
The colours of what is drawn on the stage: the model, what lies over it, a sketch on its sheet.
They are written down in BRANDING.md, "The stage". The model is a solid grey body with ink edges. What a sketch's
colours say is the editor's own code (how far a curve is defined), in the family's tones. useStage gives
them for the current theme; they are CSS colour strings (#rrggbb), which three.js takes as they are.
Properties
ThemeColors
type ThemeColors = {
accent: string;
accentFill: string;
accentHover: string;
accentLine: string;
accentTint: string;
accentWash: string;
active: string;
axisX: string;
axisY: string;
axisZ: string;
blue: string;
canvasDimLine: number;
canvasLabelBg: number;
canvasText: number;
chrome: string;
chromeLine: string;
dim: string;
geoAlt: string;
geoBody: string;
geoEdge: string;
geoHover: string;
geoPick: string;
geoSelect: string;
green: string;
hover: string;
ink: string;
ink2: string;
line: string;
line2: string;
mute: string;
onAccent: string;
onGreen: string;
orange: string;
panel: string;
panel2: string;
paper: string;
ring: string;
shadow: string;
shadowLg: string;
toastBg: string;
toastStrong: string;
toastText: string;
viewport: string;
};
The colours of the app, in light and in dark: lightTheme and darkTheme.
They are the family's own (paper and ink, one red; graphite and chalk in the dark), written down for a technical
application in BRANDING.md, "The app". Every one of them that is a string is also a custom property on :root,
--rcad-<kebab-case> (accentFill is --rcad-accent-fill), which is what the stylesheet draws from (see
AppStyle). The colours of what is drawn on the stage are StageColors.
Properties
ThemeMode
type ThemeMode = "light" | "dark";
The theme's mode: light, or dark.
Advanced
Most apps do not need these directly: types they meet through the exports above, and what is there for special cases or for building on buerli itself.
darkStage
const darkStage: StageColors;
The stage's colours in the dark theme. useStage gives the current theme's; see also stageColors.
darkTheme
const darkTheme: ThemeColors;
The app's colours in the dark theme: graphite and chalk, one red. See the example of lightTheme.
lightStage
const lightStage: StageColors;
The stage's colours in the light theme. useStage gives the current theme's; see also stageColors.
lightTheme
const lightTheme: ThemeColors;
The app's colours in the light theme: paper and ink, one red.
Example
import { darkTheme, lightTheme } from '@buerli.io/react-cad'
// a page around the app in the app's colours, whatever its theme is now
const pageColors = (dark: boolean) => {
const colors = dark ? darkTheme : lightTheme
return { background: colors.viewport, color: colors.ink, accentColor: colors.accent }
}
document.body.style.background = pageColors(true).background
stageColors()
function stageColors(mode): StageColors;
The stage's colours in a theme: lightStage or darkStage.
Parameters
| Parameter | Type | Description |
|---|---|---|
mode | ThemeMode | The theme. |
Returns
Example
import { stageColors } from '@buerli.io/react-cad'
// the stage's colours of both themes, side by side: a legend of what a sketch's colours say
const legend = (['light', 'dark'] as const).map((mode) => {
const s = stageColors(mode)
return { mode, free: s.sketchFree, solved: s.sketchSolved, over: s.sketchOver }
})
stageFont
const stageFont: string;
The face of what is written on the stage (a dimension, a readout), as a data: url: JetBrains Mono, latin, weight
500, without ligatures (woff).
It is what drei's <Text font={stageFont}> takes, so that a label of the app's own on the stage is set in the
app's face. (JetBrains Mono is under the SIL Open Font License.)
Example
import { Text } from '@react-three/drei'
import { stageFont, useStage } from '@buerli.io/react-cad'
// a label of the app's own on the stage, set as a dimension is
function Label({ text, position }: { text: string; position: [number, number, number] }) {
const stage = useStage()
return (
<Text font={stageFont} fontSize={4} color={stage.dimText} position={position}>
{text}
</Text>
)
}
useRCadThemeStore
const useRCadThemeStore: UseBoundStore<StoreApi<{
colors: ThemeColors;
mode: ThemeMode;
setMode: (mode, options?) => void;
}>>;
The theme's store (zustand): its mode, its colors, and setMode.
In a component, useRCadTheme and useRCadThemeMode read it; outside of one,
useRCadThemeStore.getState() does, and useRCadThemeStore.subscribe hears it change. The mode starts as the
reader picked it last time, or as the system has it (prefers-color-scheme), and follows the system while the
reader has picked nothing.