@dryui/theme-wizard 2.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,4 +10,4 @@
10
10
  let { value = $bindable(50), color = 'hsl(230, 65%, 55%)', onchange }: Props = $props();
11
11
  </script>
12
12
 
13
- <AlphaSlider bind:value {color} {...(onchange ? { onchange } : {})} />
13
+ <AlphaSlider bind:value {color} {...onchange ? { onchange } : {}} />
@@ -3,7 +3,7 @@
3
3
  import { useThemeOverride } from '@dryui/primitives/use-theme-override';
4
4
  import { Text } from '@dryui/ui/text';
5
5
  import { Button } from '@dryui/ui/button';
6
- import { getOverrideTokens, wizardState } from '../state.svelte.js';
6
+ import { getAllTokens, wizardState } from '../state.svelte.js';
7
7
  import StepIndicator from './StepIndicator.svelte';
8
8
 
9
9
  interface Props {
@@ -30,14 +30,14 @@
30
30
  onstep
31
31
  }: Props = $props();
32
32
 
33
- useThemeOverride(() => getOverrideTokens());
33
+ useThemeOverride(() => getAllTokens());
34
34
  </script>
35
35
 
36
36
  <div class="wizard-shell">
37
37
  <header class="wizard-header">
38
38
  <div class="wizard-header-inner">
39
39
  <Text size="lg" weight="semibold">Theme Wizard</Text>
40
- <StepIndicator currentStep={step} {...(onstep ? { onstep } : {})} />
40
+ <StepIndicator currentStep={step} {...onstep ? { onstep } : {}} />
41
41
  </div>
42
42
  </header>
43
43
 
@@ -6,5 +6,5 @@ export { encodeTheme, decodeTheme, encodeRecipe, decodeRecipe } from './url-code
6
6
  export type { DecodedTheme, WizardRecipe } from './url-codec.js';
7
7
  export { generatePalette, textToBrand } from './palette.js';
8
8
  export type { PaletteResult } from './palette.js';
9
- export { PRESETS } from './presets.js';
10
- export type { Preset } from './presets.js';
9
+ export { PRESETS, RECIPE_PRESETS } from './presets.js';
10
+ export type { Preset, RecipePreset } from './presets.js';
@@ -3,4 +3,4 @@ export { hsbToHsl, hslToHsb, hslToRgb, hslToHex, hexToHsl, cssColorToRgb, relati
3
3
  export { generateCss, downloadCss, copyCss, exportJson } from './export-css.js';
4
4
  export { encodeTheme, decodeTheme, encodeRecipe, decodeRecipe } from './url-codec.js';
5
5
  export { generatePalette, textToBrand } from './palette.js';
6
- export { PRESETS } from './presets.js';
6
+ export { PRESETS, RECIPE_PRESETS } from './presets.js';
@@ -1,6 +1,13 @@
1
1
  import type { BrandInput } from './derivation.js';
2
+ import type { WizardRecipe } from './url-codec.js';
2
3
  export interface Preset {
3
4
  name: string;
4
5
  brandInput: BrandInput;
5
6
  }
6
7
  export declare const PRESETS: Preset[];
8
+ export interface RecipePreset {
9
+ name: string;
10
+ description: string;
11
+ recipe: WizardRecipe;
12
+ }
13
+ export declare const RECIPE_PRESETS: RecipePreset[];
@@ -32,3 +32,93 @@ export const PRESETS = [
32
32
  brandInput: { h: 10, s: 85, b: 75 }
33
33
  }
34
34
  ];
35
+ export const RECIPE_PRESETS = [
36
+ {
37
+ name: 'Default',
38
+ description: 'Balanced starting point',
39
+ recipe: {
40
+ brand: { h: 230, s: 65, b: 85 },
41
+ personality: 'structured',
42
+ typography: { fontPreset: 'System', scale: 'default' },
43
+ shape: { radiusPreset: 'soft', radiusScale: 1, density: 'default' },
44
+ shadows: { preset: 'elevated', intensity: 1, tintBrand: true }
45
+ }
46
+ },
47
+ {
48
+ name: 'Dashboard',
49
+ description: 'Dense, data-first interface',
50
+ recipe: {
51
+ brand: { h: 200, s: 80, b: 70 },
52
+ personality: 'structured',
53
+ typography: { fontPreset: 'Geometric', scale: 'compact' },
54
+ shape: { radiusPreset: 'sharp', radiusScale: 1, density: 'compact' },
55
+ shadows: { preset: 'subtle', intensity: 1, tintBrand: false }
56
+ }
57
+ },
58
+ {
59
+ name: 'Editorial',
60
+ description: 'Warm, readable, content-forward',
61
+ recipe: {
62
+ brand: { h: 340, s: 65, b: 85 },
63
+ personality: 'clean',
64
+ typography: { fontPreset: 'Serif', scale: 'spacious' },
65
+ shape: { radiusPreset: 'soft', radiusScale: 1, density: 'spacious' },
66
+ shadows: { preset: 'flat', intensity: 1, tintBrand: true }
67
+ }
68
+ },
69
+ {
70
+ name: 'Terminal',
71
+ description: 'Precise, monospace, technical',
72
+ recipe: {
73
+ brand: { h: 145, s: 60, b: 55 },
74
+ personality: 'minimal',
75
+ typography: { fontPreset: 'Mono', scale: 'compact' },
76
+ shape: { radiusPreset: 'sharp', radiusScale: 1, density: 'compact' },
77
+ shadows: { preset: 'flat', intensity: 1, tintBrand: false }
78
+ }
79
+ },
80
+ {
81
+ name: 'Playful',
82
+ description: 'Warm, rounded, approachable',
83
+ recipe: {
84
+ brand: { h: 25, s: 80, b: 90 },
85
+ personality: 'rich',
86
+ typography: { fontPreset: 'Humanist', scale: 'default' },
87
+ shape: { radiusPreset: 'pill', radiusScale: 1, density: 'default' },
88
+ shadows: { preset: 'deep', intensity: 1, tintBrand: true }
89
+ }
90
+ },
91
+ {
92
+ name: 'Corporate',
93
+ description: 'Professional and measured',
94
+ recipe: {
95
+ brand: { h: 230, s: 65, b: 85 },
96
+ personality: 'structured',
97
+ typography: { fontPreset: 'Classical', scale: 'default' },
98
+ shape: { radiusPreset: 'soft', radiusScale: 1, density: 'default' },
99
+ shadows: { preset: 'elevated', intensity: 1, tintBrand: false }
100
+ }
101
+ },
102
+ {
103
+ name: 'Midnight',
104
+ description: 'Dark, atmospheric, layered',
105
+ recipe: {
106
+ brand: { h: 270, s: 45, b: 80 },
107
+ personality: 'rich',
108
+ typography: { fontPreset: 'System', scale: 'default' },
109
+ shape: { radiusPreset: 'rounded', radiusScale: 1, density: 'default' },
110
+ shadows: { preset: 'deep', intensity: 1, tintBrand: true }
111
+ }
112
+ },
113
+ {
114
+ name: 'Ember',
115
+ description: 'Bold, warm, high contrast',
116
+ recipe: {
117
+ brand: { h: 10, s: 85, b: 75 },
118
+ personality: 'structured',
119
+ typography: { fontPreset: 'Geometric', scale: 'default' },
120
+ shape: { radiusPreset: 'rounded', radiusScale: 1, density: 'default' },
121
+ shadows: { preset: 'elevated', intensity: 1, tintBrand: true }
122
+ }
123
+ }
124
+ ];
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './engine/index.js';
2
+ export { bg } from './actions.js';
2
3
  export { wizardState, getDerivedTheme, setBrandHsb, setStep, goNextStep, goPrevStep, activateFastTrack, applyPreset, getStyleString, setFontPreset, setTypeScale, resetToDefaults, applyRecipe, setRadiusPreset, setRadiusScale, setDensity, getShadowTokens, getShapeTokens, setPersonality, getPersonalityTokens, getAllTokens, getOverrideTokens, RADIUS_PRESETS, FONT_STACKS } from './state.svelte.js';
3
4
  export type { PreviewMode, NeutralMode, RadiusPreset, Density, ShadowPreset, Personality, TypeScale, FontPreset } from './state.svelte.js';
4
5
  export { default as PersonalityStep } from './steps/Personality.svelte';
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // Re-export all engine functions and types
2
2
  export * from './engine/index.js';
3
+ // Re-export actions
4
+ export { bg } from './actions.js';
3
5
  // Re-export state (Svelte 5 runes)
4
6
  export { wizardState, getDerivedTheme, setBrandHsb, setStep, goNextStep, goPrevStep, activateFastTrack, applyPreset, getStyleString, setFontPreset, setTypeScale, resetToDefaults, applyRecipe, setRadiusPreset, setRadiusScale, setDensity, getShadowTokens, getShapeTokens, setPersonality, getPersonalityTokens, getAllTokens, getOverrideTokens, RADIUS_PRESETS, FONT_STACKS } from './state.svelte.js';
5
7
  // Step components
@@ -51,6 +51,10 @@ function persistState() {
51
51
  untrack(() => {
52
52
  try {
53
53
  const { personality, brandHsb, neutralMode, statusHues, darkBgOverrides, typography, shape, shadows } = wizardState;
54
+ const tokens = {
55
+ light: getAllTokens('light'),
56
+ dark: getAllTokens('dark')
57
+ };
54
58
  sessionStorage.setItem(STORAGE_KEY, JSON.stringify({
55
59
  personality,
56
60
  brandHsb,
@@ -59,7 +63,8 @@ function persistState() {
59
63
  darkBgOverrides,
60
64
  typography,
61
65
  shape,
62
- shadows
66
+ shadows,
67
+ tokens
63
68
  }));
64
69
  }
65
70
  catch {
@@ -110,6 +115,14 @@ export function setNeutralMode(mode) {
110
115
  wizardState.neutralMode = mode;
111
116
  persistState();
112
117
  }
118
+ function resetStyleStateToDefaults() {
119
+ wizardState.neutralMode = DEFAULTS.neutralMode;
120
+ wizardState.statusHues = { ...DEFAULTS.statusHues };
121
+ wizardState.darkBgOverrides = { ...DEFAULTS.darkBgOverrides };
122
+ wizardState.typography = { ...DEFAULTS.typography };
123
+ wizardState.shape = { ...DEFAULTS.shape };
124
+ wizardState.shadows = { ...DEFAULTS.shadows };
125
+ }
113
126
  /** Set the personality (chrome level) and apply cross-step defaults. */
114
127
  function applyPersonalityDefaults(p) {
115
128
  wizardState.personality = p;
@@ -131,6 +144,7 @@ function applyPersonalityDefaults(p) {
131
144
  }
132
145
  /** Set the personality (chrome level) and apply cross-step defaults. */
133
146
  export function setPersonality(p) {
147
+ resetStyleStateToDefaults();
134
148
  applyPersonalityDefaults(p);
135
149
  persistState();
136
150
  }
@@ -188,16 +202,11 @@ export function setTypeScale(scale) {
188
202
  }
189
203
  /** Reset all wizard state to defaults. */
190
204
  export function resetToDefaults() {
191
- wizardState.currentStep = 1;
192
- wizardState.personality = 'structured';
193
- wizardState.brandHsb = { h: 230, s: 65, b: 85 };
194
- wizardState.neutralMode = 'monochromatic';
195
- wizardState.statusHues = { error: 0, warning: 40, success: 145, info: 210 };
196
- wizardState.darkBgOverrides = {};
197
- wizardState.fastTrack = false;
198
- wizardState.typography = { fontPreset: 'System', scale: 'default' };
199
- wizardState.shape = { radiusPreset: 'soft', radiusScale: 1, density: 'default' };
200
- wizardState.shadows = { preset: 'elevated', intensity: 1, tintBrand: true };
205
+ wizardState.currentStep = DEFAULTS.currentStep;
206
+ wizardState.personality = DEFAULTS.personality;
207
+ wizardState.brandHsb = { ...DEFAULTS.brandHsb };
208
+ resetStyleStateToDefaults();
209
+ wizardState.fastTrack = DEFAULTS.fastTrack;
201
210
  if (typeof sessionStorage !== 'undefined') {
202
211
  sessionStorage.removeItem(STORAGE_KEY);
203
212
  }
@@ -206,15 +215,13 @@ export function resetToDefaults() {
206
215
  export function applyRecipe(recipe) {
207
216
  wizardState.currentStep = DEFAULTS.currentStep;
208
217
  wizardState.brandHsb = { ...recipe.brand };
218
+ resetStyleStateToDefaults();
209
219
  wizardState.neutralMode = recipe.neutralMode ?? DEFAULTS.neutralMode;
210
220
  wizardState.statusHues = { ...DEFAULTS.statusHues, ...recipe.statusHues };
211
- wizardState.darkBgOverrides = { ...DEFAULTS.darkBgOverrides };
212
221
  wizardState.fastTrack = false;
213
222
  wizardState.typography = recipe.typography
214
223
  ? { ...recipe.typography }
215
224
  : { ...DEFAULTS.typography };
216
- wizardState.shape = { ...DEFAULTS.shape };
217
- wizardState.shadows = { ...DEFAULTS.shadows };
218
225
  applyPersonalityDefaults(recipe.personality ?? DEFAULTS.personality);
219
226
  if (recipe.shape) {
220
227
  wizardState.shape = { ...recipe.shape };
@@ -520,7 +527,13 @@ export function getAllTokens(mode = 'light') {
520
527
  const shadowTokens = shadows[m];
521
528
  const personalityTokens = getPersonalityTokens();
522
529
  const typographyTokens = getTypographyTokens();
523
- return { ...colorTokens, ...shapeTokens, ...shadowTokens, ...personalityTokens, ...typographyTokens };
530
+ return {
531
+ ...colorTokens,
532
+ ...shapeTokens,
533
+ ...shadowTokens,
534
+ ...personalityTokens,
535
+ ...typographyTokens
536
+ };
524
537
  }
525
538
  /** Return only tokens that the user has changed from defaults. */
526
539
  export function getOverrideTokens(mode = 'light') {
@@ -551,5 +564,11 @@ function computeDefaultAllTokens(mode) {
551
564
  const shadowTokens = getShadowTokens(DEFAULTS.shadows.preset, DEFAULTS.shadows.intensity, DEFAULTS.shadows.tintBrand, DEFAULTS.brandHsb.h)[mode];
552
565
  const personalityTokens = PERSONALITY_TOKENS[DEFAULTS.personality];
553
566
  const typographyTokens = getTypographyTokens(DEFAULTS.typography.fontPreset, DEFAULTS.typography.scale);
554
- return { ...colorTokens, ...shapeTokens, ...shadowTokens, ...personalityTokens, ...typographyTokens };
567
+ return {
568
+ ...colorTokens,
569
+ ...shapeTokens,
570
+ ...shadowTokens,
571
+ ...personalityTokens,
572
+ ...typographyTokens
573
+ };
555
574
  }
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { Button } from '@dryui/ui';
2
3
  import { ColorPicker } from '@dryui/ui/color-picker';
3
4
  import { Text } from '@dryui/ui/text';
4
5
  import { setBrandHsb, getDerivedTheme, wizardState } from '../state.svelte.js';
@@ -10,17 +11,11 @@
10
11
  hsbToHsl,
11
12
  hslToHex
12
13
  } from '../engine/index.js';
13
- import type { BrandInput } from '../engine/index.js';
14
14
  import { bg } from '../actions';
15
15
 
16
16
  let { mode = 'light' }: { mode?: 'light' | 'dark' } = $props();
17
17
 
18
18
  let isDark = $derived(mode === 'dark');
19
- let brand = $state<BrandInput>({ ...wizardState.brandHsb });
20
-
21
- $effect(() => {
22
- setBrandHsb(brand.h, brand.s, brand.b);
23
- });
24
19
 
25
20
  const SWATCH_TOKENS = [
26
21
  { key: '--dry-color-fill', label: 'Fill' },
@@ -44,32 +39,34 @@
44
39
  );
45
40
 
46
41
  let pickerColor = $derived.by(() => {
47
- const hsl = hsbToHsl(brand.h, brand.s / 100, brand.b / 100);
42
+ const hsl = hsbToHsl(
43
+ wizardState.brandHsb.h,
44
+ wizardState.brandHsb.s / 100,
45
+ wizardState.brandHsb.b / 100
46
+ );
48
47
  return hslToHex(hsl.h, hsl.s, hsl.l);
49
48
  });
50
49
 
51
- let pickerHex = $state('');
52
-
53
- $effect(() => {
54
- pickerHex = pickerColor;
55
- });
56
-
57
- $effect(() => {
58
- if (pickerHex && pickerHex !== pickerColor) {
59
- const hsl = hexToHsl(pickerHex);
50
+ const pickerModel = {
51
+ get hex() {
52
+ return pickerColor;
53
+ },
54
+ set hex(value: string) {
55
+ if (!value || value === pickerColor) return;
56
+ const hsl = hexToHsl(value);
60
57
  const hsb = hslToHsb(hsl.h, hsl.s, hsl.l);
61
- brand = { h: hsb.h, s: Math.round(hsb.s * 100), b: Math.round(hsb.b * 100) };
58
+ setBrandHsb(hsb.h, Math.round(hsb.s * 100), Math.round(hsb.b * 100));
62
59
  }
63
- });
60
+ };
64
61
 
65
62
  function selectPreset(preset: (typeof PRESETS)[number]) {
66
- brand = preset.brandInput;
63
+ setBrandHsb(preset.brandInput.h, preset.brandInput.s, preset.brandInput.b);
67
64
  }
68
65
  </script>
69
66
 
70
67
  <section class="brand-section">
71
68
  <div class="picker-wrap">
72
- <ColorPicker.Root bind:value={pickerHex} areaHeight={160}>
69
+ <ColorPicker.Root bind:value={pickerModel.hex} areaHeight={160}>
73
70
  <ColorPicker.Area />
74
71
  <ColorPicker.HueSlider />
75
72
  <ColorPicker.Input format="hex" />
@@ -90,22 +87,23 @@
90
87
  {#each PRESETS as preset, i (preset.name)}
91
88
  {@const presetTheme = PRESET_THEMES[i] ?? PRESET_THEMES[0]!}
92
89
  {@const presetTokens = isDark ? presetTheme.dark : presetTheme.light}
93
- <button
94
- class="preset-btn"
95
- data-selected={brand.h === preset.brandInput.h &&
96
- brand.s === preset.brandInput.s &&
97
- brand.b === preset.brandInput.b
98
- ? ''
99
- : undefined}
100
- onclick={() => selectPreset(preset)}
101
- >
102
- <div class="preset-thumb">
103
- {#each SWATCH_TOKENS as { key } (key)}
104
- <div class="preset-swatch" use:bg={presetTokens[key] ?? ''}></div>
105
- {/each}
90
+ <Button type="button" variant="bare" onclick={() => selectPreset(preset)}>
91
+ <div
92
+ class="preset-btn"
93
+ data-selected={wizardState.brandHsb.h === preset.brandInput.h &&
94
+ wizardState.brandHsb.s === preset.brandInput.s &&
95
+ wizardState.brandHsb.b === preset.brandInput.b
96
+ ? ''
97
+ : undefined}
98
+ >
99
+ <div class="preset-thumb">
100
+ {#each SWATCH_TOKENS as { key } (key)}
101
+ <div class="preset-swatch" use:bg={presetTokens[key] ?? ''}></div>
102
+ {/each}
103
+ </div>
104
+ <Text as="span" size="xs" color="muted">{preset.name.toLowerCase()}</Text>
106
105
  </div>
107
- <Text as="span" size="xs" color="muted">{preset.name.toLowerCase()}</Text>
108
- </button>
106
+ </Button>
109
107
  {/each}
110
108
  </div>
111
109
  </div>
@@ -12,22 +12,22 @@
12
12
  {
13
13
  value: 'minimal',
14
14
  name: 'Minimal',
15
- description: 'Flat, transparent, content-forward',
15
+ description: 'Flat, transparent, content-forward'
16
16
  },
17
17
  {
18
18
  value: 'clean',
19
19
  name: 'Clean',
20
- description: 'Subtle separation, gentle edges',
20
+ description: 'Subtle separation, gentle edges'
21
21
  },
22
22
  {
23
23
  value: 'structured',
24
24
  name: 'Structured',
25
- description: 'Clear regions, visible cards',
25
+ description: 'Clear regions, visible cards'
26
26
  },
27
27
  {
28
28
  value: 'rich',
29
29
  name: 'Rich',
30
- description: 'Elevated surfaces, deep layering',
30
+ description: 'Elevated surfaces, deep layering'
31
31
  }
32
32
  ];
33
33
 
@@ -24,6 +24,12 @@
24
24
  for (const [name, value] of Object.entries(tokens)) {
25
25
  node.style.setProperty(name, value);
26
26
  }
27
+
28
+ return () => {
29
+ for (const name of Object.keys(tokens)) {
30
+ node.style.removeProperty(name);
31
+ }
32
+ };
27
33
  };
28
34
  }
29
35
 
@@ -57,11 +63,7 @@
57
63
  </div>
58
64
  </div>
59
65
 
60
- <div
61
- class="preview-scene"
62
- data-mode={mode}
63
- {@attach attachThemeTokens(tokens)}
64
- >
66
+ <div class="preview-scene" data-mode={mode} {@attach attachThemeTokens(tokens)}>
65
67
  {#if preview}
66
68
  {@render preview()}
67
69
  {:else}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dryui/theme-wizard",
3
- "version": "2.0.0",
3
+ "version": "4.0.0",
4
4
  "author": "Rob Balfre",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,20 +37,20 @@
37
37
  "check": "svelte-check --tsconfig ./tsconfig.json"
38
38
  },
39
39
  "peerDependencies": {
40
- "@dryui/primitives": "^0.2.0",
41
- "@dryui/ui": "^0.2.0",
40
+ "@dryui/primitives": "^0.4.0",
41
+ "@dryui/ui": "^0.4.0",
42
42
  "lucide-svelte": ">=1.0.1",
43
43
  "svelte": "^5.55.1"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@dryui/lint": "^0.2.0",
47
- "@dryui/primitives": "^0.2.0",
48
- "@dryui/ui": "^0.2.0",
47
+ "@dryui/primitives": "^0.4.0",
48
+ "@dryui/ui": "^0.4.0",
49
49
  "lucide-svelte": "^1.0.1",
50
- "svelte": "^5.55.1",
50
+ "svelte": "^5.55.3",
51
51
  "@sveltejs/package": "^2.5.7",
52
52
  "svelte-check": "^4.4.6",
53
- "bun-types": "^1.3.11",
53
+ "bun-types": "^1.3.12",
54
54
  "typescript": "^6.0.2"
55
55
  }
56
56
  }