feat: shadcn/ui design system + research page sections + Google Sans#103
Merged
ahnafnafee merged 8 commits intomainfrom Apr 26, 2026
Merged
feat: shadcn/ui design system + research page sections + Google Sans#103ahnafnafee merged 8 commits intomainfrom
ahnafnafee merged 8 commits intomainfrom
Conversation
Adopt shadcn/ui (radix base, nova style) as the single primitive layer so future UI work can compose from a known design system instead of bespoke components. Visual parity is preserved by mapping shadcn semantic tokens (--background, --primary, --foreground, --muted, --border, --ring) onto the existing blue-primary / neutral-theme palette in OKLCH — same colors, new tokens — so legacy classes (bg-primary-500, text-theme-700) and shadcn classes (bg-primary, text-foreground) render identically during the transition. UI primitives added under src/components/ui/ via the shadcn CLI: button, input, field, input-group, dialog, alert-dialog, sheet, dropdown-menu, sonner, tooltip, card, badge, separator, skeleton, empty, spinner, toggle-group, label, textarea, toggle. Component swaps (call sites updated): - DialogResume → shadcn Dialog (drops framer-motion entrance/exit; gains Radix focus trap, Escape/click-outside, a11y title) - MobileNav (HeadlessUI Menu) → DropdownMenu (drops @headlessui/react) - ThemeMenu → shadcn Button + lucide Sun/Moon - Searchbar → InputGroup + InputGroupAddon (was raw input + absolute icon) - EmptyResult → Empty + EmptyMedia + EmptyDescription - BackToTop → shadcn Button with CSS transition (drops framer-motion) - UnstyledButton call sites in BlogPostClient, ResearchBibTeX, Pre, Resume → shadcn Button with appropriate variant/size - Toaster (react-hot-toast) → sonner via @/components/ui/sonner - AlertResume + dead alert state in ResumePageClient removed (was unused) Directory layout: - src/components/ui/ now holds shadcn primitives - src/components/UI/ renamed to src/components/legacy-ui/ (Windows case-insensitive FS would have collided with the lowercase ui/ shadcn default). The @/UI tsconfig alias remaps to legacy-ui/ so the rename is invisible to import sites. Bespoke composition components (Header, Footer, Nav, AppLayoutPage, Hero, links, image wrappers) live there and now compose shadcn primitives internally. Theme bridge in src/styles/globals.css: - @theme inline maps --color-* tokens onto :root / .dark CSS variables - :root pins --primary to blue-500, --background to neutral-50, etc. - .dark pins --primary to blue-400, --background to custom #111 (legacy gray-900), --border to neutral-700 — matches the legacy dark surface - --font-sans resolves to var(--font-inter) so font-sans = font-primary Dependency cleanup (drops competing systems): - @headlessui/react: removed (MobileNav was the only consumer) - react-hot-toast: removed (replaced with sonner) - framer-motion: removed (only used in deleted drawer + dialog files) - @trivago/prettier-plugin-sort-imports: replaced with the actively maintained @ianvs/prettier-plugin-sort-imports; equivalent ordering, empty-string separators between groups - shadcn (CLI): moved from runtime deps to devDependencies - shadcn pulls in: radix-ui, lucide-react, sonner, tw-animate-css, class-variance-authority, tailwind-merge Dead code deleted: - src/components/legacy-ui/drawer/ (DrawerButton, DrawerMenu — replaced by MobileNav DropdownMenu) - src/components/legacy-ui/buttons/UnstyledButton.tsx, src/components/legacy-ui/buttons/SkipToContent.tsx (no callers) - src/components/legacy-ui/common/Spinner.tsx (replaced by shadcn) - src/libs/animation/variants.ts (only consumer was BackToTop) - src/hooks/UI/useDrawer.tsx (only consumer was deleted drawer) Verification: yarn lint, yarn type-check, yarn test (48/48), yarn build (50 routes), yarn validate:json-ld (123 blocks across 39 files) all pass. Documented the new layout, theme bridge, and shadcn rules in CLAUDE.md.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Vercel's build failed because its install path fell back to yarn 1 (which silently rewrote the yarn 4 berry lockfile and resolved @types/react to 19.2.7 instead of the 19.1.11 pinned in resolutions). Two issues to fix. 1) Type robustness: @types/react 19.2.x adds "hint" to the popover HTML attribute, which radix-ui's SlotProps doesn't include. Spreading React.ComponentProps<'span'> (or 'button') onto Slot.Root therefore fails type-check because span/button now allow popover="hint" but Slot.Root doesn't. Cast Comp to React.ElementType in badge.tsx + button.tsx so the spread isn't constrained by Slot.Root's stricter prop type. Aligns resolutions with the devDeps version (both 19.2.7) so the build is consistent regardless of which yarn picks them up. 2) Vercel install command: `corepack enable && yarn install --immutable` wasn't reliably switching the active yarn binary to 4.14.1 — corepack's shim wasn't taking effect inside Vercel's PATH, so yarn 1.22.19 ran instead. Add `corepack prepare yarn@4.14.1 --activate` to explicitly download and activate yarn 4 before `yarn install`. This makes the lockfile authoritative on Vercel and stops the silent rewrite that produced the type-version drift. Verified locally: yarn lint, yarn type-check, yarn test (48/48), yarn build (50 routes) all pass with @types/react 19.2.7 + @types/react-dom 19.2.3.
Vite added `resolve.tsconfigPaths: true` natively, so the `vite-tsconfig-paths` plugin is no longer needed (and was emitting a deprecation notice on every test run). Drop the plugin, set the option directly in vitest.config.ts. Verified: yarn test still passes 48/48 with @/ path aliases resolving through the native option.
`v_{\min}` produced an invalid <msub/> in MathML because `\min` renders as
the math operator (multiple MathML children with operator spacing), and
KaTeX didn't wrap them into a single <mrow> for the subscript slot.
Browsers reported "Incorrect number of children for <msub/> tag" on the
research detail page.
Use `v_{\text{min}}` since "min" here is a label (the minimum vertex
coordinate of the bounding box), not the binary math min operator.
Renders identically; produces a single <mtext>min</mtext> child.
The NEW badge on research listing cards was orange text only, which got lost next to the purple title at small sizes. Wrap it in a subtle orange-tinted chip (bg-orange-100 / dark:bg-orange-500/15) with rounded-sm corners — keeps the existing 10px tracked-out type, just gains a rectangular background that reads as a label.
… Sans Three new sections render between the Research hero and the listings: - ResearchOverview — short intro paragraph framing the research focus (AI ↔ 3D graphics, mesh simplification, geometric processing, generative 3D content). - ResearchNews — date-column timeline of milestones. Seeded with a single Dec 2025 entry: "Joining the DCXR Group at George Mason University, starting Spring 2026." Future updates just append to the NEWS array in the component. - ResearchAreas — chip row with colored dots tagging the active research areas; matches the visual language of the topic chips on the existing listing cards. Each section header uses the same uppercase / bordered-bottom style as ResearchSections so the page reads as one continuous document. Font: site default switched from Inter to Google Sans Text — same modern geometric feel as Google's product surfaces. Loaded via Google Fonts CSS (preconnect + stylesheet); local Inter @font-face stays as the fallback in globals.css. Wiring: - layout.tsx: drop next/font Inter import, add Google Fonts <link> tags. - globals.css @theme inline: --font-sans now reads 'Google Sans Text', 'Inter', ui-sans-serif, system-ui, sans-serif. - tailwind.config.js: fontFamily.primary leads with "Google Sans Text" so the legacy `font-primary` utility picks it up too. - next.config.js CSP: font-src now includes fonts.gstatic.com. Verified: yarn lint, yarn type-check, yarn test (48/48), yarn build (50 routes) all pass.
Font: - Use 'Google Sans' (no Text suffix) directly. Verified the v67 woff/ttf files load publicly from fonts.gstatic.com — no domain restrictions. - Adopt the GitHub-style fallback stack so even if Google Fonts is slow or blocked, the site still renders in a polished system font: 'Google Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, plus the four emoji families at the tail. - tailwind.config.js fontFamily.primary mirrors the same stack. Research page: - Remove the visual Hero (title + description). Replace with an sr-only <h1>Research</h1> so accessibility / SEO still get the landmark. - Rewrite ResearchAreas to use the existing topic-chip style from BlogItem.tsx: rounded-sm bg-gray-200 dark:bg-gray-800 px-2 py-1 text-[10px] font-bold tracking-wider uppercase. Drop the colored dots + pill shape — they didn't match the rest of the site's design system. Verified locally: yarn type-check + yarn build (50 routes) clean.
Same rectangular shape, padding, and uppercase typography as the rest of the site's chip system — but each area now reads with its own tint: AI for 3D Graphics → blue Mesh Simplification → rose Geometric Processing → cyan ML for Graphics → emerald 3D Content Generation → amber Light mode uses the -100 shade for the chip background and -800 for text (strong contrast). Dark mode uses /15 opacity on -500 for the bg and -200 for text so the colors stay vibrant on the gray-900 page surface without clashing.
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.
Summary
--background,--primary,--foreground,--muted,--border,--ring) are mapped in OKLCH onto the existing blue-primary / neutral-theme palette so legacy classes (bg-primary-500,text-theme-700) and shadcn classes (bg-primary,text-foreground) render identically. Light/dark are unchanged visually.@headlessui/react,react-hot-toast,framer-motion, andvite-tsconfig-pathswere replaced and dropped.radix-uistays — it's shadcn's primitive layer, not a competing system.sr-only <h1>Research</h1>is kept for accessibility/SEO.system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, …). Inter remains via the local@font-faceas a deeper fallback.What's new under
src/components/ui/19 shadcn primitives via the CLI:
button,input,field,input-group,dialog,alert-dialog,sheet,dropdown-menu,sonner,tooltip,card,badge,separator,skeleton,empty,spinner,toggle-group,label,textarea,toggle. Usenpx shadcn@latest add <name>for more — or... add <name> --diffto merge upstream updates without losing local edits.Component migrations (parity-preserving)
DialogResume(framer-motion + plain divs)Dialog(Radix focus trap, Esc/click-outside, a11y title)MobileNav(@headlessui/reactMenu)DropdownMenuThemeMenu(inline SVG)Button+ lucideSun/MoonSearchbar(raw<input>+ absolute icon)InputGroup+InputGroupAddonEmptyResult(styled div)Empty+EmptyMedia+EmptyDescriptionBackToTop(framer-motionAnimatePresence)Button+ CSS opacity/translate transitionUnstyledButton(3 call sites)Buttonwith appropriate variant/sizereact-hot-toastToastersonnervia@/components/ui/sonnerAlertResume+ the dead alert state inResumePageClientwere removed — never rendered,isMatchwas hardcodedtrueso the effect was a no-op.Theme bridge (
src/styles/globals.css)@theme inlineblock maps--color-*→var(--*)for shadcn-aware Tailwind classes.:rootpins shadcn vars to light palette:--primary= blue-500,--background= neutral-50,--foreground= neutral-800,--muted= neutral-100,--border= neutral-200,--ring= blue-500 (matches legacy focus ring)..darkpins shadcn vars to dark palette:--primary= blue-400,--background= custom#111(legacygray-900),--border= neutral-700,--ring= blue-400.--font-sansresolves to the new Google Sans stack (see Font section).Research page additions
Three new sections rendered above the listings (
src/components/content/research/):ResearchOverview— intro paragraph framing the AI ↔ 3D-graphics focus.ResearchNews— date-column timeline. Seeded withDec 2025 — 🎉 Joining the DCXR Group at George Mason University, starting Spring 2026.Future updates just append to theNEWSarray.ResearchAreas— colorful rectangular chips (matching the existingBlogItemtopic-chip shape) — each area gets its own tint:Light mode:
bg-{c}-100+text-{c}-800. Dark mode:bg-{c}-500/15+text-{c}-200so colors stay vibrant on the dark page without competing with title accents.The visual
Hero(title + description) was removed per design feedback. Ansr-only <h1>Research</h1>keeps the landmark for screen readers and SEO.Bonus: NEW badge on listing cards now sits in a small orange-tinted rectangular chip instead of bare orange text.
Bonus: fixed the
mesh-decimation-benchmarkMDX so the<msub/>markup error stops firing in the console (v_{\min}→v_{\text{min}}).Default font: Google Sans
preconnect+ stylesheet inlayout.tsx). TheGoogle Sansfamily is publicly served fromfonts.gstatic.com(verifiedv67woff2 URLs work without referrer restriction).'Google Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'. Even if Google Fonts is slow or blocked, the page renders in a polished system font.@font-facestays inglobals.cssas a deeper fallback so we don't FOUC.tailwind.config.jsfontFamily.primarymirrors the same stack so the legacyfont-primaryutility picks it up.next.config.jsCSPfont-srcextended to includefonts.gstatic.com.next/fontInter import dropped — it was redundant with the local@font-face.Directory layout
src/components/ui/— shadcn primitives (don't hand-edit; use the CLI's--diffto merge upstream).src/components/legacy-ui/— bespoke site composition components (Header, Footer, Nav, AppLayoutPage, Hero, links, image wrappers, BackToTop, EmptyResult, Searchbar, MobileNav, ThemeMenu). They now compose shadcn primitives internally. The folder kept its name to keep the diff focused; rename tosite/is a separate PR.Build robustness fixes
badge.tsx/button.tsx— castComptoReact.ElementTypeso the prop spread isn't constrained by radix-ui'sSlotProps(which doesn't yet accept React 19.2'spopover="hint"). Robust under any@types/reactversion going forward.vercel.jsoninstallCommand— addedcorepack prepare yarn@4.14.1 --activateso Vercel actually runs Yarn 4 instead of falling back to Yarn 1 (which silently rewrote the lockfile and resolved different transitive types). Lockfile is now authoritative on Vercel.package.jsonresolutions bumped to@types/react@19.2.7/@types/react-dom@19.2.3to matchdevDependencies.Tooling modernization
@trivago/prettier-plugin-sort-imports(last published Mar 2024) →@ianvs/prettier-plugin-sort-imports(actively maintained, better Prettier-3 / TS-5 support). Same import ordering; empty-string separators inimportOrderproduce blank lines between groups.vite-tsconfig-pathsplugin dropped in favor of Vite's nativeresolve.tsconfigPaths: true(the plugin was emitting a deprecation notice on every test run).eslint.config.mjs) was already current —next/core-web-vitalsalready pulls ineslint-plugin-react-hooks. No changes.shadcn(CLI) moved from runtimedependenciestodevDependencies.radix-ui,lucide-react,sonner,tw-animate-css,class-variance-authority,tailwind-merge.Test plan
yarn lint— cleanyarn type-check— clean (verified under both@types/react@19.1.11and19.2.7)yarn test— 48/48 passyarn build— 50 routes generatedyarn validate:json-ld— 123 blocks across 39 files, all validyarn audit:alt-text— only pre-existing content issues inpostscript-preview.mdxyarn dev— light + dark mode, mobile nav, theme toggle, dialog open/close, blog/portfolio search, BackToTop scroll, copy-bibtex / copy-code buttons, research page sections (Overview / News / Research Areas), Google Sans rendering across both modesCommits in this PR
feat(ui): migrate to shadcn/ui design systemfix(ui): make Slot.Root callsites typecheck under @types/react 19.2chore(test): use Vite's native tsconfig paths resolutionfix(research): replace \min with \text{min} in mesh-decimation subscriptfeat(research): add rectangular tinted background to NEW badgefeat(research): add Overview / News / Research Areas + swap to Google Sansrefactor(research): swap to Google Sans + match existing chip designstyle(research): give each research-area chip its own colorKnown follow-ups (out of scope for this PR)
legacy-ui/→site/(or split intolayout/,templates/, etc.) once we're confident no external links/aliases depend on it.react-icons→lucide-react).react-iconsis still used in the legacy components; the migration isn't trivial because each icon needs a visual equivalent picked.react-image-lightboxis still in deps — it's a feature component (used byLightboxLazy.tsx), not a competing UI system.Card-based card style to listing pages (blog/portfolio/research) to lean further into shadcn. Today they use bespoke styled divs; that's parity-preserving but doesn't take advantage ofCardcomposition.