@stonecrop/desktop 0.4.16 → 0.4.17

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": "@stonecrop/desktop",
3
- "version": "0.4.16",
3
+ "version": "0.4.17",
4
4
  "description": "Desktop-specific components for Stonecrop UI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,10 +29,10 @@
29
29
  ],
30
30
  "dependencies": {
31
31
  "vue": "^3.5.13",
32
- "@stonecrop/aform": "0.4.16",
33
- "@stonecrop/themes": "0.4.16",
34
- "@stonecrop/stonecrop": "0.4.16",
35
- "@stonecrop/atable": "0.4.16"
32
+ "@stonecrop/aform": "0.4.17",
33
+ "@stonecrop/atable": "0.4.17",
34
+ "@stonecrop/themes": "0.4.17",
35
+ "@stonecrop/stonecrop": "0.4.17"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@microsoft/api-documenter": "^7.26.10",
@@ -1,5 +1,235 @@
1
1
  <template>
2
- <dialog></dialog>
2
+ <Teleport to="body">
3
+ <Transition name="fade">
4
+ <div v-if="isOpen" class="command-palette-overlay" @click="closeModal">
5
+ <div class="command-palette" @click.stop>
6
+ <div class="command-palette-header">
7
+ <input
8
+ ref="input"
9
+ v-model="query"
10
+ type="text"
11
+ class="command-palette-input"
12
+ :placeholder="placeholder"
13
+ autofocus
14
+ @keydown="handleKeydown" />
15
+ </div>
16
+
17
+ <div v-if="results.length" class="command-palette-results">
18
+ <div
19
+ v-for="(result, index) in results"
20
+ :key="index"
21
+ class="command-palette-result"
22
+ :class="{ selected: index === selectedIndex }"
23
+ @click="selectResult(result)"
24
+ @mouseover="selectedIndex = index">
25
+ <div class="result-title">
26
+ <slot name="title" :result="result" />
27
+ </div>
28
+ <div class="result-content">
29
+ <slot name="content" :result="result" />
30
+ </div>
31
+ </div>
32
+ </div>
33
+ <div v-else-if="query && !results.length" class="command-palette-no-results">
34
+ <slot name="empty"> No results found for "{{ query }}" </slot>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </Transition>
39
+ </Teleport>
3
40
  </template>
4
41
 
5
- <script setup lang="ts"></script>
42
+ <script setup lang="ts" generic="T">
43
+ import { ref, computed, watch, nextTick, useTemplateRef } from 'vue'
44
+
45
+ defineSlots<{
46
+ title?: { result: T }
47
+ content?: { result: T }
48
+ empty?: null
49
+ }>()
50
+
51
+ const {
52
+ search,
53
+ isOpen = false,
54
+ placeholder = 'Type a command or search...',
55
+ maxResults = 10,
56
+ } = defineProps<{
57
+ search: (query: string) => T[]
58
+ isOpen?: boolean
59
+ placeholder?: string
60
+ maxResults?: number
61
+ }>()
62
+
63
+ const emit = defineEmits<{
64
+ select: [T]
65
+ close: []
66
+ }>()
67
+
68
+ const query = ref('')
69
+ const selectedIndex = ref(0)
70
+ const inputRef = useTemplateRef('input')
71
+
72
+ const results = computed(() => {
73
+ if (!query.value) return []
74
+ const results = search(query.value)
75
+ return results.slice(0, maxResults)
76
+ })
77
+
78
+ // reset search query when modal opens
79
+ watch(
80
+ () => isOpen,
81
+ async isOpen => {
82
+ if (isOpen) {
83
+ query.value = ''
84
+ selectedIndex.value = 0
85
+ await nextTick()
86
+ ;(inputRef.value as HTMLInputElement)?.focus()
87
+ }
88
+ }
89
+ )
90
+
91
+ // reset selected index when results change
92
+ watch(results, () => {
93
+ selectedIndex.value = 0
94
+ })
95
+
96
+ const closeModal = () => {
97
+ emit('close')
98
+ }
99
+
100
+ const handleKeydown = (e: KeyboardEvent) => {
101
+ switch (e.key) {
102
+ case 'Escape':
103
+ closeModal()
104
+ break
105
+ case 'ArrowDown':
106
+ e.preventDefault()
107
+ if (results.value.length) {
108
+ selectedIndex.value = (selectedIndex.value + 1) % results.value.length
109
+ }
110
+ break
111
+ case 'ArrowUp':
112
+ e.preventDefault()
113
+ if (results.value.length) {
114
+ selectedIndex.value = (selectedIndex.value - 1 + results.value.length) % results.value.length
115
+ }
116
+ break
117
+ case 'Enter':
118
+ if (results.value.length && selectedIndex.value >= 0) {
119
+ selectResult(results.value[selectedIndex.value])
120
+ }
121
+ break
122
+ }
123
+ }
124
+
125
+ const selectResult = (result: T) => {
126
+ emit('select', result)
127
+ closeModal()
128
+ }
129
+ </script>
130
+
131
+ <style>
132
+ .fade-enter-active,
133
+ .fade-leave-active {
134
+ transition: opacity 0.2s ease;
135
+ }
136
+
137
+ .fade-enter-from,
138
+ .fade-leave-to {
139
+ opacity: 0;
140
+ }
141
+
142
+ .command-palette-overlay {
143
+ position: fixed;
144
+ top: 0;
145
+ left: 0;
146
+ width: 100%;
147
+ height: 100%;
148
+ background-color: rgba(0, 0, 0, 0.5);
149
+ display: flex;
150
+ align-items: flex-start;
151
+ justify-content: center;
152
+ z-index: 9999;
153
+ padding-top: 100px;
154
+ }
155
+
156
+ .command-palette {
157
+ width: 600px;
158
+ max-width: 90%;
159
+ background-color: white;
160
+ border-radius: 8px;
161
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
162
+ overflow: hidden;
163
+ max-height: 80vh;
164
+ display: flex;
165
+ flex-direction: column;
166
+ }
167
+
168
+ .command-palette-header {
169
+ display: flex;
170
+ border-bottom: 1px solid #eaeaea;
171
+ padding: 12px;
172
+ }
173
+
174
+ .command-palette-input {
175
+ flex: 1;
176
+ border: none;
177
+ outline: none;
178
+ font-size: 16px;
179
+ padding: 8px 12px;
180
+ background-color: transparent;
181
+ }
182
+
183
+ .command-palette-close {
184
+ background: transparent;
185
+ border: none;
186
+ font-size: 24px;
187
+ cursor: pointer;
188
+ color: #666;
189
+ padding: 0 8px;
190
+ }
191
+
192
+ .command-palette-close:hover {
193
+ color: #333;
194
+ }
195
+
196
+ .command-palette-results {
197
+ overflow-y: auto;
198
+ max-height: 60vh;
199
+ }
200
+
201
+ .command-palette-result {
202
+ padding: 12px 16px;
203
+ cursor: pointer;
204
+ border-bottom: 1px solid #f0f0f0;
205
+ }
206
+
207
+ .command-palette-result:hover,
208
+ .command-palette-result.selected {
209
+ background-color: #f5f5f5;
210
+ }
211
+
212
+ .command-palette-result.selected {
213
+ background-color: rgba(132, 60, 3, 0.1);
214
+ }
215
+
216
+ .result-title {
217
+ font-weight: 500;
218
+ margin-bottom: 4px;
219
+ color: #333;
220
+ }
221
+
222
+ .result-content {
223
+ font-size: 14px;
224
+ color: #666;
225
+ white-space: nowrap;
226
+ overflow: hidden;
227
+ text-overflow: ellipsis;
228
+ }
229
+
230
+ .command-palette-no-results {
231
+ padding: 20px 16px;
232
+ text-align: center;
233
+ color: #666;
234
+ }
235
+ </style>