@owdproject/nuxt-theme-ui 4.0.0-alpha.3 → 4.0.0-alpha.4
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/dist/module.json +1 -1
- package/dist/runtime/apps/calculator/app.config.js +1 -1
- package/dist/runtime/apps/calculator/components/Window/WindowCalculator.vue +30 -21
- package/dist/runtime/apps/notepad/components/Window/WindowNotepad.vue +27 -23
- package/dist/runtime/components/Desktop.client.vue +7 -2
- package/dist/runtime/components/NuxtLauncherOverlay.vue +91 -50
- package/dist/runtime/components/NuxtTopBar.vue +1 -1
- package/dist/runtime/components/Window/DesktopWindowContent.vue +0 -4
- package/package.json +1 -1
- package/src/runtime/apps/calculator/app.config.ts +1 -1
- package/src/runtime/apps/calculator/components/Window/WindowCalculator.vue +30 -21
- package/src/runtime/apps/notepad/components/Window/WindowNotepad.vue +27 -23
- package/src/runtime/components/Desktop.client.vue +83 -2
- package/src/runtime/components/NuxtLauncherOverlay.vue +98 -52
- package/src/runtime/components/NuxtTopBar.vue +1 -1
- package/src/runtime/components/Window/DesktopWindowContent.vue +0 -12
package/dist/module.json
CHANGED
|
@@ -57,29 +57,38 @@ function calculate() {
|
|
|
57
57
|
</script>
|
|
58
58
|
|
|
59
59
|
<template>
|
|
60
|
-
<DesktopWindow :window="window"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<div
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
<DesktopWindow :window="window" :content="{ padded: true }">
|
|
61
|
+
<div class="flex h-full min-h-0 flex-col gap-3">
|
|
62
|
+
<!-- Calculator screen -->
|
|
63
|
+
<div
|
|
64
|
+
class="flex-shrink-0 rounded-lg border border-gray-200 bg-white p-3 text-right shadow-inner dark:border-gray-800 dark:bg-gray-900"
|
|
65
|
+
>
|
|
66
|
+
<div class="mb-0.5 h-4 truncate text-xs text-gray-400 dark:text-gray-500">
|
|
67
|
+
{{ equation }}
|
|
68
|
+
</div>
|
|
69
|
+
<div
|
|
70
|
+
class="truncate font-mono text-2xl font-bold text-gray-800 dark:text-gray-100"
|
|
71
|
+
>
|
|
72
|
+
{{ display }}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
66
75
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
<!-- Buttons grid -->
|
|
77
|
+
<div class="grid grid-cols-4 grid-rows-5 gap-2 flex-1 min-h-0">
|
|
78
|
+
<template v-for="(row, rIdx) in buttons" :key="rIdx">
|
|
79
|
+
<template v-for="(btn, bIdx) in row" :key="bIdx">
|
|
80
|
+
<UButton
|
|
81
|
+
v-if="btn !== ''"
|
|
82
|
+
:label="btn"
|
|
83
|
+
class="flex h-full w-full items-center justify-center text-sm font-semibold"
|
|
84
|
+
:color="btn === '=' ? 'primary' : btn === 'C' ? 'red' : ['/', '*', '-', '+'].includes(btn) ? 'orange' : 'gray'"
|
|
85
|
+
variant="solid"
|
|
86
|
+
@click="handleClick(btn)"
|
|
87
|
+
/>
|
|
88
|
+
<div v-else />
|
|
89
|
+
</template>
|
|
81
90
|
</template>
|
|
82
|
-
</
|
|
91
|
+
</div>
|
|
83
92
|
</div>
|
|
84
93
|
</DesktopWindow>
|
|
85
94
|
</template>
|
|
@@ -10,32 +10,36 @@ function clear() {
|
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
12
|
<template>
|
|
13
|
-
<DesktopWindow :window="window"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
13
|
+
<DesktopWindow :window="window">
|
|
14
|
+
<div class="flex h-full min-h-0 flex-col bg-white dark:bg-gray-950">
|
|
15
|
+
<!-- Toolbar -->
|
|
16
|
+
<div
|
|
17
|
+
class="flex flex-shrink-0 items-center gap-2 border-b border-gray-200 bg-gray-50 p-2 dark:border-gray-800 dark:bg-gray-900"
|
|
18
|
+
>
|
|
19
|
+
<UButton
|
|
20
|
+
icon="i-heroicons-document"
|
|
21
|
+
label="New"
|
|
22
|
+
color="gray"
|
|
23
|
+
variant="ghost"
|
|
24
|
+
size="xs"
|
|
25
|
+
class="gap-2"
|
|
26
|
+
@click="clear"
|
|
27
|
+
/>
|
|
28
|
+
<UButton
|
|
29
|
+
icon="i-heroicons-trash"
|
|
30
|
+
label="Clear"
|
|
31
|
+
color="red"
|
|
32
|
+
variant="ghost"
|
|
33
|
+
size="xs"
|
|
34
|
+
class="gap-2"
|
|
35
|
+
@click="clear"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
<div class="flex-1 min-h-0">
|
|
39
|
+
<!-- Text area -->
|
|
36
40
|
<textarea
|
|
37
41
|
v-model="text"
|
|
38
|
-
class="w-full
|
|
42
|
+
class="w-full flex-1 min-h-0 resize-none border-0 bg-transparent p-4 font-mono text-sm text-gray-800 outline-none dark:text-gray-200"
|
|
39
43
|
placeholder="Type here..."
|
|
40
44
|
/>
|
|
41
45
|
</div>
|
|
@@ -26,7 +26,12 @@ onUnmounted(() => {
|
|
|
26
26
|
>
|
|
27
27
|
<NuxtTopBar />
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
<!--
|
|
30
|
+
`isolate` keeps every window's z-index inside this stacking context, so the
|
|
31
|
+
system bar and the launcher (siblings) always stay above open windows
|
|
32
|
+
instead of losing to the window manager's 100+ z-index values.
|
|
33
|
+
-->
|
|
34
|
+
<div ref="shellStageRef" class="flex-1 min-h-0 relative flex flex-col isolate">
|
|
30
35
|
<Background />
|
|
31
36
|
|
|
32
37
|
<DesktopContent>
|
|
@@ -43,5 +48,5 @@ onUnmounted(() => {
|
|
|
43
48
|
</template>
|
|
44
49
|
|
|
45
50
|
<style>
|
|
46
|
-
.owd-desktop--nuxt{--owd-system-bar-height:
|
|
51
|
+
.owd-desktop--nuxt{--owd-system-bar-height:60px}.owd-desktop--nuxt,.owd-has-nuxt-shell{overscroll-behavior:none}.owd-desktop--nuxt .desktop-window__content{display:flex;flex:1 1 auto;flex-direction:column;min-height:0}.owd-desktop--nuxt .desktop-window__content--centered{align-items:center;justify-content:center;text-align:center}.owd-desktop--nuxt .desktop-window__content--centered>*{max-width:100%}.owd-desktop--nuxt .desktop-window__content--padded{padding:12px}.owd-desktop--nuxt .desktop-window__content,.owd-desktop--nuxt .owd-desktop__scroll{scrollbar-color:rgba(0,0,0,.18) transparent;scrollbar-width:thin}.owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar,.owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar{height:10px;width:10px}.owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar-track,.owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar-track{background:transparent}.owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar-thumb,.owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar-thumb{background-clip:padding-box;background-color:rgba(0,0,0,.18);border:3px solid transparent;border-radius:9999px}.owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar-thumb:hover,.owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar-thumb:hover{background-color:rgba(0,0,0,.3)}.dark .owd-desktop--nuxt .desktop-window__content,.dark .owd-desktop--nuxt .owd-desktop__scroll,.owd-desktop--nuxt.dark .desktop-window__content,.owd-desktop--nuxt.dark .owd-desktop__scroll{scrollbar-color:hsla(0,0%,100%,.22) transparent}.dark .owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar-thumb,.dark .owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar-thumb{background-color:hsla(0,0%,100%,.22)}
|
|
47
52
|
</style>
|
|
@@ -1,69 +1,110 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { ref, watch, onMounted, onUnmounted } from "vue";
|
|
2
3
|
import { useNuxtStartMenu } from "../composables/useNuxtStartMenu";
|
|
3
|
-
import { watch, ref } from "vue";
|
|
4
4
|
const { open, searchQuery, filteredEntries, close, launchEntry } = useNuxtStartMenu();
|
|
5
5
|
const searchInput = ref(null);
|
|
6
6
|
watch(open, (isOpen) => {
|
|
7
7
|
if (isOpen) {
|
|
8
|
-
setTimeout(() =>
|
|
9
|
-
searchInput.value?.$el?.querySelector("input")?.focus();
|
|
10
|
-
}, 100);
|
|
8
|
+
setTimeout(() => searchInput.value?.focus(), 50);
|
|
11
9
|
}
|
|
12
10
|
});
|
|
11
|
+
function onKeydown(event) {
|
|
12
|
+
if (event.key === "Escape" && open.value) {
|
|
13
|
+
close();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
onMounted(() => window.addEventListener("keydown", onKeydown));
|
|
17
|
+
onUnmounted(() => window.removeEventListener("keydown", onKeydown));
|
|
13
18
|
</script>
|
|
14
19
|
|
|
15
20
|
<template>
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
<Transition
|
|
22
|
+
enter-active-class="transition duration-150 ease-out"
|
|
23
|
+
enter-from-class="opacity-0 -translate-y-1"
|
|
24
|
+
leave-active-class="transition duration-100 ease-in"
|
|
25
|
+
leave-to-class="opacity-0 -translate-y-1"
|
|
26
|
+
>
|
|
27
|
+
<div
|
|
28
|
+
v-if="open"
|
|
29
|
+
class="fixed inset-0 z-40 flex items-start justify-center pt-24"
|
|
30
|
+
@click="close"
|
|
21
31
|
>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
ref="searchInput"
|
|
25
|
-
v-model="searchQuery"
|
|
26
|
-
icon="i-heroicons-magnifying-glass"
|
|
27
|
-
size="lg"
|
|
28
|
-
color="gray"
|
|
29
|
-
variant="none"
|
|
30
|
-
placeholder="Search apps..."
|
|
31
|
-
class="w-full text-lg"
|
|
32
|
-
:ui="{ wrapper: 'shadow-none', icon: { base: 'text-gray-400 dark:text-gray-500' } }"
|
|
33
|
-
/>
|
|
34
|
-
</div>
|
|
32
|
+
<!-- Overlay scrim -->
|
|
33
|
+
<div class="absolute inset-0 bg-gray-950/10 dark:bg-black/40" />
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
<!-- Command palette -->
|
|
36
|
+
<div
|
|
37
|
+
class="relative w-full max-w-xl mx-4 flex flex-col bg-white/95 dark:bg-gray-900/95 backdrop-blur-xl border border-gray-200 dark:border-gray-800 rounded-xl shadow-2xl overflow-hidden"
|
|
38
|
+
@click.stop
|
|
39
|
+
>
|
|
40
|
+
<!-- Search -->
|
|
41
|
+
<div class="p-3 border-b border-gray-200 dark:border-gray-800">
|
|
42
|
+
<div
|
|
43
|
+
class="flex items-center gap-2.5 h-11 px-3.5 rounded-lg bg-gray-100/80 dark:bg-gray-800/60 ring-1 ring-inset ring-transparent focus-within:ring-primary-500/60 focus-within:bg-white dark:focus-within:bg-gray-900 transition"
|
|
44
|
+
>
|
|
45
|
+
<UIcon
|
|
46
|
+
name="i-heroicons-magnifying-glass"
|
|
47
|
+
class="w-4 h-4 flex-shrink-0 text-gray-400 dark:text-gray-500"
|
|
48
|
+
/>
|
|
49
|
+
<input
|
|
50
|
+
ref="searchInput"
|
|
51
|
+
v-model="searchQuery"
|
|
52
|
+
type="text"
|
|
53
|
+
placeholder="Search apps..."
|
|
54
|
+
autocomplete="off"
|
|
55
|
+
spellcheck="false"
|
|
56
|
+
class="flex-1 min-w-0 h-full bg-transparent border-0 outline-none focus:outline-none focus:ring-0 text-sm text-gray-900 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
39
59
|
</div>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class="
|
|
46
|
-
@click="launchEntry(entry)"
|
|
60
|
+
|
|
61
|
+
<!-- Results -->
|
|
62
|
+
<div class="flex-1 overflow-y-auto owd-desktop__scroll max-h-[50vh] p-2">
|
|
63
|
+
<div
|
|
64
|
+
v-if="filteredEntries.length === 0"
|
|
65
|
+
class="text-center py-12 text-gray-500 dark:text-gray-400 text-sm"
|
|
47
66
|
>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
No apps found.
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="grid grid-cols-1 gap-0.5">
|
|
71
|
+
<button
|
|
72
|
+
v-for="entry in filteredEntries"
|
|
73
|
+
:key="entry.application.id"
|
|
74
|
+
class="group flex items-center gap-3 p-2.5 rounded-lg hover:bg-gray-100/70 dark:hover:bg-gray-800/60 focus:bg-gray-100/70 dark:focus:bg-gray-800/60 focus:outline-none transition-colors text-left"
|
|
75
|
+
@click="launchEntry(entry)"
|
|
76
|
+
>
|
|
77
|
+
<div
|
|
78
|
+
class="flex-shrink-0 flex items-center justify-center w-9 h-9 bg-gray-100 dark:bg-gray-800 rounded-md group-hover:bg-white dark:group-hover:bg-gray-900 transition-colors"
|
|
79
|
+
>
|
|
80
|
+
<UIcon
|
|
81
|
+
v-if="entry.icon"
|
|
82
|
+
:name="entry.icon"
|
|
83
|
+
class="w-5 h-5 text-gray-700 dark:text-gray-300"
|
|
84
|
+
/>
|
|
85
|
+
<UIcon
|
|
86
|
+
v-else
|
|
87
|
+
name="i-heroicons-window"
|
|
88
|
+
class="w-5 h-5 text-gray-700 dark:text-gray-300"
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="flex flex-col min-w-0">
|
|
92
|
+
<span
|
|
93
|
+
class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate"
|
|
94
|
+
>
|
|
95
|
+
{{ entry.title }}
|
|
96
|
+
</span>
|
|
97
|
+
<span
|
|
98
|
+
v-if="entry.description"
|
|
99
|
+
class="text-xs text-gray-500 dark:text-gray-400 truncate"
|
|
100
|
+
>
|
|
101
|
+
{{ entry.description }}
|
|
102
|
+
</span>
|
|
103
|
+
</div>
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
65
106
|
</div>
|
|
66
107
|
</div>
|
|
67
108
|
</div>
|
|
68
|
-
</
|
|
109
|
+
</Transition>
|
|
69
110
|
</template>
|
|
@@ -39,7 +39,7 @@ const isDark = computed({
|
|
|
39
39
|
aria-label="Toggle Start Menu"
|
|
40
40
|
>
|
|
41
41
|
<UIcon name="i-heroicons-cube-transparent" class="w-6 h-6 text-green-500" />
|
|
42
|
-
<span class="font-bold text-gray-900 dark:text-white text-lg tracking-tight hidden sm:block">Nuxt
|
|
42
|
+
<span class="font-bold text-gray-900 dark:text-white text-lg tracking-tight hidden sm:block">Nuxt Desktop</span>
|
|
43
43
|
</button>
|
|
44
44
|
</div>
|
|
45
45
|
|
package/package.json
CHANGED
|
@@ -67,29 +67,38 @@ function calculate() {
|
|
|
67
67
|
</script>
|
|
68
68
|
|
|
69
69
|
<template>
|
|
70
|
-
<DesktopWindow :window="window"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<div
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
<DesktopWindow :window="window" :content="{ padded: true }">
|
|
71
|
+
<div class="flex h-full min-h-0 flex-col gap-3">
|
|
72
|
+
<!-- Calculator screen -->
|
|
73
|
+
<div
|
|
74
|
+
class="flex-shrink-0 rounded-lg border border-gray-200 bg-white p-3 text-right shadow-inner dark:border-gray-800 dark:bg-gray-900"
|
|
75
|
+
>
|
|
76
|
+
<div class="mb-0.5 h-4 truncate text-xs text-gray-400 dark:text-gray-500">
|
|
77
|
+
{{ equation }}
|
|
78
|
+
</div>
|
|
79
|
+
<div
|
|
80
|
+
class="truncate font-mono text-2xl font-bold text-gray-800 dark:text-gray-100"
|
|
81
|
+
>
|
|
82
|
+
{{ display }}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
76
85
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
<!-- Buttons grid -->
|
|
87
|
+
<div class="grid grid-cols-4 grid-rows-5 gap-2 flex-1 min-h-0">
|
|
88
|
+
<template v-for="(row, rIdx) in buttons" :key="rIdx">
|
|
89
|
+
<template v-for="(btn, bIdx) in row" :key="bIdx">
|
|
90
|
+
<UButton
|
|
91
|
+
v-if="btn !== ''"
|
|
92
|
+
:label="btn"
|
|
93
|
+
class="flex h-full w-full items-center justify-center text-sm font-semibold"
|
|
94
|
+
:color="btn === '=' ? 'primary' : btn === 'C' ? 'red' : ['/', '*', '-', '+'].includes(btn) ? 'orange' : 'gray'"
|
|
95
|
+
variant="solid"
|
|
96
|
+
@click="handleClick(btn)"
|
|
97
|
+
/>
|
|
98
|
+
<div v-else />
|
|
99
|
+
</template>
|
|
91
100
|
</template>
|
|
92
|
-
</
|
|
101
|
+
</div>
|
|
93
102
|
</div>
|
|
94
103
|
</DesktopWindow>
|
|
95
104
|
</template>
|
|
@@ -13,32 +13,36 @@ function clear() {
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
|
-
<DesktopWindow :window="window"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
16
|
+
<DesktopWindow :window="window">
|
|
17
|
+
<div class="flex h-full min-h-0 flex-col bg-white dark:bg-gray-950">
|
|
18
|
+
<!-- Toolbar -->
|
|
19
|
+
<div
|
|
20
|
+
class="flex flex-shrink-0 items-center gap-2 border-b border-gray-200 bg-gray-50 p-2 dark:border-gray-800 dark:bg-gray-900"
|
|
21
|
+
>
|
|
22
|
+
<UButton
|
|
23
|
+
icon="i-heroicons-document"
|
|
24
|
+
label="New"
|
|
25
|
+
color="gray"
|
|
26
|
+
variant="ghost"
|
|
27
|
+
size="xs"
|
|
28
|
+
class="gap-2"
|
|
29
|
+
@click="clear"
|
|
30
|
+
/>
|
|
31
|
+
<UButton
|
|
32
|
+
icon="i-heroicons-trash"
|
|
33
|
+
label="Clear"
|
|
34
|
+
color="red"
|
|
35
|
+
variant="ghost"
|
|
36
|
+
size="xs"
|
|
37
|
+
class="gap-2"
|
|
38
|
+
@click="clear"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
<div class="flex-1 min-h-0">
|
|
42
|
+
<!-- Text area -->
|
|
39
43
|
<textarea
|
|
40
44
|
v-model="text"
|
|
41
|
-
class="w-full
|
|
45
|
+
class="w-full flex-1 min-h-0 resize-none border-0 bg-transparent p-4 font-mono text-sm text-gray-800 outline-none dark:text-gray-200"
|
|
42
46
|
placeholder="Type here..."
|
|
43
47
|
/>
|
|
44
48
|
</div>
|
|
@@ -31,7 +31,12 @@ onUnmounted(() => {
|
|
|
31
31
|
>
|
|
32
32
|
<NuxtTopBar />
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
<!--
|
|
35
|
+
`isolate` keeps every window's z-index inside this stacking context, so the
|
|
36
|
+
system bar and the launcher (siblings) always stay above open windows
|
|
37
|
+
instead of losing to the window manager's 100+ z-index values.
|
|
38
|
+
-->
|
|
39
|
+
<div ref="shellStageRef" class="flex-1 min-h-0 relative flex flex-col isolate">
|
|
35
40
|
<Background />
|
|
36
41
|
|
|
37
42
|
<DesktopContent>
|
|
@@ -50,6 +55,82 @@ onUnmounted(() => {
|
|
|
50
55
|
<style>
|
|
51
56
|
/* Custom style rules for desktop shell */
|
|
52
57
|
.owd-desktop--nuxt {
|
|
53
|
-
--owd-system-bar-height:
|
|
58
|
+
--owd-system-bar-height: 60px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Desktop shell: no document scrollbars — scroll only inside windows/apps */
|
|
62
|
+
.owd-desktop--nuxt,
|
|
63
|
+
.owd-has-nuxt-shell {
|
|
64
|
+
overscroll-behavior: none;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* Window content layout. The core renders the `desktop-window__content` element
|
|
69
|
+
* and adds `--centered` / `--padded` when a window is opened with
|
|
70
|
+
* `:content="{ centered: true, padded: true }"`. These rules must be global
|
|
71
|
+
* (the element is the child component root, so scoped `:deep` does not match).
|
|
72
|
+
*/
|
|
73
|
+
.owd-desktop--nuxt .desktop-window__content {
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
flex: 1 1 auto;
|
|
77
|
+
min-height: 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.owd-desktop--nuxt .desktop-window__content--centered {
|
|
81
|
+
justify-content: center;
|
|
82
|
+
align-items: center;
|
|
83
|
+
text-align: center;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.owd-desktop--nuxt .desktop-window__content--centered > * {
|
|
87
|
+
max-width: 100%;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.owd-desktop--nuxt .desktop-window__content--padded {
|
|
91
|
+
padding: 12px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Thin, unobtrusive scrollbars inside windows and the launcher */
|
|
95
|
+
.owd-desktop--nuxt .desktop-window__content,
|
|
96
|
+
.owd-desktop--nuxt .owd-desktop__scroll {
|
|
97
|
+
scrollbar-width: thin;
|
|
98
|
+
scrollbar-color: rgb(0 0 0 / 0.18) transparent;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar,
|
|
102
|
+
.owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar {
|
|
103
|
+
width: 10px;
|
|
104
|
+
height: 10px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar-track,
|
|
108
|
+
.owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar-track {
|
|
109
|
+
background: transparent;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar-thumb,
|
|
113
|
+
.owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar-thumb {
|
|
114
|
+
background-color: rgb(0 0 0 / 0.18);
|
|
115
|
+
border: 3px solid transparent;
|
|
116
|
+
background-clip: padding-box;
|
|
117
|
+
border-radius: 9999px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar-thumb:hover,
|
|
121
|
+
.owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar-thumb:hover {
|
|
122
|
+
background-color: rgb(0 0 0 / 0.3);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.owd-desktop--nuxt.dark .desktop-window__content,
|
|
126
|
+
.owd-desktop--nuxt.dark .owd-desktop__scroll,
|
|
127
|
+
.dark .owd-desktop--nuxt .desktop-window__content,
|
|
128
|
+
.dark .owd-desktop--nuxt .owd-desktop__scroll {
|
|
129
|
+
scrollbar-color: rgb(255 255 255 / 0.22) transparent;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.dark .owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar-thumb,
|
|
133
|
+
.dark .owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar-thumb {
|
|
134
|
+
background-color: rgb(255 255 255 / 0.22);
|
|
54
135
|
}
|
|
55
136
|
</style>
|
|
@@ -1,71 +1,117 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
|
2
3
|
import { useNuxtStartMenu } from '../composables/useNuxtStartMenu'
|
|
3
|
-
import { watch, ref } from 'vue'
|
|
4
4
|
|
|
5
|
-
const { open, searchQuery, filteredEntries, close, launchEntry } =
|
|
6
|
-
|
|
5
|
+
const { open, searchQuery, filteredEntries, close, launchEntry } =
|
|
6
|
+
useNuxtStartMenu()
|
|
7
|
+
|
|
8
|
+
const searchInput = ref<HTMLInputElement | null>(null)
|
|
7
9
|
|
|
8
10
|
watch(open, (isOpen) => {
|
|
9
11
|
if (isOpen) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}, 100)
|
|
12
|
+
// Wait for the panel to be mounted before focusing the field.
|
|
13
|
+
setTimeout(() => searchInput.value?.focus(), 50)
|
|
13
14
|
}
|
|
14
15
|
})
|
|
16
|
+
|
|
17
|
+
function onKeydown(event: KeyboardEvent) {
|
|
18
|
+
if (event.key === 'Escape' && open.value) {
|
|
19
|
+
close()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
onMounted(() => window.addEventListener('keydown', onKeydown))
|
|
24
|
+
onUnmounted(() => window.removeEventListener('keydown', onKeydown))
|
|
15
25
|
</script>
|
|
16
26
|
|
|
17
27
|
<template>
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
<Transition
|
|
29
|
+
enter-active-class="transition duration-150 ease-out"
|
|
30
|
+
enter-from-class="opacity-0 -translate-y-1"
|
|
31
|
+
leave-active-class="transition duration-100 ease-in"
|
|
32
|
+
leave-to-class="opacity-0 -translate-y-1"
|
|
33
|
+
>
|
|
34
|
+
<div
|
|
35
|
+
v-if="open"
|
|
36
|
+
class="fixed inset-0 z-40 flex items-start justify-center pt-24"
|
|
37
|
+
@click="close"
|
|
23
38
|
>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
ref="searchInput"
|
|
27
|
-
v-model="searchQuery"
|
|
28
|
-
icon="i-heroicons-magnifying-glass"
|
|
29
|
-
size="lg"
|
|
30
|
-
color="gray"
|
|
31
|
-
variant="none"
|
|
32
|
-
placeholder="Search apps..."
|
|
33
|
-
class="w-full text-lg"
|
|
34
|
-
:ui="{ wrapper: 'shadow-none', icon: { base: 'text-gray-400 dark:text-gray-500' } }"
|
|
35
|
-
/>
|
|
36
|
-
</div>
|
|
39
|
+
<!-- Overlay scrim -->
|
|
40
|
+
<div class="absolute inset-0 bg-gray-950/10 dark:bg-black/40" />
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
<!-- Command palette -->
|
|
43
|
+
<div
|
|
44
|
+
class="relative w-full max-w-xl mx-4 flex flex-col bg-white/95 dark:bg-gray-900/95 backdrop-blur-xl border border-gray-200 dark:border-gray-800 rounded-xl shadow-2xl overflow-hidden"
|
|
45
|
+
@click.stop
|
|
46
|
+
>
|
|
47
|
+
<!-- Search -->
|
|
48
|
+
<div class="p-3 border-b border-gray-200 dark:border-gray-800">
|
|
49
|
+
<div
|
|
50
|
+
class="flex items-center gap-2.5 h-11 px-3.5 rounded-lg bg-gray-100/80 dark:bg-gray-800/60 ring-1 ring-inset ring-transparent focus-within:ring-primary-500/60 focus-within:bg-white dark:focus-within:bg-gray-900 transition"
|
|
51
|
+
>
|
|
52
|
+
<UIcon
|
|
53
|
+
name="i-heroicons-magnifying-glass"
|
|
54
|
+
class="w-4 h-4 flex-shrink-0 text-gray-400 dark:text-gray-500"
|
|
55
|
+
/>
|
|
56
|
+
<input
|
|
57
|
+
ref="searchInput"
|
|
58
|
+
v-model="searchQuery"
|
|
59
|
+
type="text"
|
|
60
|
+
placeholder="Search apps..."
|
|
61
|
+
autocomplete="off"
|
|
62
|
+
spellcheck="false"
|
|
63
|
+
class="flex-1 min-w-0 h-full bg-transparent border-0 outline-none focus:outline-none focus:ring-0 text-sm text-gray-900 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
41
66
|
</div>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class="
|
|
48
|
-
@click="launchEntry(entry)"
|
|
67
|
+
|
|
68
|
+
<!-- Results -->
|
|
69
|
+
<div class="flex-1 overflow-y-auto owd-desktop__scroll max-h-[50vh] p-2">
|
|
70
|
+
<div
|
|
71
|
+
v-if="filteredEntries.length === 0"
|
|
72
|
+
class="text-center py-12 text-gray-500 dark:text-gray-400 text-sm"
|
|
49
73
|
>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
No apps found.
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div class="grid grid-cols-1 gap-0.5">
|
|
78
|
+
<button
|
|
79
|
+
v-for="entry in filteredEntries"
|
|
80
|
+
:key="entry.application.id"
|
|
81
|
+
class="group flex items-center gap-3 p-2.5 rounded-lg hover:bg-gray-100/70 dark:hover:bg-gray-800/60 focus:bg-gray-100/70 dark:focus:bg-gray-800/60 focus:outline-none transition-colors text-left"
|
|
82
|
+
@click="launchEntry(entry)"
|
|
83
|
+
>
|
|
84
|
+
<div
|
|
85
|
+
class="flex-shrink-0 flex items-center justify-center w-9 h-9 bg-gray-100 dark:bg-gray-800 rounded-md group-hover:bg-white dark:group-hover:bg-gray-900 transition-colors"
|
|
86
|
+
>
|
|
87
|
+
<UIcon
|
|
88
|
+
v-if="entry.icon"
|
|
89
|
+
:name="entry.icon"
|
|
90
|
+
class="w-5 h-5 text-gray-700 dark:text-gray-300"
|
|
91
|
+
/>
|
|
92
|
+
<UIcon
|
|
93
|
+
v-else
|
|
94
|
+
name="i-heroicons-window"
|
|
95
|
+
class="w-5 h-5 text-gray-700 dark:text-gray-300"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="flex flex-col min-w-0">
|
|
99
|
+
<span
|
|
100
|
+
class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate"
|
|
101
|
+
>
|
|
102
|
+
{{ entry.title }}
|
|
103
|
+
</span>
|
|
104
|
+
<span
|
|
105
|
+
v-if="entry.description"
|
|
106
|
+
class="text-xs text-gray-500 dark:text-gray-400 truncate"
|
|
107
|
+
>
|
|
108
|
+
{{ entry.description }}
|
|
109
|
+
</span>
|
|
110
|
+
</div>
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
67
113
|
</div>
|
|
68
114
|
</div>
|
|
69
115
|
</div>
|
|
70
|
-
</
|
|
116
|
+
</Transition>
|
|
71
117
|
</template>
|
|
@@ -44,7 +44,7 @@ const isDark = computed({
|
|
|
44
44
|
aria-label="Toggle Start Menu"
|
|
45
45
|
>
|
|
46
46
|
<UIcon name="i-heroicons-cube-transparent" class="w-6 h-6 text-green-500" />
|
|
47
|
-
<span class="font-bold text-gray-900 dark:text-white text-lg tracking-tight hidden sm:block">Nuxt
|
|
47
|
+
<span class="font-bold text-gray-900 dark:text-white text-lg tracking-tight hidden sm:block">Nuxt Desktop</span>
|
|
48
48
|
</button>
|
|
49
49
|
</div>
|
|
50
50
|
|
|
@@ -3,15 +3,3 @@
|
|
|
3
3
|
<slot />
|
|
4
4
|
</DesktopCoreWindowContent>
|
|
5
5
|
</template>
|
|
6
|
-
|
|
7
|
-
<style scoped>
|
|
8
|
-
.owd-window__content--centered {
|
|
9
|
-
display: flex;
|
|
10
|
-
flex-direction: column;
|
|
11
|
-
justify-content: center;
|
|
12
|
-
align-items: center;
|
|
13
|
-
}
|
|
14
|
-
.owd-window__content--padded {
|
|
15
|
-
padding: 12px;
|
|
16
|
-
}
|
|
17
|
-
</style>
|