Skip to content

Commit 30a90ff

Browse files
committed
Use standard skin generator with custom colors.
1 parent 4c0caea commit 30a90ff

5 files changed

Lines changed: 97 additions & 40 deletions

File tree

‎frontend/purestack.studio/guides/themes.mdx‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,22 @@ The generator writes `assets/site.css` for light and `assets/site.dark.css` for
3636

3737
## Create a skin
3838

39-
Start from a standard color preset and override the values your design needs:
39+
Pass a color object to the standard skin to generate matching light and dark
40+
palettes. You can provide any subset of its semantic colors; omitted colors use
41+
the standard defaults:
4042

4143
```ts
4244
import { registerSkin, themeSkins } from '@purestack/ts-style'
4345
import { merge } from '@purestack/ts-util'
4446

4547
registerSkin('my-site', {
4648
create: () => {
47-
const standard = themeSkins.standard.create(['green'])
49+
const standard = themeSkins.standard.create({
50+
accent: '#4b64c8',
51+
neutral: '#293047',
52+
secondary: '#168a75',
53+
feature: '#b04783',
54+
})
4855
return {
4956
light: merge(standard.light, {
5057
font: { size: { body: '1rem' } },
@@ -57,7 +64,7 @@ registerSkin('my-site', {
5764
})
5865
```
5966

60-
Import the file that registers your skin before the site build begins, then set `"theme": { "skin": "my-site" }` in the style config. A skin is a full light/dark pair; the preset gives related semantic colors a coherent starting point, and `merge` preserves values you do not override.
67+
Import the file that registers your skin before the site build begins, then set `"theme": { "skin": "my-site" }` in the style config. A skin is a full light/dark pair, and `merge` preserves palette values you do not override. You can also pass a list of built-in preset names, such as `['green']`, to `themeSkins.standard.create()`.
6168

6269
## Extend styles with palette roles
6370

‎packages/ts-style/src/skins/index.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import type { ThemePalette } from '../themePalette'
2-
import { createStandardDark } from './standard/dark'
2+
import { createStandardDark, type StandardColorPreset } from './standard/dark'
33
import { createStandardLight } from './standard/light'
44

5+
export type { StandardColorPreset } from './standard/dark'
6+
57
export type ThemeSkinName = string
68
export type SkinPresetList = readonly string[]
79

@@ -10,11 +12,13 @@ export interface ThemeSkinPair {
1012
dark: ThemePalette
1113
}
1214

13-
export interface ThemeSkin {
14-
create: (presets?: SkinPresetList) => ThemeSkinPair
15+
export interface ThemeSkin<TPresets = SkinPresetList> {
16+
create: (presets?: TPresets) => ThemeSkinPair
1517
}
1618

17-
export type ThemeSkinRegistry = Record<ThemeSkinName, ThemeSkin>
19+
export type ThemeSkinRegistry = Record<ThemeSkinName, ThemeSkin> & {
20+
standard: ThemeSkin<SkinPresetList | StandardColorPreset>
21+
}
1822

1923
export const themeSkins: ThemeSkinRegistry = {
2024
standard: {

‎packages/ts-style/src/skins/standard/dark.ts‎

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,7 @@ const bestColors = {
8888
},
8989
}
9090
export type StandardColors = (typeof bestColors)['standardBlue']
91-
92-
export const feature = '#cb166e'
93-
export const custom = '#e35508'
94-
export const info = '#15a9c0'
95-
export const success = '#259740'
96-
export const warning = '#c49a1c'
97-
export const danger = '#a92a37'
91+
export type StandardColorPreset = Partial<StandardColors>
9892

9993
export interface DeltaToneColors {
10094
canvas: number
@@ -143,16 +137,22 @@ export function createToneColors(
143137
}
144138
}
145139

146-
export function getStandardColors(presets?: readonly string[]): StandardColors {
147-
let colors: StandardColors | undefined
140+
export function getStandardColors(
141+
presets?: readonly string[] | StandardColorPreset,
142+
): StandardColors {
148143
const defaultColors = bestColors.standardBlue
149-
for (const preset of presets ?? []) {
144+
if (!presets) return defaultColors
145+
if (!Array.isArray(presets)) {
146+
return merge(defaultColors, presets)
147+
}
148+
let colors: StandardColors = defaultColors
149+
for (const preset of presets) {
150150
colors = merge(
151151
defaultColors,
152152
bestColors[preset as keyof typeof bestColors] ?? defaultColors,
153153
)
154154
}
155-
return colors ?? defaultColors
155+
return colors
156156
}
157157

158158
function applyDeltaPresets(
@@ -172,13 +172,13 @@ function createStandardDarkCore(
172172
applyDeltaPresets(presets, delta)
173173
const accentScale = createColorScale(colors.accent, mode)
174174
const neutralScale = createColorScale(colors.neutral, mode)
175-
const featureScale = createColorScale(feature, mode)
175+
const featureScale = createColorScale(colors.feature, mode)
176176
const secondaryScale = createColorScale(colors.secondary, mode)
177-
const customScale = createColorScale(custom, mode)
178-
const infoScale = createColorScale(info, mode)
179-
const successScale = createColorScale(success, mode)
180-
const warningScale = createColorScale(warning, mode)
181-
const dangerScale = createColorScale(danger, mode)
177+
const customScale = createColorScale(colors.custom, mode)
178+
const infoScale = createColorScale(colors.info, mode)
179+
const successScale = createColorScale(colors.success, mode)
180+
const warningScale = createColorScale(colors.warning, mode)
181+
const dangerScale = createColorScale(colors.danger, mode)
182182

183183
return {
184184
neutral: createToneColors(neutralScale[toneIndex], neutralScale, delta),
@@ -198,11 +198,16 @@ function createStandardDarkCore(
198198
}
199199
}
200200

201-
export function createStandardDark(presets?: readonly string[]) {
201+
export function createStandardDark(
202+
presets?: readonly string[] | StandardColorPreset,
203+
) {
202204
const colors = getStandardColors(presets)
203205
return createStandardPalette({
204206
mode: 'dark',
205-
core: createStandardDarkCore(colors, presets),
207+
core: createStandardDarkCore(
208+
colors,
209+
Array.isArray(presets) ? presets : undefined,
210+
),
206211
accent: colors.accent,
207212
chromeLighting: 0.33,
208213
borderAlpha: 0.66,

‎packages/ts-style/src/skins/standard/light.ts‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,10 @@ import { createStandardPalette, type StandardCore } from './base'
33
import {
44
createColorScale,
55
createToneColors,
6-
custom,
76
type DeltaToneColors,
8-
danger,
9-
feature,
107
getStandardColors,
11-
info,
8+
type StandardColorPreset,
129
type StandardColors,
13-
success,
14-
warning,
1510
} from './dark'
1611

1712
const mode: ThemeMode = 'dark'
@@ -53,13 +48,13 @@ function createStandardLightCore(
5348
applyDeltaPresets(presets, delta, delta1)
5449
const accentScale = createColorScale(colors.accent, mode)
5550
const neutralScale = createColorScale(colors.neutral, 'light')
56-
const featureScale = createColorScale(feature, mode)
51+
const featureScale = createColorScale(colors.feature, mode)
5752
const secondaryScale = createColorScale(colors.secondary, mode)
58-
const customScale = createColorScale(custom, mode)
59-
const infoScale = createColorScale(info, mode)
60-
const successScale = createColorScale(success, mode)
61-
const warningScale = createColorScale(warning, mode)
62-
const dangerScale = createColorScale(danger, mode)
53+
const customScale = createColorScale(colors.custom, mode)
54+
const infoScale = createColorScale(colors.info, mode)
55+
const successScale = createColorScale(colors.success, mode)
56+
const warningScale = createColorScale(colors.warning, mode)
57+
const dangerScale = createColorScale(colors.danger, mode)
6358

6459
return {
6560
neutral: createToneColors(neutralScale[70], neutralScale, delta),
@@ -79,11 +74,16 @@ function createStandardLightCore(
7974
}
8075
}
8176

82-
export function createStandardLight(presets?: readonly string[]) {
77+
export function createStandardLight(
78+
presets?: readonly string[] | StandardColorPreset,
79+
) {
8380
const colors = getStandardColors(presets)
8481
return createStandardPalette({
8582
mode: 'light',
86-
core: createStandardLightCore(colors, presets),
83+
core: createStandardLightCore(
84+
colors,
85+
Array.isArray(presets) ? presets : undefined,
86+
),
8787
accent: colors.accent,
8888
chromeLighting: 0.33,
8989
borderAlpha: 1,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { themeSkins } from '..'
4+
import type { StandardColorPreset } from './dark'
5+
6+
describe('standard skin color presets', () => {
7+
it('accepts a color object for both light and dark palettes', () => {
8+
const preset = {
9+
accent: '#4b64c8',
10+
feature: '#b04783',
11+
info: '#168a75',
12+
} satisfies StandardColorPreset
13+
const standard = themeSkins.standard.create()
14+
const custom = themeSkins.standard.create(preset)
15+
16+
for (const mode of ['light', 'dark'] as const) {
17+
expect(custom[mode].accent).toBe(preset.accent)
18+
expect(custom[mode].semanticTone.feature.tone).not.toBe(
19+
standard[mode].semanticTone.feature.tone,
20+
)
21+
expect(custom[mode].semanticTone.info.tone).not.toBe(
22+
standard[mode].semanticTone.info.tone,
23+
)
24+
expect(custom[mode].semanticTone.success).toEqual(
25+
standard[mode].semanticTone.success,
26+
)
27+
}
28+
})
29+
30+
it('uses the extra color roles from named presets', () => {
31+
const standard = themeSkins.standard.create()
32+
const mint = themeSkins.standard.create(['mint'])
33+
34+
expect(mint.light.semanticTone.feature.tone).not.toBe(
35+
standard.light.semanticTone.feature.tone,
36+
)
37+
expect(mint.dark.semanticTone.success.tone).not.toBe(
38+
standard.dark.semanticTone.success.tone,
39+
)
40+
})
41+
})

0 commit comments

Comments
 (0)