@codihaus/claude-skills 1.3.0 → 1.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.
@@ -326,11 +326,52 @@ export default async function ProductsPage() {
326
326
  }
327
327
  ```
328
328
 
329
+ ## Extension Development
330
+
331
+ For custom extensions (hooks, endpoints, interfaces, panels, modules):
332
+
333
+ **See:** `references/extensions.md`
334
+
335
+ Covers:
336
+ - API Extensions: Hooks, Endpoints, Operations
337
+ - App Extensions: Interfaces, Displays, Layouts, Panels, Modules, Themes
338
+ - Bundle structure and naming conventions
339
+ - Services API (ItemsService, FilesService, etc.)
340
+ - Composables (useApi, useStores, useItems)
341
+
342
+ ### Quick Extension Example
343
+
344
+ ```typescript
345
+ // Hook: Run code on item create
346
+ export default ({ action }, { services, getSchema }) => {
347
+ const { ItemsService } = services;
348
+
349
+ action('items.create', async (meta, context) => {
350
+ const itemsService = new ItemsService('logs', {
351
+ schema: await getSchema(),
352
+ accountability: context.accountability,
353
+ });
354
+
355
+ await itemsService.createOne({
356
+ action: 'created',
357
+ collection: meta.collection,
358
+ item_id: meta.key,
359
+ }, { emitEvents: false });
360
+ });
361
+ };
362
+ ```
363
+
364
+ ```bash
365
+ # Create extension
366
+ npx create-directus-extension@latest
367
+ ```
368
+
329
369
  ## Resources
330
370
 
331
371
  ### Official Docs
332
372
  - Docs: https://docs.directus.io
333
373
  - SDK: https://docs.directus.io/reference/sdk/
374
+ - Extensions: https://docs.directus.io/extensions/
334
375
 
335
376
  ### Context7 Queries
336
377
 
@@ -339,6 +380,8 @@ Query: "Directus SDK TypeScript setup"
339
380
  Query: "Directus permissions best practices"
340
381
  Query: "Directus flows automation"
341
382
  Query: "Directus file uploads"
383
+ Query: "Directus hooks examples"
384
+ Query: "Directus custom endpoints"
342
385
  ```
343
386
 
344
387
  ### MCP Tools
@@ -0,0 +1,444 @@
1
+ # Directus Extension Development
2
+
3
+ Comprehensive guide for developing Directus extensions: hooks, endpoints, operations, interfaces, displays, layouts, panels, modules, and themes.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Create new extension
9
+ npx create-directus-extension@latest
10
+
11
+ # Enable auto-reload for development
12
+ # In docker-compose or .env:
13
+ EXTENSIONS_AUTO_RELOAD: true
14
+ ```
15
+
16
+ ## Extension Types
17
+
18
+ ### API Extensions
19
+ | Type | Purpose | Example |
20
+ |------|---------|---------|
21
+ | **Hooks** | Run code on events (CRUD, schedule) | Send email on item create |
22
+ | **Endpoints** | Custom API routes | `/custom-api/products` |
23
+ | **Operations** | Custom Flow steps | Data transformation in Flows |
24
+
25
+ ### App Extensions
26
+ | Type | Purpose | Example |
27
+ |------|---------|---------|
28
+ | **Interfaces** | Form inputs in Editor | Custom date picker |
29
+ | **Displays** | Single value display | Status badge |
30
+ | **Layouts** | Item listing views | Kanban board |
31
+ | **Panels** | Dashboard widgets | Analytics chart |
32
+ | **Modules** | Top-level areas | Custom admin section |
33
+ | **Themes** | Data Studio styling | Dark theme |
34
+
35
+ ## Bundle Structure (Recommended)
36
+
37
+ Always use bundle extensions for organization:
38
+
39
+ ```
40
+ directus-extension-{bundle-name}/
41
+ ├── package.json
42
+ ├── src/
43
+ │ ├── utils/ # Shared utilities
44
+ │ ├── commons/ # Common code
45
+ │ ├── services/ # Shared services
46
+ │ ├── hook-{name}/ # Individual hooks
47
+ │ ├── endpoint-{name}/ # Individual endpoints
48
+ │ ├── interface-{name}/
49
+ │ └── ...
50
+ └── dist/
51
+ ```
52
+
53
+ **Naming conventions:**
54
+ - `hook-send-email` or `send-email-hook`
55
+ - `endpoint-products` → API route: `/products`
56
+ - `interface-custom-input`
57
+
58
+ **Important:** Endpoint folder name includes prefix (`endpoint-products`), but `name` in package.json omits it (`products`).
59
+
60
+ ## Core Patterns
61
+
62
+ ### AsyncHandler (Required for Endpoints)
63
+
64
+ Always wrap endpoint handlers to prevent crashes:
65
+
66
+ ```typescript
67
+ import type { RequestHandler, Request, Response, NextFunction } from 'express';
68
+
69
+ const asyncHandler = (fn: RequestHandler) => (req: Request, res: Response, next: NextFunction) =>
70
+ Promise.resolve(fn(req, res, next)).catch(next);
71
+
72
+ export default asyncHandler;
73
+ ```
74
+
75
+ ### Creating Services with Accountability
76
+
77
+ ```typescript
78
+ export default ({ action }, { services, getSchema }) => {
79
+ const { ItemsService } = services;
80
+
81
+ action('items.create', async (meta, context) => {
82
+ const itemsService = new ItemsService('collection_name', {
83
+ schema: await getSchema(),
84
+ accountability: context.accountability, // Track user actions
85
+ });
86
+
87
+ // Use emitEvents: false to prevent infinite loops
88
+ await itemsService.updateOne(meta.key, { processed: true }, { emitEvents: false });
89
+ });
90
+ };
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Hooks
96
+
97
+ ### Filter Hook (Before Event)
98
+
99
+ ```typescript
100
+ export default ({ filter }, { services, getSchema }) => {
101
+ filter('items.create', async (payload, meta, context) => {
102
+ // Modify payload before creation
103
+ payload.modified_at = new Date();
104
+ return payload; // Must return payload
105
+ });
106
+ };
107
+ ```
108
+
109
+ ### Action Hook (After Event)
110
+
111
+ ```typescript
112
+ export default ({ action }, { services, getSchema, logger }) => {
113
+ action('items.create', async (meta, context) => {
114
+ logger.info(`Item ${meta.key} created in ${meta.collection}`);
115
+
116
+ // Side effects here
117
+ });
118
+ };
119
+ ```
120
+
121
+ ### Collection-Specific Hook
122
+
123
+ ```typescript
124
+ export default ({ action }) => {
125
+ // Only triggers for 'articles' collection
126
+ action('articles.items.create', async (meta, context) => {
127
+ console.log(`New article: ${meta.key}`);
128
+ });
129
+ };
130
+ ```
131
+
132
+ ### Schedule Hook (Cron)
133
+
134
+ ```typescript
135
+ export default ({ schedule }, { services, getSchema }) => {
136
+ // Every 15 minutes
137
+ schedule('*/15 * * * *', async () => {
138
+ console.log('Running scheduled task');
139
+ });
140
+
141
+ // Daily at midnight
142
+ schedule('0 0 * * *', async () => {
143
+ // Cleanup old data
144
+ });
145
+ };
146
+ ```
147
+
148
+ ### Hook Events
149
+
150
+ **Filter Events:**
151
+ | Event | Payload | Use Case |
152
+ |-------|---------|----------|
153
+ | `items.create` | New item data | Modify before create |
154
+ | `items.update` | Updated fields | Validate changes |
155
+ | `items.delete` | Item keys | Prevent deletion |
156
+ | `auth.login` | Login payload | Custom auth validation |
157
+
158
+ **Action Events:**
159
+ | Event | Meta | Use Case |
160
+ |-------|------|----------|
161
+ | `items.create` | `key`, `collection`, `payload` | Send notifications |
162
+ | `items.update` | `keys`, `collection`, `payload` | Sync external systems |
163
+ | `items.delete` | `keys`, `collection` | Cleanup related data |
164
+ | `server.start` | `server` | Initialize resources |
165
+
166
+ ---
167
+
168
+ ## Endpoints
169
+
170
+ ### Basic Endpoint
171
+
172
+ ```typescript
173
+ export default {
174
+ id: 'products',
175
+ handler: (router, { services, getSchema }) => {
176
+ const { ItemsService } = services;
177
+
178
+ router.get('/', asyncHandler(async (req, res) => {
179
+ const itemsService = new ItemsService('products', {
180
+ schema: await getSchema(),
181
+ accountability: req.accountability,
182
+ });
183
+
184
+ const items = await itemsService.readByQuery({ limit: 25 });
185
+ res.json({ data: items });
186
+ }));
187
+
188
+ router.get('/:id', asyncHandler(async (req, res) => {
189
+ const itemsService = new ItemsService('products', {
190
+ schema: await getSchema(),
191
+ accountability: req.accountability,
192
+ });
193
+
194
+ const item = await itemsService.readOne(req.params.id);
195
+ res.json({ data: item });
196
+ }));
197
+
198
+ router.post('/', asyncHandler(async (req, res) => {
199
+ const itemsService = new ItemsService('products', {
200
+ schema: await getSchema(),
201
+ accountability: req.accountability,
202
+ });
203
+
204
+ const key = await itemsService.createOne(req.body);
205
+ res.json({ data: { id: key } });
206
+ }));
207
+ },
208
+ };
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Services Reference
214
+
215
+ ### ItemsService
216
+
217
+ ```typescript
218
+ const itemsService = new ItemsService('collection', {
219
+ schema: await getSchema(),
220
+ accountability: context.accountability,
221
+ });
222
+
223
+ // Create
224
+ const id = await itemsService.createOne({ title: 'Hello' });
225
+ const ids = await itemsService.createMany([{ title: 'One' }, { title: 'Two' }]);
226
+
227
+ // Read
228
+ const item = await itemsService.readOne('item_id');
229
+ const items = await itemsService.readMany(['id1', 'id2']);
230
+ const results = await itemsService.readByQuery({
231
+ fields: ['id', 'title', 'author.*'],
232
+ filter: { status: { _eq: 'published' } },
233
+ sort: ['-date_created'],
234
+ limit: 10,
235
+ });
236
+
237
+ // Update
238
+ await itemsService.updateOne('id', { title: 'Updated' });
239
+ await itemsService.updateMany(['id1', 'id2'], { status: 'archived' });
240
+
241
+ // Delete
242
+ await itemsService.deleteOne('id');
243
+ await itemsService.deleteMany(['id1', 'id2']);
244
+ ```
245
+
246
+ ### Other Services
247
+
248
+ ```typescript
249
+ const {
250
+ CollectionsService,
251
+ FieldsService,
252
+ RelationsService,
253
+ FilesService,
254
+ UsersService,
255
+ RolesService,
256
+ } = services;
257
+ ```
258
+
259
+ ---
260
+
261
+ ## App Extensions
262
+
263
+ ### Interface
264
+
265
+ ```typescript
266
+ // index.ts
267
+ import { defineInterface } from '@directus/extensions-sdk';
268
+ import InterfaceComponent from './interface.vue';
269
+
270
+ export default defineInterface({
271
+ id: 'custom-input',
272
+ name: 'Custom Input',
273
+ icon: 'text_fields',
274
+ description: 'A custom input interface',
275
+ component: InterfaceComponent,
276
+ types: ['string'],
277
+ options: [
278
+ {
279
+ field: 'placeholder',
280
+ name: 'Placeholder',
281
+ type: 'string',
282
+ meta: { interface: 'input', width: 'full' },
283
+ },
284
+ ],
285
+ });
286
+ ```
287
+
288
+ ```vue
289
+ <!-- interface.vue -->
290
+ <template>
291
+ <input
292
+ :value="value"
293
+ :placeholder="placeholder"
294
+ :disabled="disabled"
295
+ @input="emit('input', $event.target.value)"
296
+ />
297
+ </template>
298
+
299
+ <script setup>
300
+ defineProps({
301
+ value: { type: String, default: null },
302
+ placeholder: { type: String, default: '' },
303
+ disabled: { type: Boolean, default: false },
304
+ collection: { type: String, required: true },
305
+ field: { type: String, required: true },
306
+ primaryKey: { type: String, default: null },
307
+ });
308
+
309
+ const emit = defineEmits(['input', 'setFieldValue']);
310
+ </script>
311
+ ```
312
+
313
+ ### Display
314
+
315
+ ```typescript
316
+ import { defineDisplay } from '@directus/extensions-sdk';
317
+ import DisplayComponent from './display.vue';
318
+
319
+ export default defineDisplay({
320
+ id: 'custom-display',
321
+ name: 'Custom Display',
322
+ icon: 'visibility',
323
+ component: DisplayComponent,
324
+ types: ['string'],
325
+ options: [],
326
+ });
327
+ ```
328
+
329
+ ### Panel (Dashboard)
330
+
331
+ ```typescript
332
+ import { definePanel } from '@directus/extensions-sdk';
333
+ import PanelComponent from './panel.vue';
334
+
335
+ export default definePanel({
336
+ id: 'custom-panel',
337
+ name: 'Custom Panel',
338
+ icon: 'dashboard',
339
+ component: PanelComponent,
340
+ minWidth: 12,
341
+ minHeight: 8,
342
+ options: [
343
+ {
344
+ field: 'collection',
345
+ name: 'Collection',
346
+ type: 'string',
347
+ meta: { interface: 'system-collection', width: 'full' },
348
+ },
349
+ ],
350
+ });
351
+ ```
352
+
353
+ ### Module
354
+
355
+ ```typescript
356
+ import { defineModule } from '@directus/extensions-sdk';
357
+ import ModuleComponent from './module.vue';
358
+
359
+ export default defineModule({
360
+ id: 'custom-module',
361
+ name: 'Custom Module',
362
+ icon: 'extension',
363
+ routes: [
364
+ { path: '', component: ModuleComponent },
365
+ { path: 'subpage', component: () => import('./subpage.vue') },
366
+ ],
367
+ preRegisterCheck(user) {
368
+ return user.role?.admin_access === true; // Only for admins
369
+ },
370
+ });
371
+ ```
372
+
373
+ ---
374
+
375
+ ## Composables (App Extensions)
376
+
377
+ ```typescript
378
+ import { useApi, useStores, useCollection, useItems } from '@directus/extensions-sdk';
379
+
380
+ // API calls
381
+ const api = useApi();
382
+ const response = await api.get('/items/articles');
383
+
384
+ // Stores
385
+ const { useFieldsStore, useCollectionsStore, useUserStore } = useStores();
386
+ const fieldsStore = useFieldsStore();
387
+ const fields = fieldsStore.getFieldsForCollection('articles');
388
+
389
+ // Collection metadata
390
+ const { info, fields, primaryKeyField } = useCollection('articles');
391
+
392
+ // Fetch items
393
+ const { items, getItems, loading, itemCount } = useItems(ref('articles'), {
394
+ fields: ref(['*']),
395
+ limit: ref(25),
396
+ filter: ref(null),
397
+ });
398
+ ```
399
+
400
+ ---
401
+
402
+ ## Best Practices
403
+
404
+ ### DO
405
+ - Use TypeScript for all extensions
406
+ - Use Services over raw database queries
407
+ - Include `accountability` when creating services
408
+ - Use `emitEvents: false` to prevent infinite loops
409
+ - Wrap endpoint handlers with `asyncHandler`
410
+ - Use bundle extensions for organization
411
+
412
+ ### DON'T
413
+ - Use `context.database` directly (use Services)
414
+ - Forget error handling in endpoints
415
+ - Create hooks that trigger themselves
416
+ - Expose admin functionality without permission checks
417
+ - Skip input validation in endpoints
418
+
419
+ ## Validation & Publishing
420
+
421
+ ```bash
422
+ # Validate extension
423
+ npx create-directus-extension@latest validate
424
+
425
+ # Link for development
426
+ npx create-directus-extension@latest link
427
+ ```
428
+
429
+ ### package.json for Marketplace
430
+
431
+ ```json
432
+ {
433
+ "name": "directus-extension-your-name",
434
+ "version": "1.0.0",
435
+ "keywords": ["directus-extension"],
436
+ "directus:extension": {
437
+ "type": "bundle",
438
+ "path": "dist/index.js",
439
+ "source": "src/index.ts",
440
+ "host": "^10.7.0",
441
+ "entries": []
442
+ }
443
+ }
444
+ ```
@@ -453,11 +453,56 @@ export const useCartStore = defineStore('cart', () => {
453
453
  });
454
454
  ```
455
455
 
456
+ ## NuxtUI v4
457
+
458
+ For UI components, forms, and theming with NuxtUI v4:
459
+
460
+ **See:** `references/nuxtui-v4.md`
461
+
462
+ Covers:
463
+ - 130+ production-ready components
464
+ - Forms with validation (Zod, Yup, etc.)
465
+ - Theming and customization
466
+ - Dark/light mode
467
+ - Advanced UI patterns and animations
468
+ - v3 to v4 migration guide
469
+
470
+ ### Quick NuxtUI Example
471
+
472
+ ```bash
473
+ # Create with NuxtUI template
474
+ npm create nuxt@latest my-app -- -t ui
475
+ ```
476
+
477
+ ```vue
478
+ <script setup>
479
+ import { z } from 'zod'
480
+
481
+ const schema = z.object({
482
+ email: z.string().email(),
483
+ })
484
+
485
+ const state = reactive({ email: '' })
486
+ </script>
487
+
488
+ <template>
489
+ <UForm :state="state" :schema="schema" @submit="onSubmit">
490
+ <UFormField label="Email" name="email">
491
+ <UInput v-model="state.email" type="email" />
492
+ </UFormField>
493
+ <UButton type="submit" label="Submit" />
494
+ </UForm>
495
+ </template>
496
+ ```
497
+
498
+ **Important:** NuxtUI v4 requires Nuxt 4 and `<UApp>` wrapper.
499
+
456
500
  ## Resources
457
501
 
458
502
  ### Official Docs
459
503
  - Docs: https://nuxt.com/docs
460
504
  - Modules: https://nuxt.com/modules
505
+ - NuxtUI: https://ui.nuxt.com
461
506
 
462
507
  ### Context7 Queries
463
508
 
@@ -466,4 +511,6 @@ Query: "Nuxt 3 useFetch best practices"
466
511
  Query: "Nuxt 3 server routes authentication"
467
512
  Query: "Nuxt 3 middleware patterns"
468
513
  Query: "Nuxt 3 Pinia setup"
514
+ Query: "NuxtUI v4 components"
515
+ Query: "NuxtUI theming"
469
516
  ```
@@ -0,0 +1,519 @@
1
+ # NuxtUI v4
2
+
3
+ Comprehensive guide for NuxtUI v4 - 130+ production-ready components for Nuxt applications.
4
+
5
+ ## Quick Start
6
+
7
+ ### Installation
8
+
9
+ ```bash
10
+ # Create with template (recommended)
11
+ npm create nuxt@latest my-app -- -t ui
12
+
13
+ # Or add to existing project
14
+ pnpm add @nuxt/ui
15
+ ```
16
+
17
+ ### Configuration
18
+
19
+ ```typescript
20
+ // nuxt.config.ts
21
+ export default defineNuxtConfig({
22
+ modules: ['@nuxt/ui'],
23
+ ui: {
24
+ prefix: 'U', // Component prefix (default)
25
+ colorMode: true, // Enable dark/light mode
26
+ colors: {
27
+ primary: 'green',
28
+ neutral: 'slate',
29
+ },
30
+ },
31
+ });
32
+ ```
33
+
34
+ ```css
35
+ /* app/assets/css/main.css */
36
+ @import "tailwindcss";
37
+ @import "@nuxt/ui";
38
+ ```
39
+
40
+ ```vue
41
+ <!-- app.vue - Required wrapper -->
42
+ <template>
43
+ <UApp>
44
+ <NuxtLayout>
45
+ <NuxtPage />
46
+ </NuxtLayout>
47
+ </UApp>
48
+ </template>
49
+ ```
50
+
51
+ **Important:** `<UApp>` is required for Toast, Tooltip, and Modal to work.
52
+
53
+ ---
54
+
55
+ ## Common Components
56
+
57
+ ### Button
58
+
59
+ ```vue
60
+ <UButton
61
+ label="Click me"
62
+ color="primary" <!-- primary | secondary | success | error | warning | info | neutral -->
63
+ variant="solid" <!-- solid | outline | soft | subtle | ghost | link -->
64
+ size="md" <!-- xs | sm | md | lg | xl -->
65
+ icon="i-lucide-check"
66
+ :loading="isLoading"
67
+ :loading-auto="true" <!-- Auto loading on async @click -->
68
+ @click="handleClick"
69
+ />
70
+
71
+ <!-- Icon only -->
72
+ <UButton icon="i-lucide-settings" square />
73
+
74
+ <!-- As link -->
75
+ <UButton to="/dashboard" label="Dashboard" />
76
+ ```
77
+
78
+ ### Input
79
+
80
+ ```vue
81
+ <UInput
82
+ v-model="value"
83
+ placeholder="Enter text..."
84
+ type="text" <!-- text | email | password | number | etc. -->
85
+ icon="i-lucide-user"
86
+ :loading="isLoading"
87
+ :disabled="false"
88
+ />
89
+
90
+ <!-- With model modifiers -->
91
+ <UInput v-model.nullable="value" /> <!-- Empty -> null -->
92
+ <UInput v-model.optional="value" /> <!-- Empty -> undefined -->
93
+
94
+ <!-- With slots -->
95
+ <UInput v-model="value">
96
+ <template #leading>
97
+ <UIcon name="i-lucide-search" />
98
+ </template>
99
+ <template #trailing>
100
+ <UButton icon="i-lucide-x" size="xs" variant="ghost" />
101
+ </template>
102
+ </UInput>
103
+ ```
104
+
105
+ ### Form with Validation
106
+
107
+ ```vue
108
+ <script setup>
109
+ import { z } from 'zod'
110
+
111
+ const schema = z.object({
112
+ email: z.string().email('Invalid email'),
113
+ password: z.string().min(8, 'Min 8 characters'),
114
+ })
115
+
116
+ const state = reactive({
117
+ email: '',
118
+ password: '',
119
+ })
120
+
121
+ async function onSubmit(data) {
122
+ // data is validated and transformed
123
+ console.log('Valid:', data)
124
+ }
125
+ </script>
126
+
127
+ <template>
128
+ <UForm :state="state" :schema="schema" @submit="onSubmit">
129
+ <UFormField label="Email" name="email" required>
130
+ <UInput v-model="state.email" type="email" />
131
+ </UFormField>
132
+
133
+ <UFormField label="Password" name="password" required>
134
+ <UInput v-model="state.password" type="password" />
135
+ </UFormField>
136
+
137
+ <UButton type="submit" label="Submit" />
138
+ </UForm>
139
+ </template>
140
+ ```
141
+
142
+ ### Modal
143
+
144
+ ```vue
145
+ <template>
146
+ <UModal v-model:open="isOpen" title="Confirm">
147
+ <template #default>
148
+ <UButton @click="isOpen = true" label="Open Modal" />
149
+ </template>
150
+
151
+ <template #content="{ close }">
152
+ <p>Are you sure?</p>
153
+ </template>
154
+
155
+ <template #footer="{ close }">
156
+ <UButton @click="close" label="Cancel" variant="ghost" />
157
+ <UButton @click="confirm" label="Confirm" />
158
+ </template>
159
+ </UModal>
160
+ </template>
161
+
162
+ <!-- Programmatic -->
163
+ <script setup>
164
+ const modal = useModal()
165
+
166
+ function openModal() {
167
+ modal.open(MyModalComponent, { /* props */ })
168
+ }
169
+ </script>
170
+ ```
171
+
172
+ ### Toast
173
+
174
+ ```vue
175
+ <script setup>
176
+ const toast = useToast()
177
+
178
+ function showSuccess() {
179
+ toast.add({
180
+ title: 'Success!',
181
+ description: 'Changes saved.',
182
+ color: 'success',
183
+ icon: 'i-lucide-check-circle',
184
+ })
185
+ }
186
+ </script>
187
+ ```
188
+
189
+ ### Table
190
+
191
+ ```vue
192
+ <script setup>
193
+ const columns = [
194
+ { key: 'name', label: 'Name' },
195
+ { key: 'email', label: 'Email' },
196
+ { key: 'role', label: 'Role' },
197
+ ]
198
+
199
+ const rows = [
200
+ { name: 'John', email: 'john@example.com', role: 'Admin' },
201
+ ]
202
+ </script>
203
+
204
+ <template>
205
+ <UTable :columns="columns" :rows="rows" />
206
+ </template>
207
+ ```
208
+
209
+ ### Select
210
+
211
+ ```vue
212
+ <script setup>
213
+ const options = [
214
+ { value: 'vue', label: 'Vue.js' },
215
+ { value: 'react', label: 'React' },
216
+ ]
217
+ const selected = ref('')
218
+ </script>
219
+
220
+ <template>
221
+ <USelect
222
+ v-model="selected"
223
+ :options="options"
224
+ placeholder="Select..."
225
+ value-attribute="value"
226
+ label-attribute="label"
227
+ />
228
+ </template>
229
+ ```
230
+
231
+ ### Dropdown Menu
232
+
233
+ ```vue
234
+ <template>
235
+ <UDropdownMenu>
236
+ <UButton label="Actions" trailing-icon="i-lucide-chevron-down" />
237
+
238
+ <template #content>
239
+ <UDropdownMenuItem label="Edit" icon="i-lucide-pencil" @click="edit" />
240
+ <UDropdownMenuItem label="Delete" icon="i-lucide-trash" color="error" />
241
+ </template>
242
+ </UDropdownMenu>
243
+ </template>
244
+ ```
245
+
246
+ ### Tabs
247
+
248
+ ```vue
249
+ <script setup>
250
+ const items = [
251
+ { label: 'Overview', value: 'overview' },
252
+ { label: 'Settings', value: 'settings' },
253
+ ]
254
+ const selected = ref('overview')
255
+ </script>
256
+
257
+ <template>
258
+ <UTabs v-model="selected" :items="items">
259
+ <template #overview>Overview content</template>
260
+ <template #settings>Settings content</template>
261
+ </UTabs>
262
+ </template>
263
+ ```
264
+
265
+ ---
266
+
267
+ ## Theming
268
+
269
+ ### Semantic Colors
270
+
271
+ ```typescript
272
+ // app.config.ts
273
+ export default defineAppConfig({
274
+ ui: {
275
+ colors: {
276
+ primary: 'green', // Main brand color
277
+ secondary: 'blue', // Alternative actions
278
+ success: 'emerald', // Success states
279
+ error: 'red', // Error states
280
+ warning: 'amber', // Warnings
281
+ info: 'sky', // Information
282
+ neutral: 'slate', // Text, backgrounds
283
+ },
284
+ },
285
+ })
286
+ ```
287
+
288
+ ### Custom CSS Variables
289
+
290
+ ```css
291
+ /* main.css */
292
+ @import "tailwindcss";
293
+ @import "@nuxt/ui";
294
+
295
+ @theme {
296
+ /* Custom colors */
297
+ --color-brand-500: #10b981;
298
+
299
+ /* Custom fonts */
300
+ --font-sans: 'Inter', system-ui, sans-serif;
301
+ --font-mono: 'Fira Code', monospace;
302
+ }
303
+
304
+ :root {
305
+ --color-gold: #d4af37;
306
+ }
307
+ ```
308
+
309
+ ### Component Customization
310
+
311
+ ```typescript
312
+ // app.config.ts - Global defaults
313
+ export default defineAppConfig({
314
+ ui: {
315
+ button: {
316
+ slots: {
317
+ base: 'font-semibold transition-all',
318
+ },
319
+ defaultVariants: {
320
+ color: 'primary',
321
+ variant: 'solid',
322
+ size: 'md',
323
+ },
324
+ },
325
+ },
326
+ })
327
+ ```
328
+
329
+ ```vue
330
+ <!-- Per-component customization -->
331
+ <UButton
332
+ label="Custom"
333
+ :ui="{
334
+ base: 'rounded-full',
335
+ label: 'font-bold uppercase',
336
+ }"
337
+ />
338
+ ```
339
+
340
+ ### Color Mode (Dark/Light)
341
+
342
+ ```vue
343
+ <template>
344
+ <!-- Toggle button -->
345
+ <UColorModeButton />
346
+
347
+ <!-- Select dropdown -->
348
+ <UColorModeSelect />
349
+
350
+ <!-- Switch -->
351
+ <UColorModeSwitch />
352
+
353
+ <!-- Theme-aware image -->
354
+ <UColorModeImage light="/logo-light.png" dark="/logo-dark.png" />
355
+ </template>
356
+ ```
357
+
358
+ ---
359
+
360
+ ## Advanced UI Patterns
361
+
362
+ ### Page Load Animation
363
+
364
+ ```vue
365
+ <script setup>
366
+ const isVisible = ref(false)
367
+ onMounted(() => {
368
+ setTimeout(() => { isVisible.value = true }, 50)
369
+ })
370
+ </script>
371
+
372
+ <template>
373
+ <div :class="{ 'opacity-0 translate-y-8': !isVisible, 'opacity-100 translate-y-0': isVisible }"
374
+ class="transition-all duration-700 ease-out">
375
+ <!-- Content -->
376
+ </div>
377
+ </template>
378
+ ```
379
+
380
+ ### Staggered Animations
381
+
382
+ ```vue
383
+ <template>
384
+ <div v-for="(item, i) in items" :key="item.id"
385
+ :style="{ '--delay': `${i * 0.1}s` }"
386
+ class="animate-slide-up">
387
+ {{ item.title }}
388
+ </div>
389
+ </template>
390
+
391
+ <style>
392
+ .animate-slide-up {
393
+ animation: slideUp 0.6s ease-out var(--delay) forwards;
394
+ opacity: 0;
395
+ }
396
+
397
+ @keyframes slideUp {
398
+ from { opacity: 0; transform: translateY(20px); }
399
+ to { opacity: 1; transform: translateY(0); }
400
+ }
401
+ </style>
402
+ ```
403
+
404
+ ### Gradient Mesh Background
405
+
406
+ ```vue
407
+ <style>
408
+ .mesh-bg {
409
+ background:
410
+ radial-gradient(ellipse 800px 600px at 20% 20%, rgba(99, 102, 241, 0.15), transparent),
411
+ radial-gradient(ellipse 600px 800px at 80% 80%, rgba(168, 85, 247, 0.15), transparent);
412
+ }
413
+ </style>
414
+ ```
415
+
416
+ ### Hover Effects
417
+
418
+ ```vue
419
+ <style>
420
+ .card-hover {
421
+ transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
422
+ }
423
+
424
+ .card-hover:hover {
425
+ transform: translateY(-4px);
426
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
427
+ }
428
+ </style>
429
+ ```
430
+
431
+ ---
432
+
433
+ ## v3 to v4 Migration
434
+
435
+ ### Component Renames
436
+ | v3 | v4 |
437
+ |----|-----|
438
+ | `UButtonGroup` | `UFieldGroup` |
439
+ | `UPageMarquee` | `UMarquee` |
440
+ | `UPageAccordion` | `UAccordion` |
441
+
442
+ ### Model Modifiers
443
+ | v3 | v4 |
444
+ |----|-----|
445
+ | `.nullify` | `.nullable` or `.optional` |
446
+
447
+ ### Nested Forms
448
+ ```vue
449
+ <!-- v4 requires nested prop and name -->
450
+ <UForm :state="nestedState" nested name="address">
451
+ <!-- fields -->
452
+ </UForm>
453
+ ```
454
+
455
+ ### Module Changes
456
+ - Replace `@nuxt/ui-pro` with `@nuxt/ui`
457
+ - Merge `uiPro` config into `ui` config
458
+
459
+ ---
460
+
461
+ ## Icon Format
462
+
463
+ NuxtUI uses Iconify. Reference icons like:
464
+
465
+ ```vue
466
+ <UIcon name="i-lucide-user" />
467
+ <UButton icon="i-heroicons-check" />
468
+ ```
469
+
470
+ Browse icons: https://icones.js.org
471
+
472
+ ---
473
+
474
+ ## Dashboard Layout
475
+
476
+ ```vue
477
+ <template>
478
+ <UDashboardGroup>
479
+ <UDashboardSidebar>
480
+ <!-- Navigation -->
481
+ </UDashboardSidebar>
482
+
483
+ <UDashboardPanel>
484
+ <UDashboardNavbar>
485
+ <!-- Header -->
486
+ </UDashboardNavbar>
487
+
488
+ <slot />
489
+ </UDashboardPanel>
490
+ </UDashboardGroup>
491
+ </template>
492
+ ```
493
+
494
+ ---
495
+
496
+ ## Best Practices
497
+
498
+ ### DO
499
+ - Use semantic colors (`primary`, not `green-500`)
500
+ - Use `<UApp>` wrapper (required for overlays)
501
+ - Use v4 model modifiers (`.nullable`, `.optional`)
502
+ - Use `:ui` prop for component customization
503
+ - Import styles in `main.css`
504
+
505
+ ### DON'T
506
+ - Use v3 component names (`UButtonGroup`)
507
+ - Use `.nullify` modifier (use `.nullable`)
508
+ - Forget `nested` and `name` props for nested forms
509
+ - Hardcode colors (use semantic colors)
510
+ - Skip `<UApp>` wrapper
511
+
512
+ ---
513
+
514
+ ## Resources
515
+
516
+ - Components: https://ui.nuxt.com/docs/components
517
+ - Theming: https://ui.nuxt.com/docs/getting-started/theme
518
+ - Icons: https://icones.js.org
519
+ - Tailwind Colors: https://tailwindcss.com/docs/customizing-colors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codihaus/claude-skills",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Claude Code skills for software development workflow",
5
5
  "main": "src/index.js",
6
6
  "bin": {