From 4936c952fe34e1e627f4de237be33a6fab6f35a4 Mon Sep 17 00:00:00 2001 From: Alberto Andre Date: Mon, 1 Jun 2026 15:51:38 -0300 Subject: [PATCH 1/7] feat(ui): migrate frontend to the Seven design system (@etus/seven-react) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the local shadcn `@/components/ui/*` components with the Seven design system, consumed via the `@etus/seven-react` umbrella package. - Migrate 29 components (Button, Card, Dialog, Form, Tabs, Avatar, Badge, Label, …) across 12 client files to `@etus/seven-react`. - Delete the now-redundant local components (avatar, badge, button, card, dialog, form, label, tabs) and their orphaned unit tests. - Wire Seven: import `@etus/seven-react/styles.css` and `@source` the `@etus/ui` dist so Tailwind v4 generates the components' arbitrary-value utilities; drop the hand-pasted theme tokens that @etus/tokens now owns. - Align @etus/ui + @etus/tokens to 0.4.0-beta.2 (matches the umbrella). - Migrate Badge from the deprecated `variant` prop to `type` + `color`. - Update tests coupled to old component markup to be robust against Seven. Kept local (Seven has no standalone runtime export for these — present in its .d.ts but undefined at runtime): `Input`, `Separator`. Mapping them to Seven's Divider/Field is deferred. Verified: build, lint, 252 client + 309 server unit tests, and visual checks of the login + 5 authenticated pages. --- package.json | 3 + .../bhono-app/templates/base/package.json | 3 + .../base/src/client/components/sidebar.tsx | 3 +- .../base/src/client/components/ui/avatar.tsx | 101 - .../base/src/client/components/ui/badge.tsx | 46 - .../base/src/client/components/ui/button.tsx | 72 - .../base/src/client/components/ui/card.tsx | 86 - .../base/src/client/components/ui/dialog.tsx | 140 - .../client/components/ui/error-fallback.tsx | 3 +- .../base/src/client/components/ui/form.tsx | 172 - .../base/src/client/components/ui/label.tsx | 22 - .../client/components/ui/loading-skeleton.tsx | 3 +- .../base/src/client/components/ui/tabs.tsx | 121 - .../templates/base/src/client/index.css | 73 +- .../templates/base/src/client/routes/$.tsx | 2 +- .../client/routes/_authenticated/account.tsx | 31 +- .../routes/_authenticated/dashboard.tsx | 2 +- .../routes/_authenticated/integrations.tsx | 27 +- .../client/routes/_authenticated/settings.tsx | 28 +- .../src/client/routes/_authenticated/team.tsx | 41 +- .../base/src/client/routes/index.tsx | 2 +- .../base/src/client/routes/invite.$token.tsx | 3 +- .../base/src/client/routes/login.tsx | 3 +- .../unit/client/components/sidebar.test.tsx | 10 +- .../unit/client/components/ui/avatar.test.tsx | 308 -- .../unit/client/components/ui/button.test.tsx | 30 - .../unit/client/components/ui/card.test.tsx | 214 -- .../unit/client/components/ui/dialog.test.tsx | 297 -- .../components/ui/loading-skeleton.test.tsx | 7 +- .../unit/client/components/ui/tabs.test.tsx | 233 -- .../_authenticated/integrations.test.tsx | 7 +- pnpm-lock.yaml | 2771 +++++++++++++++-- src/client/components/sidebar.tsx | 3 +- src/client/components/ui/avatar.tsx | 101 - src/client/components/ui/badge.tsx | 46 - src/client/components/ui/button.tsx | 72 - src/client/components/ui/card.tsx | 86 - src/client/components/ui/dialog.tsx | 140 - src/client/components/ui/error-fallback.tsx | 3 +- src/client/components/ui/form.tsx | 172 - src/client/components/ui/label.tsx | 22 - src/client/components/ui/loading-skeleton.tsx | 3 +- src/client/components/ui/tabs.tsx | 121 - src/client/index.css | 73 +- src/client/routes/$.tsx | 2 +- src/client/routes/_authenticated/account.tsx | 31 +- .../routes/_authenticated/dashboard.tsx | 2 +- .../routes/_authenticated/integrations.tsx | 27 +- src/client/routes/_authenticated/settings.tsx | 28 +- src/client/routes/_authenticated/team.tsx | 41 +- src/client/routes/index.tsx | 2 +- src/client/routes/invite.$token.tsx | 3 +- src/client/routes/login.tsx | 3 +- tests/unit/client/components/sidebar.test.tsx | 10 +- .../unit/client/components/ui/avatar.test.tsx | 308 -- .../unit/client/components/ui/button.test.tsx | 30 - tests/unit/client/components/ui/card.test.tsx | 214 -- .../unit/client/components/ui/dialog.test.tsx | 297 -- .../components/ui/loading-skeleton.test.tsx | 7 +- tests/unit/client/components/ui/tabs.test.tsx | 233 -- .../_authenticated/integrations.test.tsx | 7 +- 61 files changed, 2863 insertions(+), 4088 deletions(-) delete mode 100644 packages/bhono-app/templates/base/src/client/components/ui/avatar.tsx delete mode 100644 packages/bhono-app/templates/base/src/client/components/ui/badge.tsx delete mode 100644 packages/bhono-app/templates/base/src/client/components/ui/button.tsx delete mode 100644 packages/bhono-app/templates/base/src/client/components/ui/card.tsx delete mode 100644 packages/bhono-app/templates/base/src/client/components/ui/dialog.tsx delete mode 100644 packages/bhono-app/templates/base/src/client/components/ui/form.tsx delete mode 100644 packages/bhono-app/templates/base/src/client/components/ui/label.tsx delete mode 100644 packages/bhono-app/templates/base/src/client/components/ui/tabs.tsx delete mode 100644 packages/bhono-app/templates/base/tests/unit/client/components/ui/avatar.test.tsx delete mode 100644 packages/bhono-app/templates/base/tests/unit/client/components/ui/button.test.tsx delete mode 100644 packages/bhono-app/templates/base/tests/unit/client/components/ui/card.test.tsx delete mode 100644 packages/bhono-app/templates/base/tests/unit/client/components/ui/dialog.test.tsx delete mode 100644 packages/bhono-app/templates/base/tests/unit/client/components/ui/tabs.test.tsx delete mode 100644 src/client/components/ui/avatar.tsx delete mode 100644 src/client/components/ui/badge.tsx delete mode 100644 src/client/components/ui/button.tsx delete mode 100644 src/client/components/ui/card.tsx delete mode 100644 src/client/components/ui/dialog.tsx delete mode 100644 src/client/components/ui/form.tsx delete mode 100644 src/client/components/ui/label.tsx delete mode 100644 src/client/components/ui/tabs.tsx delete mode 100644 tests/unit/client/components/ui/avatar.test.tsx delete mode 100644 tests/unit/client/components/ui/button.test.tsx delete mode 100644 tests/unit/client/components/ui/card.test.tsx delete mode 100644 tests/unit/client/components/ui/dialog.test.tsx delete mode 100644 tests/unit/client/components/ui/tabs.test.tsx diff --git a/package.json b/package.json index 6d5e9ae..1bc382d 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,9 @@ }, "dependencies": { "@etus/auth": "^0.5.0", + "@etus/seven-react": "0.1.0-beta.3", + "@etus/tokens": "0.4.0-beta.2", + "@etus/ui": "0.4.0-beta.2", "@hono/swagger-ui": "^0.6.1", "@hono/zod-openapi": "^1.4.0", "@hookform/resolvers": "^5.2.2", diff --git a/packages/bhono-app/templates/base/package.json b/packages/bhono-app/templates/base/package.json index a42e788..baadc45 100644 --- a/packages/bhono-app/templates/base/package.json +++ b/packages/bhono-app/templates/base/package.json @@ -45,6 +45,9 @@ }, "dependencies": { "@etus/auth": "^0.5.0", + "@etus/seven-react": "0.1.0-beta.3", + "@etus/tokens": "0.4.0-beta.2", + "@etus/ui": "0.4.0-beta.2", "@hono/swagger-ui": "^0.6.1", "@hono/zod-openapi": "^1.4.0", "@hookform/resolvers": "^5.2.2", diff --git a/packages/bhono-app/templates/base/src/client/components/sidebar.tsx b/packages/bhono-app/templates/base/src/client/components/sidebar.tsx index cdaeeae..35c9cd6 100644 --- a/packages/bhono-app/templates/base/src/client/components/sidebar.tsx +++ b/packages/bhono-app/templates/base/src/client/components/sidebar.tsx @@ -1,8 +1,7 @@ import { Link, useLocation } from '@tanstack/react-router' import { useState, useEffect } from 'react' import { cn } from '@/lib/utils' -import { Button } from '@/components/ui/button' -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { Avatar, AvatarFallback, AvatarImage, Button } from '@etus/seven-react' import { Separator } from '@/components/ui/separator' import { Icons } from '@/components/icons' import { useAuth } from '@/hooks/use-auth' diff --git a/packages/bhono-app/templates/base/src/client/components/ui/avatar.tsx b/packages/bhono-app/templates/base/src/client/components/ui/avatar.tsx deleted file mode 100644 index 7b1d1b8..0000000 --- a/packages/bhono-app/templates/base/src/client/components/ui/avatar.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { createContext, use, useState } from "react" -import type { HTMLAttributes, ImgHTMLAttributes, Ref } from "react" - -import { cn } from "@/lib/utils" - -interface AvatarContextValue { - imageLoaded: boolean - setImageLoaded: (loaded: boolean) => void -} - -const AvatarContext = createContext(undefined) - -function Avatar({ - className, - ref, - ...props -}: HTMLAttributes & { ref?: Ref }) { - const [imageLoaded, setImageLoaded] = useState(false) - - return ( - -
- - ) -} - -function AvatarImage({ - className, - src, - onLoad, - onError, - ref, - ...props -}: ImgHTMLAttributes & { ref?: Ref }) { - const context = use(AvatarContext) - // Track previous src to detect changes - const [prevSrc, setPrevSrc] = useState(src) - const [hasError, setHasError] = useState(false) - - // Reset error state when src changes (using derived state pattern) - if (src !== prevSrc) { - setPrevSrc(src) - setHasError(false) - context?.setImageLoaded(false) - } - - if (!src || hasError) { - return null - } - - return ( - { - context?.setImageLoaded(true) - onLoad?.(e) - }} - onError={(e) => { - setHasError(true) - context?.setImageLoaded(false) - onError?.(e) - }} - {...props} - /> - ) -} - -function AvatarFallback({ - className, - ref, - ...props -}: HTMLAttributes & { ref?: Ref }) { - const context = use(AvatarContext) - - // Always show fallback if no context (standalone usage) or image not loaded - if (context?.imageLoaded) { - return null - } - - return ( -
- ) -} - -export { Avatar, AvatarFallback, AvatarImage } diff --git a/packages/bhono-app/templates/base/src/client/components/ui/badge.tsx b/packages/bhono-app/templates/base/src/client/components/ui/badge.tsx deleted file mode 100644 index 25b8e9e..0000000 --- a/packages/bhono-app/templates/base/src/client/components/ui/badge.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { VariantProps } from "class-variance-authority" - -import { cva } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const badgeVariants = cva( - "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-fast focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", - { - variants: { - variant: { - default: - "border-transparent bg-primary text-primary-foreground shadow-xs hover:bg-green-500", - secondary: - "border-transparent bg-secondary text-secondary-foreground hover:bg-gray-200 dark:hover:bg-gray-700", - destructive: - "border-transparent bg-destructive text-destructive-foreground shadow-xs hover:bg-red-600", - outline: "text-foreground border-border", - // Status variants using Seven Design System - success: - "border-transparent bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300", - warning: - "border-transparent bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300", - error: - "border-transparent bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300", - info: - "border-transparent bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -export interface BadgeProps - extends React.HTMLAttributes, - VariantProps {} - -function Badge({ className, variant, ...props }: BadgeProps) { - return ( -
- ) -} - -export { Badge, badgeVariants } diff --git a/packages/bhono-app/templates/base/src/client/components/ui/button.tsx b/packages/bhono-app/templates/base/src/client/components/ui/button.tsx deleted file mode 100644 index e452153..0000000 --- a/packages/bhono-app/templates/base/src/client/components/ui/button.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import type { VariantProps } from "class-variance-authority" -import type { ButtonHTMLAttributes, Ref } from "react" - -import { Slot } from "@radix-ui/react-slot" -import { cva } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 active:scale-[0.98]", - { - variants: { - variant: { - default: - "bg-primary text-primary-foreground shadow-sm hover:bg-green-500 hover:shadow-md", - destructive: - "bg-destructive text-destructive-foreground shadow-sm hover:bg-red-600 hover:shadow-md", - outline: - "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground hover:border-gray-300 dark:hover:border-gray-600", - secondary: - "bg-secondary text-secondary-foreground shadow-xs hover:bg-gray-200 dark:hover:bg-gray-700", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline hover:text-green-600", - // Status variants - success: - "bg-green-500 text-white shadow-sm hover:bg-green-600 hover:shadow-md", - warning: - "bg-yellow-500 text-gray-900 shadow-sm hover:bg-yellow-600 hover:shadow-md", - info: - "bg-blue-500 text-white shadow-sm hover:bg-blue-600 hover:shadow-md", - }, - size: { - default: "h-9 px-4 py-2", - sm: "h-8 rounded-md px-3 text-xs", - lg: "h-10 rounded-md px-8", - xl: "h-12 rounded-lg px-10 text-base", - icon: "h-9 w-9", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - } -) - -export interface ButtonProps - extends ButtonHTMLAttributes, - VariantProps { - asChild?: boolean - ref?: Ref -} - -function Button({ - className, - variant, - size, - asChild = false, - ref, - ...props -}: ButtonProps) { - const Comp = asChild ? Slot : "button" - return ( - - ) -} - -export { Button, buttonVariants } diff --git a/packages/bhono-app/templates/base/src/client/components/ui/card.tsx b/packages/bhono-app/templates/base/src/client/components/ui/card.tsx deleted file mode 100644 index 43d5d55..0000000 --- a/packages/bhono-app/templates/base/src/client/components/ui/card.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import type React from "react" - -import { cn } from "@/lib/utils" - -function Card({ - className, - ref, - ...props -}: React.HTMLAttributes & { ref?: React.Ref }) { - return ( -
- ) -} - -function CardHeader({ - className, - ref, - ...props -}: React.HTMLAttributes & { ref?: React.Ref }) { - return ( -
- ) -} - -function CardTitle({ - className, - ref, - ...props -}: React.HTMLAttributes & { ref?: React.Ref }) { - return ( -

- ) -} - -function CardDescription({ - className, - ref, - ...props -}: React.HTMLAttributes & { ref?: React.Ref }) { - return ( -
- ) -} - -function CardContent({ - className, - ref, - ...props -}: React.HTMLAttributes & { ref?: React.Ref }) { - return
-} - -function CardFooter({ - className, - ref, - ...props -}: React.HTMLAttributes & { ref?: React.Ref }) { - return ( -
- ) -} - -export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } diff --git a/packages/bhono-app/templates/base/src/client/components/ui/dialog.tsx b/packages/bhono-app/templates/base/src/client/components/ui/dialog.tsx deleted file mode 100644 index d7953ea..0000000 --- a/packages/bhono-app/templates/base/src/client/components/ui/dialog.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import type React from "react" - -import * as DialogPrimitive from "@radix-ui/react-dialog" - -import { Icons } from "@/components/icons" -import { cn } from "@/lib/utils" - -const Dialog = DialogPrimitive.Root - -const DialogTrigger = DialogPrimitive.Trigger - -const DialogPortal = DialogPrimitive.Portal - -const DialogClose = DialogPrimitive.Close - -function DialogOverlay({ - className, - ref, - ...props -}: React.ComponentPropsWithoutRef & { - ref?: React.Ref> -}) { - return ( - - ) -} - -function DialogContent({ - className, - children, - ref, - ...props -}: React.ComponentPropsWithoutRef & { - ref?: React.Ref> -}) { - return ( - - - - {children} - - - Close - - - - ) -} - -function DialogHeader({ - className, - ...props -}: React.HTMLAttributes) { - return ( -
- ) -} - -function DialogFooter({ - className, - ...props -}: React.HTMLAttributes) { - return ( -
- ) -} - -function DialogTitle({ - className, - ref, - ...props -}: React.ComponentPropsWithoutRef & { - ref?: React.Ref> -}) { - return ( - - ) -} - -function DialogDescription({ - className, - ref, - ...props -}: React.ComponentPropsWithoutRef & { - ref?: React.Ref> -}) { - return ( - - ) -} - -export { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogOverlay, - DialogPortal, - DialogTitle, - DialogTrigger, -} diff --git a/packages/bhono-app/templates/base/src/client/components/ui/error-fallback.tsx b/packages/bhono-app/templates/base/src/client/components/ui/error-fallback.tsx index 4875c85..0fdd348 100644 --- a/packages/bhono-app/templates/base/src/client/components/ui/error-fallback.tsx +++ b/packages/bhono-app/templates/base/src/client/components/ui/error-fallback.tsx @@ -1,7 +1,6 @@ // src/client/components/ui/error-fallback.tsx import { AlertTriangle, RefreshCw, Home, ArrowLeft } from 'lucide-react' -import { Button } from './button' - +import { Button } from '@etus/seven-react' interface ErrorFallbackProps { error?: Error title?: string diff --git a/packages/bhono-app/templates/base/src/client/components/ui/form.tsx b/packages/bhono-app/templates/base/src/client/components/ui/form.tsx deleted file mode 100644 index b9f0f5f..0000000 --- a/packages/bhono-app/templates/base/src/client/components/ui/form.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { createContext, use, useId } from "react" -import type { - ControllerProps, - FieldPath, - FieldValues, -} from "react-hook-form" -import type { HTMLAttributes, LabelHTMLAttributes, Ref } from "react" -import { - Controller, - FormProvider, - useFormContext, -} from "react-hook-form" - -import { Label } from "@/components/ui/label" -import { cn } from "@/lib/utils" - -const Form = FormProvider - -interface FormFieldContextValue< - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath -> { - name: TName -} - -const FormFieldContext = createContext( - {} as FormFieldContextValue -) - -function FormField< - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath ->({ - ...props -}: ControllerProps) { - return ( - - - - ) -} - -function useFormField() { - const fieldContext = use(FormFieldContext) - const itemContext = use(FormItemContext) - const { getFieldState, formState } = useFormContext() - - const fieldState = getFieldState(fieldContext.name, formState) - const { id } = itemContext - - return { - id, - name: fieldContext.name, - formItemId: `${id}-form-item`, - formDescriptionId: `${id}-form-item-description`, - formMessageId: `${id}-form-item-message`, - ...fieldState, - } -} - -interface FormItemContextValue { - id: string -} - -const FormItemContext = createContext( - {} as FormItemContextValue -) - -function FormItem({ - className, - ref, - ...props -}: HTMLAttributes & { ref?: Ref }) { - const id = useId() - - return ( - -
- - ) -} - -function FormLabel({ - className, - ref, - ...props -}: LabelHTMLAttributes & { ref?: Ref }) { - const { error, formItemId } = useFormField() - - return ( -