Skip to content

feat(ui): add Card, Select, Checkbox, and Badge primitives - #142

Merged
codebestia merged 3 commits into
ShadeProtocol:mainfrom
Depo-dev:feat/ui-primitives-card-select-checkbox-badge
Jul 31, 2026
Merged

feat(ui): add Card, Select, Checkbox, and Badge primitives#142
codebestia merged 3 commits into
ShadeProtocol:mainfrom
Depo-dev:feat/ui-primitives-card-select-checkbox-badge

Conversation

@Depo-dev

@Depo-dev Depo-dev commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Adds the four missing shadcn/ui-style primitives and refactors the existing ad hoc container/input markup to consume them.

Closes #88
Closes #87
Closes #99
Closes #89

Card#88

src/components/ui/card.tsx exports Card, CardHeader, CardTitle, CardDescription, and CardContent, following the same conventions as button.tsx (forwardRef, data-slot, cn()).

Card carries the exact class string that was duplicated across the app — rounded-lg border bg-card p-6 shadow-sm — so consumers can override any part of it via className (twMerge resolves p-8 over p-6, covered by a test).

Card also accepts asChild (via @radix-ui/react-slot, same pattern as Button) because several of the duplicated containers are <form> and <section> elements rather than plain divs. Without it those call sites could not adopt the primitive.

Refactored with no visual change:

  • src/app/sign-in/sign-in-client.tsx — the form wrapper named in the issue
  • src/components/RevenueChart.tsx — the card container rendered by dashboard/page.tsx (see note below)
  • src/app/register/register-client.tsx — the <form> wrapper, using asChild

Note on the dashboard/page.tsx acceptance criterion: that file has no card container of its own — it is a layout of space-y-6/grid wrappers. The rounded-lg border bg-card p-6 shadow-sm container it renders lives in RevenueChart.tsx:61, so that is what was refactored. RecentActivity.tsx was deliberately left alone: it uses rounded-xl and text-[color:var(--card-foreground)], so routing it through Card would have changed its appearance.

18 other sites still repeat variants of this class string (invoice-tools-client.tsx, customers/page.tsx, settings/layout.tsx, billing-plan-form.tsx, and others). Those are left for follow-up to keep this PR reviewable.

Select#87

src/components/ui/select.tsx built on @radix-ui/react-select (new dependency).

  • Exports the composable primitives SelectTrigger, SelectContent, SelectItem
  • Exports a convenience Select that takes options: { value, label, disabled? }[] plus controlled value/onValueChange, per the issue
  • Trigger reuses the app's existing input styling (h-11 rounded-md border bg-background focus:ring-primary/20) so it sits flush with the other fields in the register form
  • Listbox uses the card/border theme (rounded-lg border bg-card shadow-md), width-matched to the trigger

RegisterClient's business category field now uses it. The native <select> had required, but step-2 gating is done in JS via canContinue (which already checks values.businessCategory), so no validation behaviour is lost. The wrapping <label> became a <label htmlFor> + div pair, since Radix renders a button rather than a labelable control.

Checkbox#99

src/components/ui/checkbox.tsx built on @radix-ui/react-checkbox (new dependency), styled to the border/primary theme (size-4 rounded-sm border-input, data-[state=checked]:bg-primary) with the Check icon from lucide-react.

Fully controlled through Radix's checked/onCheckedChange, ready for the ApiKeyTable bulk-select and settings confirmations the issue describes. Not yet wired into ApiKeyTable — that is a behavioural change beyond this issue's scope.

Check was chosen deliberately: tsconfig.json aliases lucide-react to the local src/lib/lucide-react.tsx shim, which only exports a fixed icon set. Check and ChevronDown both exist there, so these components resolve correctly under both the Next build and Vitest (which does not apply that alias).

Badge#89

src/components/ui/badge.tsx using class-variance-authority, mirroring buttonVariants. Variants: default, secondary, outline, destructive. Pill-shaped (rounded-full), accepts arbitrary text and icon children via [&_svg]:size-3.

Tests

Added card.test.tsx, badge.test.tsx, checkbox.test.tsx, and select.test.tsx covering each issue's acceptance criteria: class application and className override, asChild rendering, every badge variant, checkbox toggle/disabled behaviour, and select open/select/disabled-option behaviour.

vitest.setup.ts now polyfills hasPointerCapture, setPointerCapture, releasePointerCapture, and scrollIntoView. jsdom does not implement the Pointer Capture API, and Radix Select calls it on pointer interaction — without the polyfill the select tests throw TypeError: target.hasPointerCapture is not a function. This also unblocks tests for any future Radix primitive.

Verification

Check Result
npx tsc --noEmit clean
npx vitest run 46 passed / 11 files
npx next build ✓ Compiled successfully in 30.0s
npx prettier --check all changed files pass

next lint reports 4 errors, all of which reproduce on unmodified main with these changes stashed: payment/[id]/page.tsx:16 (unused reject), InvoiceRowActions.test.tsx:1 (unused waitFor), ProfileDetailsForm.test.tsx:82 (@ts-ignore), and api-key-reveal.tsx:32 (unused error). None are in files this PR touches, and none were introduced here. Because next build runs lint, the build fails at that stage on main too — happy to fix them in a separate PR if you want the build green.

Depo-dev added 2 commits July 30, 2026 02:05
Adds four shadcn/ui-style primitives and refactors existing ad hoc
container/input markup to consume them.

Card (ShadeProtocol#88)
- Card, CardHeader, CardTitle, CardDescription, CardContent
- Card supports asChild so form wrappers can reuse the container
- Refactors sign-in-client.tsx, RevenueChart.tsx (dashboard card
  container), and the register form wrapper with no visual change

Select (ShadeProtocol#87)
- Radix @radix-ui/react-select with SelectTrigger/Content/Item exported
- Convenience Select takes options plus controlled value/onValueChange
- Refactors the RegisterClient business category field

Checkbox (ShadeProtocol#99)
- Radix @radix-ui/react-checkbox with the lucide Check indicator
- Controlled checked/onCheckedChange, border/primary theme styling

Badge (ShadeProtocol#89)
- class-variance-authority variants: default, secondary, outline,
  destructive, following the buttonVariants pattern

Adds tests for all four components and polyfills the jsdom Pointer
Capture and scrollIntoView gaps that Radix primitives require.
@drips-wave

drips-wave Bot commented Jul 30, 2026

Copy link
Copy Markdown

@Depo-dev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@codebestia
codebestia merged commit 4374eaa into ShadeProtocol:main Jul 31, 2026
1 check failed
@grantfox-oss grantfox-oss Bot mentioned this pull request Jul 31, 2026
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.

Create Checkbox Component Create Badge Component Create Card Component Create Select Component

2 participants