Skip to content

feat(shadcn): #52 swap search AnimalCard/Progress/Form Button#59

Closed
fray-cloud wants to merge 5 commits into
devfrom
shadcn/52-search
Closed

feat(shadcn): #52 swap search AnimalCard/Progress/Form Button#59
fray-cloud wants to merge 5 commits into
devfrom
shadcn/52-search

Conversation

@fray-cloud

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

Copy link
Copy Markdown
Owner

Summary

Stacked on #58.

AnimalCard.tsx

  • daisyUI card bg-base-100 shadow-xl + card-body → shadcn Card + CardContent
  • "use client" added (was missing; useLike is a Zustand hook)
  • Inner table: dropped daisyUI table table-sm hover classes; plain <table> with Tailwind padding + font-medium

SearchView.tsx

  • react-infinite-scroller loader <progress className="progress"/> → shadcn <Progress className="h-2" />

Form.tsx

  • Submit <button className="btn btn-sm"> → shadcn <Button size="sm" type="submit">

Closes #52.

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 and theming across the web app and replace existing daisyUI elements in search and header views.

New Features:

  • Introduce reusable shadcn-inspired UI components for buttons, cards, avatars, progress bars, and skeleton loaders.
  • Add a shared cn utility for merging Tailwind class names.

Enhancements:

  • Refine the search animal card layout to use the new Card component and simplified table styling.
  • Update the header and home search CTA to use the new Button component instead of daisyUI buttons.
  • Improve the home Count list with avatar-based logos and skeleton loaders for loading states.
  • Configure Tailwind with CSS variables, color tokens, border radius settings, and class-based dark mode compatible with the new UI components.

Build:

  • Extend Tailwind configuration with animate plugin and design tokens for shadcn-style components.
  • Add UI-related dependencies such as Radix UI primitives, class-variance-authority, clsx, lucide-react, tailwind-merge, and tailwindcss-animate to support the new component system.

Chores:

  • Remove unused sidebar export from the new-component barrel file.

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
AnimalCard.tsx:
- daisyUI `card bg-base-100 shadow-xl` + `card-body` → shadcn
  Card + CardContent
- `"use client"` added (was missing; useLike is a Zustand hook)
- Inner `<table className="table table-sm">` rewritten without
  daisyUI table classes — plain <table> with Tailwind padding/
  font utilities

SearchView.tsx:
- react-infinite-scroller loader `<progress className="progress"/>`
  → shadcn <Progress className="h-2" />

Form.tsx:
- Submit `<button className="btn btn-sm">` → shadcn
  <Button size="sm" type="submit">

- nx affected -t lint test build --exclude web-e2e green
- daisyUI plugin still present

Refs #52
@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:46am

@sourcery-ai

sourcery-ai Bot commented Apr 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Migrates key web UI pieces from daisyUI/native elements to shadcn-style Radix-based components, wires up supporting Tailwind theme tokens/utilities, and replaces ad‑hoc skeleton/loading UIs with reusable primitives.

Class diagram for new shadcn-style UI primitives

classDiagram
  class cn {
    +cn(inputs) string
  }

  class ButtonVariants {
    <<utility>>
    +buttonVariants(options) string
  }

  class ButtonProps {
    +boolean asChild
    +string variant
    +string size
    +string className
  }

  class Button {
    <<ReactComponent>>
    +render(props ButtonProps)
  }

  class Card {
    <<ReactComponent>>
    +render(className, children, otherProps)
  }

  class CardHeader {
    <<ReactComponent>>
    +render(className, children, otherProps)
  }

  class CardTitle {
    <<ReactComponent>>
    +render(className, children, otherProps)
  }

  class CardDescription {
    <<ReactComponent>>
    +render(className, children, otherProps)
  }

  class CardContent {
    <<ReactComponent>>
    +render(className, children, otherProps)
  }

  class CardFooter {
    <<ReactComponent>>
    +render(className, children, otherProps)
  }

  class Avatar {
    <<ReactComponent>>
    +render(className, children, otherProps)
  }

  class AvatarImage {
    <<ReactComponent>>
    +render(className, otherProps)
  }

  class AvatarFallback {
    <<ReactComponent>>
    +render(className, children, otherProps)
  }

  class Progress {
    <<ReactComponent>>
    +render(className, value, otherProps)
  }

  class Skeleton {
    <<ReactComponent>>
    +render(className, otherProps)
  }

  %% dependencies on cn
  Button ..> cn : uses
  Card ..> cn : uses
  CardHeader ..> cn : uses
  CardTitle ..> cn : uses
  CardDescription ..> cn : uses
  CardContent ..> cn : uses
  CardFooter ..> cn : uses
  Avatar ..> cn : uses
  AvatarImage ..> cn : uses
  AvatarFallback ..> cn : uses
  Progress ..> cn : uses
  Skeleton ..> cn : uses

  %% button variants utility
  Button ..> ButtonVariants : uses

  %% group card subcomponents
  Card "1" o-- "*" CardHeader
  Card "1" o-- "*" CardTitle
  Card "1" o-- "*" CardDescription
  Card "1" o-- "*" CardContent
  Card "1" o-- "*" CardFooter

  %% avatar subcomponents
  Avatar "1" o-- "*" AvatarImage
  Avatar "1" o-- "*" AvatarFallback
Loading

File-Level Changes

Change Details Files
Introduce shared shadcn-style UI primitives (button, card, avatar, progress, skeleton) and supporting utilities/theme.
  • Add cn() utility based on clsx + tailwind-merge for class composition
  • Define Card, Button, Avatar, Progress, Skeleton components under new-component/ui using Radix primitives and class-variance-authority where needed
  • Configure Tailwind dark-mode via class strategy and extend theme colors/border-radius from CSS variables in globals.css
  • Enable tailwindcss-animate plugin alongside existing daisyUI plugin and add Radix-related dependencies 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
apps/web/app/globals.css
apps/web/tailwind.config.js
package.json
package-lock.json
Refactor search AnimalCard and related views to use new shadcn-style components instead of daisyUI elements.
  • Mark AnimalCard as a client component because it consumes a Zustand hook
  • Swap daisyUI Card markup for shadcn Card/CardContent with Tailwind-based table styling
  • Replace infinite-scroll loader element with new Progress component
  • Update search form submit control from plain button with daisy classes to shadcn Button
apps/web/src/new-site/search/card/AnimalCard.tsx
apps/web/src/new-site/search/SearchView.tsx
apps/web/src/new-site/search/Form.tsx
Update header and home content call-to-action to use new Button and improve layout/styling.
  • Replace daisyUI navbar/button in header with flex layout and shadcn Button styled for a dark header bar
  • Change home contents CTA from daisyUI button to Button with link variant
  • Tighten new-component barrel exports and remove unused sidebar component
apps/web/src/new-component/header.tsx
apps/web/src/new-site/home/Contents.tsx
apps/web/src/new-component/index.ts
apps/web/src/new-component/sidebar.tsx
Modernize Count list marquee to use Avatar/Skeleton components for loading states and mark as client component.
  • Add 'use client' directive since CountList uses hooks and Radix-based UI
  • Replace custom avatar/skeleton divs with Avatar + AvatarImage when loaded and Skeleton placeholders while loading
  • Simplify wrapper markup by removing unnecessary React fragment
apps/web/src/new-site/home/Count.tsx
Add shadcn UI configuration stub for future use.
  • Introduce components.json under apps/web to host shadcn generator/config state
apps/web/components.json

Possibly linked issues


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 773b656

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

☁️ Nx Cloud last updated this comment at 2026-04-13 08:47:51 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 2 issues, and left some high level feedback:

  • In the new CardTitle and CardDescription components, the generic element types (HTMLDivElement) don’t match the HTML attributes (HTMLHeadingElement/HTMLParagraphElement), and they render <div>s instead of semantic headings/paragraphs; consider aligning the types and DOM elements (e.g., h3/p) for correctness and accessibility.
  • The custom Progress component is used as the infinite scroll loader without a value, which makes the indicator always render at 0% width; if you want a visible loading state, either provide a value or add an indeterminate/animated variant for the loader case.
  • In CountList, each item still uses uuidv4() as the React key, which changes on every render; consider using a stable identifier from result.data (e.g., sido.orgCd) to avoid unnecessary remounting and skeleton flicker.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the new `CardTitle` and `CardDescription` components, the generic element types (`HTMLDivElement`) don’t match the HTML attributes (`HTMLHeadingElement`/`HTMLParagraphElement`), and they render `<div>`s instead of semantic headings/paragraphs; consider aligning the types and DOM elements (e.g., `h3`/`p`) for correctness and accessibility.
- The custom `Progress` component is used as the infinite scroll loader without a `value`, which makes the indicator always render at 0% width; if you want a visible loading state, either provide a value or add an indeterminate/animated variant for the loader case.
- In `CountList`, each item still uses `uuidv4()` as the React key, which changes on every render; consider using a stable identifier from `result.data` (e.g., `sido.orgCd`) to avoid unnecessary remounting and skeleton flicker.

## Individual Comments

### Comment 1
<location path="apps/web/src/new-component/ui/card.tsx" line_range="47-8" />
<code_context>
+));
+CardTitle.displayName = 'CardTitle';
+
+const CardDescription = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
</code_context>
<issue_to_address>
**issue (bug_risk):** The `CardDescription` generics also mix element types and should be aligned with the rendered element.

`CardDescription` mirrors the `CardTitle` issue: the ref is typed as `HTMLDivElement`, props as `React.HTMLAttributes<HTMLParagraphElement>`, but the component renders a `<div>`. Please either render a `<p>` and keep `HTMLParagraphElement`, or keep the `<div>` and change the props to `React.HTMLAttributes<HTMLDivElement>` so the types match the actual element.
</issue_to_address>

### Comment 2
<location path="apps/web/src/new-component/ui/progress.tsx" line_range="21-22" />
<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:** The `Progress` indicator defaults to an empty bar when no value is passed, which may not match its current usage.

Because the transform uses `value || 0`, an undefined `value` becomes `translateX(-100%)`, fully hiding the indicator. In `SearchView`, `<Progress className="h-2" />` is rendered without `value`, so users get no visible loading state. Please either default `value` to `100` for this case or introduce an explicit indeterminate/animated variant for no-`value` usage.

Suggested implementation:

```typescript
>(({ className, value = 100, ...props }, ref) => (

```

```typescript
      className="h-full w-full flex-1 bg-primary transition-all"
      style={{ transform: `translateX(-${100 - value}%)` }}

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

const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The CardDescription generics also mix element types and should be aligned with the rendered element.

CardDescription mirrors the CardTitle issue: the ref is typed as HTMLDivElement, props as React.HTMLAttributes<HTMLParagraphElement>, but the component renders a <div>. Please either render a <p> and keep HTMLParagraphElement, or keep the <div> and change the props to React.HTMLAttributes<HTMLDivElement> so the types match the actual element.

Comment on lines +21 to +22
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}

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: The Progress indicator defaults to an empty bar when no value is passed, which may not match its current usage.

Because the transform uses value || 0, an undefined value becomes translateX(-100%), fully hiding the indicator. In SearchView, <Progress className="h-2" /> is rendered without value, so users get no visible loading state. Please either default value to 100 for this case or introduce an explicit indeterminate/animated variant for no-value usage.

Suggested implementation:

>(({ className, value = 100, ...props }, ref) => (
      className="h-full w-full flex-1 bg-primary transition-all"
      style={{ transform: `translateX(-${100 - value}%)` }}

@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/52-search 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