Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6f0a3d7
Simple pub/sub store
delucis Aug 7, 2023
13c126b
Color manipulation with culori
delucis Aug 7, 2023
36b2089
Create theme designer component
delucis Aug 7, 2023
d253e36
Page including theme editor
delucis Aug 7, 2023
da14f5a
Tweak theme defaults and use smaller slider step size
delucis Aug 9, 2023
bb731c5
Fix greyscale palette preview for dark mode
delucis Aug 9, 2023
cc96384
Tweak palette and preview layout
delucis Aug 9, 2023
cf9e05e
Restore finer grain resolution on chroma sliders
delucis Aug 9, 2023
4a65d0e
Add preset system to colour theme store
delucis Aug 9, 2023
e734214
Add preset buttons component
delucis Aug 9, 2023
8c044dc
Smaller text in preview boxes
delucis Aug 9, 2023
482d4a9
Small docs wording tweaks
delucis Aug 9, 2023
979c315
Make preset picker i18n ready
delucis Aug 9, 2023
42e2705
Refactor label props and move preview panel to component
delucis Aug 9, 2023
bfd3761
style: format
delucis Aug 9, 2023
e18152f
Merge branch 'main' into chris/theme-editor
delucis Aug 9, 2023
a4ab2cc
Don’t set page theme on page load
delucis Aug 9, 2023
6e76990
Tweak default theme to bring it closer to defaults
delucis Aug 9, 2023
ce34b4f
Tweak default theme & drop `--sl-hue-accent` support
delucis Aug 9, 2023
2ef3b24
Hexen 🧙‍♀️
delucis Aug 9, 2023
f7c1cae
Revert "Hexen 🧙‍♀️"
delucis Aug 9, 2023
88bf7b7
Fix changeset whitespace
delucis Aug 9, 2023
bfa1256
Improve swatch hover style
delucis Aug 9, 2023
200ecff
`cursor: pointer` on preset buttons
delucis Aug 9, 2023
8d9c496
Update custom CSS example not to use accent hue
delucis Aug 10, 2023
ffe5a0c
Merge branch 'main' into chris/theme-editor
delucis Aug 10, 2023
48aa0cb
Resimplify theme-designer initialisation
delucis Aug 10, 2023
6bebab0
Make generated code keyboard focusable
delucis Aug 10, 2023
0f8f126
Merge branch 'main' into chris/theme-editor
delucis Aug 10, 2023
05a4fe7
Make generated code block markup more similar to regular code block
delucis Aug 10, 2023
7020d9b
Does it need content?
delucis Aug 10, 2023
0ccd019
Revert "Does it need content?"
delucis Aug 10, 2023
f7805c1
Revert "Make generated code block markup more similar to regular code…
delucis Aug 10, 2023
131bfe3
Fix pa11y error
delucis Aug 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .changeset/red-rockets-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'@astrojs/starlight': minor
---

Drop support for the `--sl-hue-accent` CSS custom property.

⚠️ **BREAKING CHANGE** — In previous Starlight versions you could control the accent color by setting the `--sl-hue-accent` custom property. This could result in inaccessible color contrast and unpredictable results.

You must now set accent colors directly. If you relied on setting `--sl-hue-accent`, migrate by setting light and dark mode colors in your custom CSS:

```css
:root {
--sl-hue-accent: 234;
--sl-color-accent-low: hsl(var(--sl-hue-accent), 54%, 20%);
--sl-color-accent: hsl(var(--sl-hue-accent), 100%, 60%);
--sl-color-accent-high: hsl(var(--sl-hue-accent), 100%, 87%);
}

:root[data-theme="light"] {
--sl-color-accent-high: hsl(var(--sl-hue-accent), 80%, 30%);
--sl-color-accent: hsl(var(--sl-hue-accent), 90%, 60%);
--sl-color-accent-low: hsl(var(--sl-hue-accent), 88%, 90%);
}
```

The [new color theme editor](https://starlight.astro.build/guides/css-and-tailwind/#color-theme-editor) might help if you’d prefer to set a new color scheme.
2 changes: 2 additions & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
},
"dependencies": {
"@astrojs/starlight": "workspace:*",
"@types/culori": "^2.0.0",
"astro": "^2.10.4",
"culori": "^3.2.0",
"sharp": "^0.32.3"
},
"devDependencies": {
Expand Down
147 changes: 147 additions & 0 deletions docs/src/components/theme-designer.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
import { TabItem, Tabs } from '@astrojs/starlight/components';
import ColorEditor, { Props as EditorProps } from './theme-designer/color-editor.astro';
import Presets, { Props as PresetsProps } from './theme-designer/presets.astro';
import Preview from './theme-designer/preview.astro';

interface Props {
labels: {
presets: PresetsProps['labels'];
editor: EditorProps['labels'] & { accentColor: string; grayColor: string };
preview: Record<
'darkMode' | 'lightMode' | 'bodyText' | 'linkText' | 'dimText' | 'inlineCode',
string
>;
};
}
const {
labels: { presets, editor, preview },
} = Astro.props;
---

<Presets labels={presets} />

<div>
<theme-designer>
<div class="flex controls not-content">
<ColorEditor key="accent" legend={editor.accentColor} labels={editor} />
<ColorEditor key="gray" legend={editor.grayColor} labels={editor} />
</div>

<div class="preview" data-accent-preview>
<Preview labels={preview} data-dark />
<Preview labels={preview} data-light />
</div>

<Tabs>
<TabItem label="CSS">
<slot name="css-docs" />
<pre class="generated-code" tabindex="0"><code style="background-color: var(--astro-code-color-background);color: var(--sl-color-text)" data-theme-css /></pre>
</TabItem>
<TabItem label="Tailwind">
<slot name="tailwind-docs" />
<pre class="generated-code" tabindex="0"><code style="background-color: var(--astro-code-color-background);color: var(--sl-color-text)" data-theme-tailwind /></pre>
</TabItem>
</Tabs>
</theme-designer>
</div>

<script>
import { getPalettes } from './theme-designer/color-lib';
import { store } from './theme-designer/store';

class ThemeDesigner extends HTMLElement {
#stylesheet = new CSSStyleSheet();

constructor() {
super();
// Add our stylesheet to the document.
document.adoptedStyleSheets = [...document.adoptedStyleSheets, this.#stylesheet];
// Update theme palettes on user input.
const onInput = () => this.#update();
store.accent.subscribe(onInput);
store.gray.subscribe(onInput);
}

#update() {
const palettes = getPalettes({ accent: store.accent.get(), gray: store.gray.get() });
this.#updatePreview(palettes);
this.#updateStylesheet(palettes);
this.#updateTailwindConfig(palettes);
}

/** Apply the generated palettes to the style attributes of the in-content preview panes. */
#updatePreview({ dark, light }: ReturnType<typeof getPalettes>) {
const previews = this.querySelectorAll<HTMLDivElement>('[data-accent-preview] > *');
previews.forEach((preview) => {
const theme = 'dark' in preview.dataset ? dark : light;
Object.entries(theme).forEach(([key, color]) => {
preview.style.setProperty(`--sl-color-${key}`, color);
});
});
}

/** Convert a color palette into a string of CSS rules. */
#paletteToRules(palette: ReturnType<typeof getPalettes>['dark' | 'light']) {
return Object.entries(palette)
.map(([key, color]) => `--sl-color-${key}: ${color};`)
.join('\n\t');
}

/** Update the CSS stylesheet applied to the current page and offered to users to copy. */
#updateStylesheet({ dark, light }: ReturnType<typeof getPalettes>) {
const styles = `/* Dark mode colors. */
:root {\n\t${this.#paletteToRules(dark)}\n}
/* Light mode colors. */
:root[data-theme='light'] {\n\t${this.#paletteToRules(light)}\n}`;
this.#stylesheet.replaceSync(styles);
const codePreview = this.querySelector('[data-theme-css]');
if (codePreview) codePreview.innerHTML = styles;
}

#updateTailwindConfig({ dark, light }: ReturnType<typeof getPalettes>) {
const config = `const starlightPlugin = require('@astrojs/starlight-tailwind');

// Generated color palettes
const accent = { 200: '${dark['accent-high']}', 600: '${light.accent}', 900: '${light['accent-high']}', 950: '${dark['accent-low']}' };
const gray = { 100: '${light['gray-7']}', 200: '${light['gray-6']}', 300: '${light['gray-5']}', 400: '${light['gray-4']}', 500: '${light['gray-3']}', 700: '${light['gray-2']}', 800: '${light['gray-1']}', 900: '${light.white}' };

/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
extend: {
colors: { accent, gray },
},
},
plugins: [starlightPlugin()],
};`;
const codePreview = this.querySelector('[data-theme-tailwind]');
if (codePreview) codePreview.innerHTML = config;
}
}

customElements.define('theme-designer', ThemeDesigner);
</script>

<style>
.controls {
flex-wrap: wrap;
gap: 1.5rem;
}
.controls > :global(*) {
flex: 1 1;
}
.preview {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
}

.generated-code {
height: 16rem;
background-color: var(--astro-code-color-background);
overflow: auto scroll;
user-select: all;
}
</style>
31 changes: 31 additions & 0 deletions docs/src/components/theme-designer/atom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class Atom<T> {
#v: T;
#subscribers = new Map<(v: T) => void, (v: T) => void>();
#notify = () => this.#subscribers.forEach((cb) => cb(this.#v));
constructor(init: T) {
this.#v = init;
}
get(): T {
return this.#v;
}
set(v: T): void {
this.#v = v;
this.#notify();
}
subscribe(cb: (v: T) => void): () => boolean {
cb(this.#v);
this.#subscribers.set(cb, cb);
return () => this.#subscribers.delete(cb);
}
}

type MapStore<T> = Atom<T> & { setKey: (key: keyof T, value: T[typeof key]) => void };

export function map<T extends Record<string, unknown>>(value: T): MapStore<T> {
const atom = new Atom(value) as MapStore<T>;
atom.setKey = (key: keyof T, value: T[typeof key]) => {
const curr = atom.get();
if (curr[key] !== value) atom.set({ ...curr, [key]: value });
};
return atom;
}
93 changes: 93 additions & 0 deletions docs/src/components/theme-designer/color-editor.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
import { oklchToHex } from './color-lib';
import { store } from './store';
import ValueSlider from './value-slider.astro';

export interface Props {
key: keyof typeof store;
legend: string;
labels: Record<'pickColor' | 'hue' | 'chroma', string>;
}
const { key, legend, labels } = Astro.props;
const { hue, chroma } = store[key].get();
const initialColor = oklchToHex(52, chroma, hue);
---

<color-editor data-key={key}>
<fieldset>
<legend>{legend}</legend>
<label class="color-picker">
<span class="sr-only">{labels.pickColor}</span>
<input type="color" value={initialColor} />
</label>
<div class="sliders">
<ValueSlider label={labels.hue} storeKey={key} type="hue" />
<ValueSlider label={labels.chroma} storeKey={key} type="chroma" />
</div>
</fieldset>
</color-editor>

<script>
import { oklch, oklchToHex } from './color-lib';
import { store } from './store';

export class ColorEditor extends HTMLElement {
#store = store[this.dataset.key as keyof typeof store];
#colorInput = this.querySelector<HTMLInputElement>('input[type="color"]')!;

constructor() {
super();
// Update color on user input.
this.#store.subscribe(({ chroma, hue }) => {
this.#colorInput.value = oklchToHex(52, chroma, hue);
});
this.#colorInput.addEventListener('input', (e) => {
if (!(e.target instanceof HTMLInputElement)) return;
const old = this.#store.get();
const { c, h } = { ...oklch(e.target.value) };
this.#store.set({ hue: h ?? old.hue, chroma: c ?? old.chroma });
});
}
}
customElements.define('color-editor', ColorEditor);
</script>

<style>
fieldset {
border: 1px solid var(--sl-color-gray-5);
background-color: var(--sl-color-gray-7, var(--sl-color-gray-6));
padding: 1rem;
color: var(--sl-color-white);
}
legend {
float: left;
float: inline-start;
font-weight: 600;
}
.color-picker {
float: right;
float: inline-end;
}
.sliders {
clear: both;
}
input[type='color'] {
border: 0;
background: transparent;
height: 2em;
width: 3rem;
cursor: pointer;
--swatch-border: var(--sl-color-gray-3);
}
input[type='color']:hover {
--swatch-border: var(--sl-color-gray-1);
}
input[type='color']::-webkit-color-swatch {
border: 1px solid var(--swatch-border);
border-radius: 0.5rem;
}
input[type='color']::-moz-color-swatch {
border: 1px solid var(--swatch-border);
border-radius: 0.5rem;
}
</style>
Loading