slidev-workspace 0.9.0 → 0.9.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slidev-workspace",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "A workspace tool for managing multiple Slidev presentations with API-based content management",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,14 +37,16 @@
37
37
  "dependencies": {
38
38
  "@tailwindcss/vite": "^4.1.11",
39
39
  "@vitejs/plugin-vue": "^6.0.0",
40
- "@vueuse/core": "^13.5.0",
40
+ "@vueuse/core": "^13.8.0",
41
41
  "class-variance-authority": "^0.7.1",
42
42
  "clsx": "^2.1.1",
43
43
  "commander": "^14.0.0",
44
44
  "lucide-vue-next": "^0.525.0",
45
+ "reka-ui": "^2.8.0",
45
46
  "tailwind-merge": "^3.3.1",
46
47
  "tw-animate-css": "^1.3.5",
47
48
  "unhead": "^2.0.19",
49
+ "vaul-vue": "^0.4.1",
48
50
  "vite": "npm:rolldown-vite@latest",
49
51
  "vue": "^3.5.17",
50
52
  "yaml": "^2.8.0"
@@ -1,14 +1,11 @@
1
1
  <template>
2
- <div class="min-h-screen transition-colors">
3
- <div
4
- class="min-h-screen lg:grid lg:grid-cols-[minmax(0,1fr)_280px_minmax(0,3fr)_minmax(0,1fr)]"
5
- >
6
- <div class="hidden bg-[#F1F1F1] dark:bg-[#191919] lg:block" />
2
+ <div class="min-h-screen transition-colors sw-page">
3
+ <div class="min-h-screen sw-layout">
7
4
  <aside
8
- class="w-full border-r border-[#E8E8E8] bg-[#F1F1F1] text-sidebar-foreground dark:border-[#212121] dark:bg-[#191919]"
5
+ class="sw-sidebar w-full border-r border-[#E8E8E8] bg-[#F1F1F1] text-sidebar-foreground dark:border-[#212121] dark:bg-[#191919]"
9
6
  >
10
7
  <div
11
- class="sticky top-0 flex h-screen flex-col px-6 py-10 text-sidebar-foreground"
8
+ class="sticky top-0 flex h-screen flex-col px-6 py-10 text-sidebar-foreground w-[270px]"
12
9
  >
13
10
  <div class="px-1 pb-4">
14
11
  <h2 class="text-lg font-semibold tracking-tight">
@@ -84,65 +81,164 @@
84
81
  </div>
85
82
  </aside>
86
83
 
87
- <section class="bg-[#F5F5F5] dark:bg-[#121212]">
88
- <div class="px-6 py-10 lg:px-12">
89
- <div
90
- class="flex flex-col gap-4 md:flex-row md:items-start md:justify-between"
91
- >
92
- <div>
93
- <h1 class="text-3xl font-semibold">{{ hero.title }}</h1>
94
- <p class="mt-2 text-sm text-muted-foreground">
95
- {{ hero.description }}
84
+ <header class="sw-header bg-[#F5F5F5] dark:bg-[#121212]">
85
+ <div class="max-w-[900px]">
86
+ <div class="px-6 py-8 lg:px-12 lg:py-10">
87
+ <Drawer direction="left">
88
+ <DrawerTrigger as-child>
89
+ <button
90
+ type="button"
91
+ class="sw-drawer-trigger mb-6 inline-flex items-center justify-center rounded-xl border border-border bg-background p-2 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
92
+ aria-label="Open sidebar"
93
+ >
94
+ <PanelLeft class="size-5" />
95
+ </button>
96
+ </DrawerTrigger>
97
+ <DrawerContent
98
+ class="bg-[#F1F1F1] text-sidebar-foreground dark:bg-[#191919]"
99
+ >
100
+ <div
101
+ class="flex h-full flex-col px-6 py-8 text-sidebar-foreground"
102
+ >
103
+ <div class="px-1 pb-4">
104
+ <h2 class="text-lg font-semibold tracking-tight">
105
+ {{ sidebar.title }}
106
+ </h2>
107
+ </div>
108
+
109
+ <div class="px-1 pb-6">
110
+ <div class="relative w-full">
111
+ <Input
112
+ class="pl-10 h-10 rounded-xl bg-background/70"
113
+ placeholder="Search slides..."
114
+ v-model="searchTerm"
115
+ />
116
+ <span
117
+ class="absolute start-0 inset-y-0 flex items-center justify-center px-3"
118
+ >
119
+ <Search class="size-5 text-muted-foreground/50" />
120
+ </span>
121
+ </div>
122
+ </div>
123
+
124
+ <div class="px-1 pb-2">
125
+ <h3
126
+ class="text-xs font-semibold uppercase tracking-widest text-muted-foreground"
127
+ >
128
+ Categories
129
+ </h3>
130
+ </div>
131
+
132
+ <div class="px-0.5 space-y-1 flex-1 overflow-auto">
133
+ <button
134
+ v-for="category in categoryOptions"
135
+ :key="category.name"
136
+ type="button"
137
+ @click="selectedCategory = category.name"
138
+ class="w-full flex items-center justify-between rounded-xl px-3 py-2 text-left text-sm transition-colors"
139
+ :class="
140
+ selectedCategory === category.name
141
+ ? 'bg-sidebar-accent text-sidebar-accent-foreground'
142
+ : 'hover:bg-sidebar-accent/70 text-sidebar-foreground'
143
+ "
144
+ >
145
+ <span class="truncate">{{ category.name }}</span>
146
+ <span class="text-xs text-muted-foreground">{{
147
+ category.count
148
+ }}</span>
149
+ </button>
150
+ </div>
151
+
152
+ <div
153
+ class="mt-auto flex items-center justify-between gap-3 pt-6"
154
+ >
155
+ <a
156
+ v-if="sidebar.githubUrl"
157
+ :href="sidebar.githubUrl"
158
+ target="_blank"
159
+ rel="noreferrer"
160
+ class="inline-flex items-center justify-center rounded-lg p-2 text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-colors cursor-pointer"
161
+ aria-label="Open GitHub repository"
162
+ >
163
+ <Github class="size-5" />
164
+ </a>
165
+ <div v-else />
166
+ <button
167
+ @click="toggleDarkMode"
168
+ class="p-2 rounded-lg hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-colors cursor-pointer"
169
+ aria-label="Toggle dark mode"
170
+ type="button"
171
+ >
172
+ <Moon v-if="!isDark" class="size-5" />
173
+ <Sun v-else class="size-5" />
174
+ </button>
175
+ </div>
176
+ </div>
177
+ </DrawerContent>
178
+ </Drawer>
179
+
180
+ <div
181
+ class="flex flex-col gap-4 md:flex-row md:items-start md:justify-between"
182
+ >
183
+ <div>
184
+ <h1 class="text-3xl font-semibold">{{ hero.title }}</h1>
185
+ <p class="mt-2 text-sm text-muted-foreground">
186
+ {{ hero.description }}
187
+ </p>
188
+ </div>
189
+ <div class="self-start" />
190
+ </div>
191
+
192
+ <div
193
+ class="flex flex-col gap-3 md:mt-6 md:flex-row md:items-center md:justify-between"
194
+ >
195
+ <p class="text-sm text-muted-foreground">
196
+ Found {{ filteredSlides.length }} of {{ slidesCount }} slides
197
+ <template v-if="searchTerm">
198
+ <span>
199
+ containing "
200
+ <span class="font-medium">{{ searchTerm }}</span>
201
+ "
202
+ </span>
203
+ </template>
96
204
  </p>
205
+ <div />
97
206
  </div>
98
- <div class="self-start" />
99
207
  </div>
208
+ </div>
209
+ </header>
100
210
 
211
+ <section class="sw-main bg-[#F5F5F5] dark:bg-[#121212]">
212
+ <div class="max-w-[900px]">
101
213
  <div
102
- class="mt-6 flex flex-col gap-2 md:flex-row md:items-center md:justify-between"
214
+ class="grid grid-cols-1 gap-6 px-6 pb-12 sm:grid-cols-2 xl:grid-cols-3 lg:px-12"
103
215
  >
104
- <p class="text-sm text-muted-foreground">
105
- Found {{ filteredSlides.length }} of {{ slidesCount }} slides
106
- <template v-if="searchTerm">
107
- <span>
108
- containing "
109
- <span class="font-medium">{{ searchTerm }}</span>
110
- "
111
- </span>
112
- </template>
113
- </p>
114
- <div />
216
+ <SlideCard
217
+ v-for="slide in filteredSlides"
218
+ :key="slide.url"
219
+ :title="slide.title"
220
+ :image="slide.image"
221
+ :description="slide.description"
222
+ :url="slide.url"
223
+ :author="slide.author"
224
+ :date="slide.date"
225
+ />
115
226
  </div>
116
227
  </div>
117
-
118
- <div
119
- class="grid grid-cols-1 gap-6 px-6 pb-12 sm:grid-cols-2 xl:grid-cols-3 lg:px-12"
120
- >
121
- <SlideCard
122
- v-for="slide in filteredSlides"
123
- :key="slide.url"
124
- :title="slide.title"
125
- :image="slide.image"
126
- :description="slide.description"
127
- :url="slide.url"
128
- :author="slide.author"
129
- :date="slide.date"
130
- />
131
- </div>
132
228
  </section>
133
- <div class="hidden bg-[#F5F5F5] dark:bg-[#121212] lg:block" />
134
229
  </div>
135
230
  </div>
136
231
  </template>
137
232
 
138
233
  <script setup lang="ts">
139
234
  import { ref, computed, watch } from "vue";
140
- import { Search, Moon, Sun, Github } from "lucide-vue-next";
235
+ import { Search, Moon, Sun, Github, PanelLeft } from "lucide-vue-next";
141
236
 
142
237
  import { useSlides } from "../composables/useSlides";
143
238
  import { useConfig } from "../composables/useConfig";
144
239
  import { useDarkMode } from "../composables/useDarkMode";
145
240
  import { Input } from "../components/ui/input";
241
+ import { Drawer, DrawerContent, DrawerTrigger } from "../components/ui/drawer";
146
242
  import SlideCard from "./SlideCard.vue";
147
243
 
148
244
  const searchTerm = ref("");
@@ -213,3 +309,88 @@ const filteredSlides = computed(() => {
213
309
  );
214
310
  });
215
311
  </script>
312
+
313
+ <style scoped>
314
+ .sw-layout {
315
+ display: grid;
316
+ width: 100%;
317
+ max-width: var(--sw-layout-width, 97rem);
318
+ margin: 0 auto;
319
+ grid-template-columns: var(--sw-sidebar-width, 270px) minmax(0, 1fr);
320
+ grid-template-rows: auto auto 1fr;
321
+ grid-template-areas:
322
+ "sidebar header"
323
+ "sidebar main"
324
+ "sidebar main";
325
+ }
326
+
327
+ .sw-sidebar {
328
+ grid-area: sidebar;
329
+ position: relative;
330
+ display: flex;
331
+ justify-content: flex-end;
332
+ background: var(--sw-sidebar-bg);
333
+ z-index: 0;
334
+ }
335
+
336
+ .sw-sidebar::before {
337
+ content: "";
338
+ position: absolute;
339
+ inset: 0;
340
+ left: -100vw;
341
+ background: var(--sw-sidebar-bg);
342
+ z-index: -1;
343
+ }
344
+
345
+ .sw-header {
346
+ grid-area: header;
347
+ display: flex;
348
+ justify-content: flex-start;
349
+ }
350
+
351
+ .sw-main {
352
+ grid-area: main;
353
+ display: flex;
354
+ justify-content: flex-start;
355
+ }
356
+
357
+ .sw-drawer-trigger {
358
+ display: inline-flex;
359
+ }
360
+
361
+ .sw-page {
362
+ --sw-sidebar-bg: #f1f1f1;
363
+ --sw-main-bg: #f5f5f5;
364
+ background: var(--sw-main-bg);
365
+ }
366
+
367
+ :global(.dark) .sw-page {
368
+ --sw-sidebar-bg: #191919;
369
+ --sw-main-bg: #121212;
370
+ }
371
+
372
+ @media (max-width: 1024px) {
373
+ .sw-layout {
374
+ grid-template-columns: 1fr;
375
+ grid-template-rows: auto auto 1fr;
376
+ grid-template-areas:
377
+ "sidebar"
378
+ "header"
379
+ "main";
380
+ }
381
+
382
+ .sw-sidebar {
383
+ display: none;
384
+ }
385
+
386
+ .sw-drawer-trigger {
387
+ display: inline-flex;
388
+ }
389
+ }
390
+
391
+ @media (min-width: 1024px) {
392
+ .sw-drawer-trigger {
393
+ display: none;
394
+ }
395
+ }
396
+ </style>
@@ -0,0 +1,22 @@
1
+ <script lang="ts" setup>
2
+ import type { DrawerRootEmits, DrawerRootProps } from "vaul-vue";
3
+ import { useForwardPropsEmits } from "reka-ui";
4
+ import { DrawerRoot } from "vaul-vue";
5
+
6
+ const props = withDefaults(defineProps<DrawerRootProps>(), {
7
+ shouldScaleBackground: true,
8
+ });
9
+
10
+ const emits = defineEmits<DrawerRootEmits>();
11
+
12
+ const forwarded = useForwardPropsEmits<DrawerRootProps, keyof DrawerRootEmits>(
13
+ props,
14
+ emits,
15
+ );
16
+ </script>
17
+
18
+ <template>
19
+ <DrawerRoot v-slot="slotProps" data-slot="drawer" v-bind="forwarded">
20
+ <slot v-bind="slotProps" />
21
+ </DrawerRoot>
22
+ </template>
@@ -0,0 +1,12 @@
1
+ <script lang="ts" setup>
2
+ import type { DrawerCloseProps } from "vaul-vue";
3
+ import { DrawerClose } from "vaul-vue";
4
+
5
+ const props = defineProps<DrawerCloseProps>();
6
+ </script>
7
+
8
+ <template>
9
+ <DrawerClose data-slot="drawer-close" v-bind="props">
10
+ <slot />
11
+ </DrawerClose>
12
+ </template>
@@ -0,0 +1,44 @@
1
+ <script lang="ts" setup>
2
+ import type { DialogContentEmits, DialogContentProps } from "reka-ui";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { useForwardPropsEmits } from "reka-ui";
5
+ import { DrawerContent, DrawerPortal } from "vaul-vue";
6
+ import { cn } from "@/lib/utils";
7
+ import DrawerOverlay from "./DrawerOverlay.vue";
8
+
9
+ defineOptions({
10
+ inheritAttrs: false,
11
+ });
12
+
13
+ const props = defineProps<
14
+ DialogContentProps & { class?: HTMLAttributes["class"] }
15
+ >();
16
+ const emits = defineEmits<DialogContentEmits>();
17
+
18
+ const forwarded = useForwardPropsEmits(props, emits);
19
+ </script>
20
+
21
+ <template>
22
+ <DrawerPortal>
23
+ <DrawerOverlay />
24
+ <DrawerContent
25
+ data-slot="drawer-content"
26
+ v-bind="{ ...$attrs, ...forwarded }"
27
+ :class="
28
+ cn(
29
+ 'group/drawer-content bg-background fixed z-50 flex h-auto flex-col',
30
+ 'data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg',
31
+ 'data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg',
32
+ 'data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:sm:max-w-sm',
33
+ 'data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:sm:max-w-sm',
34
+ props.class,
35
+ )
36
+ "
37
+ >
38
+ <div
39
+ class="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block"
40
+ />
41
+ <slot />
42
+ </DrawerContent>
43
+ </DrawerPortal>
44
+ </template>
@@ -0,0 +1,23 @@
1
+ <script lang="ts" setup>
2
+ import type { DrawerDescriptionProps } from "vaul-vue";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { DrawerDescription } from "vaul-vue";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const props = defineProps<
9
+ DrawerDescriptionProps & { class?: HTMLAttributes["class"] }
10
+ >();
11
+
12
+ const delegatedProps = reactiveOmit(props, "class");
13
+ </script>
14
+
15
+ <template>
16
+ <DrawerDescription
17
+ data-slot="drawer-description"
18
+ v-bind="delegatedProps"
19
+ :class="cn('text-muted-foreground text-sm', props.class)"
20
+ >
21
+ <slot />
22
+ </DrawerDescription>
23
+ </template>
@@ -0,0 +1,17 @@
1
+ <script lang="ts" setup>
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <div
12
+ data-slot="drawer-footer"
13
+ :class="cn('mt-auto flex flex-col gap-2 p-4', props.class)"
14
+ >
15
+ <slot />
16
+ </div>
17
+ </template>
@@ -0,0 +1,17 @@
1
+ <script lang="ts" setup>
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <div
12
+ data-slot="drawer-header"
13
+ :class="cn('flex flex-col gap-1.5 p-4', props.class)"
14
+ >
15
+ <slot />
16
+ </div>
17
+ </template>
@@ -0,0 +1,26 @@
1
+ <script lang="ts" setup>
2
+ import type { DialogOverlayProps } from "reka-ui";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { DrawerOverlay } from "vaul-vue";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const props = defineProps<
9
+ DialogOverlayProps & { class?: HTMLAttributes["class"] }
10
+ >();
11
+
12
+ const delegatedProps = reactiveOmit(props, "class");
13
+ </script>
14
+
15
+ <template>
16
+ <DrawerOverlay
17
+ data-slot="drawer-overlay"
18
+ v-bind="delegatedProps"
19
+ :class="
20
+ cn(
21
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
22
+ props.class,
23
+ )
24
+ "
25
+ />
26
+ </template>
@@ -0,0 +1,23 @@
1
+ <script lang="ts" setup>
2
+ import type { DrawerTitleProps } from "vaul-vue";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { DrawerTitle } from "vaul-vue";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const props = defineProps<
9
+ DrawerTitleProps & { class?: HTMLAttributes["class"] }
10
+ >();
11
+
12
+ const delegatedProps = reactiveOmit(props, "class");
13
+ </script>
14
+
15
+ <template>
16
+ <DrawerTitle
17
+ data-slot="drawer-title"
18
+ v-bind="delegatedProps"
19
+ :class="cn('text-foreground font-semibold', props.class)"
20
+ >
21
+ <slot />
22
+ </DrawerTitle>
23
+ </template>
@@ -0,0 +1,12 @@
1
+ <script lang="ts" setup>
2
+ import type { DrawerTriggerProps } from "vaul-vue";
3
+ import { DrawerTrigger } from "vaul-vue";
4
+
5
+ const props = defineProps<DrawerTriggerProps>();
6
+ </script>
7
+
8
+ <template>
9
+ <DrawerTrigger data-slot="drawer-trigger" v-bind="props">
10
+ <slot />
11
+ </DrawerTrigger>
12
+ </template>
@@ -0,0 +1,9 @@
1
+ export { default as Drawer } from "./Drawer.vue";
2
+ export { default as DrawerClose } from "./DrawerClose.vue";
3
+ export { default as DrawerContent } from "./DrawerContent.vue";
4
+ export { default as DrawerDescription } from "./DrawerDescription.vue";
5
+ export { default as DrawerFooter } from "./DrawerFooter.vue";
6
+ export { default as DrawerHeader } from "./DrawerHeader.vue";
7
+ export { default as DrawerOverlay } from "./DrawerOverlay.vue";
8
+ export { default as DrawerTitle } from "./DrawerTitle.vue";
9
+ export { default as DrawerTrigger } from "./DrawerTrigger.vue";