@thxgg/steward 0.1.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.
- package/.env.example +7 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/app/app.vue +14 -0
- package/app/assets/css/main.css +129 -0
- package/app/components/CommandPalette.vue +182 -0
- package/app/components/ShortcutsHelp.vue +85 -0
- package/app/components/git/ChangesMinimap.vue +143 -0
- package/app/components/git/CommitList.vue +224 -0
- package/app/components/git/DiffPanel.vue +402 -0
- package/app/components/git/DiffViewer.vue +803 -0
- package/app/components/layout/RepoSelector.vue +358 -0
- package/app/components/layout/Sidebar.vue +91 -0
- package/app/components/prd/Meta.vue +69 -0
- package/app/components/prd/Viewer.vue +285 -0
- package/app/components/tasks/Board.vue +86 -0
- package/app/components/tasks/Card.vue +108 -0
- package/app/components/tasks/Column.vue +108 -0
- package/app/components/tasks/Detail.vue +291 -0
- package/app/components/ui/badge/Badge.vue +26 -0
- package/app/components/ui/badge/index.ts +26 -0
- package/app/components/ui/button/Button.vue +29 -0
- package/app/components/ui/button/index.ts +38 -0
- package/app/components/ui/card/Card.vue +22 -0
- package/app/components/ui/card/CardAction.vue +17 -0
- package/app/components/ui/card/CardContent.vue +17 -0
- package/app/components/ui/card/CardDescription.vue +17 -0
- package/app/components/ui/card/CardFooter.vue +17 -0
- package/app/components/ui/card/CardHeader.vue +17 -0
- package/app/components/ui/card/CardTitle.vue +17 -0
- package/app/components/ui/card/index.ts +7 -0
- package/app/components/ui/combobox/Combobox.vue +19 -0
- package/app/components/ui/combobox/ComboboxAnchor.vue +23 -0
- package/app/components/ui/combobox/ComboboxEmpty.vue +21 -0
- package/app/components/ui/combobox/ComboboxGroup.vue +27 -0
- package/app/components/ui/combobox/ComboboxInput.vue +42 -0
- package/app/components/ui/combobox/ComboboxItem.vue +24 -0
- package/app/components/ui/combobox/ComboboxItemIndicator.vue +23 -0
- package/app/components/ui/combobox/ComboboxList.vue +33 -0
- package/app/components/ui/combobox/ComboboxSeparator.vue +21 -0
- package/app/components/ui/combobox/ComboboxTrigger.vue +24 -0
- package/app/components/ui/combobox/ComboboxViewport.vue +23 -0
- package/app/components/ui/combobox/index.ts +13 -0
- package/app/components/ui/command/Command.vue +103 -0
- package/app/components/ui/command/CommandDialog.vue +33 -0
- package/app/components/ui/command/CommandEmpty.vue +27 -0
- package/app/components/ui/command/CommandGroup.vue +45 -0
- package/app/components/ui/command/CommandInput.vue +54 -0
- package/app/components/ui/command/CommandItem.vue +76 -0
- package/app/components/ui/command/CommandList.vue +25 -0
- package/app/components/ui/command/CommandSeparator.vue +21 -0
- package/app/components/ui/command/CommandShortcut.vue +17 -0
- package/app/components/ui/command/index.ts +25 -0
- package/app/components/ui/dialog/Dialog.vue +19 -0
- package/app/components/ui/dialog/DialogClose.vue +15 -0
- package/app/components/ui/dialog/DialogContent.vue +53 -0
- package/app/components/ui/dialog/DialogDescription.vue +23 -0
- package/app/components/ui/dialog/DialogFooter.vue +15 -0
- package/app/components/ui/dialog/DialogHeader.vue +17 -0
- package/app/components/ui/dialog/DialogOverlay.vue +21 -0
- package/app/components/ui/dialog/DialogScrollContent.vue +59 -0
- package/app/components/ui/dialog/DialogTitle.vue +23 -0
- package/app/components/ui/dialog/DialogTrigger.vue +15 -0
- package/app/components/ui/dialog/index.ts +10 -0
- package/app/components/ui/input/Input.vue +33 -0
- package/app/components/ui/input/index.ts +1 -0
- package/app/components/ui/scroll-area/ScrollArea.vue +33 -0
- package/app/components/ui/scroll-area/ScrollBar.vue +32 -0
- package/app/components/ui/scroll-area/index.ts +2 -0
- package/app/components/ui/separator/Separator.vue +29 -0
- package/app/components/ui/separator/index.ts +1 -0
- package/app/components/ui/sheet/Sheet.vue +19 -0
- package/app/components/ui/sheet/SheetClose.vue +15 -0
- package/app/components/ui/sheet/SheetContent.vue +62 -0
- package/app/components/ui/sheet/SheetDescription.vue +21 -0
- package/app/components/ui/sheet/SheetFooter.vue +16 -0
- package/app/components/ui/sheet/SheetHeader.vue +15 -0
- package/app/components/ui/sheet/SheetOverlay.vue +21 -0
- package/app/components/ui/sheet/SheetTitle.vue +21 -0
- package/app/components/ui/sheet/SheetTrigger.vue +15 -0
- package/app/components/ui/sheet/index.ts +8 -0
- package/app/components/ui/tabs/Tabs.vue +24 -0
- package/app/components/ui/tabs/TabsContent.vue +21 -0
- package/app/components/ui/tabs/TabsList.vue +24 -0
- package/app/components/ui/tabs/TabsTrigger.vue +26 -0
- package/app/components/ui/tabs/index.ts +4 -0
- package/app/components/ui/tooltip/Tooltip.vue +19 -0
- package/app/components/ui/tooltip/TooltipContent.vue +34 -0
- package/app/components/ui/tooltip/TooltipProvider.vue +14 -0
- package/app/components/ui/tooltip/TooltipTrigger.vue +15 -0
- package/app/components/ui/tooltip/index.ts +4 -0
- package/app/composables/useFileWatch.ts +78 -0
- package/app/composables/useGit.ts +180 -0
- package/app/composables/useKeyboard.ts +180 -0
- package/app/composables/usePrd.ts +86 -0
- package/app/composables/useRepos.ts +108 -0
- package/app/composables/useThemeMode.ts +38 -0
- package/app/composables/useToast.ts +31 -0
- package/app/layouts/default.vue +197 -0
- package/app/lib/utils.ts +7 -0
- package/app/pages/[repo]/[prd].vue +263 -0
- package/app/pages/index.vue +257 -0
- package/app/types/git.ts +81 -0
- package/app/types/index.ts +29 -0
- package/app/types/prd.ts +49 -0
- package/app/types/repo.ts +37 -0
- package/app/types/task.ts +134 -0
- package/bin/prd +21 -0
- package/components.json +21 -0
- package/dist/app/types/git.js +1 -0
- package/dist/app/types/prd.js +1 -0
- package/dist/app/types/repo.js +1 -0
- package/dist/app/types/task.js +1 -0
- package/dist/host/src/api/git.js +96 -0
- package/dist/host/src/api/index.js +4 -0
- package/dist/host/src/api/prds.js +195 -0
- package/dist/host/src/api/repos.js +47 -0
- package/dist/host/src/api/state.js +63 -0
- package/dist/host/src/executor.js +109 -0
- package/dist/host/src/index.js +95 -0
- package/dist/host/src/mcp.js +62 -0
- package/dist/host/src/ui.js +64 -0
- package/dist/server/utils/db.js +125 -0
- package/dist/server/utils/git.js +396 -0
- package/dist/server/utils/prd-state.js +229 -0
- package/dist/server/utils/repos.js +256 -0
- package/docs/MCP.md +180 -0
- package/nuxt.config.ts +34 -0
- package/package.json +88 -0
- package/public/favicon.ico +0 -0
- package/public/robots.txt +1 -0
- package/server/api/browse.get.ts +52 -0
- package/server/api/repos/[repoId]/git/commits.get.ts +103 -0
- package/server/api/repos/[repoId]/git/diff.get.ts +77 -0
- package/server/api/repos/[repoId]/git/file-content.get.ts +66 -0
- package/server/api/repos/[repoId]/git/file-diff.get.ts +109 -0
- package/server/api/repos/[repoId]/prd/[prdSlug]/progress.get.ts +36 -0
- package/server/api/repos/[repoId]/prd/[prdSlug]/tasks/[taskId]/commits.get.ts +146 -0
- package/server/api/repos/[repoId]/prd/[prdSlug]/tasks.get.ts +36 -0
- package/server/api/repos/[repoId]/prd/[prdSlug].get.ts +97 -0
- package/server/api/repos/[repoId]/prds.get.ts +85 -0
- package/server/api/repos/[repoId]/refresh-git-repos.post.ts +42 -0
- package/server/api/repos/[repoId].delete.ts +27 -0
- package/server/api/repos/index.get.ts +5 -0
- package/server/api/repos/index.post.ts +39 -0
- package/server/api/watch.get.ts +63 -0
- package/server/plugins/migrate-legacy-state.ts +19 -0
- package/server/tsconfig.json +3 -0
- package/server/utils/db.ts +169 -0
- package/server/utils/git.ts +478 -0
- package/server/utils/prd-state.ts +335 -0
- package/server/utils/repos.ts +322 -0
- package/server/utils/watcher.ts +179 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Calendar,
|
|
4
|
+
Clock,
|
|
5
|
+
Tag,
|
|
6
|
+
AlertTriangle,
|
|
7
|
+
CheckCircle2,
|
|
8
|
+
Circle,
|
|
9
|
+
ListOrdered,
|
|
10
|
+
CheckSquare,
|
|
11
|
+
Link2,
|
|
12
|
+
Check,
|
|
13
|
+
Diff,
|
|
14
|
+
ArrowLeft
|
|
15
|
+
} from 'lucide-vue-next'
|
|
16
|
+
import {
|
|
17
|
+
Sheet,
|
|
18
|
+
SheetContent,
|
|
19
|
+
SheetHeader,
|
|
20
|
+
SheetTitle,
|
|
21
|
+
SheetDescription,
|
|
22
|
+
SheetFooter,
|
|
23
|
+
SheetClose
|
|
24
|
+
} from '~/components/ui/sheet'
|
|
25
|
+
import { Badge } from '~/components/ui/badge'
|
|
26
|
+
import { Separator } from '~/components/ui/separator'
|
|
27
|
+
import { Button } from '~/components/ui/button'
|
|
28
|
+
import type { Task, CommitRef } from '~/types/task'
|
|
29
|
+
|
|
30
|
+
const props = defineProps<{
|
|
31
|
+
task: Task | null
|
|
32
|
+
/** Map of task ID to task title for displaying dependency names */
|
|
33
|
+
taskTitles?: Map<string, string>
|
|
34
|
+
/** Git commits associated with this task (resolved with repo context) */
|
|
35
|
+
commits?: CommitRef[]
|
|
36
|
+
/** Repository ID for fetching commit details */
|
|
37
|
+
repoId?: string
|
|
38
|
+
}>()
|
|
39
|
+
|
|
40
|
+
const open = defineModel<boolean>('open', { default: false })
|
|
41
|
+
|
|
42
|
+
// Category badge styling
|
|
43
|
+
const categoryConfig = computed(() => {
|
|
44
|
+
if (!props.task) return { label: '', variant: 'secondary' as const }
|
|
45
|
+
switch (props.task.category) {
|
|
46
|
+
case 'setup':
|
|
47
|
+
return { label: 'Setup', variant: 'secondary' as const }
|
|
48
|
+
case 'feature':
|
|
49
|
+
return { label: 'Feature', variant: 'default' as const }
|
|
50
|
+
case 'integration':
|
|
51
|
+
return { label: 'Integration', variant: 'outline' as const }
|
|
52
|
+
case 'testing':
|
|
53
|
+
return { label: 'Testing', variant: 'secondary' as const }
|
|
54
|
+
case 'documentation':
|
|
55
|
+
return { label: 'Docs', variant: 'secondary' as const }
|
|
56
|
+
default:
|
|
57
|
+
return { label: props.task.category, variant: 'secondary' as const }
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Priority styling
|
|
62
|
+
const priorityConfig = computed(() => {
|
|
63
|
+
if (!props.task) return { label: '', class: '' }
|
|
64
|
+
switch (props.task.priority) {
|
|
65
|
+
case 'critical':
|
|
66
|
+
return { label: 'Critical', class: 'text-destructive' }
|
|
67
|
+
case 'high':
|
|
68
|
+
return { label: 'High', class: 'text-orange-500' }
|
|
69
|
+
case 'medium':
|
|
70
|
+
return { label: 'Medium', class: 'text-muted-foreground' }
|
|
71
|
+
case 'low':
|
|
72
|
+
return { label: 'Low', class: 'text-muted-foreground' }
|
|
73
|
+
default:
|
|
74
|
+
return { label: props.task.priority, class: 'text-muted-foreground' }
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Status styling
|
|
79
|
+
const statusConfig = computed(() => {
|
|
80
|
+
if (!props.task) return { label: '', icon: Circle, class: '' }
|
|
81
|
+
switch (props.task.status) {
|
|
82
|
+
case 'pending':
|
|
83
|
+
return { label: 'Pending', icon: Circle, class: 'text-muted-foreground' }
|
|
84
|
+
case 'in_progress':
|
|
85
|
+
return { label: 'In Progress', icon: Clock, class: 'text-blue-500' }
|
|
86
|
+
case 'completed':
|
|
87
|
+
return { label: 'Completed', icon: CheckCircle2, class: 'text-green-500' }
|
|
88
|
+
default:
|
|
89
|
+
return { label: props.task.status, icon: Circle, class: 'text-muted-foreground' }
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Format ISO date to readable string
|
|
94
|
+
function formatDate(isoString?: string): string {
|
|
95
|
+
if (!isoString) return ''
|
|
96
|
+
try {
|
|
97
|
+
return new Date(isoString).toLocaleString()
|
|
98
|
+
} catch {
|
|
99
|
+
return isoString
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Get task title from ID
|
|
104
|
+
function getTaskTitle(taskId: string): string {
|
|
105
|
+
return props.taskTitles?.get(taskId) ?? taskId
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Has commits to show
|
|
109
|
+
const hasCommits = computed(() => props.commits && props.commits.length > 0 && props.repoId)
|
|
110
|
+
|
|
111
|
+
// View state: 'details' or 'diff'
|
|
112
|
+
const viewMode = ref<'details' | 'diff'>('details')
|
|
113
|
+
const selectedCommitSha = ref<string | null>(null)
|
|
114
|
+
const selectedCommitRepo = ref<string | null>(null)
|
|
115
|
+
|
|
116
|
+
// Handle commit selection (now receives sha and repo)
|
|
117
|
+
function handleCommitSelect(sha: string, repo?: string) {
|
|
118
|
+
selectedCommitSha.value = sha
|
|
119
|
+
selectedCommitRepo.value = repo || null
|
|
120
|
+
viewMode.value = 'diff'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Go back to details view
|
|
124
|
+
function handleBackToDetails() {
|
|
125
|
+
viewMode.value = 'details'
|
|
126
|
+
selectedCommitSha.value = null
|
|
127
|
+
selectedCommitRepo.value = null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Reset view when task changes or sheet closes
|
|
131
|
+
watch(() => props.task, () => {
|
|
132
|
+
viewMode.value = 'details'
|
|
133
|
+
selectedCommitSha.value = null
|
|
134
|
+
selectedCommitRepo.value = null
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
watch(open, (isOpen) => {
|
|
138
|
+
if (!isOpen) {
|
|
139
|
+
viewMode.value = 'details'
|
|
140
|
+
selectedCommitSha.value = null
|
|
141
|
+
selectedCommitRepo.value = null
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<template>
|
|
147
|
+
<Sheet v-model:open="open">
|
|
148
|
+
<SheetContent class="flex h-full w-full flex-col overflow-hidden sm:max-w-lg" :class="{ 'sm:!max-w-none !max-w-none': viewMode === 'diff' }">
|
|
149
|
+
<!-- Header with back button when viewing diff -->
|
|
150
|
+
<SheetHeader v-if="task" class="px-6 pr-12">
|
|
151
|
+
<div class="flex items-center gap-2">
|
|
152
|
+
<Button
|
|
153
|
+
v-if="viewMode === 'diff'"
|
|
154
|
+
variant="ghost"
|
|
155
|
+
size="icon"
|
|
156
|
+
class="size-8 shrink-0"
|
|
157
|
+
@click="handleBackToDetails"
|
|
158
|
+
>
|
|
159
|
+
<ArrowLeft class="size-4" />
|
|
160
|
+
</Button>
|
|
161
|
+
<SheetTitle class="text-left text-lg">
|
|
162
|
+
{{ viewMode === 'diff' ? 'Commit Changes' : task.title }}
|
|
163
|
+
</SheetTitle>
|
|
164
|
+
</div>
|
|
165
|
+
<SheetDescription class="sr-only">Task details</SheetDescription>
|
|
166
|
+
</SheetHeader>
|
|
167
|
+
|
|
168
|
+
<!-- Diff View -->
|
|
169
|
+
<div v-if="task && viewMode === 'diff' && selectedCommitSha && repoId" class="min-h-0 flex-1 overflow-hidden">
|
|
170
|
+
<GitDiffPanel :repo-id="repoId" :commit-sha="selectedCommitSha" :repo-path="selectedCommitRepo || undefined" class="h-full" @close="handleBackToDetails" />
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<!-- Details View -->
|
|
174
|
+
<div v-else-if="task" class="min-h-0 flex-1 space-y-4 overflow-y-auto px-6">
|
|
175
|
+
<!-- Status, Category, Priority row -->
|
|
176
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
177
|
+
<Badge :variant="categoryConfig.variant">
|
|
178
|
+
<Tag class="mr-1 size-3" />
|
|
179
|
+
{{ categoryConfig.label }}
|
|
180
|
+
</Badge>
|
|
181
|
+
<Badge variant="outline" :class="priorityConfig.class">
|
|
182
|
+
<AlertTriangle class="mr-1 size-3" />
|
|
183
|
+
{{ priorityConfig.label }}
|
|
184
|
+
</Badge>
|
|
185
|
+
<Badge variant="outline" :class="statusConfig.class">
|
|
186
|
+
<component :is="statusConfig.icon" class="mr-1 size-3" />
|
|
187
|
+
{{ statusConfig.label }}
|
|
188
|
+
</Badge>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<!-- Description -->
|
|
192
|
+
<div class="space-y-2">
|
|
193
|
+
<h4 class="text-sm font-medium">Description</h4>
|
|
194
|
+
<p class="text-sm text-muted-foreground leading-relaxed">
|
|
195
|
+
{{ task.description }}
|
|
196
|
+
</p>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<Separator />
|
|
200
|
+
|
|
201
|
+
<!-- Steps -->
|
|
202
|
+
<div v-if="task.steps.length > 0" class="space-y-2">
|
|
203
|
+
<h4 class="flex items-center gap-2 text-sm font-medium">
|
|
204
|
+
<ListOrdered class="size-4" />
|
|
205
|
+
Steps
|
|
206
|
+
</h4>
|
|
207
|
+
<ol class="ml-4 list-decimal space-y-1 text-sm text-muted-foreground">
|
|
208
|
+
<li v-for="(step, index) in task.steps" :key="index" class="pl-1">
|
|
209
|
+
{{ step }}
|
|
210
|
+
</li>
|
|
211
|
+
</ol>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<Separator v-if="task.steps.length > 0 && task.passes.length > 0" />
|
|
215
|
+
|
|
216
|
+
<!-- Passes (criteria) -->
|
|
217
|
+
<div v-if="task.passes.length > 0" class="space-y-2">
|
|
218
|
+
<h4 class="flex items-center gap-2 text-sm font-medium">
|
|
219
|
+
<CheckSquare class="size-4" />
|
|
220
|
+
Pass Criteria
|
|
221
|
+
</h4>
|
|
222
|
+
<ul class="space-y-1.5 text-sm text-muted-foreground">
|
|
223
|
+
<li v-for="(pass, index) in task.passes" :key="index" class="flex items-start gap-2">
|
|
224
|
+
<div
|
|
225
|
+
class="mt-0.5 flex size-4 shrink-0 items-center justify-center rounded border"
|
|
226
|
+
:class="task.status === 'completed' ? 'border-primary bg-primary text-primary-foreground' : 'border-muted-foreground/50'"
|
|
227
|
+
>
|
|
228
|
+
<Check v-if="task.status === 'completed'" class="size-3" />
|
|
229
|
+
</div>
|
|
230
|
+
<span>{{ pass }}</span>
|
|
231
|
+
</li>
|
|
232
|
+
</ul>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<Separator v-if="task.dependencies.length > 0" />
|
|
236
|
+
|
|
237
|
+
<!-- Dependencies -->
|
|
238
|
+
<div v-if="task.dependencies.length > 0" class="space-y-2">
|
|
239
|
+
<h4 class="flex items-center gap-2 text-sm font-medium">
|
|
240
|
+
<Link2 class="size-4" />
|
|
241
|
+
Dependencies
|
|
242
|
+
</h4>
|
|
243
|
+
<ul class="space-y-1 text-sm text-muted-foreground">
|
|
244
|
+
<li v-for="depId in task.dependencies" :key="depId" class="flex items-center gap-2">
|
|
245
|
+
<span class="font-mono text-xs">{{ depId }}</span>
|
|
246
|
+
<span>{{ getTaskTitle(depId) }}</span>
|
|
247
|
+
</li>
|
|
248
|
+
</ul>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<!-- Changes (commits) -->
|
|
252
|
+
<template v-if="hasCommits">
|
|
253
|
+
<Separator />
|
|
254
|
+
<div class="space-y-2">
|
|
255
|
+
<h4 class="flex items-center gap-2 text-sm font-medium">
|
|
256
|
+
<Diff class="size-4" />
|
|
257
|
+
Changes
|
|
258
|
+
</h4>
|
|
259
|
+
<GitCommitList
|
|
260
|
+
:commits="commits!"
|
|
261
|
+
:repo-id="repoId!"
|
|
262
|
+
@select="handleCommitSelect"
|
|
263
|
+
/>
|
|
264
|
+
</div>
|
|
265
|
+
</template>
|
|
266
|
+
|
|
267
|
+
<!-- Timestamps -->
|
|
268
|
+
<div v-if="task.startedAt || task.completedAt" class="space-y-2">
|
|
269
|
+
<Separator />
|
|
270
|
+
<div class="flex flex-col gap-1 text-xs text-muted-foreground">
|
|
271
|
+
<div v-if="task.startedAt" class="flex items-center gap-2">
|
|
272
|
+
<Calendar class="size-3" />
|
|
273
|
+
<span>Started: {{ formatDate(task.startedAt) }}</span>
|
|
274
|
+
</div>
|
|
275
|
+
<div v-if="task.completedAt" class="flex items-center gap-2">
|
|
276
|
+
<CheckCircle2 class="size-3" />
|
|
277
|
+
<span>Completed: {{ formatDate(task.completedAt) }}</span>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
<!-- Close button - always at bottom -->
|
|
284
|
+
<SheetFooter class="px-6 pb-6">
|
|
285
|
+
<SheetClose as-child>
|
|
286
|
+
<Button variant="outline" class="w-full">Close</Button>
|
|
287
|
+
</SheetClose>
|
|
288
|
+
</SheetFooter>
|
|
289
|
+
</SheetContent>
|
|
290
|
+
</Sheet>
|
|
291
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { PrimitiveProps } from "reka-ui"
|
|
3
|
+
import type { HTMLAttributes } from "vue"
|
|
4
|
+
import type { BadgeVariants } from "."
|
|
5
|
+
import { reactiveOmit } from "@vueuse/core"
|
|
6
|
+
import { Primitive } from "reka-ui"
|
|
7
|
+
import { cn } from "@/lib/utils"
|
|
8
|
+
import { badgeVariants } from "."
|
|
9
|
+
|
|
10
|
+
const props = defineProps<PrimitiveProps & {
|
|
11
|
+
variant?: BadgeVariants["variant"]
|
|
12
|
+
class?: HTMLAttributes["class"]
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const delegatedProps = reactiveOmit(props, "class")
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<Primitive
|
|
20
|
+
data-slot="badge"
|
|
21
|
+
:class="cn(badgeVariants({ variant }), props.class)"
|
|
22
|
+
v-bind="delegatedProps"
|
|
23
|
+
>
|
|
24
|
+
<slot />
|
|
25
|
+
</Primitive>
|
|
26
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { VariantProps } from "class-variance-authority"
|
|
2
|
+
import { cva } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
export { default as Badge } from "./Badge.vue"
|
|
5
|
+
|
|
6
|
+
export const badgeVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default:
|
|
12
|
+
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
13
|
+
secondary:
|
|
14
|
+
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
15
|
+
destructive:
|
|
16
|
+
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
17
|
+
outline:
|
|
18
|
+
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
variant: "default",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { PrimitiveProps } from "reka-ui"
|
|
3
|
+
import type { HTMLAttributes } from "vue"
|
|
4
|
+
import type { ButtonVariants } from "."
|
|
5
|
+
import { Primitive } from "reka-ui"
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { buttonVariants } from "."
|
|
8
|
+
|
|
9
|
+
interface Props extends PrimitiveProps {
|
|
10
|
+
variant?: ButtonVariants["variant"]
|
|
11
|
+
size?: ButtonVariants["size"]
|
|
12
|
+
class?: HTMLAttributes["class"]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
16
|
+
as: "button",
|
|
17
|
+
})
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<Primitive
|
|
22
|
+
data-slot="button"
|
|
23
|
+
:as="as"
|
|
24
|
+
:as-child="asChild"
|
|
25
|
+
:class="cn(buttonVariants({ variant, size }), props.class)"
|
|
26
|
+
>
|
|
27
|
+
<slot />
|
|
28
|
+
</Primitive>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { VariantProps } from "class-variance-authority"
|
|
2
|
+
import { cva } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
export { default as Button } from "./Button.vue"
|
|
5
|
+
|
|
6
|
+
export const buttonVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default:
|
|
12
|
+
"bg-primary text-primary-foreground hover:bg-primary/90",
|
|
13
|
+
destructive:
|
|
14
|
+
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
15
|
+
outline:
|
|
16
|
+
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
17
|
+
secondary:
|
|
18
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
19
|
+
ghost:
|
|
20
|
+
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
22
|
+
},
|
|
23
|
+
size: {
|
|
24
|
+
"default": "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
25
|
+
"sm": "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
26
|
+
"lg": "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
27
|
+
"icon": "size-9",
|
|
28
|
+
"icon-sm": "size-8",
|
|
29
|
+
"icon-lg": "size-10",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
variant: "default",
|
|
34
|
+
size: "default",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes["class"]
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
data-slot="card"
|
|
13
|
+
:class="
|
|
14
|
+
cn(
|
|
15
|
+
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
|
|
16
|
+
props.class,
|
|
17
|
+
)
|
|
18
|
+
"
|
|
19
|
+
>
|
|
20
|
+
<slot />
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes["class"]
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
data-slot="card-action"
|
|
13
|
+
:class="cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes["class"]
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
data-slot="card-content"
|
|
13
|
+
:class="cn('px-6', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes["class"]
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<p
|
|
12
|
+
data-slot="card-description"
|
|
13
|
+
:class="cn('text-muted-foreground text-sm', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</p>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes["class"]
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
data-slot="card-footer"
|
|
13
|
+
:class="cn('flex items-center px-6 [.border-t]:pt-6', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes["class"]
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
data-slot="card-header"
|
|
13
|
+
:class="cn('@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes["class"]
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<h3
|
|
12
|
+
data-slot="card-title"
|
|
13
|
+
:class="cn('leading-none font-semibold', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</h3>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as Card } from "./Card.vue"
|
|
2
|
+
export { default as CardAction } from "./CardAction.vue"
|
|
3
|
+
export { default as CardContent } from "./CardContent.vue"
|
|
4
|
+
export { default as CardDescription } from "./CardDescription.vue"
|
|
5
|
+
export { default as CardFooter } from "./CardFooter.vue"
|
|
6
|
+
export { default as CardHeader } from "./CardHeader.vue"
|
|
7
|
+
export { default as CardTitle } from "./CardTitle.vue"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ComboboxRootEmits, ComboboxRootProps } from "reka-ui"
|
|
3
|
+
import { ComboboxRoot, useForwardPropsEmits } from "reka-ui"
|
|
4
|
+
|
|
5
|
+
const props = defineProps<ComboboxRootProps>()
|
|
6
|
+
const emits = defineEmits<ComboboxRootEmits>()
|
|
7
|
+
|
|
8
|
+
const forwarded = useForwardPropsEmits(props, emits)
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<ComboboxRoot
|
|
13
|
+
v-slot="slotProps"
|
|
14
|
+
data-slot="combobox"
|
|
15
|
+
v-bind="forwarded"
|
|
16
|
+
>
|
|
17
|
+
<slot v-bind="slotProps" />
|
|
18
|
+
</ComboboxRoot>
|
|
19
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ComboboxAnchorProps } from "reka-ui"
|
|
3
|
+
import type { HTMLAttributes } from "vue"
|
|
4
|
+
import { reactiveOmit } from "@vueuse/core"
|
|
5
|
+
import { ComboboxAnchor, useForwardProps } from "reka-ui"
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
const props = defineProps<ComboboxAnchorProps & { class?: HTMLAttributes["class"] }>()
|
|
9
|
+
|
|
10
|
+
const delegatedProps = reactiveOmit(props, "class")
|
|
11
|
+
|
|
12
|
+
const forwarded = useForwardProps(delegatedProps)
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<ComboboxAnchor
|
|
17
|
+
data-slot="combobox-anchor"
|
|
18
|
+
v-bind="forwarded"
|
|
19
|
+
:class="cn('w-[200px]', props.class)"
|
|
20
|
+
>
|
|
21
|
+
<slot />
|
|
22
|
+
</ComboboxAnchor>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ComboboxEmptyProps } from "reka-ui"
|
|
3
|
+
import type { HTMLAttributes } from "vue"
|
|
4
|
+
import { reactiveOmit } from "@vueuse/core"
|
|
5
|
+
import { ComboboxEmpty } from "reka-ui"
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
const props = defineProps<ComboboxEmptyProps & { class?: HTMLAttributes["class"] }>()
|
|
9
|
+
|
|
10
|
+
const delegatedProps = reactiveOmit(props, "class")
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<ComboboxEmpty
|
|
15
|
+
data-slot="combobox-empty"
|
|
16
|
+
v-bind="delegatedProps"
|
|
17
|
+
:class="cn('py-6 text-center text-sm', props.class)"
|
|
18
|
+
>
|
|
19
|
+
<slot />
|
|
20
|
+
</ComboboxEmpty>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ComboboxGroupProps } from "reka-ui"
|
|
3
|
+
import type { HTMLAttributes } from "vue"
|
|
4
|
+
import { reactiveOmit } from "@vueuse/core"
|
|
5
|
+
import { ComboboxGroup, ComboboxLabel } from "reka-ui"
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
const props = defineProps<ComboboxGroupProps & {
|
|
9
|
+
class?: HTMLAttributes["class"]
|
|
10
|
+
heading?: string
|
|
11
|
+
}>()
|
|
12
|
+
|
|
13
|
+
const delegatedProps = reactiveOmit(props, "class")
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<ComboboxGroup
|
|
18
|
+
data-slot="combobox-group"
|
|
19
|
+
v-bind="delegatedProps"
|
|
20
|
+
:class="cn('overflow-hidden p-1 text-foreground', props.class)"
|
|
21
|
+
>
|
|
22
|
+
<ComboboxLabel v-if="heading" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
|
|
23
|
+
{{ heading }}
|
|
24
|
+
</ComboboxLabel>
|
|
25
|
+
<slot />
|
|
26
|
+
</ComboboxGroup>
|
|
27
|
+
</template>
|