slidev-workspace 0.9.0 → 0.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slidev-workspace",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
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-border text-sidebar-foreground"
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,162 @@
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">
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 class="sw-drawer text-sidebar-foreground">
98
+ <div
99
+ class="flex h-full flex-col px-6 py-8 text-sidebar-foreground"
100
+ >
101
+ <div class="px-1 pb-4">
102
+ <h2 class="text-lg font-semibold tracking-tight">
103
+ {{ sidebar.title }}
104
+ </h2>
105
+ </div>
106
+
107
+ <div class="px-1 pb-6">
108
+ <div class="relative w-full">
109
+ <Input
110
+ class="pl-10 h-10 rounded-xl bg-background/70"
111
+ placeholder="Search slides..."
112
+ v-model="searchTerm"
113
+ />
114
+ <span
115
+ class="absolute start-0 inset-y-0 flex items-center justify-center px-3"
116
+ >
117
+ <Search class="size-5 text-muted-foreground/50" />
118
+ </span>
119
+ </div>
120
+ </div>
121
+
122
+ <div class="px-1 pb-2">
123
+ <h3
124
+ class="text-xs font-semibold uppercase tracking-widest text-muted-foreground"
125
+ >
126
+ Categories
127
+ </h3>
128
+ </div>
129
+
130
+ <div class="px-0.5 space-y-1 flex-1 overflow-auto">
131
+ <button
132
+ v-for="category in categoryOptions"
133
+ :key="category.name"
134
+ type="button"
135
+ @click="selectedCategory = category.name"
136
+ class="w-full flex items-center justify-between rounded-xl px-3 py-2 text-left text-sm transition-colors"
137
+ :class="
138
+ selectedCategory === category.name
139
+ ? 'bg-sidebar-accent text-sidebar-accent-foreground'
140
+ : 'hover:bg-sidebar-accent/70 text-sidebar-foreground'
141
+ "
142
+ >
143
+ <span class="truncate">{{ category.name }}</span>
144
+ <span class="text-xs text-muted-foreground">{{
145
+ category.count
146
+ }}</span>
147
+ </button>
148
+ </div>
149
+
150
+ <div
151
+ class="mt-auto flex items-center justify-between gap-3 pt-6"
152
+ >
153
+ <a
154
+ v-if="sidebar.githubUrl"
155
+ :href="sidebar.githubUrl"
156
+ target="_blank"
157
+ rel="noreferrer"
158
+ 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"
159
+ aria-label="Open GitHub repository"
160
+ >
161
+ <Github class="size-5" />
162
+ </a>
163
+ <div v-else />
164
+ <button
165
+ @click="toggleDarkMode"
166
+ class="p-2 rounded-lg hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-colors cursor-pointer"
167
+ aria-label="Toggle dark mode"
168
+ type="button"
169
+ >
170
+ <Moon v-if="!isDark" class="size-5" />
171
+ <Sun v-else class="size-5" />
172
+ </button>
173
+ </div>
174
+ </div>
175
+ </DrawerContent>
176
+ </Drawer>
177
+
178
+ <div
179
+ class="flex flex-col gap-4 md:flex-row md:items-start md:justify-between"
180
+ >
181
+ <div>
182
+ <h1 class="text-3xl font-semibold">{{ hero.title }}</h1>
183
+ <p class="mt-2 text-sm text-muted-foreground">
184
+ {{ hero.description }}
185
+ </p>
186
+ </div>
187
+ <div class="self-start" />
188
+ </div>
189
+
190
+ <div
191
+ class="flex flex-col gap-3 md:mt-6 md:flex-row md:items-center md:justify-between"
192
+ >
193
+ <p class="text-sm text-muted-foreground">
194
+ Found {{ filteredSlides.length }} of {{ slidesCount }} slides
195
+ <template v-if="searchTerm">
196
+ <span>
197
+ containing "
198
+ <span class="font-medium">{{ searchTerm }}</span>
199
+ "
200
+ </span>
201
+ </template>
96
202
  </p>
203
+ <div />
97
204
  </div>
98
- <div class="self-start" />
99
205
  </div>
206
+ </div>
207
+ </header>
100
208
 
209
+ <section class="sw-main">
210
+ <div class="max-w-[900px]">
101
211
  <div
102
- class="mt-6 flex flex-col gap-2 md:flex-row md:items-center md:justify-between"
212
+ class="grid grid-cols-1 gap-6 px-6 pb-12 sm:grid-cols-2 xl:grid-cols-3 lg:px-12"
103
213
  >
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 />
214
+ <SlideCard
215
+ v-for="slide in filteredSlides"
216
+ :key="slide.url"
217
+ :title="slide.title"
218
+ :image="slide.image"
219
+ :description="slide.description"
220
+ :url="slide.url"
221
+ :author="slide.author"
222
+ :date="slide.date"
223
+ />
115
224
  </div>
116
225
  </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
226
  </section>
133
- <div class="hidden bg-[#F5F5F5] dark:bg-[#121212] lg:block" />
134
227
  </div>
135
228
  </div>
136
229
  </template>
137
230
 
138
231
  <script setup lang="ts">
139
232
  import { ref, computed, watch } from "vue";
140
- import { Search, Moon, Sun, Github } from "lucide-vue-next";
233
+ import { Search, Moon, Sun, Github, PanelLeft } from "lucide-vue-next";
141
234
 
142
235
  import { useSlides } from "../composables/useSlides";
143
236
  import { useConfig } from "../composables/useConfig";
144
237
  import { useDarkMode } from "../composables/useDarkMode";
145
238
  import { Input } from "../components/ui/input";
239
+ import { Drawer, DrawerContent, DrawerTrigger } from "../components/ui/drawer";
146
240
  import SlideCard from "./SlideCard.vue";
147
241
 
148
242
  const searchTerm = ref("");
@@ -213,3 +307,93 @@ const filteredSlides = computed(() => {
213
307
  );
214
308
  });
215
309
  </script>
310
+
311
+ <style scoped>
312
+ .sw-layout {
313
+ display: grid;
314
+ width: 100%;
315
+ max-width: var(--sw-layout-width, 97rem);
316
+ margin: 0 auto;
317
+ grid-template-columns: var(--sw-sidebar-width, 270px) minmax(0, 1fr);
318
+ grid-template-rows: auto auto 1fr;
319
+ grid-template-areas:
320
+ "sidebar header"
321
+ "sidebar main"
322
+ "sidebar main";
323
+ }
324
+
325
+ .sw-sidebar {
326
+ grid-area: sidebar;
327
+ position: relative;
328
+ display: flex;
329
+ justify-content: flex-end;
330
+ background: var(--sw-sidebar-bg);
331
+ z-index: 0;
332
+ }
333
+
334
+ .sw-sidebar::before {
335
+ content: "";
336
+ position: absolute;
337
+ inset: 0;
338
+ left: -100vw;
339
+ background: var(--sw-sidebar-bg);
340
+ z-index: -1;
341
+ }
342
+
343
+ .sw-header {
344
+ grid-area: header;
345
+ display: flex;
346
+ justify-content: flex-start;
347
+ }
348
+
349
+ .sw-main {
350
+ grid-area: main;
351
+ display: flex;
352
+ justify-content: flex-start;
353
+ }
354
+
355
+ .sw-drawer-trigger {
356
+ display: inline-flex;
357
+ }
358
+
359
+ .sw-page {
360
+ background: var(--sw-main-bg);
361
+ }
362
+
363
+ :global(:root) {
364
+ --sw-sidebar-bg: #f1f1f1;
365
+ }
366
+
367
+ :global(.dark) {
368
+ --sw-sidebar-bg: #191919;
369
+ }
370
+
371
+ :global(.sw-drawer) {
372
+ background: var(--sw-sidebar-bg);
373
+ }
374
+
375
+ @media (max-width: 1024px) {
376
+ .sw-layout {
377
+ grid-template-columns: 1fr;
378
+ grid-template-rows: auto auto 1fr;
379
+ grid-template-areas:
380
+ "sidebar"
381
+ "header"
382
+ "main";
383
+ }
384
+
385
+ .sw-sidebar {
386
+ display: none;
387
+ }
388
+
389
+ .sw-drawer-trigger {
390
+ display: inline-flex;
391
+ }
392
+ }
393
+
394
+ @media (min-width: 1024px) {
395
+ .sw-drawer-trigger {
396
+ display: none;
397
+ }
398
+ }
399
+ </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";