nuxtseo-layer-devtools 0.3.8 → 0.4.0

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.
@@ -106,7 +106,7 @@ function disconnectStandalone() {
106
106
  type="button"
107
107
  aria-label="Nuxt SEO Modules"
108
108
  class="flex items-center opacity-90 hover:opacity-100 transition-opacity cursor-pointer"
109
- @click="showModuleSplash = true"
109
+ @click="showModuleSplash = !showModuleSplash"
110
110
  >
111
111
  <NuxtSeoLogo class="h-6 sm:h-7" />
112
112
  </button>
@@ -114,12 +114,19 @@ function disconnectStandalone() {
114
114
  <div class="devtools-divider" />
115
115
 
116
116
  <div class="flex items-center gap-2">
117
- <div class="devtools-brand-icon" aria-hidden="true">
118
- <UIcon :name="icon" class="text-base sm:text-lg" />
119
- </div>
120
- <h1 class="text-sm sm:text-base font-semibold tracking-tight text-[var(--color-text)]">
121
- {{ title }}
122
- </h1>
117
+ <button
118
+ type="button"
119
+ class="devtools-module-switcher"
120
+ @click="showModuleSplash = !showModuleSplash"
121
+ >
122
+ <div class="devtools-brand-icon" aria-hidden="true">
123
+ <UIcon :name="icon" class="text-base sm:text-lg" />
124
+ </div>
125
+ <span class="text-sm sm:text-base font-semibold tracking-tight text-[var(--color-text)]">
126
+ {{ title }}
127
+ </span>
128
+ <UIcon name="carbon:chevron-down" class="w-3 h-3 opacity-50 transition-transform" :class="showModuleSplash ? 'rotate-180' : ''" />
129
+ </button>
123
130
  <UBadge
124
131
  v-if="version"
125
132
  class="font-mono text-[10px] sm:text-xs hidden sm:inline-flex"
@@ -282,6 +289,22 @@ function disconnectStandalone() {
282
289
  </template>
283
290
 
284
291
  <style scoped>
292
+ .devtools-module-switcher {
293
+ display: flex;
294
+ align-items: center;
295
+ gap: 0.5rem;
296
+ padding: 0.25rem 0.5rem 0.25rem 0.25rem;
297
+ border-radius: var(--radius-md);
298
+ border: 1px solid transparent;
299
+ cursor: pointer;
300
+ transition: background 100ms, border-color 100ms;
301
+ }
302
+
303
+ .devtools-module-switcher:hover {
304
+ background: var(--color-surface-elevated);
305
+ border-color: var(--color-border);
306
+ }
307
+
285
308
  .mode-dropdown-wrapper {
286
309
  position: relative;
287
310
  }
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { onClickOutside } from '@vueuse/core'
3
- import { ref } from 'vue'
3
+ import { computed, ref } from 'vue'
4
4
  import { moduleCatalog, showModuleSplash, switchToModule } from '../composables/modules'
5
5
  import { isConnected } from '../composables/state'
6
6
 
@@ -13,6 +13,9 @@ onClickOutside(panelRef, () => {
13
13
  showModuleSplash.value = false
14
14
  })
15
15
 
16
+ const coreModules = computed(() => moduleCatalog.value.filter(m => !m.pro))
17
+ const proModules = computed(() => moduleCatalog.value.filter(m => m.pro))
18
+
16
19
  function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
17
20
  if (!mod.installed || mod.name === props.currentModule)
18
21
  return
@@ -25,59 +28,92 @@ function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
25
28
  <Transition name="splash">
26
29
  <div v-if="showModuleSplash" class="splash-overlay">
27
30
  <div ref="panelRef" class="splash-panel">
31
+ <!-- Header -->
28
32
  <div class="splash-header">
29
- <NuxtSeoLogo class="h-7" />
30
- <p class="splash-subtitle">
31
- All the SEO modules you need for Nuxt, in one place.
32
- </p>
33
+ <NuxtSeoLogo class="h-5" />
34
+ <button type="button" class="splash-header-close" @click="showModuleSplash = false">
35
+ <UIcon name="carbon:close" class="w-4 h-4" />
36
+ </button>
33
37
  </div>
34
38
 
35
- <div class="splash-grid">
36
- <button
37
- v-for="mod of moduleCatalog"
38
- :key="mod.name"
39
- type="button"
40
- class="splash-module"
41
- :class="{
42
- 'is-installed': mod.installed,
43
- 'is-current': mod.name === currentModule,
44
- 'is-unavailable': !mod.installed,
45
- 'is-switchable': mod.installed && mod.name !== currentModule && isConnected,
46
- }"
47
- :disabled="!mod.installed || mod.name === currentModule"
48
- @click="handleModuleClick(mod)"
49
- >
50
- <div class="splash-module-icon">
51
- <UIcon :name="mod.icon" class="text-xl" />
52
- </div>
53
- <div class="splash-module-info">
54
- <div class="splash-module-title">
55
- {{ mod.title }}
56
- <UBadge v-if="mod.pro" size="xs" color="neutral" variant="outline" class="ml-1 text-[9px]">
57
- PRO
58
- </UBadge>
59
- </div>
60
- <div class="splash-module-desc">
61
- {{ mod.description }}
39
+ <!-- Modules Section -->
40
+ <div class="splash-section">
41
+ <div class="splash-section-label">
42
+ <span class="splash-section-dot" />
43
+ Modules
44
+ </div>
45
+ <div class="splash-grid">
46
+ <button
47
+ v-for="mod of coreModules"
48
+ :key="mod.name"
49
+ type="button"
50
+ class="splash-module"
51
+ :class="{
52
+ 'is-current': mod.name === currentModule,
53
+ 'is-unavailable': !mod.installed,
54
+ 'is-switchable': mod.installed && mod.name !== currentModule && isConnected,
55
+ }"
56
+ :disabled="!mod.installed || mod.name === currentModule"
57
+ @click="handleModuleClick(mod)"
58
+ >
59
+ <div class="splash-module-icon">
60
+ <UIcon :name="mod.icon" class="text-base" />
62
61
  </div>
63
- </div>
64
- <div class="splash-module-status">
62
+ <span class="splash-module-title">{{ mod.title }}</span>
65
63
  <span v-if="mod.name === currentModule" class="splash-current-badge">Current</span>
66
- <UIcon v-else-if="mod.installed && isConnected" name="carbon:arrow-right" class="text-sm opacity-0 group-hover:opacity-100 transition-opacity" />
67
64
  <span v-else-if="!mod.installed" class="splash-not-installed">Not installed</span>
65
+ </button>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- Pro Section -->
70
+ <div v-if="proModules.length" class="splash-section splash-pro-area">
71
+ <div class="splash-section-label">
72
+ <span class="splash-section-dot splash-section-dot--pro" />
73
+ Pro
74
+ </div>
75
+ <div class="splash-grid">
76
+ <button
77
+ v-for="mod of proModules"
78
+ :key="mod.name"
79
+ type="button"
80
+ class="splash-module"
81
+ :class="{
82
+ 'is-current': mod.name === currentModule,
83
+ 'is-unavailable': !mod.installed,
84
+ 'is-switchable': mod.installed && mod.name !== currentModule && isConnected,
85
+ }"
86
+ :disabled="!mod.installed || mod.name === currentModule"
87
+ @click="handleModuleClick(mod)"
88
+ >
89
+ <div class="splash-module-icon">
90
+ <UIcon :name="mod.icon" class="text-base" />
91
+ </div>
92
+ <span class="splash-module-title">{{ mod.title }}</span>
93
+ <UBadge size="xs" color="neutral" variant="outline" class="splash-pro-badge">
94
+ PRO
95
+ </UBadge>
96
+ </button>
97
+ </div>
98
+ <!-- Pro status / CTA -->
99
+ <div class="splash-pro-status">
100
+ <div class="splash-pro-status-inner">
101
+ <UIcon name="carbon:locked" class="w-3.5 h-3.5 opacity-50" />
102
+ <span>Unlock Pro modules with a license key</span>
68
103
  </div>
69
- </button>
104
+ <a href="https://nuxtseo.com/pro" target="_blank" rel="noopener" class="splash-pro-cta">
105
+ Learn more
106
+ <UIcon name="carbon:arrow-right" class="w-3 h-3" />
107
+ </a>
108
+ </div>
70
109
  </div>
71
110
 
111
+ <!-- Footer -->
72
112
  <div class="splash-footer">
73
113
  <a href="https://nuxtseo.com" target="_blank" rel="noopener" class="splash-link">
74
114
  <UIcon name="carbon:earth" class="w-3.5 h-3.5" />
75
115
  nuxtseo.com
76
116
  </a>
77
- <button type="button" class="splash-close" @click="showModuleSplash = false">
78
- Close
79
- <UIcon name="carbon:close" class="w-3.5 h-3.5" />
80
- </button>
81
117
  </div>
82
118
  </div>
83
119
  </div>
@@ -98,42 +134,89 @@ function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
98
134
  }
99
135
 
100
136
  .splash-panel {
101
- width: min(580px, calc(100vw - 2rem));
102
- max-height: calc(100vh - 2rem);
103
- overflow-y: auto;
137
+ width: min(480px, calc(100vw - 2rem));
104
138
  border-radius: var(--radius-lg);
105
139
  border: 1px solid var(--color-border);
106
140
  background: var(--color-surface);
107
141
  box-shadow: 0 24px 48px oklch(0% 0 0 / 0.2);
142
+ overflow: hidden;
108
143
  }
109
144
 
110
145
  .dark .splash-panel {
111
146
  box-shadow: 0 24px 48px oklch(0% 0 0 / 0.5);
112
147
  }
113
148
 
149
+ /* Header */
114
150
  .splash-header {
115
- padding: 1.5rem 1.5rem 0;
116
- text-align: center;
151
+ display: flex;
152
+ align-items: center;
153
+ justify-content: space-between;
154
+ padding: 0.75rem 1rem;
155
+ border-bottom: 1px solid var(--color-border);
117
156
  }
118
157
 
119
- .splash-subtitle {
120
- margin-top: 0.5rem;
121
- font-size: 0.8125rem;
158
+ .splash-header-close {
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ width: 1.5rem;
163
+ height: 1.5rem;
164
+ border-radius: var(--radius-sm);
122
165
  color: var(--color-text-muted);
166
+ cursor: pointer;
167
+ transition: background 100ms, color 100ms;
123
168
  }
124
169
 
125
- .splash-grid {
170
+ .splash-header-close:hover {
171
+ background: var(--color-surface-sunken);
172
+ color: var(--color-text);
173
+ }
174
+
175
+ /* Sections */
176
+ .splash-section {
177
+ padding: 0.5rem 0.75rem 0.625rem;
178
+ }
179
+
180
+ .splash-section + .splash-section {
181
+ border-top: 1px solid var(--color-border);
182
+ }
183
+
184
+ .splash-section-label {
126
185
  display: flex;
127
- flex-direction: column;
128
- gap: 2px;
129
- padding: 1rem 0.75rem;
186
+ align-items: center;
187
+ gap: 0.375rem;
188
+ font-size: 0.625rem;
189
+ font-weight: 600;
190
+ text-transform: uppercase;
191
+ letter-spacing: 0.05em;
192
+ color: var(--color-text-muted);
193
+ padding: 0.25rem 0.375rem 0.375rem;
194
+ }
195
+
196
+ .splash-section-dot {
197
+ width: 0.3125rem;
198
+ height: 0.3125rem;
199
+ border-radius: 50%;
200
+ background: var(--color-text-subtle);
201
+ }
202
+
203
+ .splash-section-dot--pro {
204
+ background: var(--seo-green);
130
205
  }
131
206
 
207
+ /* Grid */
208
+ .splash-grid {
209
+ display: grid;
210
+ grid-template-columns: repeat(2, 1fr);
211
+ gap: 1px;
212
+ }
213
+
214
+ /* Module items */
132
215
  .splash-module {
133
216
  display: flex;
134
217
  align-items: center;
135
- gap: 0.75rem;
136
- padding: 0.625rem 0.75rem;
218
+ gap: 0.5rem;
219
+ padding: 0.375rem 0.5rem;
137
220
  border-radius: var(--radius-md);
138
221
  text-align: left;
139
222
  cursor: default;
@@ -150,6 +233,7 @@ function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
150
233
 
151
234
  .splash-module.is-switchable:hover .splash-module-icon {
152
235
  color: var(--seo-green);
236
+ background: oklch(from var(--seo-green) l c h / 0.12);
153
237
  }
154
238
 
155
239
  .splash-module.is-current {
@@ -157,20 +241,20 @@ function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
157
241
  }
158
242
 
159
243
  .splash-module.is-unavailable {
160
- opacity: 0.45;
244
+ opacity: 0.5;
161
245
  }
162
246
 
163
247
  .splash-module-icon {
164
248
  display: flex;
165
249
  align-items: center;
166
250
  justify-content: center;
167
- width: 2.25rem;
168
- height: 2.25rem;
251
+ width: 1.625rem;
252
+ height: 1.625rem;
169
253
  flex-shrink: 0;
170
254
  border-radius: var(--radius-sm);
171
255
  background: var(--color-surface-sunken);
172
256
  color: var(--color-text-muted);
173
- transition: color 100ms;
257
+ transition: color 100ms, background 100ms;
174
258
  }
175
259
 
176
260
  .splash-module.is-current .splash-module-icon {
@@ -178,47 +262,73 @@ function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
178
262
  color: var(--seo-green);
179
263
  }
180
264
 
181
- .splash-module-info {
265
+ .splash-module-title {
266
+ font-size: 0.75rem;
267
+ font-weight: 500;
268
+ color: var(--color-text);
182
269
  flex: 1;
183
270
  min-width: 0;
184
271
  }
185
272
 
186
- .splash-module-title {
187
- display: flex;
188
- align-items: center;
189
- font-size: 0.8125rem;
273
+ .splash-current-badge {
274
+ font-size: 0.5625rem;
190
275
  font-weight: 600;
191
- color: var(--color-text);
276
+ text-transform: uppercase;
277
+ letter-spacing: 0.05em;
278
+ color: var(--seo-green);
279
+ flex-shrink: 0;
280
+ }
281
+
282
+ .splash-not-installed {
283
+ font-size: 0.625rem;
284
+ color: var(--color-text-subtle);
285
+ flex-shrink: 0;
192
286
  }
193
287
 
194
- .splash-module-desc {
288
+ .splash-pro-badge {
289
+ font-size: 9px !important;
290
+ flex-shrink: 0;
291
+ }
292
+
293
+ /* Pro status / CTA */
294
+ .splash-pro-status {
295
+ display: flex;
296
+ align-items: center;
297
+ justify-content: space-between;
298
+ margin: 0.375rem 0.25rem 0;
299
+ padding: 0.4rem 0.625rem;
300
+ border-radius: var(--radius-md);
301
+ background: var(--color-surface-sunken);
195
302
  font-size: 0.6875rem;
196
303
  color: var(--color-text-muted);
197
- margin-top: 1px;
198
304
  }
199
305
 
200
- .splash-module-status {
201
- flex-shrink: 0;
306
+ .splash-pro-status-inner {
307
+ display: flex;
308
+ align-items: center;
309
+ gap: 0.375rem;
202
310
  }
203
311
 
204
- .splash-current-badge {
205
- font-size: 0.625rem;
206
- font-weight: 600;
207
- text-transform: uppercase;
208
- letter-spacing: 0.05em;
312
+ .splash-pro-cta {
313
+ display: flex;
314
+ align-items: center;
315
+ gap: 0.25rem;
316
+ font-size: 0.6875rem;
317
+ font-weight: 500;
209
318
  color: var(--seo-green);
319
+ text-decoration: none;
320
+ transition: opacity 100ms;
210
321
  }
211
322
 
212
- .splash-not-installed {
213
- font-size: 0.6875rem;
214
- color: var(--color-text-subtle);
323
+ .splash-pro-cta:hover {
324
+ opacity: 0.8;
215
325
  }
216
326
 
327
+ /* Footer */
217
328
  .splash-footer {
218
329
  display: flex;
219
330
  align-items: center;
220
- justify-content: space-between;
221
- padding: 0.75rem 1.25rem;
331
+ padding: 0.5rem 1rem;
222
332
  border-top: 1px solid var(--color-border);
223
333
  }
224
334
 
@@ -226,7 +336,7 @@ function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
226
336
  display: flex;
227
337
  align-items: center;
228
338
  gap: 0.375rem;
229
- font-size: 0.75rem;
339
+ font-size: 0.6875rem;
230
340
  color: var(--color-text-muted);
231
341
  text-decoration: none;
232
342
  transition: color 100ms;
@@ -236,24 +346,6 @@ function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
236
346
  color: var(--seo-green);
237
347
  }
238
348
 
239
- .splash-close {
240
- display: flex;
241
- align-items: center;
242
- gap: 0.375rem;
243
- font-size: 0.75rem;
244
- font-weight: 500;
245
- color: var(--color-text-muted);
246
- padding: 0.25rem 0.625rem;
247
- border-radius: var(--radius-sm);
248
- cursor: pointer;
249
- transition: background 100ms, color 100ms;
250
- }
251
-
252
- .splash-close:hover {
253
- background: var(--color-surface-sunken);
254
- color: var(--color-text);
255
- }
256
-
257
349
  /* Transitions */
258
350
  .splash-enter-active {
259
351
  transition: opacity 200ms ease;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxtseo-layer-devtools",
3
3
  "type": "module",
4
- "version": "0.3.8",
4
+ "version": "0.4.0",
5
5
  "description": "Shared Nuxt layer for Nuxt SEO devtools clients.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",