Skip to content

Commit 9c4fc61

Browse files
danwritecodeclaude
andcommitted
(feat) install core shadcn-vue components and update landing page
Add button, card, badge, alert, input, label, and separator as pre-installed components. Rewrite the landing page to use Card, Button, Badge, and Alert instead of raw HTML. Update README with a component table, docs link, and revised install examples. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b051cdd commit 9c4fc61

26 files changed

Lines changed: 463 additions & 20 deletions

‎README.md‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,34 @@ bufstack/
8585
└── .claude/skills/ # Claude Code skills (e.g. scaffold-entity)
8686
```
8787

88-
## Adding UI Components
88+
## UI Components (shadcn-vue)
8989

90-
shadcn-vue components are added on-demand -- you only install what you use:
90+
Bufstack uses [shadcn-vue](https://www.shadcn-vue.com/) for UI components -- accessible, composable primitives built on [Reka UI](https://reka-ui.com/) and styled with Tailwind CSS. Browse the full component library at **[shadcn-vue.com/docs/components](https://www.shadcn-vue.com/docs/components/accordion.html)**.
91+
92+
### Pre-installed components
93+
94+
These components are included out of the box:
95+
96+
| Component | Usage |
97+
|-----------|-------|
98+
| `Button` | Primary actions, form submits, links |
99+
| `Card` | Content containers with header/content/footer sections |
100+
| `Badge` | Labels, tags, status indicators |
101+
| `Alert` | Inline messages, success/error feedback |
102+
| `Input` | Text inputs |
103+
| `Label` | Form labels |
104+
| `Separator` | Visual dividers |
105+
106+
### Adding more components
107+
108+
Components are installed on-demand -- you only add what you use:
91109

92110
```bash
93111
# Add a single component
94-
cd frontend && bunx shadcn-vue@latest add button
112+
cd frontend && bunx shadcn-vue@latest add dialog
95113

96114
# Add multiple components at once
97-
cd frontend && bunx shadcn-vue@latest add card dialog select
115+
cd frontend && bunx shadcn-vue@latest add select tooltip dropdown-menu
98116
```
99117
100118
Components are installed to `frontend/app/components/ui/` and are auto-imported by Nuxt. Use them directly in templates:
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import type { AlertVariants } from "."
4+
import { cn } from "@/lib/utils"
5+
import { alertVariants } from "."
6+
7+
const props = defineProps<{
8+
class?: HTMLAttributes["class"]
9+
variant?: AlertVariants["variant"]
10+
}>()
11+
</script>
12+
13+
<template>
14+
<div
15+
data-slot="alert"
16+
:class="cn(alertVariants({ variant }), props.class)"
17+
role="alert"
18+
>
19+
<slot />
20+
</div>
21+
</template>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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="alert-description"
13+
:class="cn('text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed', props.class)"
14+
>
15+
<slot />
16+
</div>
17+
</template>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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="alert-title"
13+
:class="cn('col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight', props.class)"
14+
>
15+
<slot />
16+
</div>
17+
</template>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { VariantProps } from "class-variance-authority"
2+
import { cva } from "class-variance-authority"
3+
4+
export { default as Alert } from "./Alert.vue"
5+
export { default as AlertDescription } from "./AlertDescription.vue"
6+
export { default as AlertTitle } from "./AlertTitle.vue"
7+
8+
export const alertVariants = cva(
9+
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
10+
{
11+
variants: {
12+
variant: {
13+
default: "bg-card text-card-foreground",
14+
destructive:
15+
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
16+
},
17+
},
18+
defaultVariants: {
19+
variant: "default",
20+
},
21+
},
22+
)
23+
24+
export type AlertVariants = VariantProps<typeof alertVariants>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commit comments

Comments
 (0)