docs: update documentation for v0.4.0 release

- Update README.md with v0.4.0 features and screenshots placeholders
- Update architecture.md with v0.4.0 implementation status
- Update progress.md marking all 27 tasks as completed
- Create CHANGELOG.md with complete release notes
- Add v0.4.0 frontend components and hooks
This commit is contained in:
Luca Sacchi Ricciardi
2026-04-07 18:07:23 +02:00
parent e19ef64085
commit d222d21618
21 changed files with 1024 additions and 313 deletions

View File

@@ -0,0 +1,21 @@
import { cva } from "class-variance-authority"
export const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)

View File

@@ -1,26 +1,7 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import type { VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
import { badgeVariants } from "./badge-variants"
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
@@ -32,4 +13,4 @@ function Badge({ className, variant, ...props }: BadgeProps) {
)
}
export { Badge, badgeVariants }
export { Badge }

View File

@@ -0,0 +1,30 @@
import { cva } from "class-variance-authority"
export const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)

View File

@@ -1,35 +1,7 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import type { VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
import { buttonVariants } from "./button-variants"
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
@@ -48,4 +20,4 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
)
Button.displayName = "Button"
export { Button, buttonVariants }
export { Button }

View File

@@ -11,7 +11,10 @@ const DropdownMenu = React.forwardRef<
<div ref={ref} {...props}>
{React.Children.map(children, (child) =>
React.isValidElement(child)
? React.cloneElement(child as React.ReactElement<any>, {
? React.cloneElement(child as React.ReactElement<{
open?: boolean;
setOpen?: (open: boolean) => void;
}>, {
open,
setOpen,
})

View File

@@ -1,5 +1,5 @@
import { Moon, Sun, Monitor } from 'lucide-react';
import { useTheme } from '@/providers/ThemeProvider';
import { useTheme } from '@/hooks/useTheme';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,

View File

@@ -0,0 +1,14 @@
interface Toast {
id: string
title?: string
description?: string
variant?: 'default' | 'destructive'
}
// Toast helper function - exported separately to avoid fast refresh issues
export function showToast(props: Omit<Toast, 'id'>) {
window.dispatchEvent(new CustomEvent('toast', { detail: props }))
}
// Re-export Toast type for consumers
export type { Toast };

View File

@@ -1,44 +1,40 @@
import { useState, useEffect } from 'react'
import type { Toast } from './toast-utils'
interface Toast {
id: string
title?: string
description?: string
variant?: 'default' | 'destructive'
}
type ToastEvent = CustomEvent<Toast>
const Toaster = () => {
const [toasts, setToasts] = useState<Toast[]>([])
useEffect(() => {
const handleToast = (e: CustomEvent<Toast>) => {
const toast = { ...e.detail, id: Math.random().toString(36) }
setToasts((prev) => [...prev, toast])
const handleToast = (e: ToastEvent) => {
const toastItem = { ...e.detail, id: Math.random().toString(36) }
setToasts((prev) => [...prev, toastItem])
setTimeout(() => {
setToasts((prev) => prev.filter((t) => t.id !== toast.id))
setToasts((prev) => prev.filter((t) => t.id !== toastItem.id))
}, 5000)
}
window.addEventListener('toast' as any, handleToast)
return () => window.removeEventListener('toast' as any, handleToast)
window.addEventListener('toast', handleToast as EventListener)
return () => window.removeEventListener('toast', handleToast as EventListener)
}, [])
if (toasts.length === 0) return null
return (
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
{toasts.map((toast) => (
{toasts.map((toastItem) => (
<div
key={toast.id}
key={toastItem.id}
className={`rounded-lg border p-4 shadow-lg ${
toast.variant === 'destructive'
toastItem.variant === 'destructive'
? 'border-destructive bg-destructive text-destructive-foreground'
: 'border-border bg-background'
}`}
>
{toast.title && <div className="font-semibold">{toast.title}</div>}
{toast.description && <div className="text-sm">{toast.description}</div>}
{toastItem.title && <div className="font-semibold">{toastItem.title}</div>}
{toastItem.description && <div className="text-sm">{toastItem.description}</div>}
</div>
))}
</div>
@@ -46,6 +42,3 @@ const Toaster = () => {
}
export { Toaster }
export const toast = (props: Omit<Toast, 'id'>) => {
window.dispatchEvent(new CustomEvent('toast', { detail: props }))
}