@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.
Files changed (154) hide show
  1. package/.env.example +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +175 -0
  4. package/app/app.vue +14 -0
  5. package/app/assets/css/main.css +129 -0
  6. package/app/components/CommandPalette.vue +182 -0
  7. package/app/components/ShortcutsHelp.vue +85 -0
  8. package/app/components/git/ChangesMinimap.vue +143 -0
  9. package/app/components/git/CommitList.vue +224 -0
  10. package/app/components/git/DiffPanel.vue +402 -0
  11. package/app/components/git/DiffViewer.vue +803 -0
  12. package/app/components/layout/RepoSelector.vue +358 -0
  13. package/app/components/layout/Sidebar.vue +91 -0
  14. package/app/components/prd/Meta.vue +69 -0
  15. package/app/components/prd/Viewer.vue +285 -0
  16. package/app/components/tasks/Board.vue +86 -0
  17. package/app/components/tasks/Card.vue +108 -0
  18. package/app/components/tasks/Column.vue +108 -0
  19. package/app/components/tasks/Detail.vue +291 -0
  20. package/app/components/ui/badge/Badge.vue +26 -0
  21. package/app/components/ui/badge/index.ts +26 -0
  22. package/app/components/ui/button/Button.vue +29 -0
  23. package/app/components/ui/button/index.ts +38 -0
  24. package/app/components/ui/card/Card.vue +22 -0
  25. package/app/components/ui/card/CardAction.vue +17 -0
  26. package/app/components/ui/card/CardContent.vue +17 -0
  27. package/app/components/ui/card/CardDescription.vue +17 -0
  28. package/app/components/ui/card/CardFooter.vue +17 -0
  29. package/app/components/ui/card/CardHeader.vue +17 -0
  30. package/app/components/ui/card/CardTitle.vue +17 -0
  31. package/app/components/ui/card/index.ts +7 -0
  32. package/app/components/ui/combobox/Combobox.vue +19 -0
  33. package/app/components/ui/combobox/ComboboxAnchor.vue +23 -0
  34. package/app/components/ui/combobox/ComboboxEmpty.vue +21 -0
  35. package/app/components/ui/combobox/ComboboxGroup.vue +27 -0
  36. package/app/components/ui/combobox/ComboboxInput.vue +42 -0
  37. package/app/components/ui/combobox/ComboboxItem.vue +24 -0
  38. package/app/components/ui/combobox/ComboboxItemIndicator.vue +23 -0
  39. package/app/components/ui/combobox/ComboboxList.vue +33 -0
  40. package/app/components/ui/combobox/ComboboxSeparator.vue +21 -0
  41. package/app/components/ui/combobox/ComboboxTrigger.vue +24 -0
  42. package/app/components/ui/combobox/ComboboxViewport.vue +23 -0
  43. package/app/components/ui/combobox/index.ts +13 -0
  44. package/app/components/ui/command/Command.vue +103 -0
  45. package/app/components/ui/command/CommandDialog.vue +33 -0
  46. package/app/components/ui/command/CommandEmpty.vue +27 -0
  47. package/app/components/ui/command/CommandGroup.vue +45 -0
  48. package/app/components/ui/command/CommandInput.vue +54 -0
  49. package/app/components/ui/command/CommandItem.vue +76 -0
  50. package/app/components/ui/command/CommandList.vue +25 -0
  51. package/app/components/ui/command/CommandSeparator.vue +21 -0
  52. package/app/components/ui/command/CommandShortcut.vue +17 -0
  53. package/app/components/ui/command/index.ts +25 -0
  54. package/app/components/ui/dialog/Dialog.vue +19 -0
  55. package/app/components/ui/dialog/DialogClose.vue +15 -0
  56. package/app/components/ui/dialog/DialogContent.vue +53 -0
  57. package/app/components/ui/dialog/DialogDescription.vue +23 -0
  58. package/app/components/ui/dialog/DialogFooter.vue +15 -0
  59. package/app/components/ui/dialog/DialogHeader.vue +17 -0
  60. package/app/components/ui/dialog/DialogOverlay.vue +21 -0
  61. package/app/components/ui/dialog/DialogScrollContent.vue +59 -0
  62. package/app/components/ui/dialog/DialogTitle.vue +23 -0
  63. package/app/components/ui/dialog/DialogTrigger.vue +15 -0
  64. package/app/components/ui/dialog/index.ts +10 -0
  65. package/app/components/ui/input/Input.vue +33 -0
  66. package/app/components/ui/input/index.ts +1 -0
  67. package/app/components/ui/scroll-area/ScrollArea.vue +33 -0
  68. package/app/components/ui/scroll-area/ScrollBar.vue +32 -0
  69. package/app/components/ui/scroll-area/index.ts +2 -0
  70. package/app/components/ui/separator/Separator.vue +29 -0
  71. package/app/components/ui/separator/index.ts +1 -0
  72. package/app/components/ui/sheet/Sheet.vue +19 -0
  73. package/app/components/ui/sheet/SheetClose.vue +15 -0
  74. package/app/components/ui/sheet/SheetContent.vue +62 -0
  75. package/app/components/ui/sheet/SheetDescription.vue +21 -0
  76. package/app/components/ui/sheet/SheetFooter.vue +16 -0
  77. package/app/components/ui/sheet/SheetHeader.vue +15 -0
  78. package/app/components/ui/sheet/SheetOverlay.vue +21 -0
  79. package/app/components/ui/sheet/SheetTitle.vue +21 -0
  80. package/app/components/ui/sheet/SheetTrigger.vue +15 -0
  81. package/app/components/ui/sheet/index.ts +8 -0
  82. package/app/components/ui/tabs/Tabs.vue +24 -0
  83. package/app/components/ui/tabs/TabsContent.vue +21 -0
  84. package/app/components/ui/tabs/TabsList.vue +24 -0
  85. package/app/components/ui/tabs/TabsTrigger.vue +26 -0
  86. package/app/components/ui/tabs/index.ts +4 -0
  87. package/app/components/ui/tooltip/Tooltip.vue +19 -0
  88. package/app/components/ui/tooltip/TooltipContent.vue +34 -0
  89. package/app/components/ui/tooltip/TooltipProvider.vue +14 -0
  90. package/app/components/ui/tooltip/TooltipTrigger.vue +15 -0
  91. package/app/components/ui/tooltip/index.ts +4 -0
  92. package/app/composables/useFileWatch.ts +78 -0
  93. package/app/composables/useGit.ts +180 -0
  94. package/app/composables/useKeyboard.ts +180 -0
  95. package/app/composables/usePrd.ts +86 -0
  96. package/app/composables/useRepos.ts +108 -0
  97. package/app/composables/useThemeMode.ts +38 -0
  98. package/app/composables/useToast.ts +31 -0
  99. package/app/layouts/default.vue +197 -0
  100. package/app/lib/utils.ts +7 -0
  101. package/app/pages/[repo]/[prd].vue +263 -0
  102. package/app/pages/index.vue +257 -0
  103. package/app/types/git.ts +81 -0
  104. package/app/types/index.ts +29 -0
  105. package/app/types/prd.ts +49 -0
  106. package/app/types/repo.ts +37 -0
  107. package/app/types/task.ts +134 -0
  108. package/bin/prd +21 -0
  109. package/components.json +21 -0
  110. package/dist/app/types/git.js +1 -0
  111. package/dist/app/types/prd.js +1 -0
  112. package/dist/app/types/repo.js +1 -0
  113. package/dist/app/types/task.js +1 -0
  114. package/dist/host/src/api/git.js +96 -0
  115. package/dist/host/src/api/index.js +4 -0
  116. package/dist/host/src/api/prds.js +195 -0
  117. package/dist/host/src/api/repos.js +47 -0
  118. package/dist/host/src/api/state.js +63 -0
  119. package/dist/host/src/executor.js +109 -0
  120. package/dist/host/src/index.js +95 -0
  121. package/dist/host/src/mcp.js +62 -0
  122. package/dist/host/src/ui.js +64 -0
  123. package/dist/server/utils/db.js +125 -0
  124. package/dist/server/utils/git.js +396 -0
  125. package/dist/server/utils/prd-state.js +229 -0
  126. package/dist/server/utils/repos.js +256 -0
  127. package/docs/MCP.md +180 -0
  128. package/nuxt.config.ts +34 -0
  129. package/package.json +88 -0
  130. package/public/favicon.ico +0 -0
  131. package/public/robots.txt +1 -0
  132. package/server/api/browse.get.ts +52 -0
  133. package/server/api/repos/[repoId]/git/commits.get.ts +103 -0
  134. package/server/api/repos/[repoId]/git/diff.get.ts +77 -0
  135. package/server/api/repos/[repoId]/git/file-content.get.ts +66 -0
  136. package/server/api/repos/[repoId]/git/file-diff.get.ts +109 -0
  137. package/server/api/repos/[repoId]/prd/[prdSlug]/progress.get.ts +36 -0
  138. package/server/api/repos/[repoId]/prd/[prdSlug]/tasks/[taskId]/commits.get.ts +146 -0
  139. package/server/api/repos/[repoId]/prd/[prdSlug]/tasks.get.ts +36 -0
  140. package/server/api/repos/[repoId]/prd/[prdSlug].get.ts +97 -0
  141. package/server/api/repos/[repoId]/prds.get.ts +85 -0
  142. package/server/api/repos/[repoId]/refresh-git-repos.post.ts +42 -0
  143. package/server/api/repos/[repoId].delete.ts +27 -0
  144. package/server/api/repos/index.get.ts +5 -0
  145. package/server/api/repos/index.post.ts +39 -0
  146. package/server/api/watch.get.ts +63 -0
  147. package/server/plugins/migrate-legacy-state.ts +19 -0
  148. package/server/tsconfig.json +3 -0
  149. package/server/utils/db.ts +169 -0
  150. package/server/utils/git.ts +478 -0
  151. package/server/utils/prd-state.ts +335 -0
  152. package/server/utils/repos.ts +322 -0
  153. package/server/utils/watcher.ts +179 -0
  154. 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>