@plutocms/supabase 0.0.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/nuxt/GENERATION.md +5 -0
- package/.agents/skills/nuxt/SKILL.md +55 -0
- package/.agents/skills/nuxt/references/advanced-hooks.md +289 -0
- package/.agents/skills/nuxt/references/advanced-layers.md +299 -0
- package/.agents/skills/nuxt/references/advanced-module-authoring.md +554 -0
- package/.agents/skills/nuxt/references/best-practices-data-fetching.md +357 -0
- package/.agents/skills/nuxt/references/best-practices-ssr.md +355 -0
- package/.agents/skills/nuxt/references/core-cli.md +263 -0
- package/.agents/skills/nuxt/references/core-config.md +162 -0
- package/.agents/skills/nuxt/references/core-data-fetching.md +236 -0
- package/.agents/skills/nuxt/references/core-deployment.md +224 -0
- package/.agents/skills/nuxt/references/core-directory-structure.md +269 -0
- package/.agents/skills/nuxt/references/core-modules.md +292 -0
- package/.agents/skills/nuxt/references/core-routing.md +226 -0
- package/.agents/skills/nuxt/references/features-components-autoimport.md +328 -0
- package/.agents/skills/nuxt/references/features-components.md +264 -0
- package/.agents/skills/nuxt/references/features-composables.md +276 -0
- package/.agents/skills/nuxt/references/features-server.md +265 -0
- package/.agents/skills/nuxt/references/features-state.md +194 -0
- package/.agents/skills/nuxt/references/rendering-modes.md +237 -0
- package/.agents/skills/nuxt-ui/SKILL.md +334 -0
- package/.agents/skills/nuxt-ui/references/components.md +377 -0
- package/.agents/skills/nuxt-ui/references/composables.md +127 -0
- package/.agents/skills/nuxt-ui/references/layouts/chat.md +195 -0
- package/.agents/skills/nuxt-ui/references/layouts/dashboard.md +220 -0
- package/.agents/skills/nuxt-ui/references/layouts/docs.md +141 -0
- package/.agents/skills/nuxt-ui/references/layouts/editor.md +167 -0
- package/.agents/skills/nuxt-ui/references/layouts/page.md +260 -0
- package/.agents/skills/nuxt-ui/references/theming.md +427 -0
- package/.agents/skills/supabase-postgres-best-practices/AGENTS.md +68 -0
- package/.agents/skills/supabase-postgres-best-practices/CLAUDE.md +1 -0
- package/.agents/skills/supabase-postgres-best-practices/README.md +116 -0
- package/.agents/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
- package/.editorconfig +12 -0
- package/.env.example +2 -0
- package/.eslintignore +1 -0
- package/.prettierrc +9 -0
- package/.vscode/extensions.json +10 -0
- package/.vscode/settings.json +61 -0
- package/README.md +62 -0
- package/app/components/navbar/NavbarAdmin.vue +169 -0
- package/app/composables/auth.ts +81 -0
- package/app/layouts/setup.vue +13 -0
- package/app/middleware/auth.ts +27 -0
- package/app/middleware/setup-check.ts +25 -0
- package/app/pages/admin/(auth)/login.vue +84 -0
- package/app/pages/admin/(auth)/signup.vue +128 -0
- package/app/pages/admin/settings.vue +116 -0
- package/app/pages/admin/setup.vue +300 -0
- package/app/pages/admin.vue +38 -0
- package/bun.lock +2685 -0
- package/eslint.config.mjs +88 -0
- package/nuxt.config.ts +55 -0
- package/package.json +44 -0
- package/public/schema.sql +103 -0
- package/scripts/supabase-typegen.ts +36 -0
- package/server/api/category/list.get.ts +9 -0
- package/server/api/category/new.post.ts +31 -0
- package/server/api/settings/first_setup.get.ts +39 -0
- package/server/api/settings/index.get.ts +27 -0
- package/server/api/settings/update.post.ts +36 -0
- package/server/api/setup/create.post.ts +58 -0
- package/server/api/signup.post.ts +28 -0
- package/server/api/users/[id].get.ts +21 -0
- package/server/api/users/index.get.ts +16 -0
- package/shared/types/supabase.ts +406 -0
- package/skills-lock.json +20 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# Components
|
|
2
|
+
|
|
3
|
+
125+ Vue components powered by Tailwind CSS and Reka UI. For any component's theme slots, read the generated theme file (Nuxt: `.nuxt/ui/<component>.ts`, Vue: `node_modules/.nuxt-ui/ui/<component>.ts`).
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
Core structural components for organizing your application's layout.
|
|
8
|
+
|
|
9
|
+
| Component | Purpose |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `UApp` | **Required** root wrapper for toasts, tooltips, overlays |
|
|
12
|
+
| `UHeader` | Responsive header with mobile menu (`#title`, `#default`, `#right`, `#body`) |
|
|
13
|
+
| `UFooter` | Footer (`#left`, `#default`, `#right`, `#top`, `#bottom`) |
|
|
14
|
+
| `UFooterColumns` | Multi-column footer with link groups |
|
|
15
|
+
| `UMain` | Main content area (respects `--ui-header-height`) |
|
|
16
|
+
| `UContainer` | Centered max-width container (`--ui-container`) |
|
|
17
|
+
|
|
18
|
+
## Element
|
|
19
|
+
|
|
20
|
+
Essential UI building blocks.
|
|
21
|
+
|
|
22
|
+
| Component | Key props |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `UButton` | `label`, `icon`, `color`, `variant`, `size`, `loading`, `disabled`, `to` |
|
|
25
|
+
| `UBadge` | `label`, `color`, `variant`, `size` |
|
|
26
|
+
| `UAvatar` | `src`, `alt`, `icon`, `text`, `size` |
|
|
27
|
+
| `UAvatarGroup` | `max`, `size` — wraps multiple `UAvatar` |
|
|
28
|
+
| `UIcon` | `name`, `size` |
|
|
29
|
+
| `UCard` | `variant` — slots: `#header`, `#default`, `#footer` |
|
|
30
|
+
| `UAlert` | `title`, `description`, `icon`, `color`, `variant`, `close` |
|
|
31
|
+
| `UBanner` | `title`, `icon`, `close` — sticky top banner |
|
|
32
|
+
| `UChip` | `color`, `size`, `position` — notification dot on children |
|
|
33
|
+
| `UKbd` | `value` — keyboard key display |
|
|
34
|
+
| `USeparator` | `label`, `icon`, `orientation`, `type` |
|
|
35
|
+
| `USkeleton` | `class` — loading placeholder |
|
|
36
|
+
| `UProgress` | `value`, `max`, `color`, `size` |
|
|
37
|
+
| `UCalendar` | `v-model`, `range` (boolean), `multiple` (boolean) |
|
|
38
|
+
| `UCollapsible` | `v-model:open` — animated expand/collapse |
|
|
39
|
+
| `UFieldGroup` | Groups form inputs horizontally/vertically |
|
|
40
|
+
|
|
41
|
+
## Form
|
|
42
|
+
|
|
43
|
+
Comprehensive form components for user input.
|
|
44
|
+
|
|
45
|
+
| Component | Key props |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `UInput` | `v-model`, `type`, `placeholder`, `icon`, `loading` |
|
|
48
|
+
| `UTextarea` | `v-model`, `rows`, `autoresize`, `maxrows` |
|
|
49
|
+
| `USelect` | `v-model`, `items` (flat `T[]` or grouped `T[][]`), `placeholder` |
|
|
50
|
+
| `USelectMenu` | `v-model`, `items` (flat `T[]` or grouped `T[][]`), `searchable`, `multiple` |
|
|
51
|
+
| `UInputMenu` | `v-model`, `items` (flat `T[]` or grouped `T[][]`), `searchable` — autocomplete |
|
|
52
|
+
| `UInputNumber` | `v-model`, `min`, `max`, `step` |
|
|
53
|
+
| `UInputDate` | `v-model`, `range` (boolean for range selection), `locale` |
|
|
54
|
+
| `UInputTime` | `v-model`, `hour-cycle` (12/24), `granularity` |
|
|
55
|
+
| `UInputTags` | `v-model`, `max`, `placeholder` |
|
|
56
|
+
| `UPinInput` | `v-model`, `length`, `type`, `mask` |
|
|
57
|
+
| `UCheckbox` | `v-model`, `label`, `description` |
|
|
58
|
+
| `UCheckboxGroup` | `v-model`, `items`, `orientation` |
|
|
59
|
+
| `URadioGroup` | `v-model`, `items`, `orientation` |
|
|
60
|
+
| `USwitch` | `v-model`, `label`, `on-icon`, `off-icon` |
|
|
61
|
+
| `USlider` | `v-model`, `min`, `max`, `step` |
|
|
62
|
+
| `UColorPicker` | `v-model`, `format` (hex/rgb/hsl/cmyk/lab), `size` |
|
|
63
|
+
| `UFileUpload` | `v-model`, `accept`, `multiple`, `variant` (area/button) |
|
|
64
|
+
| `UForm` | `schema`, `state`, `@submit` — validation wrapper |
|
|
65
|
+
| `UFormField` | `name`, `label`, `description`, `hint`, `required` |
|
|
66
|
+
|
|
67
|
+
### Form validation
|
|
68
|
+
|
|
69
|
+
Uses Standard Schema — works with Zod, Valibot, Yup, or Joi.
|
|
70
|
+
|
|
71
|
+
```vue
|
|
72
|
+
<script setup lang="ts">
|
|
73
|
+
import { z } from 'zod'
|
|
74
|
+
|
|
75
|
+
const schema = z.object({
|
|
76
|
+
email: z.string().email('Invalid email'),
|
|
77
|
+
password: z.string().min(8, 'Min 8 characters')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
type Schema = z.output<typeof schema>
|
|
81
|
+
const state = reactive<Partial<Schema>>({ email: '', password: '' })
|
|
82
|
+
const form = ref()
|
|
83
|
+
|
|
84
|
+
async function onSubmit() {
|
|
85
|
+
await form.value.validate()
|
|
86
|
+
}
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<template>
|
|
90
|
+
<UForm ref="form" :schema="schema" :state="state" @submit="onSubmit">
|
|
91
|
+
<UFormField name="email" label="Email" required>
|
|
92
|
+
<UInput v-model="state.email" type="email" />
|
|
93
|
+
</UFormField>
|
|
94
|
+
|
|
95
|
+
<UFormField name="password" label="Password" required>
|
|
96
|
+
<UInput v-model="state.password" type="password" />
|
|
97
|
+
</UFormField>
|
|
98
|
+
|
|
99
|
+
<UButton type="submit">Submit</UButton>
|
|
100
|
+
</UForm>
|
|
101
|
+
</template>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
With Valibot:
|
|
105
|
+
|
|
106
|
+
```vue
|
|
107
|
+
<script setup lang="ts">
|
|
108
|
+
import * as v from 'valibot'
|
|
109
|
+
|
|
110
|
+
const schema = v.object({
|
|
111
|
+
email: v.pipe(v.string(), v.email('Invalid email')),
|
|
112
|
+
password: v.pipe(v.string(), v.minLength(8, 'Min 8 characters'))
|
|
113
|
+
})
|
|
114
|
+
</script>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### File upload
|
|
118
|
+
|
|
119
|
+
```vue
|
|
120
|
+
<script setup>
|
|
121
|
+
const files = ref<File[]>([])
|
|
122
|
+
</script>
|
|
123
|
+
|
|
124
|
+
<template>
|
|
125
|
+
<UFileUpload v-model="files" accept="image/*" multiple>
|
|
126
|
+
<template #actions="{ open }">
|
|
127
|
+
<UButton label="Upload" icon="i-lucide-upload" color="neutral" variant="outline" @click="open()" />
|
|
128
|
+
</template>
|
|
129
|
+
</UFileUpload>
|
|
130
|
+
</template>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Data
|
|
134
|
+
|
|
135
|
+
Components for displaying and organizing data.
|
|
136
|
+
|
|
137
|
+
| Component | Key props |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `UTable` | `data`, `columns`, `loading`, `sticky` |
|
|
140
|
+
| `UAccordion` | `items`, `type` (single/multiple), `collapsible` |
|
|
141
|
+
| `UCarousel` | `items`, `orientation`, `arrows`, `dots` |
|
|
142
|
+
| `UTimeline` | `items` — vertical timeline |
|
|
143
|
+
| `UTree` | `items` — hierarchical tree |
|
|
144
|
+
| `UUser` | `name`, `description`, `avatar` — user display |
|
|
145
|
+
| `UEmpty` | `icon`, `title`, `description` — empty state |
|
|
146
|
+
| `UMarquee` | `repeat`, `reverse`, `orientation`, `pauseOnHover` — infinite scroll |
|
|
147
|
+
| `UScrollArea` | Custom scrollbar wrapper |
|
|
148
|
+
|
|
149
|
+
## Navigation
|
|
150
|
+
|
|
151
|
+
Components for user navigation and wayfinding.
|
|
152
|
+
|
|
153
|
+
| Component | Key props |
|
|
154
|
+
|---|---|
|
|
155
|
+
| `UNavigationMenu` | `items` (flat `T[]` or grouped `T[][]`), `orientation` (horizontal/vertical) |
|
|
156
|
+
| `UBreadcrumb` | `items` |
|
|
157
|
+
| `UTabs` | `items`, `orientation`, `variant` |
|
|
158
|
+
| `UStepper` | `items`, `orientation`, `color` |
|
|
159
|
+
| `UPagination` | `v-model`, `total`, `items-per-page` |
|
|
160
|
+
| `ULink` | `to`, `active`, `inactive` — styled NuxtLink |
|
|
161
|
+
| `UCommandPalette` | `v-model:open`, `groups` (`{ id, label, items }[]`), `placeholder` |
|
|
162
|
+
|
|
163
|
+
## Overlay
|
|
164
|
+
|
|
165
|
+
Floating UI elements that appear above the main content. **All require `<UApp>` wrapper.**
|
|
166
|
+
|
|
167
|
+
| Component | Key props |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `UModal` | `v-model:open`, `title`, `description`, `fullscreen`, `scrollable` |
|
|
170
|
+
| `USlideover` | `v-model:open`, `title`, `side` (left/right/top/bottom) |
|
|
171
|
+
| `UDrawer` | `v-model:open`, `title`, `handle` |
|
|
172
|
+
| `UPopover` | `arrow`, `content: { side, align }`, `openDelay`, `closeDelay` |
|
|
173
|
+
| `UTooltip` | `text`, `content: { side }`, `delayDuration` |
|
|
174
|
+
| `UDropdownMenu` | `items` (flat `T[]` or grouped `T[][]` with separators, supports nested `children`) |
|
|
175
|
+
| `UContextMenu` | `items` (flat `T[]` or grouped `T[][]`) — right-click menu |
|
|
176
|
+
| `UToast` | Used via `useToast()` composable |
|
|
177
|
+
|
|
178
|
+
### Modal
|
|
179
|
+
|
|
180
|
+
```vue
|
|
181
|
+
<UModal v-model:open="isOpen" title="Edit" description="Edit your profile">
|
|
182
|
+
<template #body>Content</template>
|
|
183
|
+
<template #footer>
|
|
184
|
+
<UButton variant="ghost" @click="isOpen = false">Cancel</UButton>
|
|
185
|
+
<UButton @click="save">Save</UButton>
|
|
186
|
+
</template>
|
|
187
|
+
</UModal>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Slots: `#content`, `#header`, `#body`, `#footer`
|
|
191
|
+
|
|
192
|
+
### Slideover
|
|
193
|
+
|
|
194
|
+
```vue
|
|
195
|
+
<USlideover v-model:open="isOpen" title="Settings" side="right">
|
|
196
|
+
<template #body>Content</template>
|
|
197
|
+
</USlideover>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Drawer
|
|
201
|
+
|
|
202
|
+
```vue
|
|
203
|
+
<UDrawer v-model:open="isOpen" title="Options" handle>
|
|
204
|
+
<template #body>Content</template>
|
|
205
|
+
</UDrawer>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### DropdownMenu
|
|
209
|
+
|
|
210
|
+
Items accept a flat array or a nested array (each sub-array is rendered as a group separated by dividers):
|
|
211
|
+
|
|
212
|
+
```vue
|
|
213
|
+
<!-- Flat array -->
|
|
214
|
+
<UDropdownMenu :items="[
|
|
215
|
+
{ label: 'Edit', icon: 'i-lucide-pencil', onSelect: () => edit() },
|
|
216
|
+
{ type: 'separator' },
|
|
217
|
+
{ label: 'Delete', icon: 'i-lucide-trash', color: 'error' }
|
|
218
|
+
]">
|
|
219
|
+
<UButton icon="i-lucide-ellipsis-vertical" variant="ghost" />
|
|
220
|
+
</UDropdownMenu>
|
|
221
|
+
|
|
222
|
+
<!-- Nested array (groups with automatic separators) -->
|
|
223
|
+
<UDropdownMenu :items="[
|
|
224
|
+
[{ label: 'Edit', icon: 'i-lucide-pencil' }, { label: 'Duplicate', icon: 'i-lucide-copy' }],
|
|
225
|
+
[{ label: 'Delete', icon: 'i-lucide-trash', color: 'error' }]
|
|
226
|
+
]">
|
|
227
|
+
<UButton icon="i-lucide-ellipsis-vertical" variant="ghost" />
|
|
228
|
+
</UDropdownMenu>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Toast
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
const toast = useToast()
|
|
235
|
+
|
|
236
|
+
toast.add({
|
|
237
|
+
title: 'Success',
|
|
238
|
+
description: 'Changes saved',
|
|
239
|
+
color: 'success',
|
|
240
|
+
icon: 'i-lucide-check-circle',
|
|
241
|
+
duration: 5000,
|
|
242
|
+
actions: [{ label: 'Undo', onClick: () => undo() }]
|
|
243
|
+
})
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Programmatic overlays
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
const overlay = useOverlay()
|
|
250
|
+
|
|
251
|
+
// create() returns a reusable instance
|
|
252
|
+
const confirmDialog = overlay.create(ConfirmDialog)
|
|
253
|
+
|
|
254
|
+
// open() returns an object with .result (a Promise)
|
|
255
|
+
const { result } = confirmDialog.open({
|
|
256
|
+
title: 'Delete?',
|
|
257
|
+
message: 'This cannot be undone.'
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
if (await result) {
|
|
261
|
+
// User confirmed
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Inside the overlay component, emit close with a value:
|
|
265
|
+
// emit('close', true) or emit('close', false)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### CommandPalette
|
|
269
|
+
|
|
270
|
+
```vue
|
|
271
|
+
<script setup>
|
|
272
|
+
const groups = [{
|
|
273
|
+
id: 'actions',
|
|
274
|
+
label: 'Actions',
|
|
275
|
+
items: [
|
|
276
|
+
{ label: 'New file', icon: 'i-lucide-file-plus', onSelect: () => {} },
|
|
277
|
+
{ label: 'Settings', to: '/settings' }
|
|
278
|
+
]
|
|
279
|
+
}]
|
|
280
|
+
|
|
281
|
+
defineShortcuts({ meta_k: () => { isOpen.value = true } })
|
|
282
|
+
</script>
|
|
283
|
+
|
|
284
|
+
<UCommandPalette v-model:open="isOpen" :groups="groups" placeholder="Search..." />
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Page
|
|
288
|
+
|
|
289
|
+
Pre-built sections for marketing and content pages.
|
|
290
|
+
|
|
291
|
+
| Component | Purpose |
|
|
292
|
+
|---|---|
|
|
293
|
+
| `UPage` | Multi-column grid (`#left`, `#default`, `#right`) |
|
|
294
|
+
| `UPageAside` | Sticky sidebar wrapper (visible from `lg`) |
|
|
295
|
+
| `UPageHero` | Hero section with title, description, links, media |
|
|
296
|
+
| `UPageSection` | Content section with headline, features grid |
|
|
297
|
+
| `UPageCTA` | Call to action block |
|
|
298
|
+
| `UPageHeader` | Page title and description |
|
|
299
|
+
| `UPageBody` | Main content area with prose styling |
|
|
300
|
+
| `UPageFeature` | Individual feature item |
|
|
301
|
+
| `UPageGrid` | Grid layout for cards |
|
|
302
|
+
| `UPageColumns` | Multi-column layout |
|
|
303
|
+
| `UPageCard` | Content card for grids |
|
|
304
|
+
| `UPageLogos` | Logo wall |
|
|
305
|
+
| `UPageAnchors` | Anchor links (simpler TOC) |
|
|
306
|
+
| `UPageLinks` | Related resource links |
|
|
307
|
+
| `UPageList` | List items |
|
|
308
|
+
| `UBlogPosts` | Responsive grid of blog posts (`orientation`) |
|
|
309
|
+
| `UBlogPost` | Individual blog post card |
|
|
310
|
+
| `UChangelogVersions` | Changelog version list |
|
|
311
|
+
| `UChangelogVersion` | Individual changelog entry |
|
|
312
|
+
| `UPricingPlans` | Pricing plan cards |
|
|
313
|
+
| `UPricingTable` | Feature comparison table |
|
|
314
|
+
|
|
315
|
+
## Dashboard
|
|
316
|
+
|
|
317
|
+
Specialized components for admin interfaces with resizable panels and sidebars.
|
|
318
|
+
|
|
319
|
+
| Component | Purpose |
|
|
320
|
+
|---|---|
|
|
321
|
+
| `UDashboardGroup` | Root wrapper — manages sidebar state |
|
|
322
|
+
| `UDashboardSidebar` | Resizable/collapsible sidebar (`#header`, `#default`, `#footer`) |
|
|
323
|
+
| `UDashboardPanel` | Content panel (`#header`, `#body`, `#footer`) |
|
|
324
|
+
| `UDashboardNavbar` | Panel navbar (`#left`, `#default`, `#right`) |
|
|
325
|
+
| `UDashboardToolbar` | Toolbar for filters/actions |
|
|
326
|
+
| `UDashboardSearch` | Command palette for dashboards |
|
|
327
|
+
| `UDashboardSearchButton` | Search trigger button |
|
|
328
|
+
| `UDashboardSidebarToggle` | Mobile sidebar toggle |
|
|
329
|
+
| `UDashboardSidebarCollapse` | Desktop collapse button |
|
|
330
|
+
| `UDashboardResizeHandle` | Custom resize handle |
|
|
331
|
+
|
|
332
|
+
## Chat
|
|
333
|
+
|
|
334
|
+
Components for conversational AI interfaces, powered by [Vercel AI SDK](https://ai-sdk.dev/).
|
|
335
|
+
|
|
336
|
+
| Component | Purpose |
|
|
337
|
+
|---|---|
|
|
338
|
+
| `UChatMessages` | Scrollable message list with auto-scroll |
|
|
339
|
+
| `UChatMessage` | Individual message display |
|
|
340
|
+
| `UChatPrompt` | Enhanced textarea for prompts |
|
|
341
|
+
| `UChatPromptSubmit` | Submit button with status handling |
|
|
342
|
+
| `UChatPalette` | Chat layout for overlays |
|
|
343
|
+
|
|
344
|
+
## Editor
|
|
345
|
+
|
|
346
|
+
Rich text editor powered by [TipTap](https://tiptap.dev/).
|
|
347
|
+
|
|
348
|
+
| Component | Purpose |
|
|
349
|
+
|---|---|
|
|
350
|
+
| `UEditor` | Editor (`v-model`, `content-type`: json/html/markdown) |
|
|
351
|
+
| `UEditorToolbar` | Toolbar (`layout`: fixed/bubble/floating) |
|
|
352
|
+
| `UEditorDragHandle` | Block drag-and-drop |
|
|
353
|
+
| `UEditorSuggestionMenu` | Slash command menu |
|
|
354
|
+
| `UEditorMentionMenu` | @ mention menu |
|
|
355
|
+
| `UEditorEmojiMenu` | Emoji picker |
|
|
356
|
+
|
|
357
|
+
## Content
|
|
358
|
+
|
|
359
|
+
Components integrating with `@nuxt/content`.
|
|
360
|
+
|
|
361
|
+
| Component | Purpose |
|
|
362
|
+
|---|---|
|
|
363
|
+
| `UContentNavigation` | Sidebar navigation tree |
|
|
364
|
+
| `UContentToc` | Table of contents |
|
|
365
|
+
| `UContentSurround` | Prev/next links |
|
|
366
|
+
| `UContentSearch` | Search command palette |
|
|
367
|
+
| `UContentSearchButton` | Search trigger button |
|
|
368
|
+
|
|
369
|
+
## Color Mode
|
|
370
|
+
|
|
371
|
+
| Component | Purpose |
|
|
372
|
+
|---|---|
|
|
373
|
+
| `UColorModeButton` | Toggle light/dark button |
|
|
374
|
+
| `UColorModeSwitch` | Toggle light/dark switch |
|
|
375
|
+
| `UColorModeSelect` | Dropdown selector |
|
|
376
|
+
| `UColorModeAvatar` | Avatar with different src per mode |
|
|
377
|
+
| `UColorModeImage` | Image with different src per mode |
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Composables
|
|
2
|
+
|
|
3
|
+
## useToast
|
|
4
|
+
|
|
5
|
+
Show notifications. Requires `<UApp>` wrapper.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
const toast = useToast()
|
|
9
|
+
|
|
10
|
+
toast.add({
|
|
11
|
+
title: 'Success',
|
|
12
|
+
description: 'Item saved',
|
|
13
|
+
color: 'success', // primary, success, error, warning, info
|
|
14
|
+
icon: 'i-lucide-check-circle',
|
|
15
|
+
duration: 5000, // 0 = never dismiss
|
|
16
|
+
actions: [{ label: 'Undo', onClick: () => {} }]
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
toast.remove('toast-id')
|
|
20
|
+
toast.clear()
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## useOverlay
|
|
24
|
+
|
|
25
|
+
Programmatically create modals, slideovers, drawers.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const overlay = useOverlay()
|
|
29
|
+
|
|
30
|
+
// create() returns a reusable instance with open(), close(), patch()
|
|
31
|
+
const modal = overlay.create(MyComponent)
|
|
32
|
+
|
|
33
|
+
// open() accepts props and returns an object with .result (a Promise)
|
|
34
|
+
const { result } = modal.open({ title: 'Confirm' })
|
|
35
|
+
|
|
36
|
+
if (await result) {
|
|
37
|
+
// User confirmed
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Inside the overlay component, emit close with a value:
|
|
41
|
+
// emit('close', true) or emit('close', false)
|
|
42
|
+
|
|
43
|
+
// You can also close from outside:
|
|
44
|
+
modal.close(false)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## defineShortcuts
|
|
48
|
+
|
|
49
|
+
Define keyboard shortcuts.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
defineShortcuts({
|
|
53
|
+
meta_k: () => openSearch(), // Cmd+K (Mac) / Ctrl+K (Win)
|
|
54
|
+
meta_shift_p: () => openPalette(), // Cmd+Shift+P
|
|
55
|
+
escape: () => close(),
|
|
56
|
+
ctrl_s: () => save(),
|
|
57
|
+
|
|
58
|
+
// With condition
|
|
59
|
+
meta_enter: {
|
|
60
|
+
handler: () => submit(),
|
|
61
|
+
whenever: [isFormValid]
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
| Key | Meaning |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `meta` | Cmd (Mac) / Ctrl (Windows) |
|
|
69
|
+
| `ctrl` | Ctrl key |
|
|
70
|
+
| `alt` | Alt / Option key |
|
|
71
|
+
| `shift` | Shift key |
|
|
72
|
+
| `_` | Key separator |
|
|
73
|
+
|
|
74
|
+
## defineLocale / extendLocale
|
|
75
|
+
|
|
76
|
+
i18n locale definition.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { fr } from '@nuxt/ui/locale'
|
|
80
|
+
|
|
81
|
+
// Use a built-in locale (50+ available)
|
|
82
|
+
// <UApp :locale="fr">
|
|
83
|
+
|
|
84
|
+
// Define custom locale
|
|
85
|
+
const locale = defineLocale({
|
|
86
|
+
name: 'Español',
|
|
87
|
+
code: 'es',
|
|
88
|
+
dir: 'ltr',
|
|
89
|
+
messages: {
|
|
90
|
+
select: { placeholder: 'Seleccionar...' }
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// Extend existing locale
|
|
95
|
+
import { en } from '@nuxt/ui/locale'
|
|
96
|
+
|
|
97
|
+
const customEn = extendLocale(en, {
|
|
98
|
+
messages: { commandPalette: { placeholder: 'Search a component...' } }
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```vue
|
|
103
|
+
<UApp :locale="fr"><NuxtPage /></UApp>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## extractShortcuts
|
|
107
|
+
|
|
108
|
+
Extract shortcut keys from a list of items (e.g., dropdown menu items) into a shortcuts map for `defineShortcuts`.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const items = [
|
|
112
|
+
{ label: 'New file', kbds: ['meta', 'n'], onSelect: () => newFile() },
|
|
113
|
+
{ label: 'Save', kbds: ['meta', 's'], onSelect: () => save() }
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
defineShortcuts(extractShortcuts(items))
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Quick reference
|
|
120
|
+
|
|
121
|
+
| Composable | Purpose |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `useToast` | Show notifications |
|
|
124
|
+
| `useOverlay` | Programmatic modals/slideovers |
|
|
125
|
+
| `defineShortcuts` | Keyboard shortcuts |
|
|
126
|
+
| `defineLocale` / `extendLocale` | i18n locale |
|
|
127
|
+
| `extractShortcuts` | Parse shortcut definitions |
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Chat Layout
|
|
2
|
+
|
|
3
|
+
Build AI chat interfaces with message streams, prompts, and Vercel AI SDK integration.
|
|
4
|
+
|
|
5
|
+
## Component tree
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
UApp
|
|
9
|
+
└── NuxtLayout (dashboard)
|
|
10
|
+
└── UDashboardGroup
|
|
11
|
+
├── UDashboardSidebar (conversations)
|
|
12
|
+
└── NuxtPage
|
|
13
|
+
└── UDashboardPanel
|
|
14
|
+
├── #header → UDashboardNavbar
|
|
15
|
+
├── #body → UContainer → UChatMessages
|
|
16
|
+
└── #footer → UContainer → UChatPrompt
|
|
17
|
+
└── UChatPromptSubmit
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
### Install AI SDK
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add ai @ai-sdk/gateway @ai-sdk/vue
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Server endpoint
|
|
29
|
+
|
|
30
|
+
```ts [server/api/chat.post.ts]
|
|
31
|
+
import { streamText, convertToModelMessages } from 'ai'
|
|
32
|
+
import { gateway } from '@ai-sdk/gateway'
|
|
33
|
+
|
|
34
|
+
export default defineEventHandler(async (event) => {
|
|
35
|
+
const { messages } = await readBody(event)
|
|
36
|
+
|
|
37
|
+
return streamText({
|
|
38
|
+
model: gateway('openai/gpt-4o-mini'),
|
|
39
|
+
system: 'You are a helpful assistant.',
|
|
40
|
+
messages: await convertToModelMessages(messages)
|
|
41
|
+
}).toUIMessageStreamResponse()
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Full page chat
|
|
46
|
+
|
|
47
|
+
```vue [pages/chat/[id].vue]
|
|
48
|
+
<script setup lang="ts">
|
|
49
|
+
import { Chat } from '@ai-sdk/vue'
|
|
50
|
+
|
|
51
|
+
definePageMeta({ layout: 'dashboard' })
|
|
52
|
+
|
|
53
|
+
const input = ref('')
|
|
54
|
+
|
|
55
|
+
const chat = new Chat({
|
|
56
|
+
onError(error) {
|
|
57
|
+
console.error(error)
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
function onSubmit() {
|
|
62
|
+
chat.sendMessage({ text: input.value })
|
|
63
|
+
input.value = ''
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<UDashboardPanel>
|
|
69
|
+
<template #header>
|
|
70
|
+
<UDashboardNavbar title="Chat" />
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<template #body>
|
|
74
|
+
<UContainer>
|
|
75
|
+
<UChatMessages :messages="chat.messages" :status="chat.status">
|
|
76
|
+
<template #content="{ message }">
|
|
77
|
+
<template v-for="(part, index) in message.parts" :key="`${message.id}-${part.type}-${index}`">
|
|
78
|
+
<MDC
|
|
79
|
+
v-if="part.type === 'text' && message.role === 'assistant'"
|
|
80
|
+
:value="part.text"
|
|
81
|
+
:cache-key="`${message.id}-${index}`"
|
|
82
|
+
class="*:first:mt-0 *:last:mb-0"
|
|
83
|
+
/>
|
|
84
|
+
<p v-else-if="part.type === 'text' && message.role === 'user'" class="whitespace-pre-wrap">
|
|
85
|
+
{{ part.text }}
|
|
86
|
+
</p>
|
|
87
|
+
</template>
|
|
88
|
+
</template>
|
|
89
|
+
</UChatMessages>
|
|
90
|
+
</UContainer>
|
|
91
|
+
</template>
|
|
92
|
+
|
|
93
|
+
<template #footer>
|
|
94
|
+
<UContainer class="pb-4 sm:pb-6">
|
|
95
|
+
<UChatPrompt v-model="input" :error="chat.error" @submit="onSubmit">
|
|
96
|
+
<UChatPromptSubmit :status="chat.status" @stop="chat.stop()" @reload="chat.regenerate()" />
|
|
97
|
+
</UChatPrompt>
|
|
98
|
+
</UContainer>
|
|
99
|
+
</template>
|
|
100
|
+
</UDashboardPanel>
|
|
101
|
+
</template>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Key components
|
|
105
|
+
|
|
106
|
+
### ChatMessages
|
|
107
|
+
|
|
108
|
+
Scrollable message list with auto-scroll and loading indicator.
|
|
109
|
+
|
|
110
|
+
| Prop | Description |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `messages` | Array of AI SDK messages |
|
|
113
|
+
| `status` | `'submitted'`, `'streaming'`, `'ready'`, `'error'` |
|
|
114
|
+
|
|
115
|
+
Slots: `#content` (receives `{ message }`), `#actions` (per-message), `#indicator` (loading)
|
|
116
|
+
|
|
117
|
+
### ChatPrompt
|
|
118
|
+
|
|
119
|
+
Enhanced textarea form for prompts. Accepts all Textarea props.
|
|
120
|
+
|
|
121
|
+
| Prop | Description |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `v-model` | Input text binding |
|
|
124
|
+
| `error` | Error from chat instance |
|
|
125
|
+
| `variant` | `'outline'` (default), `'subtle'`, `'soft'`, `'ghost'`, `'none'` |
|
|
126
|
+
|
|
127
|
+
Slots: `#default` (submit button), `#footer` (below input, e.g. model selector)
|
|
128
|
+
|
|
129
|
+
### ChatPromptSubmit
|
|
130
|
+
|
|
131
|
+
Submit button with automatic status handling (send/stop/reload).
|
|
132
|
+
|
|
133
|
+
### ChatPalette
|
|
134
|
+
|
|
135
|
+
Layout wrapper for chat inside overlays (Modal, Slideover, Drawer).
|
|
136
|
+
|
|
137
|
+
## Chat in a modal
|
|
138
|
+
|
|
139
|
+
```vue
|
|
140
|
+
<UModal v-model:open="isOpen">
|
|
141
|
+
<template #content>
|
|
142
|
+
<UChatPalette>
|
|
143
|
+
<UChatMessages :messages="chat.messages" :status="chat.status" />
|
|
144
|
+
|
|
145
|
+
<template #prompt>
|
|
146
|
+
<UChatPrompt v-model="input" @submit="onSubmit">
|
|
147
|
+
<UChatPromptSubmit :status="chat.status" />
|
|
148
|
+
</UChatPrompt>
|
|
149
|
+
</template>
|
|
150
|
+
</UChatPalette>
|
|
151
|
+
</template>
|
|
152
|
+
</UModal>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## With model selector
|
|
156
|
+
|
|
157
|
+
```vue
|
|
158
|
+
<UChatPrompt v-model="input" @submit="onSubmit">
|
|
159
|
+
<UChatPromptSubmit :status="chat.status" />
|
|
160
|
+
|
|
161
|
+
<template #footer>
|
|
162
|
+
<USelect
|
|
163
|
+
v-model="model"
|
|
164
|
+
placeholder="Select a model"
|
|
165
|
+
variant="ghost"
|
|
166
|
+
icon="i-simple-icons-openai"
|
|
167
|
+
:items="[
|
|
168
|
+
{ label: 'GPT-4o', value: 'gpt-4o' },
|
|
169
|
+
{ label: 'Claude 3.5 Sonnet', value: 'claude-3.5-sonnet' },
|
|
170
|
+
{ label: 'Gemini 2.5 Pro', value: 'gemini-2.5-pro' }
|
|
171
|
+
]"
|
|
172
|
+
/>
|
|
173
|
+
</template>
|
|
174
|
+
</UChatPrompt>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Conversation sidebar
|
|
178
|
+
|
|
179
|
+
```vue [layouts/dashboard.vue]
|
|
180
|
+
<template>
|
|
181
|
+
<UDashboardGroup>
|
|
182
|
+
<UDashboardSidebar collapsible resizable>
|
|
183
|
+
<template #header>
|
|
184
|
+
<UButton icon="i-lucide-plus" label="New chat" block />
|
|
185
|
+
</template>
|
|
186
|
+
|
|
187
|
+
<template #default>
|
|
188
|
+
<UNavigationMenu :items="conversations" orientation="vertical" />
|
|
189
|
+
</template>
|
|
190
|
+
</UDashboardSidebar>
|
|
191
|
+
|
|
192
|
+
<slot />
|
|
193
|
+
</UDashboardGroup>
|
|
194
|
+
</template>
|
|
195
|
+
```
|