supaslidev 0.3.0 → 0.3.2

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.
package/app/app.config.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  export default defineAppConfig({
2
2
  ui: {
3
3
  colors: {
4
- primary: 'blue',
4
+ primary: 'teal',
5
5
  secondary: 'violet',
6
- neutral: 'slate',
6
+ neutral: 'stone',
7
7
  },
8
8
  },
9
9
  });
@@ -6,17 +6,40 @@
6
6
  }
7
7
 
8
8
  :root {
9
+ --ui-primary: var(--color-teal-700);
10
+ --ui-success: var(--color-green-700);
9
11
  --supaslidev-border: #d1d5db;
10
12
  --supaslidev-header-bg: rgba(0, 0, 0, 0.02);
11
13
  --supaslidev-text-muted: #6b7280;
12
14
  }
13
15
 
14
16
  .dark {
17
+ --ui-primary: var(--color-teal-500);
18
+ --ui-success: var(--color-green-500);
15
19
  --supaslidev-border: #4b5563;
16
20
  --supaslidev-header-bg: rgba(255, 255, 255, 0.03);
17
21
  --supaslidev-text-muted: #9ca3af;
18
22
  }
19
23
 
24
+ a,
25
+ button,
26
+ [role='button'],
27
+ select,
28
+ summary {
29
+ cursor: pointer;
30
+ }
31
+
32
+ .settings-icon {
33
+ transition:
34
+ transform 0.3s ease,
35
+ color 0.3s ease;
36
+ }
37
+
38
+ .settings-btn:hover .settings-icon {
39
+ transform: rotate(45deg);
40
+ color: var(--ui-primary);
41
+ }
42
+
20
43
  *,
21
44
  *::before,
22
45
  *::after {
@@ -16,13 +16,7 @@ const emit = defineEmits<{
16
16
  'execute-command': [command: string];
17
17
  }>();
18
18
 
19
- const colorMode = useColorMode();
20
- const isDark = computed({
21
- get: () => colorMode.value === 'dark',
22
- set: (value: boolean) => {
23
- colorMode.preference = value ? 'dark' : 'light';
24
- },
25
- });
19
+ const router = useRouter();
26
20
 
27
21
  const isMac = computed(() => {
28
22
  if (typeof navigator === 'undefined') return true;
@@ -152,13 +146,15 @@ defineExpose({ focusInput, inputRef });
152
146
  </div>
153
147
 
154
148
  <UButton
155
- :icon="isDark ? 'i-lucide-sun' : 'i-lucide-moon'"
149
+ icon="i-lucide-settings"
156
150
  color="neutral"
157
151
  variant="ghost"
158
152
  size="md"
159
- class="theme-toggle"
160
- :title="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
161
- @click="isDark = !isDark"
153
+ class="settings-btn"
154
+ aria-label="Open settings"
155
+ title="Settings"
156
+ :ui="{ leadingIcon: 'settings-icon' }"
157
+ @click="router.push('/settings')"
162
158
  />
163
159
  </div>
164
160
  </div>
@@ -375,14 +371,6 @@ defineExpose({ focusInput, inputRef });
375
371
  opacity: 0.85;
376
372
  }
377
373
 
378
- .theme-toggle {
379
- transition: all 0.2s ease;
380
- }
381
-
382
- .theme-toggle:hover {
383
- box-shadow: 0 0 20px rgba(99, 102, 241, 0.2);
384
- }
385
-
386
374
  .dropdown {
387
375
  border-top: 1px solid var(--supaslidev-border);
388
376
  max-height: 240px;
@@ -37,7 +37,7 @@ async function handleDev(event: Event) {
37
37
  event.stopPropagation();
38
38
 
39
39
  if (deployMode.value) {
40
- showDeployDemoToast();
40
+ window.open(`${deployBasePath.value}/presentations/${props.presentation.id}/`, '_blank');
41
41
  return;
42
42
  }
43
43
 
@@ -215,7 +215,7 @@ function handleCardClick() {
215
215
  <template v-if="!loading.dev" #leading>
216
216
  <span class="terminal-prompt-symbol">$</span>
217
217
  </template>
218
- {{ running ? 'stop' : 'dev' }}
218
+ {{ running ? 'stop' : 'present' }}
219
219
  </UButton>
220
220
 
221
221
  <UButton
@@ -37,7 +37,7 @@ async function handleDev(event: Event) {
37
37
  event.stopPropagation();
38
38
 
39
39
  if (deployMode.value) {
40
- showDeployDemoToast();
40
+ window.open(`${deployBasePath.value}/presentations/${props.presentation.id}/`, '_blank');
41
41
  return;
42
42
  }
43
43
 
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ colors: readonly { name: string; value: string }[];
4
+ modelValue: string | undefined;
5
+ name: string;
6
+ label: string;
7
+ }>();
8
+
9
+ const emit = defineEmits<{
10
+ 'update:modelValue': [value: string];
11
+ }>();
12
+ </script>
13
+
14
+ <template>
15
+ <fieldset
16
+ class="flex items-center gap-4 rounded-xl w-fit p-2 -m-2 has-[input:focus-visible]:outline-[0.375rem] has-[input:focus-visible]:outline-double has-[input:focus-visible]:outline-primary has-[input:focus-visible]:shadow-[0_0_0_0.25rem_white]"
17
+ >
18
+ <legend class="sr-only">{{ label }}</legend>
19
+ <label
20
+ v-for="color in colors"
21
+ :key="color.name"
22
+ class="size-6 rounded-full cursor-pointer transition-transform duration-150 hover:scale-110 has-checked:outline-2 has-checked:outline-black dark:has-checked:outline-white has-checked:outline-offset-2"
23
+ :style="{ backgroundColor: color.value }"
24
+ :title="color.name"
25
+ >
26
+ <input
27
+ type="radio"
28
+ :name="name"
29
+ class="sr-only"
30
+ :checked="modelValue === color.name"
31
+ :aria-label="color.name"
32
+ :value="color.name"
33
+ @change="emit('update:modelValue', color.name)"
34
+ />
35
+ </label>
36
+ </fieldset>
37
+ </template>
@@ -0,0 +1,78 @@
1
+ const ACCENT_COLORS = ['blue', 'green', 'red', 'orange', 'teal', 'indigo', 'violet'] as const;
2
+ const NEUTRAL_COLORS = ['slate', 'gray', 'zinc', 'neutral', 'stone'] as const;
3
+
4
+ type AccentColor = (typeof ACCENT_COLORS)[number];
5
+ type NeutralColor = (typeof NEUTRAL_COLORS)[number];
6
+
7
+ interface AppSettings {
8
+ accentColor: AccentColor;
9
+ neutralColor: NeutralColor;
10
+ }
11
+
12
+ const STORAGE_KEY = 'supaslidev-settings';
13
+
14
+ const DEFAULT_SETTINGS: AppSettings = {
15
+ accentColor: 'teal',
16
+ neutralColor: 'stone',
17
+ };
18
+
19
+ function loadSettings(): AppSettings {
20
+ if (import.meta.server) return { ...DEFAULT_SETTINGS };
21
+ try {
22
+ const raw = localStorage.getItem(STORAGE_KEY);
23
+ if (raw) {
24
+ const parsed = JSON.parse(raw) as Partial<AppSettings>;
25
+ return {
26
+ accentColor:
27
+ parsed.accentColor && ACCENT_COLORS.includes(parsed.accentColor as AccentColor)
28
+ ? (parsed.accentColor as AccentColor)
29
+ : DEFAULT_SETTINGS.accentColor,
30
+ neutralColor:
31
+ parsed.neutralColor && NEUTRAL_COLORS.includes(parsed.neutralColor as NeutralColor)
32
+ ? (parsed.neutralColor as NeutralColor)
33
+ : DEFAULT_SETTINGS.neutralColor,
34
+ };
35
+ }
36
+ } catch {
37
+ // Ignore localStorage errors
38
+ }
39
+ return { ...DEFAULT_SETTINGS };
40
+ }
41
+
42
+ const _settings = ref<AppSettings>(loadSettings());
43
+ let watcherInitialized = false;
44
+
45
+ function useSettings() {
46
+ if (!watcherInitialized) {
47
+ watcherInitialized = true;
48
+ watch(
49
+ _settings,
50
+ (value) => {
51
+ try {
52
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
53
+ } catch (error) {
54
+ console.error(`Failed to persist settings to "${STORAGE_KEY}":`, error, value);
55
+ }
56
+ updateAppConfig({
57
+ ui: { colors: { primary: value.accentColor, neutral: value.neutralColor } },
58
+ });
59
+ },
60
+ { deep: true },
61
+ );
62
+ }
63
+
64
+ // Apply on first use
65
+ updateAppConfig({
66
+ ui: {
67
+ colors: {
68
+ primary: _settings.value.accentColor,
69
+ neutral: _settings.value.neutralColor,
70
+ },
71
+ },
72
+ });
73
+
74
+ return { settings: _settings };
75
+ }
76
+
77
+ export { ACCENT_COLORS, DEFAULT_SETTINGS, NEUTRAL_COLORS, useSettings };
78
+ export type { AccentColor, NeutralColor, AppSettings };
@@ -1,3 +1,9 @@
1
+ <script setup lang="ts">
2
+ import { useSettings } from '../composables/useSettings';
3
+
4
+ useSettings();
5
+ </script>
6
+
1
7
  <template>
2
8
  <UApp>
3
9
  <div class="min-h-screen bg-default flex flex-col">
@@ -13,7 +13,6 @@ const {
13
13
  } = useServers();
14
14
  const { deployMode, showDeployDemoToast } = useDeployMode();
15
15
  const toast = useToast();
16
- const colorMode = useColorMode();
17
16
  const deployBasePath = computed(() => (import.meta.env.BASE_URL || '/').replace(/\/$/, ''));
18
17
 
19
18
  function handleExportError(message: string) {
@@ -97,9 +96,13 @@ onMounted(async () => {
97
96
  let response: Response | undefined;
98
97
 
99
98
  if (deployMode.value) {
100
- response = await fetch(`${deployBasePath.value}/presentations.json`);
101
- if (!response.ok) {
102
- response = await fetch(`${deployBasePath.value}/api/presentations`);
99
+ try {
100
+ response = await fetch(`${deployBasePath.value}/presentations.json`);
101
+ } catch {
102
+ // static file not available
103
+ }
104
+ if (!response?.ok) {
105
+ response = await fetch('/api/presentations');
103
106
  }
104
107
  } else {
105
108
  response = await fetch('/api/presentations');
@@ -187,7 +190,6 @@ function handleCreateCommand() {
187
190
  isCommandPaletteOpen.value = false;
188
191
  if (deployMode.value) {
189
192
  showDeployDemoToast();
190
- return;
191
193
  }
192
194
  isDialogOpen.value = true;
193
195
  }
@@ -196,14 +198,15 @@ function handleImportCommand() {
196
198
  isCommandPaletteOpen.value = false;
197
199
  if (deployMode.value) {
198
200
  showDeployDemoToast();
199
- return;
200
201
  }
201
202
  isImportDialogOpen.value = true;
202
203
  }
203
204
 
204
- function handleToggleThemeCommand() {
205
+ const router = useRouter();
206
+
207
+ function handleSettingsCommand() {
205
208
  isCommandPaletteOpen.value = false;
206
- colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark';
209
+ router.push('/settings');
207
210
  }
208
211
 
209
212
  function handleToggleViewModeCommand() {
@@ -328,10 +331,10 @@ const commandPaletteGroups = computed<CommandPaletteGroup[]>(() => {
328
331
  onSelect: handleImportCommand,
329
332
  },
330
333
  {
331
- label: 'Toggle theme',
332
- suffix: colorMode.value === 'dark' ? 'Switch to light mode' : 'Switch to dark mode',
333
- icon: colorMode.value === 'dark' ? 'i-lucide-sun' : 'i-lucide-moon',
334
- onSelect: handleToggleThemeCommand,
334
+ label: 'Settings',
335
+ suffix: 'Customize appearance',
336
+ icon: 'i-lucide-settings',
337
+ onSelect: handleSettingsCommand,
335
338
  },
336
339
  {
337
340
  label: 'Toggle view',
@@ -399,9 +402,9 @@ const commandOptions = computed(() => {
399
402
  onSelect: handleImportCommand,
400
403
  },
401
404
  {
402
- label: 'Toggle theme',
403
- description: colorMode.value === 'dark' ? 'Switch to light mode' : 'Switch to dark mode',
404
- onSelect: handleToggleThemeCommand,
405
+ label: 'Settings',
406
+ description: 'Customize appearance',
407
+ onSelect: handleSettingsCommand,
405
408
  },
406
409
  {
407
410
  label: 'Toggle view',
@@ -0,0 +1,127 @@
1
+ <script setup lang="ts">
2
+ import type { AccentColor, NeutralColor } from '../composables/useSettings';
3
+ import {
4
+ ACCENT_COLORS,
5
+ DEFAULT_SETTINGS,
6
+ NEUTRAL_COLORS,
7
+ useSettings,
8
+ } from '../composables/useSettings';
9
+
10
+ const router = useRouter();
11
+ const colorMode = useColorMode();
12
+ const { settings } = useSettings();
13
+
14
+ const ACCENT_HEX: Record<string, string> = {
15
+ blue: '#3b82f6',
16
+ green: '#22c55e',
17
+ red: '#ef4444',
18
+ orange: '#f97316',
19
+ teal: '#14b8a6',
20
+ indigo: '#6366f1',
21
+ violet: '#8b5cf6',
22
+ };
23
+
24
+ const NEUTRAL_HEX: Record<string, string> = {
25
+ slate: '#64748b',
26
+ gray: '#6b7280',
27
+ zinc: '#71717a',
28
+ neutral: '#737373',
29
+ stone: '#78716c',
30
+ };
31
+
32
+ const accentColorSwatches = ACCENT_COLORS.map((name: string) => ({
33
+ name,
34
+ value: ACCENT_HEX[name] ?? '#000000',
35
+ }));
36
+
37
+ const neutralColorSwatches = NEUTRAL_COLORS.map((name: string) => ({
38
+ name,
39
+ value: NEUTRAL_HEX[name] ?? '#000000',
40
+ }));
41
+
42
+ const themeOptions = [
43
+ { label: 'System', value: 'system' },
44
+ { label: 'Light', value: 'light' },
45
+ { label: 'Dark', value: 'dark' },
46
+ ];
47
+ </script>
48
+
49
+ <template>
50
+ <div>
51
+ <header class="mb-10">
52
+ <div class="flex items-center justify-between gap-4 mb-4">
53
+ <h1 class="text-2xl sm:text-3xl font-semibold font-mono">$ settings</h1>
54
+ <UButton
55
+ color="neutral"
56
+ variant="ghost"
57
+ icon="i-lucide-arrow-left"
58
+ class="cursor-pointer"
59
+ @click="router.push('/')"
60
+ >
61
+ Back
62
+ </UButton>
63
+ </div>
64
+ <p class="text-sm font-mono" style="color: var(--supaslidev-text-muted)">
65
+ Customize the dashboard appearance.
66
+ </p>
67
+ </header>
68
+
69
+ <div class="max-w-2xl space-y-8">
70
+ <section>
71
+ <h2
72
+ class="text-xs uppercase tracking-wider mb-4 font-mono"
73
+ style="color: var(--supaslidev-text-muted)"
74
+ >
75
+ Appearance
76
+ </h2>
77
+ <div class="settings-card space-y-6">
78
+ <div class="space-y-2">
79
+ <label for="theme-select" class="block text-sm font-medium font-mono"> Theme </label>
80
+ <USelect
81
+ id="theme-select"
82
+ v-model="colorMode.preference"
83
+ :items="themeOptions"
84
+ value-key="value"
85
+ class="max-w-48 cursor-pointer"
86
+ />
87
+ </div>
88
+
89
+ <div class="space-y-3">
90
+ <span class="block text-sm font-medium font-mono"> Accent color </span>
91
+ <SettingsColorPicker
92
+ :colors="accentColorSwatches"
93
+ :model-value="settings.accentColor"
94
+ label="Accent color"
95
+ name="accent-color"
96
+ @update:model-value="
97
+ settings.accentColor = ($event ?? DEFAULT_SETTINGS.accentColor) as AccentColor
98
+ "
99
+ />
100
+ </div>
101
+
102
+ <div class="space-y-3">
103
+ <span class="block text-sm font-medium font-mono"> Background shade </span>
104
+ <SettingsColorPicker
105
+ :colors="neutralColorSwatches"
106
+ :model-value="settings.neutralColor"
107
+ label="Background shade"
108
+ name="background-shade"
109
+ @update:model-value="
110
+ settings.neutralColor = ($event ?? DEFAULT_SETTINGS.neutralColor) as NeutralColor
111
+ "
112
+ />
113
+ </div>
114
+ </div>
115
+ </section>
116
+ </div>
117
+ </div>
118
+ </template>
119
+
120
+ <style scoped>
121
+ .settings-card {
122
+ background: var(--ui-bg-elevated);
123
+ border: 1px solid var(--supaslidev-border);
124
+ border-radius: 0.75rem;
125
+ padding: 1.5rem;
126
+ }
127
+ </style>
package/dist/cli/index.js CHANGED
@@ -9028,7 +9028,7 @@ var require_dist$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
9028
9028
  exports.visitAsync = visit.visitAsync;
9029
9029
  }));
9030
9030
  //#endregion
9031
- //#region ../../node_modules/.pnpm/tsdown@0.21.6_typescript@6.0.2_vue-tsc@3.2.6_typescript@6.0.2_/node_modules/tsdown/esm-shims.js
9031
+ //#region ../../node_modules/.pnpm/tsdown@0.21.6_oxc-resolver@11.19.1_typescript@6.0.2_vue-tsc@3.2.6_typescript@6.0.2_/node_modules/tsdown/esm-shims.js
9032
9032
  var getFilename, getDirname, __dirname, __filename;
9033
9033
  var init_esm_shims = __esmMin((() => {
9034
9034
  getFilename = () => fileURLToPath(import.meta.url);
@@ -186210,7 +186210,7 @@ var require_ts_morph = /* @__PURE__ */ __commonJSMin(((exports) => {
186210
186210
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
186211
186211
  require_dist$2();
186212
186212
  require_ts_morph();
186213
- const CLI_VERSION = "0.3.0";
186213
+ const CLI_VERSION = "0.3.2";
186214
186214
  const PACKAGE_NAME = "@supaslidev/cli";
186215
186215
  const CACHE_DIR = join(tmpdir(), "supaslidev-cli");
186216
186216
  const CACHE_FILE = join(CACHE_DIR, "version-cache.json");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supaslidev",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "CLI toolkit for managing Supaslidev presentations",
5
5
  "keywords": [
6
6
  "slidev",
@@ -64,7 +64,7 @@
64
64
  "typescript": "^6.0.0",
65
65
  "vitest": "^4.0.0",
66
66
  "vue-tsc": "^3.0.0",
67
- "create-supaslidev": "^0.3.0"
67
+ "create-supaslidev": "^0.3.2"
68
68
  },
69
69
  "scripts": {
70
70
  "dev": "nuxt dev",
@@ -4,7 +4,7 @@ import { existsSync, readFileSync } from 'node:fs';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { findProjectRoot } from '../utils.js';
6
6
 
7
- export function findSupaslidevPackageRoot(): string {
7
+ function findSupaslidevPackageRoot(): string {
8
8
  let dir = dirname(fileURLToPath(import.meta.url));
9
9
 
10
10
  while (dir !== dirname(dir)) {