diff options
| author | IwanIDev <iwan@iwani.dev> | 2026-03-21 13:47:35 +0000 |
|---|---|---|
| committer | IwanIDev <iwan@iwani.dev> | 2026-03-21 13:47:35 +0000 |
| commit | c2ad94b04f290b5197a1cc80c98015be9e7b25da (patch) | |
| tree | cae81947194a92ec9dfe50f079f6816e5fba6033 | |
| parent | 928fbed509e1dd872de601b75712bcfd13fc00bd (diff) | |
feat: add blog post listing and UI components for the blog
| -rw-r--r-- | src/components/BlogPosts.astro | 34 | ||||
| -rw-r--r-- | src/components/post-list.tsx | 0 | ||||
| -rw-r--r-- | src/components/ui/badge.tsx | 49 | ||||
| -rw-r--r-- | src/components/ui/card.tsx | 103 | ||||
| -rw-r--r-- | src/components/ui/separator.tsx | 26 | ||||
| -rw-r--r-- | src/content/blog/getting-started-astro.md | 9 | ||||
| -rw-r--r-- | src/content/config.ts | 14 | ||||
| -rw-r--r-- | src/pages/index.astro | 23 |
8 files changed, 249 insertions, 9 deletions
diff --git a/src/components/BlogPosts.astro b/src/components/BlogPosts.astro new file mode 100644 index 0000000..54b7529 --- /dev/null +++ b/src/components/BlogPosts.astro @@ -0,0 +1,34 @@ +--- +import { getCollection } from 'astro:content'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; + +// Fetch all blog posts from the content collection +const posts = await getCollection('blog'); + +// Sort posts by date (newest first) +const sortedPosts = posts.sort((a, b) => + new Date(b.data.publishDate).getTime() - new Date(a.data.publishDate).getTime() +); +--- + +<div class="w-full max-w-2xl mx-auto p-4"> + <h1 class="text-3xl font-bold mb-8">Blog Posts</h1> + <div class="space-y-4 max-h-96 overflow-y-auto"> + {sortedPosts.map((post) => ( + <a href={`/posts/${post.id}`} class="block hover:opacity-80 transition-opacity"> + <Card> + <CardHeader> + <CardTitle>{post.data.title}</CardTitle> + <CardDescription>{post.data.description}</CardDescription> + </CardHeader> + <CardContent> + <div class="flex justify-between text-sm text-gray-500"> + <span>{post.data.publishDate.toLocaleDateString()}</span> + <span>by {post.data.author}</span> + </div> + </CardContent> + </Card> + </a> + ))} + </div> +</div>
\ No newline at end of file diff --git a/src/components/post-list.tsx b/src/components/post-list.tsx new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/components/post-list.tsx diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..fe670e9 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,49 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-none border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", + secondary: + "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", + destructive: + "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", + outline: + "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", + ghost: + "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", + link: "text-primary underline-offset-4 hover:underline", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant = "default", + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps<typeof badgeVariants> & { asChild?: boolean }) { + const Comp = asChild ? Slot.Root : "span" + + return ( + <Comp + data-slot="badge" + data-variant={variant} + className={cn(badgeVariants({ variant }), className)} + {...props} + /> + ) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..45ddfa0 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,103 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ + className, + size = "default", + ...props +}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { + return ( + <div + data-slot="card" + data-size={size} + className={cn( + "group/card flex flex-col gap-4 overflow-hidden rounded-none bg-card py-4 text-xs/relaxed text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none", + className + )} + {...props} + /> + ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card-header" + className={cn( + "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-none px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3", + className + )} + {...props} + /> + ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card-title" + className={cn( + "font-heading text-sm font-medium group-data-[size=sm]/card:text-sm", + className + )} + {...props} + /> + ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card-description" + className={cn("text-xs/relaxed text-muted-foreground", className)} + {...props} + /> + ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card-action" + className={cn( + "col-start-2 row-span-2 row-start-1 self-start justify-self-end", + className + )} + {...props} + /> + ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card-content" + className={cn("px-4 group-data-[size=sm]/card:px-3", className)} + {...props} + /> + ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card-footer" + className={cn( + "flex items-center rounded-none border-t p-4 group-data-[size=sm]/card:p-3", + className + )} + {...props} + /> + ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx new file mode 100644 index 0000000..ca11501 --- /dev/null +++ b/src/components/ui/separator.tsx @@ -0,0 +1,26 @@ +import * as React from "react" +import { Separator as SeparatorPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps<typeof SeparatorPrimitive.Root>) { + return ( + <SeparatorPrimitive.Root + data-slot="separator" + decorative={decorative} + orientation={orientation} + className={cn( + "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch", + className + )} + {...props} + /> + ) +} + +export { Separator } diff --git a/src/content/blog/getting-started-astro.md b/src/content/blog/getting-started-astro.md new file mode 100644 index 0000000..fcddbcc --- /dev/null +++ b/src/content/blog/getting-started-astro.md @@ -0,0 +1,9 @@ +--- +title: "Getting Started with Astro" +description: "Basic Astro Blog Post" +publishDate: 2026-03-21 +author: "Iwan R. Ingman" +--- + +# Test Blog Post +This is a test Astro blog post.
\ No newline at end of file diff --git a/src/content/config.ts b/src/content/config.ts new file mode 100644 index 0000000..3886d49 --- /dev/null +++ b/src/content/config.ts @@ -0,0 +1,14 @@ +import { defineCollection, z } from 'astro:content'; + +const blog = defineCollection({ + schema: z.object({ + title: z.string(), + description: z.string(), + publishDate: z.coerce.date(), + author: z.string(), + }), +}); + +export const collections = { + blog, +}; diff --git a/src/pages/index.astro b/src/pages/index.astro index ad15f25..9d04eae 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,17 +1,22 @@ --- import Layout from "@/layouts/main.astro" import { Button } from "@/components/ui/button" +import BlogPosts from "@/components/BlogPosts.astro" --- <Layout> - <div class="flex min-h-svh p-6"> - <div class="flex max-w-md min-w-0 flex-col gap-4 text-sm leading-loose"> - <div> - <h1 class="font-medium">Project ready!</h1> - <p>You may now add components and start building.</p> - <p>We've already added the button component for you.</p> - <Button client:load className="mt-2">Button</Button> - </div> + <main class="flex flex-col min-h-svh p-6 items-center"> + <!-- Title --> + <div class="flex flex-col items-center"> + <h1 class="text-4xl font-bold text-center mt-10">Iwan Ingman's Blog</h1> + <p class="text-lg text-center text-muted-foreground mt-4"> + This is my blog. + </p> </div> - </div> + + <!-- Content --> + <!-- List Astro posts --> + <BlogPosts /> + </main> </Layout> + |
