@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "desktop-theme-nuxt",
3
3
  "configKey": "desktop",
4
- "version": "4.0.0-alpha.3",
4
+ "version": "4.0.0-alpha.4",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "3.6.1"
@@ -13,7 +13,7 @@ export default {
13
13
  resizable: false,
14
14
  size: {
15
15
  width: 260,
16
- height: 380
16
+ height: 420
17
17
  },
18
18
  position: {
19
19
  x: 200,
@@ -57,29 +57,38 @@ function calculate() {
57
57
  </script>
58
58
 
59
59
  <template>
60
- <DesktopWindow :window="window" class="h-full bg-gray-50 dark:bg-gray-950 flex flex-col p-3">
61
- <!-- Calculator screen -->
62
- <div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-lg p-3 mb-3 text-right shadow-inner flex-shrink-0">
63
- <div class="text-xs text-gray-400 dark:text-gray-500 h-4 mb-0.5 truncate">{{ equation }}</div>
64
- <div class="text-2xl font-bold font-mono text-gray-800 dark:text-gray-100 truncate">{{ display }}</div>
65
- </div>
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
- <!-- Buttons Grid -->
68
- <div class="grid grid-cols-4 gap-2 flex-1">
69
- <template v-for="(row, rIdx) in buttons" :key="rIdx">
70
- <template v-for="(btn, bIdx) in row" :key="bIdx">
71
- <UButton
72
- v-if="btn !== ''"
73
- :label="btn"
74
- size="md"
75
- class="flex items-center justify-center font-bold text-sm h-11 w-full"
76
- :color="btn === '=' ? 'primary' : btn === 'C' ? 'red' : ['/', '*', '-', '+'].includes(btn) ? 'orange' : 'gray'"
77
- variant="solid"
78
- @click="handleClick(btn)"
79
- />
80
- <div v-else class="h-11 w-full" />
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
- </template>
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" class="h-full bg-white dark:bg-gray-950 flex flex-col">
14
- <!-- Toolbar -->
15
- <div class="flex items-center space-x-2 p-2 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 flex-shrink-0">
16
- <UButton
17
- icon="i-heroicons-document"
18
- label="New"
19
- color="gray"
20
- variant="ghost"
21
- size="xs"
22
- @click="clear"
23
- />
24
- <UButton
25
- icon="i-heroicons-trash"
26
- label="Clear"
27
- color="red"
28
- variant="ghost"
29
- size="xs"
30
- @click="clear"
31
- />
32
- </div>
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
- <!-- Text area -->
35
- <div class="flex-1 min-h-0">
39
+ <!-- Text area -->
36
40
  <textarea
37
41
  v-model="text"
38
- class="w-full h-full p-4 resize-none outline-none border-0 text-sm font-mono bg-transparent text-gray-800 dark:text-gray-200"
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
- <div ref="shellStageRef" class="flex-1 min-h-0 relative flex flex-col">
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:48px}
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
- <div v-if="open" class="fixed inset-0 z-40 flex items-start justify-center pt-24" @click="close">
17
- <!-- Overlay/Command Palette -->
18
- <div
19
- class="w-full max-w-xl mx-4 flex flex-col bg-white/75 dark:bg-gray-900/75 backdrop-blur-xl border border-gray-200 dark:border-gray-800 rounded-xl shadow-2xl overflow-hidden ring-1 ring-gray-200 dark:ring-gray-800"
20
- @click.stop
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
- <div class="p-4 border-b border-gray-200 dark:border-gray-800">
23
- <UInput
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
- <div class="flex-1 overflow-y-auto max-h-[50vh] p-2">
37
- <div v-if="filteredEntries.length === 0" class="text-center py-12 text-gray-500 dark:text-gray-400 text-sm">
38
- No apps found.
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
- <div class="grid grid-cols-1 gap-1">
42
- <button
43
- v-for="entry in filteredEntries"
44
- :key="entry.application.id"
45
- class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-100/50 dark:hover:bg-gray-800/50 focus:bg-gray-100/50 dark:focus:bg-gray-800/50 transition-colors text-left outline-none"
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
- <div class="flex-shrink-0 p-2 bg-gray-100 dark:bg-gray-800 rounded-md">
49
- <UIcon
50
- v-if="entry.icon"
51
- :name="entry.icon"
52
- class="w-5 h-5 text-gray-700 dark:text-gray-300"
53
- />
54
- <UIcon
55
- v-else
56
- name="i-heroicons-window"
57
- class="w-5 h-5 text-gray-700 dark:text-gray-300"
58
- />
59
- </div>
60
- <div class="flex flex-col min-w-0">
61
- <span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ entry.title }}</span>
62
- <span class="text-xs text-gray-500 dark:text-gray-400 truncate" v-if="entry.description">{{ entry.description }}</span>
63
- </div>
64
- </button>
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
- </div>
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 OS</span>
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
 
@@ -3,7 +3,3 @@
3
3
  <slot />
4
4
  </DesktopCoreWindowContent>
5
5
  </template>
6
-
7
- <style scoped>
8
- .owd-window__content--centered{align-items:center;display:flex;flex-direction:column;justify-content:center}.owd-window__content--padded{padding:12px}
9
- </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owdproject/nuxt-theme-ui",
3
- "version": "4.0.0-alpha.3",
3
+ "version": "4.0.0-alpha.4",
4
4
  "description": "Nuxt UI theme for Open Web Desktop",
5
5
  "keywords": [
6
6
  "owd-client",
@@ -13,7 +13,7 @@ export default {
13
13
  resizable: false,
14
14
  size: {
15
15
  width: 260,
16
- height: 380,
16
+ height: 420,
17
17
  },
18
18
  position: {
19
19
  x: 200,
@@ -67,29 +67,38 @@ function calculate() {
67
67
  </script>
68
68
 
69
69
  <template>
70
- <DesktopWindow :window="window" class="h-full bg-gray-50 dark:bg-gray-950 flex flex-col p-3">
71
- <!-- Calculator screen -->
72
- <div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-lg p-3 mb-3 text-right shadow-inner flex-shrink-0">
73
- <div class="text-xs text-gray-400 dark:text-gray-500 h-4 mb-0.5 truncate">{{ equation }}</div>
74
- <div class="text-2xl font-bold font-mono text-gray-800 dark:text-gray-100 truncate">{{ display }}</div>
75
- </div>
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
- <!-- Buttons Grid -->
78
- <div class="grid grid-cols-4 gap-2 flex-1">
79
- <template v-for="(row, rIdx) in buttons" :key="rIdx">
80
- <template v-for="(btn, bIdx) in row" :key="bIdx">
81
- <UButton
82
- v-if="btn !== ''"
83
- :label="btn"
84
- size="md"
85
- class="flex items-center justify-center font-bold text-sm h-11 w-full"
86
- :color="btn === '=' ? 'primary' : btn === 'C' ? 'red' : ['/', '*', '-', '+'].includes(btn) ? 'orange' : 'gray'"
87
- variant="solid"
88
- @click="handleClick(btn)"
89
- />
90
- <div v-else class="h-11 w-full" />
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
- </template>
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" class="h-full bg-white dark:bg-gray-950 flex flex-col">
17
- <!-- Toolbar -->
18
- <div class="flex items-center space-x-2 p-2 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 flex-shrink-0">
19
- <UButton
20
- icon="i-heroicons-document"
21
- label="New"
22
- color="gray"
23
- variant="ghost"
24
- size="xs"
25
- @click="clear"
26
- />
27
- <UButton
28
- icon="i-heroicons-trash"
29
- label="Clear"
30
- color="red"
31
- variant="ghost"
32
- size="xs"
33
- @click="clear"
34
- />
35
- </div>
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
- <!-- Text area -->
38
- <div class="flex-1 min-h-0">
42
+ <!-- Text area -->
39
43
  <textarea
40
44
  v-model="text"
41
- class="w-full h-full p-4 resize-none outline-none border-0 text-sm font-mono bg-transparent text-gray-800 dark:text-gray-200"
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
- <div ref="shellStageRef" class="flex-1 min-h-0 relative flex flex-col">
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: 48px;
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 } = useNuxtStartMenu()
6
- const searchInput = ref<any>(null)
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
- setTimeout(() => {
11
- searchInput.value?.$el?.querySelector('input')?.focus()
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
- <div v-if="open" class="fixed inset-0 z-40 flex items-start justify-center pt-24" @click="close">
19
- <!-- Overlay/Command Palette -->
20
- <div
21
- class="w-full max-w-xl mx-4 flex flex-col bg-white/75 dark:bg-gray-900/75 backdrop-blur-xl border border-gray-200 dark:border-gray-800 rounded-xl shadow-2xl overflow-hidden ring-1 ring-gray-200 dark:ring-gray-800"
22
- @click.stop
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
- <div class="p-4 border-b border-gray-200 dark:border-gray-800">
25
- <UInput
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
- <div class="flex-1 overflow-y-auto max-h-[50vh] p-2">
39
- <div v-if="filteredEntries.length === 0" class="text-center py-12 text-gray-500 dark:text-gray-400 text-sm">
40
- No apps found.
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
- <div class="grid grid-cols-1 gap-1">
44
- <button
45
- v-for="entry in filteredEntries"
46
- :key="entry.application.id"
47
- class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-100/50 dark:hover:bg-gray-800/50 focus:bg-gray-100/50 dark:focus:bg-gray-800/50 transition-colors text-left outline-none"
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
- <div class="flex-shrink-0 p-2 bg-gray-100 dark:bg-gray-800 rounded-md">
51
- <UIcon
52
- v-if="entry.icon"
53
- :name="entry.icon"
54
- class="w-5 h-5 text-gray-700 dark:text-gray-300"
55
- />
56
- <UIcon
57
- v-else
58
- name="i-heroicons-window"
59
- class="w-5 h-5 text-gray-700 dark:text-gray-300"
60
- />
61
- </div>
62
- <div class="flex flex-col min-w-0">
63
- <span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ entry.title }}</span>
64
- <span class="text-xs text-gray-500 dark:text-gray-400 truncate" v-if="entry.description">{{ entry.description }}</span>
65
- </div>
66
- </button>
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
- </div>
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 OS</span>
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>