Skip to content

feat(shadcn): #51 swap home Count/Contents to shadcn#58

Closed
fray-cloud wants to merge 4 commits into
devfrom
shadcn/51-home
Closed

feat(shadcn): #51 swap home Count/Contents to shadcn#58
fray-cloud wants to merge 4 commits into
devfrom
shadcn/51-home

Conversation

@fray-cloud

@fray-cloud fray-cloud commented Apr 13, 2026

Copy link
Copy Markdown
Owner

Summary

Stacked on #57.

Count.tsx

  • daisyUI avatar + nested div.skeleton → shadcn Avatar/AvatarImage and Skeleton
  • Loading state is an explicit <Skeleton/> branch instead of a class toggle — no more empty .avatar box during isLoading
  • Two text rows (orgdownNm, totalCount) use Skeleton during load
  • "use client" added (was missing; Count uses useAnimalInfoSidoCount)

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-e2e green

🤖 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:

  • Introduce shared shadcn-style UI components (Button, Card, Avatar, Progress, Skeleton) backed by Tailwind and Radix primitives.
  • Add a cn utility helper for class name composition using clsx and tailwind-merge.

Enhancements:

  • Refine the home header layout and styling to use the new Button component and a darker neutral theme.
  • Improve the home Count list loading experience with explicit skeleton placeholders for avatars and text, and mark the component as a client component.

Build:

  • Configure Tailwind for class-based dark mode and extend the theme with CSS variable–driven colors, radii, and animation plugin support.
  • Add new UI-related dependencies (Radix Avatar/Progress/Slot, class-variance-authority, clsx, lucide-react, tailwind-merge, tailwindcss-animate) to the web app.

Chores:

  • Clean up the new-component barrel exports and remove the unused sidebar component.

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
@vercel

vercel Bot commented Apr 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
animal-project Error Error Apr 13, 2026 8:44am

@sourcery-ai

sourcery-ai Bot commented Apr 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Migrates 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 utilities

classDiagram
  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
Loading

Class diagram for updated CountList and header usage of shadcn components

classDiagram
  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
Loading

File-Level Changes

Change Details Files
Refactor Count list UI to use shadcn Avatar and Skeleton components with explicit loading branches.
  • Marked Count.tsx as a client component because it uses a hook.
  • Replaced daisyUI avatar markup and CSS-based skeletons with shadcn Avatar/AvatarImage and Skeleton components.
  • Changed loading logic from conditional class toggling to explicit conditional rendering for avatar and text rows.
  • Simplified CountList JSX by removing the superfluous fragment wrapper.
apps/web/src/new-site/home/Count.tsx
Introduce shadcn design tokens and Tailwind theme extensions, including dark mode and animation plugin.
  • Defined CSS custom properties for light and dark themes under :root and .dark.
  • Enabled class-based dark mode in Tailwind config.
  • Extended Tailwind theme colors and border radius to read from CSS variables.
  • Added tailwindcss-animate plugin alongside existing daisyUI plugin.
apps/web/app/globals.css
apps/web/tailwind.config.js
Replace legacy header and contents buttons with shadcn Button and align header styling with Tailwind utilities.
  • Swapped daisyUI navbar/button markup in Header for a flex-based header using shadcn Button as a ghost variant.
  • Updated SearchContentsItem CTA button to use shadcn Button with link variant instead of daisyUI button classes.
apps/web/src/new-component/header.tsx
apps/web/src/new-site/home/Contents.tsx
Add shared shadcn-style UI primitives (button, card, avatar, progress, skeleton) and utility helpers.
  • Implemented a cn() helper using clsx and tailwind-merge for composing class names.
  • Added Button component with variant/size support using class-variance-authority and Radix Slot.
  • Added Card, Avatar, Progress, and Skeleton components styled using the new design tokens and Tailwind utilities.
  • Declared new dependencies for Radix UI, class-variance-authority, clsx, tailwind-merge, tailwindcss-animate, and lucide-react in package.json.
apps/web/src/lib/utils.ts
apps/web/src/new-component/ui/button.tsx
apps/web/src/new-component/ui/card.tsx
apps/web/src/new-component/ui/avatar.tsx
apps/web/src/new-component/ui/progress.tsx
apps/web/src/new-component/ui/skeleton.tsx
package.json
package-lock.json
Tidy new-component barrel exports and remove unused sidebar component.
  • Formatted new-component index barrel file and removed exports for sidebar and bottom.
  • Deleted the now-unused sidebar component file.
apps/web/src/new-component/index.ts
apps/web/src/new-component/sidebar.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@nx-cloud

nx-cloud Bot commented Apr 13, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 0422e22

Command Status Duration Result
nx build web ✅ Succeeded 1s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-13 08:45:44 UTC

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +11 to +25
>(({ 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>
));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
>(({ 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>
);
});

@fray-cloud

Copy link
Copy Markdown
Owner Author

Superseded by #61 — full #48#54 chain lands via #61.

@fray-cloud fray-cloud closed this Apr 13, 2026
@fray-cloud fray-cloud deleted the shadcn/51-home branch April 13, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant