From 4ad8f73cbf2f647414e799370542041023631580 Mon Sep 17 00:00:00 2001 From: prasiddhi-105 Date: Sat, 18 Jul 2026 21:18:30 +0530 Subject: [PATCH] feat: complete reusable button component with pill-shaped variants #102 --- frontend/src/App.tsx | 80 ++++++++++++++++-------------- frontend/src/components/Button.tsx | 73 +++++++++++++++++++++++++++ frontend/src/index.css | 14 ++++++ 3 files changed, 129 insertions(+), 38 deletions(-) create mode 100644 frontend/src/components/Button.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b91f402a..74727e1a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -9,9 +9,9 @@ import { AuthModal } from "./AuthModal"; import { Footer } from "./Footer"; import AnalysisSkeleton from "./components/AnalysisSkeleton/AnalysisSkeleton"; import { InfoTooltip } from "./components/InfoTooltip"; +import { Button } from "./components/Button"; type Theme = "light" | "dark"; - function getInitialTheme(): Theme { try { const saved = localStorage.getItem("theme"); @@ -178,7 +178,7 @@ function App() { if (user) { await fetchDbHistory(user.token); - } + } else { addEntry({ score: res.data.score, @@ -189,7 +189,7 @@ function App() { targetRole: targetRole, fileName: fileToAnalyze.name, }); - } + } } catch (error: unknown) { console.error(error); @@ -288,9 +288,9 @@ function App() { setHistoryOpen(false); }; const handleLogout = () => { - logout(); - clearHistory(); -}; + logout(); + clearHistory(); + }; return ( <>
- {/* Theme toggle */} - - - {/* Auth bar */} -
- {user ? ( - <> - 👤 {user.username} - - - ) : ( - - )} + {/* Top Control Bar: Side-by-Side Theme & Auth Buttons */} +
+ + +
+ {user ? ( +
+ 👤 {user.username} + +
+ ) : ( + + )} +
{showAuthModal && ( @@ -337,7 +338,7 @@ function App() {

🚀 AI Resume Analyzer

- {/* Role Selector Dropdown */} + {/* Role Selector Dropdown Container */}
-
+
-
- - + {loading && analysisSource === "sample" ? "⏳ Loading Sample..." : "Or try with a sample resume"} +
+ {/* Loading skeleton — shown while the resume is being analyzed */} {loading && } diff --git a/frontend/src/components/Button.tsx b/frontend/src/components/Button.tsx new file mode 100644 index 00000000..158173ec --- /dev/null +++ b/frontend/src/components/Button.tsx @@ -0,0 +1,73 @@ +import React from "react"; + +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: "primary" | "secondary" | "ghost"; + children: React.ReactNode; +} + +export const Button: React.FC = ({ + variant = "primary", + children, + className = "", + style, + disabled, + ...props +}) => { + // Base structural styles shared across all buttons + const baseStyle: React.CSSProperties = { + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + fontWeight: "600", + border: "none", + cursor: disabled ? "not-allowed" : "pointer", + transition: "all 0.2s ease", + opacity: disabled ? 0.6 : 1, + }; + + // Variant-specific styles mapping to our new scale and system tokens +const variantStyles: Record = { + primary: { + padding: "12px 36px", + fontSize: "var(--font-size-base)", + backgroundColor: "#6366f1", + color: "#fff", + borderRadius: "9999px", + boxShadow: "var(--shadow-card)", + width: "100%", + maxWidth: "280px" + }, + secondary: { + background: "transparent", + color: "var(--btn-secondary-text)", + fontSize: "var(--font-size-sm)", + textDecoration: "underline", + marginTop: "8px", + }, + ghost: { + padding: "8px 20px", + fontSize: "var(--font-size-sm)", + background: "rgba(255, 255, 255, 0.15)", + color: "#ffffff", + borderRadius: "9999px", /* Makes it a pill / capsule shape */ + border: "1px solid rgba(255, 255, 255, 0.2)", + }, + }; + + const combinedStyle = { + ...baseStyle, + ...variantStyles[variant], + ...style, + }; + + return ( + + ); +}; \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css index 07975756..ef6e249d 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -726,3 +726,17 @@ border:0; transform: translateX(-50%) translateY(-10px); } } +/* Interactive states for our reusable Button component */ +.app-btn-custom:not(:disabled):hover { + filter: brightness(1.1); + transform: translateY(-1px); +} + +.app-btn-custom:not(:disabled):active { + transform: translateY(0); +} + +.app-btn-custom--secondary:hover { + color: #fff !important; + text-decoration: none !important; +} \ No newline at end of file