feat(shadcn): #51 swap home Count/Contents to shadcn#58
Conversation
Handcrafted shadcn init to preserve the front/* alias and Nx layout. Scaffolding only — no call-site swaps; daisyUI plugin remains active. - apps/web/components.json — shadcn schema 1, style default, rsc true, baseColor neutral, cssVariables, aliases mapped onto front/* (components → front/new-component, ui → front/new-component/ui, utils → front/lib/utils, lib → front/lib, hooks → front/hooks), iconLibrary lucide - apps/web/src/lib/utils.ts — cn helper (clsx + tailwind-merge) - apps/web/src/new-component/ui/ directory created (empty; #49 fills) - apps/web/tailwind.config.js — darkMode ['class'], theme.extend.colors wired to hsl(var(--*)) shadcn tokens, theme.extend.borderRadius, plugins keep `require('daisyui')` and add `tailwindcss-animate` - apps/web/app/globals.css — :root + .dark CSS variables under @layer base - package.json — class-variance-authority, clsx, tailwind-merge, tailwindcss-animate, lucide-react added - nx affected -t lint test build --exclude web-e2e green (4 projects, 10 tasks) - daisyUI classes still render identically (coexistence by design through #54) Refs #48
Five shadcn/ui components written directly into apps/web/src/new-component/ui/ to match the aliases configured in #48. Standard shadcn default-style implementations using cn(), class-variance-authority (button only), and Radix primitives for Avatar and Progress. No call-site swaps — files are unreferenced until #50–#54 consume them. - button.tsx — variants (default/destructive/outline/secondary/ghost/ link), sizes (default/sm/lg/icon), asChild via @radix-ui/react-slot - card.tsx — Card + CardHeader/Title/Description/Content/Footer - skeleton.tsx — animated muted placeholder - avatar.tsx — Radix Avatar/Image/Fallback ("use client") - progress.tsx — Radix Progress with translate-based indicator ("use client") - package.json — @radix-ui/react-slot, @radix-ui/react-avatar, @radix-ui/react-progress added - nx affected -t lint test build --exclude web-e2e green Refs #49
…50) Header: - Drops daisyUI navbar/bg-neutral/text-neutral-content/btn-ghost and rebuilds as a plain Tailwind flex row using shadcn Button variant="ghost" for the title link - bg-neutral-900 / text-neutral-100 token pair preserves the dark contrast originally supplied by daisyUI's neutral theme tokens Sidebar: - Deleted. The component was pulled only via the new-component/ barrel export and rendered nowhere (no consumers after the catch-all bridge and SiteRouter were removed in #35). - new-component/index.ts drops `export * from './sidebar'`. - nx affected -t lint test build --exclude web-e2e green - daisyUI plugin still present; coexists with shadcn through #54 Refs #50
Count.tsx: - daisyUI `avatar` + nested `div.skeleton` → shadcn Avatar/AvatarImage and Skeleton. Loading state is now an explicit Skeleton branch instead of a class toggle, so there's no more empty .avatar box while loading. - The two text rows (name, totalCount) likewise use Skeleton during isLoading. - "use client" added (was missing; Count uses hooks). Contents.tsx: - `<button className="btn btn-link btn-primary">` → shadcn <Button variant="link">. router.push wiring unchanged. - nx affected -t lint test build --exclude web-e2e green - daisyUI plugin still present Refs #51
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Reviewer's GuideMigrates selected home page components from daisyUI to shadcn-based UI primitives and wires in the required Tailwind, Radix, and utility infrastructure to support the new design system, including updated loading skeletons and header styling. Class diagram for new shadcn UI primitives and utilitiesclassDiagram
direction LR
class cn {
+cn(inputs) returns_string
}
class ButtonVariants {
+buttonVariants(options) returns_string
}
class ButtonProps {
+variant
+size
+asChild
}
class Button {
+asChild
+variant
+size
+onClick(event)
}
class Slot
class Card {
}
class CardHeader {
}
class CardTitle {
}
class CardDescription {
}
class CardContent {
}
class CardFooter {
}
class Avatar {
}
class AvatarImage {
}
class AvatarFallback {
}
class AvatarPrimitiveRoot
class AvatarPrimitiveImage
class AvatarPrimitiveFallback
class Progress {
+value
}
class ProgressPrimitiveRoot
class ProgressPrimitiveIndicator
class Skeleton {
}
cn <.. Button : uses
ButtonVariants <.. Button : uses
Slot <.. Button : optional wrapper
ButtonProps <.. Button : implements
cn <.. Card : uses
cn <.. CardHeader : uses
cn <.. CardTitle : uses
cn <.. CardDescription : uses
cn <.. CardContent : uses
cn <.. CardFooter : uses
cn <.. Avatar : uses
cn <.. AvatarImage : uses
cn <.. AvatarFallback : uses
AvatarPrimitiveRoot <|-- Avatar
AvatarPrimitiveImage <|-- AvatarImage
AvatarPrimitiveFallback <|-- AvatarFallback
cn <.. Progress : uses
ProgressPrimitiveRoot <|-- Progress
ProgressPrimitiveIndicator <.. Progress : contains
cn <.. Skeleton : uses
Class diagram for updated CountList and header usage of shadcn componentsclassDiagram
direction LR
class CountList {
+items
+render()
}
class useAnimalInfoSidoCount {
+invoke(items) $returns queryResults
}
class QueryResult {
+data
+isLoading
}
class Sido {
+orgCd
+orgdownNm
}
class InfiniteLoopSlider {
+onHoverStop
}
class Avatar {
}
class AvatarImage {
}
class Skeleton {
}
class Header {
+render()
}
class useSido {
+invoke(options) $returns data
}
class Button {
}
class NextRouter {
+push(path)
}
CountList --> useAnimalInfoSidoCount : calls
CountList --> InfiniteLoopSlider : composes
CountList --> QueryResult : iterates
QueryResult --> Sido : contains
CountList --> Avatar : renders when loaded
CountList --> AvatarImage : renders when loaded
CountList --> Skeleton : renders when loading
Header --> useSido : calls
Header --> CountList : renders
Header --> Button : renders
Header --> NextRouter : uses push
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
View your CI Pipeline Execution ↗ for commit 0422e22
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
CountList, consider using a stable key derived from the data (e.g.result.data?.sido.orgCd) instead ofuuidv4()so React can better preserve list item state between renders. - In
ui/card.tsx, theCardTitle/CardDescriptioncomponents are typed asHTMLDivElementbut use heading/paragraph element props in the generics; it would be clearer and more semantically correct to render actual<h*>/<p>elements and align the ref/prop types with those.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `CountList`, consider using a stable key derived from the data (e.g. `result.data?.sido.orgCd`) instead of `uuidv4()` so React can better preserve list item state between renders.
- In `ui/card.tsx`, the `CardTitle`/`CardDescription` components are typed as `HTMLDivElement` but use heading/paragraph element props in the generics; it would be clearer and more semantically correct to render actual `<h*>`/`<p>` elements and align the ref/prop types with those.
## Individual Comments
### Comment 1
<location path="apps/web/src/new-component/ui/progress.tsx" line_range="11-25" />
<code_context>
+ {...props}
+ >
+ <ProgressPrimitive.Indicator
+ className="h-full w-full flex-1 bg-primary transition-all"
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
+ />
+ </ProgressPrimitive.Root>
</code_context>
<issue_to_address>
**suggestion:** Clamp the Progress value to a valid range before computing the transform.
If `value` can be < 0 or > 100, the transform will go out of bounds and may cause visual glitches. Consider clamping it, e.g. `const clamped = Math.min(100, Math.max(0, value ?? 0));` and using `clamped` in `translateX`.
```suggestion
>(({ className, value, ...props }, ref) => {
const clampedValue = Math.min(100, Math.max(0, value ?? 0));
return (
<ProgressPrimitive.Root
ref={ref}
className={cn(
'relative h-4 w-full overflow-hidden rounded-full bg-secondary',
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - clampedValue}%)` }}
/>
</ProgressPrimitive.Root>
);
});
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| >(({ className, value, ...props }, ref) => ( | ||
| <ProgressPrimitive.Root | ||
| ref={ref} | ||
| className={cn( | ||
| 'relative h-4 w-full overflow-hidden rounded-full bg-secondary', | ||
| className | ||
| )} | ||
| {...props} | ||
| > | ||
| <ProgressPrimitive.Indicator | ||
| className="h-full w-full flex-1 bg-primary transition-all" | ||
| style={{ transform: `translateX(-${100 - (value || 0)}%)` }} | ||
| /> | ||
| </ProgressPrimitive.Root> | ||
| )); |
There was a problem hiding this comment.
suggestion: Clamp the Progress value to a valid range before computing the transform.
If value can be < 0 or > 100, the transform will go out of bounds and may cause visual glitches. Consider clamping it, e.g. const clamped = Math.min(100, Math.max(0, value ?? 0)); and using clamped in translateX.
| >(({ className, value, ...props }, ref) => ( | |
| <ProgressPrimitive.Root | |
| ref={ref} | |
| className={cn( | |
| 'relative h-4 w-full overflow-hidden rounded-full bg-secondary', | |
| className | |
| )} | |
| {...props} | |
| > | |
| <ProgressPrimitive.Indicator | |
| className="h-full w-full flex-1 bg-primary transition-all" | |
| style={{ transform: `translateX(-${100 - (value || 0)}%)` }} | |
| /> | |
| </ProgressPrimitive.Root> | |
| )); | |
| >(({ className, value, ...props }, ref) => { | |
| const clampedValue = Math.min(100, Math.max(0, value ?? 0)); | |
| return ( | |
| <ProgressPrimitive.Root | |
| ref={ref} | |
| className={cn( | |
| 'relative h-4 w-full overflow-hidden rounded-full bg-secondary', | |
| className | |
| )} | |
| {...props} | |
| > | |
| <ProgressPrimitive.Indicator | |
| className="h-full w-full flex-1 bg-primary transition-all" | |
| style={{ transform: `translateX(-${100 - clampedValue}%)` }} | |
| /> | |
| </ProgressPrimitive.Root> | |
| ); | |
| }); |
Summary
Stacked on #57.
Count.tsx
avatar+ nesteddiv.skeleton→ shadcnAvatar/AvatarImageandSkeleton<Skeleton/>branch instead of a class toggle — no more empty.avatarbox duringisLoadingorgdownNm,totalCount) useSkeletonduring load"use client"added (was missing;CountusesuseAnimalInfoSidoCount)Contents.tsx
<button className="btn btn-link btn-primary">→ shadcn<Button variant="link">Closes #51.
Test plan
npx nx affected -t lint test build --exclude web-e2egreen🤖 Generated with Claude Code
Summary by Sourcery
Adopt shadcn-style UI primitives for the home page and shared components, including theming updates and new utility helpers.
New Features:
Enhancements:
Build:
Chores: