Add nuxt-skills and update auto quotes to use new policy API structure
- Add nuxt-skills (vue, nuxt, nuxt-ui) to .claude/skills/ - Create useCustomerSelection() composable for managing insured/buyer selection - Create usePolicyApi() composable for policy API operations - Update auto quote components to use insured/buyer instead of client - Update vehicle fields: remove valorVehiculo, add market_value, requested_value, rc_limits - Make chassis_number and engine_number optional - Update auto quote types and composables to match new API structure - Update auto quote page to submit to policy API with new structure
This commit is contained in:
161
.claude/skills/nuxt-ui/references/components.md
Normal file
161
.claude/skills/nuxt-ui/references/components.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# Components
|
||||
|
||||
> Auto-generated from Nuxt UI docs. Run `npx tsx skills/nuxt-ui/scripts/generate-components.ts` to update.
|
||||
|
||||
> **For headless primitives (API, accessibility):** see `reka-ui` skill
|
||||
|
||||
## Data
|
||||
|
||||
| Component | Description |
|
||||
| --------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| [Accordion](components/accordion.md) | A stacked set of collapsible panels. |
|
||||
| [Carousel](components/carousel.md) | A carousel with motion and swipe built using Embla. |
|
||||
| [Empty](components/empty.md) | A component to display an empty state. (v4.1+) |
|
||||
| [Marquee](components/marquee.md) | A component to create infinite scrolling content. |
|
||||
| [ScrollArea](components/scroll-area.md) | A flexible scroll container with virtualization support. (v4.3+) |
|
||||
| [Table](components/table.md) | A responsive table element to display data in rows and columns. |
|
||||
| [Timeline](components/timeline.md) | A component that displays a sequence of events with dates, titles, icons or avatars. |
|
||||
| [Tree](components/tree.md) | A tree view component to display and interact with hierarchical data structures. |
|
||||
| [User](components/user.md) | Display user information with name, description and avatar. |
|
||||
|
||||
## Element
|
||||
|
||||
| Component | Description |
|
||||
| ----------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| [Alert](components/alert.md) | A callout to draw user's attention. |
|
||||
| [Avatar](components/avatar.md) | An img element with fallback and Nuxt Image support. |
|
||||
| [AvatarGroup](components/avatar-group.md) | Stack multiple avatars in a group. |
|
||||
| [Badge](components/badge.md) | A short text to represent a status or a category. |
|
||||
| [Banner](components/banner.md) | Display a banner at the top of your website to inform users about important information. |
|
||||
| [Button](components/button.md) | A button element that can act as a link or trigger an action. |
|
||||
| [Calendar](components/calendar.md) | A calendar component for selecting single dates, multiple dates or date ranges. |
|
||||
| [Card](components/card.md) | Display content in a card with a header, body and footer. |
|
||||
| [Chip](components/chip.md) | An indicator of a numeric value or a state. |
|
||||
| [Collapsible](components/collapsible.md) | A collapsible element to toggle visibility of its content. |
|
||||
| [FieldGroup](components/field-group.md) | Group multiple button-like elements together. |
|
||||
| [Icon](components/icon.md) | A component to display any icon from Iconify or another component. |
|
||||
| [Kbd](components/kbd.md) | A kbd element to display a keyboard key. |
|
||||
| [Progress](components/progress.md) | An indicator showing the progress of a task. |
|
||||
| [Separator](components/separator.md) | Separates content horizontally or vertically. |
|
||||
| [Skeleton](components/skeleton.md) | A placeholder to show while content is loading. |
|
||||
|
||||
## Form
|
||||
|
||||
| Component | Description |
|
||||
| --------------------------------------------- | ------------------------------------------------------------------------ |
|
||||
| [Checkbox](components/checkbox.md) | An input element to toggle between checked and unchecked states. |
|
||||
| [CheckboxGroup](components/checkbox-group.md) | A set of checklist buttons to select multiple option from a list. |
|
||||
| [ColorPicker](components/color-picker.md) | A component to select a color. |
|
||||
| [FileUpload](components/file-upload.md) | An input element to upload files. |
|
||||
| [Form](components/form.md) | A form component with built-in validation and submission handling. |
|
||||
| [FormField](components/form-field.md) | A wrapper for form elements that provides validation and error handling. |
|
||||
| [Input](components/input.md) | An input element to enter text. |
|
||||
| [InputDate](components/input-date.md) | An input component for date selection. (v4.2+) |
|
||||
| [InputMenu](components/input-menu.md) | An autocomplete input with real-time suggestions. |
|
||||
| [InputNumber](components/input-number.md) | An input for numerical values with a customizable range. |
|
||||
| [InputTags](components/input-tags.md) | An input element that displays interactive tags. |
|
||||
| [InputTime](components/input-time.md) | An input for selecting a time. (v4.2+) |
|
||||
| [PinInput](components/pin-input.md) | An input element to enter a pin. |
|
||||
| [RadioGroup](components/radio-group.md) | A set of radio buttons to select a single option from a list. |
|
||||
| [Select](components/select.md) | A select element to choose from a list of options. |
|
||||
| [SelectMenu](components/select-menu.md) | An advanced searchable select element. |
|
||||
| [Slider](components/slider.md) | An input to select a numeric value within a range. |
|
||||
| [Switch](components/switch.md) | A control that toggles between two states. |
|
||||
| [Textarea](components/textarea.md) | A textarea element to input multi-line text. |
|
||||
|
||||
## Layout
|
||||
|
||||
| Component | Description |
|
||||
| ------------------------------------ | -------------------------------------------------------------------- |
|
||||
| [App](components/app.md) | Wraps your app to provide global configurations and more. |
|
||||
| [Container](components/container.md) | A container lets you center and constrain the width of your content. |
|
||||
| [Error](components/error.md) | A pre-built error component with NuxtError support. |
|
||||
| [Footer](components/footer.md) | A responsive footer component. |
|
||||
| [Header](components/header.md) | A responsive header component. |
|
||||
| [Main](components/main.md) | A main element that fills the available viewport height. |
|
||||
|
||||
## Navigation
|
||||
|
||||
| Component | Description |
|
||||
| ----------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| [Breadcrumb](components/breadcrumb.md) | A hierarchy of links to navigate through a website. |
|
||||
| [CommandPalette](components/command-palette.md) | A command palette with full-text search powered by Fuse.js for efficient fuzzy matching. |
|
||||
| [FooterColumns](components/footer-columns.md) | A list of links as columns to display in your Footer. |
|
||||
| [Link](components/link.md) | A wrapper around <NuxtLink> with extra props. |
|
||||
| [NavigationMenu](components/navigation-menu.md) | A list of links that can be displayed horizontally or vertically. |
|
||||
| [Pagination](components/pagination.md) | A list of buttons or links to navigate through pages. |
|
||||
| [Stepper](components/stepper.md) | A set of steps that are used to indicate progress through a multi-step process. |
|
||||
| [Tabs](components/tabs.md) | A set of tab panels that are displayed one at a time. |
|
||||
|
||||
## Other
|
||||
|
||||
| Component | Description | Version |
|
||||
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| [AuthForm](components/auth-form.md) | A customizable Form to create login, register or password reset forms. | |
|
||||
| [BlogPost](components/blog-post.md) | A customizable article to display in a blog page. | |
|
||||
| [BlogPosts](components/blog-posts.md) | Display a list of blog posts in a responsive grid layout. | |
|
||||
| [ChangelogVersion](components/changelog-version.md) | A customizable article to display in a changelog. | |
|
||||
| [ChangelogVersions](components/changelog-versions.md) | Display a list of changelog versions in a timeline. | |
|
||||
| [ChatMessage](components/chat-message.md) | Display a chat message with icon, avatar, and actions. | |
|
||||
| [ChatMessages](components/chat-messages.md) | Display a list of chat messages, designed to work seamlessly with Vercel AI SDK. | |
|
||||
| [ChatPalette](components/chat-palette.md) | A chat palette to create a chatbot interface inside an overlay. | |
|
||||
| [ChatPrompt](components/chat-prompt.md) | An enhanced Textarea for submitting prompts in AI chat interfaces. | |
|
||||
| [ChatPromptSubmit](components/chat-prompt-submit.md) | A Button for submitting chat prompts with automatic status handling. | |
|
||||
| [ColorModeAvatar](components/color-mode-avatar.md) | An Avatar with a different source for light and dark mode. | |
|
||||
| [ColorModeButton](components/color-mode-button.md) | A Button to switch between light and dark mode. | |
|
||||
| [ColorModeImage](components/color-mode-image.md) | An image element with a different source for light and dark mode. | |
|
||||
| [ColorModeSelect](components/color-mode-select.md) | A Select to switch between system, dark & light mode. | |
|
||||
| [ColorModeSwitch](components/color-mode-switch.md) | A switch to toggle between light and dark mode. | |
|
||||
| [ContentNavigation](components/content-navigation.md) | An accordion-style navigation component for organizing page links. | |
|
||||
| [ContentSearch](components/content-search.md) | A ready to use CommandPalette to add to your documentation. | |
|
||||
| [ContentSearchButton](components/content-search-button.md) | A pre-styled Button to open the ContentSearch modal. | |
|
||||
| [ContentSurround](components/content-surround.md) | A pair of prev and next links to navigate between pages. | |
|
||||
| [ContentToc](components/content-toc.md) | A sticky Table of Contents with automatic active anchor link highlighting. | |
|
||||
| [DashboardGroup](components/dashboard-group.md) | A fixed layout component that provides context for dashboard components with sidebar state management and persistence. | |
|
||||
| [DashboardNavbar](components/dashboard-navbar.md) | A responsive navbar to display in a dashboard. | |
|
||||
| [DashboardPanel](components/dashboard-panel.md) | A resizable panel to display in a dashboard. | |
|
||||
| [DashboardResizeHandle](components/dashboard-resize-handle.md) | A handle to resize a sidebar or panel. | |
|
||||
| [DashboardSearch](components/dashboard-search.md) | A ready to use CommandPalette to add to your dashboard. | |
|
||||
| [DashboardSearchButton](components/dashboard-search-button.md) | A pre-styled Button to open the DashboardSearch modal. | |
|
||||
| [DashboardSidebar](components/dashboard-sidebar.md) | A resizable and collapsible sidebar to display in a dashboard. | |
|
||||
| [DashboardSidebarCollapse](components/dashboard-sidebar-collapse.md) | A Button to collapse the sidebar on desktop. | |
|
||||
| [DashboardSidebarToggle](components/dashboard-sidebar-toggle.md) | A Button to toggle the sidebar on mobile. | |
|
||||
| [DashboardToolbar](components/dashboard-toolbar.md) | A toolbar to display under the navbar in a dashboard. | |
|
||||
| [Editor](components/editor.md) | A rich text editor component based on TipTap with support for markdown, HTML, and JSON content types. (v4.3+) | v4.3+ |
|
||||
| [EditorDragHandle](components/editor-drag-handle.md) | A draggable handle for reordering and selecting blocks in the editor. (v4.3+) | v4.3+ |
|
||||
| [EditorEmojiMenu](components/editor-emoji-menu.md) | An emoji picker menu that displays emoji suggestions when typing the : character in the editor. (v4.3+) | v4.3+ |
|
||||
| [EditorMentionMenu](components/editor-mention-menu.md) | A mention menu that displays user suggestions when typing the @ character in the editor. (v4.3+) | v4.3+ |
|
||||
| [EditorSuggestionMenu](components/editor-suggestion-menu.md) | A command menu that displays formatting and action suggestions when typing the / character in the editor. (v4.3+) | v4.3+ |
|
||||
| [EditorToolbar](components/editor-toolbar.md) | A customizable toolbar for editor actions that can be displayed as fixed, bubble, or floating menu. (v4.3+) | v4.3+ |
|
||||
| [LocaleSelect](components/locale-select.md) | A Select to switch between locales. | |
|
||||
| [Page](components/page.md) | A grid layout for your pages with left and right columns. | |
|
||||
| [PageAnchors](components/page-anchors.md) | A list of anchors to be displayed in the page. | |
|
||||
| [PageAside](components/page-aside.md) | A sticky aside to display your page navigation. | |
|
||||
| [PageBody](components/page-body.md) | The main content of your page. | |
|
||||
| [PageCard](components/page-card.md) | A pre-styled card component that displays a title, description and optional link. | |
|
||||
| [PageColumns](components/page-columns.md) | A responsive multi-column layout system for organizing content side-by-side. | |
|
||||
| [PageCta](components/page-cta.md) | A call to action section to display in your pages. | |
|
||||
| [PageFeature](components/page-feature.md) | A component to showcase key features of your application. | |
|
||||
| [PageGrid](components/page-grid.md) | A responsive grid system for displaying content in a flexible layout. | |
|
||||
| [PageHeader](components/page-header.md) | A responsive header for your pages. | |
|
||||
| [PageHero](components/page-hero.md) | A responsive hero for your pages. | |
|
||||
| [PageLinks](components/page-links.md) | A list of links to be displayed in the page. | |
|
||||
| [PageList](components/page-list.md) | A vertical list layout for displaying content in a stacked format. | |
|
||||
| [PageLogos](components/page-logos.md) | A list of logos or images to display on your pages. | |
|
||||
| [PageSection](components/page-section.md) | A responsive section for your pages. | |
|
||||
| [PricingPlan](components/pricing-plan.md) | A customizable pricing plan to display in a pricing page. | |
|
||||
| [PricingPlans](components/pricing-plans.md) | Display a list of pricing plans in a responsive grid layout. | |
|
||||
| [PricingTable](components/pricing-table.md) | A responsive pricing table component that displays tiered pricing plans with feature comparisons. | |
|
||||
|
||||
## Overlay
|
||||
|
||||
| Component | Description |
|
||||
| ------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| [ContextMenu](components/context-menu.md) | A menu to display actions when right-clicking on an element. |
|
||||
| [Drawer](components/drawer.md) | A drawer that smoothly slides in & out of the screen. |
|
||||
| [DropdownMenu](components/dropdown-menu.md) | A menu to display actions when clicking on an element. |
|
||||
| [Modal](components/modal.md) | A dialog window that can be used to display a message or request user input. |
|
||||
| [Popover](components/popover.md) | A non-modal dialog that floats around a trigger element. |
|
||||
| [Slideover](components/slideover.md) | A dialog that slides in from any side of the screen. |
|
||||
| [Toast](components/toast.md) | A succinct message to provide information or feedback to the user. |
|
||||
| [Tooltip](components/tooltip.md) | A popup that reveals information when hovering over an element. |
|
||||
328
.claude/skills/nuxt-ui/references/composables.md
Normal file
328
.claude/skills/nuxt-ui/references/composables.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# Composables
|
||||
|
||||
## useToast
|
||||
|
||||
Show notifications. Requires `<UApp>` wrapper.
|
||||
|
||||
```ts
|
||||
const toast = useToast()
|
||||
|
||||
// Add toast
|
||||
toast.add({
|
||||
title: 'Success',
|
||||
description: 'Item saved',
|
||||
color: 'success',
|
||||
icon: 'i-heroicons-check-circle',
|
||||
timeout: 5000
|
||||
})
|
||||
|
||||
// Remove specific toast
|
||||
toast.remove('toast-id')
|
||||
|
||||
// Clear all toasts
|
||||
toast.clear()
|
||||
```
|
||||
|
||||
See overlays.md for full toast options.
|
||||
|
||||
## useOverlay
|
||||
|
||||
Programmatically create modals, slidelovers, drawers.
|
||||
|
||||
```ts
|
||||
const overlay = useOverlay()
|
||||
|
||||
// Create modal
|
||||
const modal = overlay.create(MyModalComponent, {
|
||||
props: { title: 'Confirm' },
|
||||
modal: true // Default: true
|
||||
})
|
||||
|
||||
// Wait for result
|
||||
const result = await modal.result
|
||||
|
||||
// Close programmatically
|
||||
modal.close(returnValue)
|
||||
```
|
||||
|
||||
### Confirm Dialog Pattern
|
||||
|
||||
```vue
|
||||
<!-- ConfirmDialog.vue -->
|
||||
<script setup>
|
||||
const props = defineProps<{ title: string; message: string }>()
|
||||
const emit = defineEmits(['confirm', 'cancel'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal :open="true">
|
||||
<template #header>{{ title }}</template>
|
||||
<p>{{ message }}</p>
|
||||
<template #footer>
|
||||
<UButton variant="ghost" @click="emit('cancel')">Cancel</UButton>
|
||||
<UButton color="error" @click="emit('confirm')">Confirm</UButton>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
```
|
||||
|
||||
```ts
|
||||
// Usage
|
||||
const overlay = useOverlay()
|
||||
|
||||
async function confirmDelete() {
|
||||
const modal = overlay.create(ConfirmDialog, {
|
||||
props: { title: 'Delete?', message: 'This cannot be undone.' },
|
||||
events: {
|
||||
confirm: () => modal.close(true),
|
||||
cancel: () => modal.close(false)
|
||||
}
|
||||
})
|
||||
|
||||
if (await modal.result) {
|
||||
// Delete item
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## defineShortcuts
|
||||
|
||||
Define keyboard shortcuts.
|
||||
|
||||
```ts
|
||||
defineShortcuts({
|
||||
// Single key
|
||||
escape: () => closeModal(),
|
||||
|
||||
// Modifier + key (meta = Cmd on Mac, Ctrl on Windows)
|
||||
meta_k: () => openSearch(),
|
||||
meta_shift_p: () => openCommandPalette(),
|
||||
|
||||
// Ctrl specific
|
||||
ctrl_s: () => save(),
|
||||
|
||||
// Alt/Option
|
||||
alt_n: () => newItem(),
|
||||
|
||||
// Arrow keys
|
||||
arrowup: () => navigateUp(),
|
||||
arrowdown: () => navigateDown(),
|
||||
|
||||
// With condition
|
||||
meta_enter: {
|
||||
handler: () => submit(),
|
||||
whenever: [isFormValid]
|
||||
}
|
||||
}, {
|
||||
layoutIndependent: true // Ignore keyboard layout (v4.3+)
|
||||
})
|
||||
```
|
||||
|
||||
### Shortcut Syntax
|
||||
|
||||
| Key | Meaning |
|
||||
| ------- | -------------------------- |
|
||||
| `meta` | Cmd (Mac) / Ctrl (Windows) |
|
||||
| `ctrl` | Ctrl key |
|
||||
| `alt` | Alt / Option key |
|
||||
| `shift` | Shift key |
|
||||
| `_` | Key separator |
|
||||
|
||||
### Extract Shortcuts (for display)
|
||||
|
||||
```ts
|
||||
const shortcuts = extractShortcuts({
|
||||
meta_k: () => {},
|
||||
escape: () => {}
|
||||
})
|
||||
// Returns: { meta_k: { key: 'K', metaKey: true }, ... }
|
||||
```
|
||||
|
||||
## useKbd
|
||||
|
||||
Detect current keyboard state.
|
||||
|
||||
```ts
|
||||
const kbd = useKbd()
|
||||
|
||||
// Reactive state
|
||||
kbd.meta // true if Cmd/Ctrl pressed
|
||||
kbd.ctrl // true if Ctrl pressed
|
||||
kbd.shift // true if Shift pressed
|
||||
kbd.alt // true if Alt/Option pressed
|
||||
```
|
||||
|
||||
## useScrollspy
|
||||
|
||||
Track scroll position for anchor navigation.
|
||||
|
||||
```ts
|
||||
const { activeId } = useScrollspy({
|
||||
ids: ['section-1', 'section-2', 'section-3'],
|
||||
offset: 100 // Pixels from top
|
||||
})
|
||||
|
||||
// activeId.value = 'section-2' (currently visible)
|
||||
```
|
||||
|
||||
### With Table of Contents
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const sections = ['intro', 'features', 'pricing']
|
||||
const { activeId } = useScrollspy({ ids: sections })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav>
|
||||
<a
|
||||
v-for="id in sections"
|
||||
:href="`#${id}`"
|
||||
:class="{ 'font-bold': activeId === id }"
|
||||
>
|
||||
{{ id }}
|
||||
</a>
|
||||
</nav>
|
||||
</template>
|
||||
```
|
||||
|
||||
## useFileUpload
|
||||
|
||||
Handle file uploads.
|
||||
|
||||
```ts
|
||||
const { files, open, reset, remove } = useFileUpload({
|
||||
accept: 'image/*',
|
||||
multiple: true,
|
||||
maxFiles: 5,
|
||||
maxSize: 5 * 1024 * 1024 // 5MB
|
||||
})
|
||||
|
||||
// Open file picker
|
||||
open()
|
||||
|
||||
// Files selected
|
||||
files.value // FileList
|
||||
|
||||
// Reset selection
|
||||
reset()
|
||||
|
||||
// Remove specific file
|
||||
remove(index)
|
||||
```
|
||||
|
||||
### With UFileUpload
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const { files, open, reset } = useFileUpload()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UFileUpload v-model="files" accept="image/*" @change="handleFiles">
|
||||
<template #default="{ dragover }">
|
||||
<div :class="{ 'border-primary': dragover }">
|
||||
Drop files here or <UButton @click="open">Browse</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UFileUpload>
|
||||
</template>
|
||||
```
|
||||
|
||||
## defineLocale
|
||||
|
||||
Define/extend locale for i18n.
|
||||
|
||||
```ts
|
||||
// locales/es.ts
|
||||
export default defineLocale({
|
||||
name: 'Español',
|
||||
code: 'es',
|
||||
messages: {
|
||||
select: {
|
||||
placeholder: 'Seleccionar...',
|
||||
noResults: 'Sin resultados'
|
||||
},
|
||||
pagination: {
|
||||
previous: 'Anterior',
|
||||
next: 'Siguiente'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Extend Existing Locale
|
||||
|
||||
```ts
|
||||
import en from '@nuxt/ui/locale/en'
|
||||
|
||||
export default extendLocale(en, {
|
||||
messages: {
|
||||
select: {
|
||||
placeholder: 'Choose an option...'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Use in App
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import es from '~/locales/es'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :locale="es">
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
## useFormField
|
||||
|
||||
Access form field context in custom components.
|
||||
|
||||
```ts
|
||||
// Inside custom form component
|
||||
const { name, error, disabled } = useFormField()
|
||||
|
||||
// name: field name from UFormField
|
||||
// error: validation error message
|
||||
// disabled: if field is disabled
|
||||
```
|
||||
|
||||
### Custom Input Component
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const props = defineProps<{ modelValue: string }>()
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const { error } = useFormField()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
:value="modelValue"
|
||||
:class="{ 'border-error': error }"
|
||||
@input="emit('update:modelValue', $event.target.value)"
|
||||
/>
|
||||
<span v-if="error" class="text-error">{{ error }}</span>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Composable | Purpose |
|
||||
| ------------------ | ------------------------------- |
|
||||
| `useToast` | Show notifications |
|
||||
| `useOverlay` | Programmatic modals/slidelovers |
|
||||
| `defineShortcuts` | Keyboard shortcuts |
|
||||
| `useKbd` | Keyboard state detection |
|
||||
| `useScrollspy` | Track scroll for TOC |
|
||||
| `useFileUpload` | File upload handling |
|
||||
| `defineLocale` | i18n locale definition |
|
||||
| `extendLocale` | Extend existing locale |
|
||||
| `useFormField` | Form field context |
|
||||
| `extractShortcuts` | Parse shortcut definitions |
|
||||
328
.claude/skills/nuxt-ui/references/forms.md
Normal file
328
.claude/skills/nuxt-ui/references/forms.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# Forms
|
||||
|
||||
## Form Components
|
||||
|
||||
| Component | Purpose | Key Props | Version |
|
||||
| ---------------- | ------------------- | --------------------------------------------- | ------- |
|
||||
| `UInput` | Text input | `type`, `placeholder`, `icon`, `loading` | |
|
||||
| `UTextarea` | Multi-line text | `rows`, `autoresize`, `maxrows` | |
|
||||
| `USelect` | Native select | `options`, `placeholder` | |
|
||||
| `USelectMenu` | Custom select | `options`, `searchable`, `multiple` | |
|
||||
| `UInputMenu` | Autocomplete | `options`, `searchable` | |
|
||||
| `UCheckbox` | Single checkbox | `label`, `description` | |
|
||||
| `UCheckboxGroup` | Multiple checkboxes | `items`, `orientation` | |
|
||||
| `URadioGroup` | Radio options | `items`, `orientation` | |
|
||||
| `USwitch` | Toggle switch | `label`, `description`, `on-icon`, `off-icon` | |
|
||||
| `USlider` | Range slider | `min`, `max`, `step` | |
|
||||
| `UPinInput` | Code/OTP input | `length`, `type`, `mask` | |
|
||||
| `UInputNumber` | Number input | `min`, `max`, `step`, `format-options` | |
|
||||
| `UInputDate` | Date picker | `mode`, `range`, `locale` | v4.2+ |
|
||||
| `UInputTime` | Time picker | `hour-cycle`, `minute-step` | v4.2+ |
|
||||
| `UInputTags` | Tag input | `max`, `placeholder` | |
|
||||
| `UColorPicker` | Color selection | `format`, `swatches` | |
|
||||
| `UFileUpload` | File upload | `accept`, `multiple`, `max-files` | |
|
||||
|
||||
## Basic Input Examples
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const email = ref('')
|
||||
const bio = ref('')
|
||||
const country = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Text input -->
|
||||
<UInput v-model="email" type="email" placeholder="Email" icon="i-heroicons-envelope" />
|
||||
|
||||
<!-- With validation state -->
|
||||
<UInput v-model="email" :color="emailError ? 'error' : undefined" />
|
||||
|
||||
<!-- Textarea -->
|
||||
<UTextarea v-model="bio" placeholder="Bio" :rows="3" autoresize />
|
||||
|
||||
<!-- Select -->
|
||||
<USelect v-model="country" :options="['USA', 'Canada', 'Mexico']" placeholder="Country" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## SelectMenu (Custom Dropdown)
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const selected = ref()
|
||||
const options = [
|
||||
{ label: 'John', value: 'john', avatar: { src: '/john.png' } },
|
||||
{ label: 'Jane', value: 'jane', avatar: { src: '/jane.png' } }
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
v-model="selected"
|
||||
:options="options"
|
||||
searchable
|
||||
clear
|
||||
placeholder="Select user"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<UAvatar v-bind="option.avatar" size="xs" />
|
||||
<span>{{ option.label }}</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
|
||||
### SelectMenu/InputMenu Props (v4.4+)
|
||||
|
||||
```vue
|
||||
<USelectMenu
|
||||
v-model="selected"
|
||||
:options="options"
|
||||
clear <!-- Add clear button (v4.4+) -->
|
||||
:viewport-ref="ref" <!-- For infinite scroll (v4.4+) -->
|
||||
/>
|
||||
```
|
||||
|
||||
## Checkbox & Radio
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const agreed = ref(false)
|
||||
const plan = ref('free')
|
||||
const features = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Single checkbox -->
|
||||
<UCheckbox v-model="agreed" label="I agree to terms" description="Required" />
|
||||
|
||||
<!-- Radio group -->
|
||||
<URadioGroup
|
||||
v-model="plan"
|
||||
:items="[
|
||||
{ label: 'Free', value: 'free', description: '$0/mo' },
|
||||
{ label: 'Pro', value: 'pro', description: '$10/mo' }
|
||||
]"
|
||||
/>
|
||||
|
||||
<!-- Checkbox group -->
|
||||
<UCheckboxGroup
|
||||
v-model="features"
|
||||
:items="[
|
||||
{ label: 'Dark mode', value: 'dark' },
|
||||
{ label: 'Notifications', value: 'notifications' }
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Form Validation
|
||||
|
||||
Uses Standard Schema (Zod, Valibot, Yup, Joi, etc.)
|
||||
|
||||
### With Zod
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email('Invalid email'),
|
||||
password: z.string().min(8, 'Min 8 characters')
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = reactive<Partial<Schema>>({
|
||||
email: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
const form = ref()
|
||||
|
||||
async function onSubmit() {
|
||||
await form.value.validate()
|
||||
// Submit logic
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm ref="form" :schema="schema" :state="state" @submit="onSubmit">
|
||||
<UFormField name="email" label="Email">
|
||||
<UInput v-model="state.email" type="email" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="password" label="Password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit">Submit</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
|
||||
### With Valibot
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import * as v from 'valibot'
|
||||
|
||||
const schema = v.object({
|
||||
email: v.pipe(v.string(), v.email('Invalid email')),
|
||||
password: v.pipe(v.string(), v.minLength(8, 'Min 8 characters'))
|
||||
})
|
||||
|
||||
type Schema = v.InferOutput<typeof schema>
|
||||
|
||||
const state = reactive<Partial<Schema>>({
|
||||
email: '',
|
||||
password: ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm :schema="schema" :state="state" @submit="onSubmit">
|
||||
<!-- Same as above -->
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
|
||||
## UFormField Props
|
||||
|
||||
```vue
|
||||
<UFormField
|
||||
name="email" <!-- Field name (matches state key) -->
|
||||
label="Email" <!-- Label text -->
|
||||
description="Your email" <!-- Help text -->
|
||||
hint="Optional" <!-- Right-aligned hint -->
|
||||
required <!-- Shows asterisk -->
|
||||
:help="error?.message" <!-- Error message -->
|
||||
>
|
||||
<UInput v-model="state.email" />
|
||||
</UFormField>
|
||||
```
|
||||
|
||||
## UFieldGroup (Group Fields)
|
||||
|
||||
```vue
|
||||
<UFieldGroup label="Name">
|
||||
<UInput v-model="firstName" placeholder="First" />
|
||||
<UInput v-model="lastName" placeholder="Last" />
|
||||
</UFieldGroup>
|
||||
```
|
||||
|
||||
## Input States
|
||||
|
||||
```vue
|
||||
<!-- Disabled -->
|
||||
<UInput disabled placeholder="Disabled" />
|
||||
|
||||
<!-- Loading -->
|
||||
<UInput :loading="true" placeholder="Loading..." />
|
||||
|
||||
<!-- With icon -->
|
||||
<UInput icon="i-heroicons-magnifying-glass" placeholder="Search" />
|
||||
|
||||
<!-- Trailing icon -->
|
||||
<UInput trailing-icon="i-heroicons-x-mark" placeholder="Clearable" />
|
||||
```
|
||||
|
||||
## File Upload
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const { files, open, reset } = useFileUpload()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UFileUpload v-model="files" accept="image/*" multiple :max-files="5">
|
||||
<UButton @click="open">Upload</UButton>
|
||||
</UFileUpload>
|
||||
</template>
|
||||
```
|
||||
|
||||
**Note (v4.4):** FileUpload now emits `null` when clearing files (previously emitted empty array).
|
||||
|
||||
## Date & Time Pickers (v4.2+)
|
||||
|
||||
### Date Picker
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const date = ref(new Date())
|
||||
const range = ref({ start: new Date(), end: new Date() })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Single date -->
|
||||
<UInputDate v-model="date" />
|
||||
|
||||
<!-- Date range -->
|
||||
<UInputDate v-model="range" mode="range" />
|
||||
|
||||
<!-- With locale -->
|
||||
<UInputDate v-model="date" locale="es" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### Time Picker
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { Time } from '@internationalized/date'
|
||||
|
||||
const time = ref(new Time(12, 0))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Basic time picker -->
|
||||
<UInputTime v-model="time" />
|
||||
|
||||
<!-- 24-hour format -->
|
||||
<UInputTime v-model="time" hour-cycle="24" />
|
||||
|
||||
<!-- Custom step (minutes) -->
|
||||
<UInputTime v-model="time" :minute-step="15" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Login Form
|
||||
|
||||
```vue
|
||||
<UForm :schema="loginSchema" :state="state" @submit="login">
|
||||
<UFormField name="email" label="Email" required>
|
||||
<UInput v-model="state.email" type="email" icon="i-heroicons-envelope" />
|
||||
</UFormField>
|
||||
<UFormField name="password" label="Password" required>
|
||||
<UInput v-model="state.password" type="password" icon="i-heroicons-lock-closed" />
|
||||
</UFormField>
|
||||
<UButton type="submit" block :loading="loading">Sign in</UButton>
|
||||
</UForm>
|
||||
```
|
||||
|
||||
### Settings Form
|
||||
|
||||
```vue
|
||||
<UForm :state="settings" @submit="save">
|
||||
<UFormField name="notifications" label="Notifications">
|
||||
<USwitch v-model="settings.notifications" label="Enable notifications" />
|
||||
</UFormField>
|
||||
<UFormField name="theme" label="Theme">
|
||||
<URadioGroup v-model="settings.theme" :items="themeOptions" />
|
||||
</UFormField>
|
||||
<UButton type="submit">Save</UButton>
|
||||
</UForm>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
| Do | Don't |
|
||||
| --------------------------------- | -------------------------- |
|
||||
| Use UForm + UFormField | Build custom form wrappers |
|
||||
| Use Standard Schema (Zod/Valibot) | Write custom validation |
|
||||
| Use v-model on form inputs | Manually sync form state |
|
||||
| Use `required` prop for asterisks | Add asterisks manually |
|
||||
| Use `description` for help text | Use separate help elements |
|
||||
214
.claude/skills/nuxt-ui/references/installation.md
Normal file
214
.claude/skills/nuxt-ui/references/installation.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# Installation
|
||||
|
||||
## Nuxt Installation
|
||||
|
||||
```bash
|
||||
pnpm add @nuxt/ui
|
||||
```
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxt/ui'],
|
||||
css: ['~/assets/css/main.css']
|
||||
})
|
||||
```
|
||||
|
||||
```css
|
||||
/* assets/css/main.css */
|
||||
@import 'tailwindcss';
|
||||
@import '@nuxt/ui';
|
||||
```
|
||||
|
||||
**Critical**: Wrap app in UApp for Toast, Tooltip, and overlays to work:
|
||||
|
||||
```vue
|
||||
<!-- app.vue -->
|
||||
<template>
|
||||
<UApp>
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
### pnpm Gotcha
|
||||
|
||||
If using pnpm, either:
|
||||
|
||||
1. Add `shamefully-hoist=true` to `.npmrc`, OR
|
||||
2. Install tailwindcss explicitly: `pnpm add tailwindcss`
|
||||
|
||||
## Vue Installation (Vite)
|
||||
|
||||
```bash
|
||||
pnpm add @nuxt/ui
|
||||
```
|
||||
|
||||
```ts
|
||||
import ui from '@nuxt/ui/vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
// vite.config.ts
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue(), ui()]
|
||||
})
|
||||
```
|
||||
|
||||
```ts
|
||||
import ui from '@nuxt/ui/vue-plugin'
|
||||
// main.ts
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import './assets/main.css'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(ui)
|
||||
app.mount('#app')
|
||||
```
|
||||
|
||||
```css
|
||||
/* assets/main.css */
|
||||
@import 'tailwindcss';
|
||||
@import '@nuxt/ui';
|
||||
```
|
||||
|
||||
**Critical**: Add `isolate` class to root for overlay stacking:
|
||||
|
||||
```vue
|
||||
<!-- App.vue -->
|
||||
<template>
|
||||
<div class="isolate">
|
||||
<UApp>
|
||||
<RouterView />
|
||||
</UApp>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Auto-imports
|
||||
|
||||
Vue generates `auto-imports.d.ts` and `components.d.ts`. Add to `.gitignore`:
|
||||
|
||||
```gitignore
|
||||
auto-imports.d.ts
|
||||
components.d.ts
|
||||
```
|
||||
|
||||
## Module Options
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxt/ui'],
|
||||
ui: {
|
||||
prefix: 'U', // Component prefix (default 'U')
|
||||
fonts: true, // Enable @nuxt/fonts
|
||||
colorMode: true, // Enable @nuxtjs/color-mode
|
||||
theme: {
|
||||
colors: ['primary', 'secondary', 'success', 'info', 'warning', 'error', 'neutral'],
|
||||
transitions: true, // transition-colors on components
|
||||
defaultVariants: {
|
||||
color: 'primary',
|
||||
size: 'md'
|
||||
},
|
||||
prefix: '' // Tailwind CSS prefix (v4.2+) - ensures prefixed utilities work
|
||||
},
|
||||
mdc: false, // Force Prose components
|
||||
content: false, // Force UContent* components
|
||||
experimental: {
|
||||
componentDetection: false // Tree-shake unused components (v4.1+) - auto-generates CSS only for used components
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Vue Vite Options
|
||||
|
||||
```ts
|
||||
// vite.config.ts
|
||||
ui({
|
||||
prefix: 'U',
|
||||
colorMode: true,
|
||||
inertia: true, // Inertia.js support
|
||||
theme: {
|
||||
colors: ['primary', 'secondary', 'success', 'info', 'warning', 'error', 'neutral'],
|
||||
transitions: true,
|
||||
defaultVariants: { color: 'primary', size: 'md' },
|
||||
prefix: ''
|
||||
},
|
||||
ui: {
|
||||
colors: { primary: 'green' }, // Runtime color config
|
||||
button: { /* theme overrides */ }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Auto-installed Modules
|
||||
|
||||
Nuxt UI automatically installs:
|
||||
|
||||
- `@nuxt/icon` - Icon system
|
||||
- `@nuxt/fonts` - Web fonts (if `fonts: true`)
|
||||
- `@nuxtjs/color-mode` - Dark mode (if `colorMode: true`)
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Solution |
|
||||
| ------------------------- | -------------------------------------------------- |
|
||||
| Tailwind not found (pnpm) | Add `shamefully-hoist=true` or install tailwindcss |
|
||||
| Overlays not showing | Wrap app in `<UApp>` |
|
||||
| Vue overlays broken | Add `isolate` class to root element |
|
||||
| Icons not loading | Check @nuxt/icon is installed |
|
||||
| Dark mode not working | Ensure `colorMode: true` in config |
|
||||
|
||||
## Performance Features (v4.1+)
|
||||
|
||||
### Component Virtualization
|
||||
|
||||
Large datasets in CommandPalette, InputMenu, SelectMenu, Table, and Tree automatically use virtualization for better performance.
|
||||
|
||||
### Experimental Component Detection
|
||||
|
||||
Enable `experimental.componentDetection` to auto-generate CSS only for components you actually use:
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
ui: {
|
||||
experimental: {
|
||||
componentDetection: true
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Benefits:** Smaller CSS bundle, faster builds, reduced unused styles.
|
||||
|
||||
### Tailwind CSS Prefix Support (v4.2+)
|
||||
|
||||
Avoid style conflicts in complex apps:
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
ui: {
|
||||
theme: {
|
||||
prefix: 'ui-' // Prefixes all Tailwind utilities
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Result:** Components use `ui-bg-primary` instead of `bg-primary`.
|
||||
|
||||
## Best Practices
|
||||
|
||||
| Do | Don't |
|
||||
| -------------------------- | ------------------------- |
|
||||
| Wrap in UApp first | Forget UApp wrapper |
|
||||
| Use semantic colors | Hardcode color values |
|
||||
| Import CSS correctly | Skip @nuxt/ui import |
|
||||
| Check pnpm hoisting | Ignore tailwindcss errors |
|
||||
| Use component detection | Ship unused component CSS |
|
||||
| Use prefix in complex apps | Risk style conflicts |
|
||||
357
.claude/skills/nuxt-ui/references/overlays.md
Normal file
357
.claude/skills/nuxt-ui/references/overlays.md
Normal file
@@ -0,0 +1,357 @@
|
||||
# Overlays
|
||||
|
||||
**Prerequisite**: All overlays require `<UApp>` wrapper in app root.
|
||||
|
||||
**Important:** v4.4.0 includes security fixes for XSS vulnerabilities in Banner and CommandPalette components. Upgrade recommended.
|
||||
|
||||
## Toast (Notifications)
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
|
||||
function showToast() {
|
||||
toast.add({
|
||||
title: 'Success!',
|
||||
description: 'Your changes have been saved.',
|
||||
color: 'success',
|
||||
icon: 'i-heroicons-check-circle'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton @click="showToast">Show Toast</UButton>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Toast Options
|
||||
|
||||
```ts
|
||||
toast.add({
|
||||
id: 'unique-id', // Custom ID (auto-generated if omitted)
|
||||
title: 'Title', // Toast title
|
||||
description: 'Details', // Toast body
|
||||
color: 'success', // primary, success, error, warning, info
|
||||
icon: 'i-heroicons-check', // Left icon
|
||||
avatar: { src: '...' }, // Avatar instead of icon
|
||||
timeout: 5000, // Auto-dismiss (0 = never)
|
||||
actions: [{ // Action buttons
|
||||
label: 'Undo',
|
||||
click: () => {}
|
||||
}],
|
||||
callback: () => {} // Called on dismiss
|
||||
})
|
||||
```
|
||||
|
||||
### Toast Types
|
||||
|
||||
```ts
|
||||
// Success
|
||||
toast.add({ title: 'Saved', color: 'success', icon: 'i-heroicons-check-circle' })
|
||||
|
||||
// Error
|
||||
toast.add({ title: 'Error', color: 'error', icon: 'i-heroicons-x-circle' })
|
||||
|
||||
// Warning
|
||||
toast.add({ title: 'Warning', color: 'warning', icon: 'i-heroicons-exclamation-triangle' })
|
||||
|
||||
// Info
|
||||
toast.add({ title: 'Info', color: 'info', icon: 'i-heroicons-information-circle' })
|
||||
|
||||
// Remove toast
|
||||
toast.remove('toast-id')
|
||||
|
||||
// Clear all
|
||||
toast.clear()
|
||||
```
|
||||
|
||||
## Modal
|
||||
|
||||
### Basic Modal
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton @click="isOpen = true">Open Modal</UButton>
|
||||
|
||||
<UModal v-model:open="isOpen">
|
||||
<template #header>
|
||||
<h3>Modal Title</h3>
|
||||
</template>
|
||||
|
||||
<p>Modal content goes here...</p>
|
||||
|
||||
<template #footer>
|
||||
<UButton variant="ghost" @click="isOpen = false">Cancel</UButton>
|
||||
<UButton @click="save">Save</UButton>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Modal Props
|
||||
|
||||
```vue
|
||||
<UModal
|
||||
v-model:open="isOpen"
|
||||
title="Modal Title" <!-- Alternative to #header slot -->
|
||||
description="Subtitle" <!-- Below title -->
|
||||
:close="true" <!-- Show close button -->
|
||||
:close-icon="'i-heroicons-x-mark'"
|
||||
:overlay="true" <!-- Show backdrop -->
|
||||
:transition="true" <!-- Enable animation -->
|
||||
:prevent-close="false" <!-- Prevent close on overlay click -->
|
||||
fullscreen <!-- Full screen mode -->
|
||||
>
|
||||
```
|
||||
|
||||
### Programmatic Modal (useOverlay)
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const overlay = useOverlay()
|
||||
|
||||
async function openConfirm() {
|
||||
const modal = overlay.create(ConfirmModal, {
|
||||
props: { title: 'Confirm action?' },
|
||||
events: {
|
||||
confirm: () => modal.close(true),
|
||||
cancel: () => modal.close(false)
|
||||
}
|
||||
})
|
||||
|
||||
const result = await modal.result
|
||||
if (result) {
|
||||
// User confirmed
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Slideover
|
||||
|
||||
Side panel overlay (from edge of screen).
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton @click="isOpen = true">Open Slideover</UButton>
|
||||
|
||||
<USlideover v-model:open="isOpen" title="Settings" side="right">
|
||||
<div class="p-4">
|
||||
Settings content...
|
||||
</div>
|
||||
</USlideover>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Slideover Props
|
||||
|
||||
```vue
|
||||
<USlideover
|
||||
v-model:open="isOpen"
|
||||
title="Title"
|
||||
description="Subtitle"
|
||||
side="right" <!-- left, right, top, bottom -->
|
||||
:overlay="true"
|
||||
:transition="true"
|
||||
:prevent-close="false"
|
||||
>
|
||||
```
|
||||
|
||||
## Drawer
|
||||
|
||||
Bottom sheet overlay (vaul-vue).
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton @click="isOpen = true">Open Drawer</UButton>
|
||||
|
||||
<UDrawer v-model:open="isOpen">
|
||||
<div class="p-4">
|
||||
Drawer content...
|
||||
</div>
|
||||
</UDrawer>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Drawer Props
|
||||
|
||||
```vue
|
||||
<UDrawer
|
||||
v-model:open="isOpen"
|
||||
title="Drawer Title"
|
||||
description="Subtitle"
|
||||
handle <!-- Show drag handle -->
|
||||
:should-scale-background="true"
|
||||
:close-threshold="0.25" <!-- Swipe threshold to close -->
|
||||
>
|
||||
```
|
||||
|
||||
## Popover
|
||||
|
||||
```vue
|
||||
<UPopover>
|
||||
<UButton>Open Popover</UButton>
|
||||
|
||||
<template #content>
|
||||
<div class="p-4">
|
||||
Popover content
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
```
|
||||
|
||||
### Popover Props
|
||||
|
||||
```vue
|
||||
<UPopover
|
||||
:open="isOpen"
|
||||
side="bottom" <!-- top, right, bottom, left -->
|
||||
align="center" <!-- start, center, end -->
|
||||
:arrow="true"
|
||||
:delay="{ open: 0, close: 0 }"
|
||||
>
|
||||
```
|
||||
|
||||
## Tooltip
|
||||
|
||||
```vue
|
||||
<UTooltip text="Helpful tip">
|
||||
<UButton icon="i-heroicons-question-mark-circle" />
|
||||
</UTooltip>
|
||||
|
||||
<!-- With slot content -->
|
||||
<UTooltip>
|
||||
<UButton>Hover me</UButton>
|
||||
<template #content>
|
||||
<p>Rich tooltip content</p>
|
||||
</template>
|
||||
</UTooltip>
|
||||
```
|
||||
|
||||
## DropdownMenu
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [
|
||||
{ label: 'Edit', icon: 'i-heroicons-pencil', click: () => {} },
|
||||
{ label: 'Duplicate', icon: 'i-heroicons-document-duplicate' },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Delete', icon: 'i-heroicons-trash', color: 'error' }
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdownMenu :items="items">
|
||||
<UButton icon="i-heroicons-ellipsis-vertical" variant="ghost" />
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Nested Items
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [
|
||||
{ label: 'New', children: [
|
||||
{ label: 'File', click: () => {} },
|
||||
{ label: 'Folder', click: () => {} }
|
||||
]},
|
||||
{ label: 'Delete' }
|
||||
]
|
||||
</script>
|
||||
```
|
||||
|
||||
## ContextMenu
|
||||
|
||||
Right-click menu.
|
||||
|
||||
```vue
|
||||
<UContextMenu :items="items">
|
||||
<div class="h-40 border rounded flex items-center justify-center">
|
||||
Right-click here
|
||||
</div>
|
||||
</UContextMenu>
|
||||
```
|
||||
|
||||
## CommandPalette
|
||||
|
||||
Search-driven command menu (Fuse.js powered).
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
|
||||
const groups = [{
|
||||
key: 'actions',
|
||||
label: 'Actions',
|
||||
items: [
|
||||
{ label: 'New file', icon: 'i-heroicons-document-plus', click: () => {} },
|
||||
{ label: 'New folder', icon: 'i-heroicons-folder-plus', click: () => {} }
|
||||
]
|
||||
}, {
|
||||
key: 'navigation',
|
||||
label: 'Navigation',
|
||||
items: [
|
||||
{ label: 'Home', to: '/' },
|
||||
{ label: 'Settings', to: '/settings' }
|
||||
]
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette
|
||||
v-model:open="isOpen"
|
||||
:groups="groups"
|
||||
placeholder="Search..."
|
||||
size="md"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### CommandPalette Props (v4.4+)
|
||||
|
||||
```vue
|
||||
<UCommandPalette
|
||||
v-model:open="isOpen"
|
||||
:groups="groups"
|
||||
placeholder="Search..."
|
||||
size="md" <!-- Size: sm, md, lg (v4.4+) -->
|
||||
:input="{ /* props */ }" <!-- Custom input props (v4.4+) -->
|
||||
/>
|
||||
```
|
||||
|
||||
### Keyboard Shortcut
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
defineShortcuts({
|
||||
meta_k: () => { isOpen.value = true }
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
| Do | Don't |
|
||||
| -------------------------------------- | --------------------------- |
|
||||
| Use useToast for notifications | Build custom toast systems |
|
||||
| Use UModal for dialogs | Use alerts for complex UI |
|
||||
| Use Slideover for panels | Use modals for side content |
|
||||
| Use Drawer for mobile sheets | Use slideover on mobile |
|
||||
| Use CommandPalette for search | Build custom search UI |
|
||||
| Use programmatic overlays for confirms | Create confirm components |
|
||||
323
.claude/skills/nuxt-ui/references/theming.md
Normal file
323
.claude/skills/nuxt-ui/references/theming.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# Theming
|
||||
|
||||
## Semantic Colors
|
||||
|
||||
| Color | Default | Purpose |
|
||||
| ----------- | ------- | ------------------------------------------- |
|
||||
| `primary` | green | CTAs, active states, brand, important links |
|
||||
| `secondary` | blue | Secondary buttons, alternatives |
|
||||
| `success` | green | Success messages, positive states |
|
||||
| `info` | blue | Info alerts, help text |
|
||||
| `warning` | yellow | Warnings, pending states |
|
||||
| `error` | red | Errors, destructive actions |
|
||||
| `neutral` | slate | Text, borders, disabled states |
|
||||
|
||||
## Configuring Colors
|
||||
|
||||
### Nuxt (app.config.ts)
|
||||
|
||||
```ts
|
||||
// app.config.ts
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
colors: {
|
||||
primary: 'indigo',
|
||||
secondary: 'violet',
|
||||
success: 'emerald',
|
||||
error: 'rose'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Vue (vite.config.ts)
|
||||
|
||||
```ts
|
||||
ui({
|
||||
ui: {
|
||||
colors: {
|
||||
primary: 'indigo',
|
||||
secondary: 'violet'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Adding Custom Colors
|
||||
|
||||
1. Register in theme config:
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
ui: {
|
||||
theme: {
|
||||
colors: ['primary', 'secondary', 'tertiary'] // Add new color
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
2. Define in CSS (all 11 shades required):
|
||||
|
||||
```css
|
||||
@theme {
|
||||
--color-tertiary-50: #fef2f2;
|
||||
--color-tertiary-100: #fee2e2;
|
||||
--color-tertiary-200: #fecaca;
|
||||
--color-tertiary-300: #fca5a5;
|
||||
--color-tertiary-400: #f87171;
|
||||
--color-tertiary-500: #ef4444;
|
||||
--color-tertiary-600: #dc2626;
|
||||
--color-tertiary-700: #b91c1c;
|
||||
--color-tertiary-800: #991b1b;
|
||||
--color-tertiary-900: #7f1d1d;
|
||||
--color-tertiary-950: #450a0a;
|
||||
}
|
||||
```
|
||||
|
||||
3. Assign and use:
|
||||
|
||||
```ts
|
||||
// app.config.ts
|
||||
export default defineAppConfig({
|
||||
ui: { colors: { tertiary: 'tertiary' } }
|
||||
})
|
||||
```
|
||||
|
||||
```vue
|
||||
<UButton color="tertiary">Custom Color</UButton>
|
||||
```
|
||||
|
||||
## CSS Variables
|
||||
|
||||
### Text Utilities
|
||||
|
||||
| Class | Light | Dark | Use |
|
||||
| ------------------ | ----- | ---- | ------------------------- |
|
||||
| `text-dimmed` | 400 | 500 | Placeholders, hints |
|
||||
| `text-muted` | 500 | 400 | Secondary text |
|
||||
| `text-toned` | 600 | 300 | Subtitles |
|
||||
| `text-default` | 700 | 200 | Body text |
|
||||
| `text-highlighted` | 900 | 100 | Headings, emphasis |
|
||||
| `text-inverted` | 50 | 950 | On dark/light backgrounds |
|
||||
|
||||
### Background Utilities
|
||||
|
||||
| Class | Light | Dark | Use |
|
||||
| ------------- | ----- | ---- | ----------------- |
|
||||
| `bg-default` | white | 900 | Page background |
|
||||
| `bg-muted` | 50 | 800 | Subtle sections |
|
||||
| `bg-elevated` | white | 800 | Cards, modals |
|
||||
| `bg-accented` | 100 | 700 | Hover states |
|
||||
| `bg-inverted` | 900 | 100 | Inverted sections |
|
||||
|
||||
### Border Utilities
|
||||
|
||||
| Class | Light | Dark |
|
||||
| ----------------- | ----- | ---- |
|
||||
| `border-default` | 200 | 800 |
|
||||
| `border-muted` | 100 | 800 |
|
||||
| `border-accented` | 200 | 700 |
|
||||
| `border-inverted` | 900 | 100 |
|
||||
|
||||
### Global Variables
|
||||
|
||||
```css
|
||||
:root {
|
||||
--ui-radius: 0.25rem; /* Base border radius */
|
||||
--ui-container: 80rem; /* Container max-width */
|
||||
--ui-header-height: 4rem; /* Header height */
|
||||
}
|
||||
```
|
||||
|
||||
## Custom CSS Variables
|
||||
|
||||
```css
|
||||
/* assets/css/main.css */
|
||||
:root {
|
||||
--ui-primary: var(--ui-color-primary-700);
|
||||
--ui-radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--ui-primary: var(--ui-color-primary-200);
|
||||
}
|
||||
```
|
||||
|
||||
## Solid Colors (Black/White)
|
||||
|
||||
Can't use `primary: 'black'` - set in CSS:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--ui-primary: black;
|
||||
}
|
||||
.dark {
|
||||
--ui-primary: white;
|
||||
}
|
||||
```
|
||||
|
||||
## Tailwind Variants Override
|
||||
|
||||
### Global Override (app.config.ts)
|
||||
|
||||
```ts
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
button: {
|
||||
slots: {
|
||||
base: 'font-bold rounded-full'
|
||||
},
|
||||
variants: {
|
||||
size: {
|
||||
md: { base: 'px-6 py-3' }
|
||||
}
|
||||
},
|
||||
compoundVariants: [{
|
||||
color: 'neutral',
|
||||
variant: 'outline',
|
||||
class: { base: 'ring-2' }
|
||||
}],
|
||||
defaultVariants: {
|
||||
color: 'neutral',
|
||||
variant: 'outline'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Per-Component Override
|
||||
|
||||
```vue
|
||||
<!-- ui prop overrides slots -->
|
||||
<UButton :ui="{ base: 'font-mono' }">Custom</UButton>
|
||||
|
||||
<!-- class prop overrides root/base slot -->
|
||||
<UButton class="rounded-none">Square</UButton>
|
||||
```
|
||||
|
||||
## Matching Theme Structure in app.config
|
||||
|
||||
**CRITICAL**: Components have two theme structure patterns. Your app.config MUST match the component's theme structure.
|
||||
|
||||
### Pattern 1: Slots-Based Themes (Most Components)
|
||||
|
||||
Components like Button, Card, Input, Select use `slots:` in their theme:
|
||||
|
||||
```ts
|
||||
// Component theme (Button, Card, etc.)
|
||||
export default {
|
||||
slots: {
|
||||
base: '...',
|
||||
root: '...',
|
||||
icon: '...'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**app.config usage**:
|
||||
|
||||
```ts
|
||||
ui: {
|
||||
button: {
|
||||
slots: { base: 'font-bold' } // ✅ Match slots structure
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Flat Base Themes (Container, Skeleton, etc.)
|
||||
|
||||
Components like Container, Skeleton, Form, Main use flat `base:` in their theme:
|
||||
|
||||
```ts
|
||||
// Component theme (Container, Skeleton, etc.)
|
||||
export default {
|
||||
base: 'w-full max-w-container'
|
||||
}
|
||||
```
|
||||
|
||||
**app.config usage**:
|
||||
|
||||
```ts
|
||||
ui: {
|
||||
container: {
|
||||
base: 'max-w-lg' // ✅ Match flat structure
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Common Mistake
|
||||
|
||||
```ts
|
||||
// ❌ WRONG - Don't use slots for flat-base components
|
||||
ui: {
|
||||
container: {
|
||||
slots: { base: 'max-w-lg' } // TypeScript error!
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ WRONG - Don't use flat for slots-based components
|
||||
ui: {
|
||||
button: {
|
||||
base: 'font-bold' // Won't work correctly
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### How to Check Component Structure
|
||||
|
||||
1. Check component docs: https://ui.nuxt.com/components/[component]
|
||||
2. Look at "Theme" section - shows the structure
|
||||
3. Match that structure in your app.config
|
||||
|
||||
## Component Theme Structure
|
||||
|
||||
```ts
|
||||
// Each component has slots, variants, compoundVariants, defaultVariants
|
||||
export default {
|
||||
slots: {
|
||||
root: 'relative',
|
||||
base: 'px-4 py-2',
|
||||
icon: 'size-5'
|
||||
},
|
||||
variants: {
|
||||
color: {
|
||||
primary: { base: 'bg-primary text-inverted' },
|
||||
neutral: { base: 'bg-neutral text-default' }
|
||||
},
|
||||
size: {
|
||||
sm: { base: 'text-sm', icon: 'size-4' },
|
||||
md: { base: 'text-base', icon: 'size-5' },
|
||||
lg: { base: 'text-lg', icon: 'size-6' }
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
color: 'primary',
|
||||
size: 'md'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dark Mode
|
||||
|
||||
Handled by `@nuxtjs/color-mode`. Access via:
|
||||
|
||||
```ts
|
||||
const colorMode = useColorMode()
|
||||
colorMode.preference = 'dark' // 'light', 'dark', 'system'
|
||||
```
|
||||
|
||||
```vue
|
||||
<UColorModeButton /> <!-- Toggle button -->
|
||||
<UColorModeSelect /> <!-- Dropdown select -->
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
| Do | Don't |
|
||||
| -------------------------------------- | ----------------------------- |
|
||||
| Use semantic colors | Hardcode hex values |
|
||||
| Override via app.config | Modify source theme files |
|
||||
| Use CSS variables for custom colors | Skip dark mode variants |
|
||||
| Define all 11 shades for custom colors | Use partial shade definitions |
|
||||
Reference in New Issue
Block a user