feat(ui): add Card, Select, Checkbox, and Badge primitives - #142
Merged
codebestia merged 3 commits intoJul 31, 2026
Merged
Conversation
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.
|
@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! 🚀 |
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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— #88src/components/ui/card.tsxexportsCard,CardHeader,CardTitle,CardDescription, andCardContent, following the same conventions asbutton.tsx(forwardRef,data-slot,cn()).Cardcarries 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 viaclassName(twMergeresolvesp-8overp-6, covered by a test).Cardalso acceptsasChild(via@radix-ui/react-slot, same pattern asButton) because several of the duplicated containers are<form>and<section>elements rather than plaindivs. 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 issuesrc/components/RevenueChart.tsx— the card container rendered bydashboard/page.tsx(see note below)src/app/register/register-client.tsx— the<form>wrapper, usingasChildNote on the
dashboard/page.tsxacceptance criterion: that file has no card container of its own — it is a layout ofspace-y-6/gridwrappers. Therounded-lg border bg-card p-6 shadow-smcontainer it renders lives inRevenueChart.tsx:61, so that is what was refactored.RecentActivity.tsxwas deliberately left alone: it usesrounded-xlandtext-[color:var(--card-foreground)], so routing it throughCardwould 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— #87src/components/ui/select.tsxbuilt on@radix-ui/react-select(new dependency).SelectTrigger,SelectContent,SelectItemSelectthat takesoptions: { value, label, disabled? }[]plus controlledvalue/onValueChange, per the issueh-11 rounded-md border bg-background focus:ring-primary/20) so it sits flush with the other fields in the register formrounded-lg border bg-card shadow-md), width-matched to the triggerRegisterClient's business category field now uses it. The native<select>hadrequired, but step-2 gating is done in JS viacanContinue(which already checksvalues.businessCategory), so no validation behaviour is lost. The wrapping<label>became a<label htmlFor>+divpair, since Radix renders a button rather than a labelable control.Checkbox— #99src/components/ui/checkbox.tsxbuilt 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 theCheckicon fromlucide-react.Fully controlled through Radix's
checked/onCheckedChange, ready for theApiKeyTablebulk-select and settings confirmations the issue describes. Not yet wired intoApiKeyTable— that is a behavioural change beyond this issue's scope.Checkwas chosen deliberately:tsconfig.jsonaliaseslucide-reactto the localsrc/lib/lucide-react.tsxshim, which only exports a fixed icon set.CheckandChevronDownboth exist there, so these components resolve correctly under both the Next build and Vitest (which does not apply that alias).Badge— #89src/components/ui/badge.tsxusingclass-variance-authority, mirroringbuttonVariants. 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, andselect.test.tsxcovering each issue's acceptance criteria: class application andclassNameoverride,asChildrendering, every badge variant, checkbox toggle/disabled behaviour, and select open/select/disabled-option behaviour.vitest.setup.tsnow polyfillshasPointerCapture,setPointerCapture,releasePointerCapture, andscrollIntoView. jsdom does not implement the Pointer Capture API, and Radix Select calls it on pointer interaction — without the polyfill the select tests throwTypeError: target.hasPointerCapture is not a function. This also unblocks tests for any future Radix primitive.Verification
npx tsc --noEmitnpx vitest runnpx next build✓ Compiled successfully in 30.0snpx prettier --checknext lintreports 4 errors, all of which reproduce on unmodifiedmainwith these changes stashed:payment/[id]/page.tsx:16(unusedreject),InvoiceRowActions.test.tsx:1(unusedwaitFor),ProfileDetailsForm.test.tsx:82(@ts-ignore), andapi-key-reveal.tsx:32(unusederror). None are in files this PR touches, and none were introduced here. Becausenext buildruns lint, the build fails at that stage onmaintoo — happy to fix them in a separate PR if you want the build green.