From 6c6dfba85b990a1859e1f4cd6da6bfa9dd8a65e6 Mon Sep 17 00:00:00 2001
From: ABILASH <114130954+Abilash-19@users.noreply.github.com>
Date: Mon, 16 Feb 2026 23:25:25 +0530
Subject: [PATCH 01/14] feat: button compoent and its varients
---
src/assets/Icons/Spinner.tsx | 28 ++++
src/components/Button/Button.stories.tsx | 156 ++++++++++-----------
src/components/Button/Button.test.tsx | 34 ++---
src/components/Button/Button.tsx | 170 +++++++++++++++--------
src/components/Button/Button.types.tsx | 38 +++--
src/components/Modal/Modal.stories.tsx | 81 +++--------
6 files changed, 280 insertions(+), 227 deletions(-)
create mode 100644 src/assets/Icons/Spinner.tsx
diff --git a/src/assets/Icons/Spinner.tsx b/src/assets/Icons/Spinner.tsx
new file mode 100644
index 0000000..44721fc
--- /dev/null
+++ b/src/assets/Icons/Spinner.tsx
@@ -0,0 +1,28 @@
+import { cn } from "@/utils";
+
+export const Spinner = ({ size }: { size: string }) => {
+ return (
+
+
+
+
+ );
+};
diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx
index d3ee671..6310817 100644
--- a/src/components/Button/Button.stories.tsx
+++ b/src/components/Button/Button.stories.tsx
@@ -1,119 +1,119 @@
-import type { Meta, StoryObj } from '@storybook/react-vite';
-import { Button } from './Button';
+import type { Meta, StoryObj } from "@storybook/react-vite";
+import { Button } from "./Button";
const meta: Meta = {
- title: 'Components/Button',
- component: Button,
- parameters: {
- layout: 'centered',
+ title: "Components/Button",
+ component: Button,
+ parameters: {
+ layout: "centered",
+ },
+ tags: ["autodocs"],
+ argTypes: {
+ variant: {
+ control: "select",
+ options: ["primary", "secondary", "outline", "ghost", "danger"],
},
- tags: ['autodocs'],
- argTypes: {
- variant: {
- control: 'select',
- options: ['primary', 'secondary', 'outline', 'ghost', 'danger'],
- },
- size: {
- control: 'select',
- options: ['sm', 'md', 'lg'],
- },
- isLoading: {
- control: 'boolean',
- },
- disabled: {
- control: 'boolean',
- },
+ size: {
+ control: "select",
+ options: ["sm", "md", "lg"],
},
+ isLoading: {
+ control: "boolean",
+ },
+ disabled: {
+ control: "boolean",
+ },
+ },
};
export default meta;
type Story = StoryObj;
export const Primary: Story = {
- args: {
- children: 'Primary Button',
- variant: 'primary',
- },
+ args: {
+ text: "Primary Button",
+ variant: "primary",
+ },
};
export const Secondary: Story = {
- args: {
- children: 'Secondary Button',
- variant: 'secondary',
- },
+ args: {
+ text: "Secondary Button",
+ variant: "secondary",
+ },
};
export const Outline: Story = {
- args: {
- children: 'Outline Button',
- variant: 'outline',
- },
+ args: {
+ text: "Outline Button",
+ variant: "outline",
+ },
};
export const Ghost: Story = {
- args: {
- children: 'Ghost Button',
- variant: 'ghost',
- },
+ args: {
+ text: "Ghost Button",
+ variant: "ghost",
+ },
};
export const Danger: Story = {
- args: {
- children: 'Danger Button',
- variant: 'danger',
- },
+ args: {
+ text: "Danger Button",
+ variant: "danger",
+ },
};
export const Small: Story = {
- args: {
- children: 'Small Button',
- size: 'sm',
- },
+ args: {
+ text: "Small Button",
+ size: "sm",
+ },
};
export const Medium: Story = {
- args: {
- children: 'Medium Button',
- size: 'md',
- },
+ args: {
+ text: "Medium Button",
+ size: "md",
+ },
};
export const Large: Story = {
- args: {
- children: 'Large Button',
- size: 'lg',
- },
+ args: {
+ text: "Large Button",
+ size: "lg",
+ },
};
export const Loading: Story = {
- args: {
- children: 'Loading Button',
- isLoading: true,
- },
+ args: {
+ text: "Loading Button",
+ isLoading: true,
+ },
};
export const Disabled: Story = {
- args: {
- children: 'Disabled Button',
- disabled: true,
- },
+ args: {
+ text: "Disabled Button",
+ disabled: true,
+ },
};
export const AllVariants: Story = {
- render: () => (
-
-
- Primary
- Secondary
- Outline
- Ghost
- Danger
-
-
- Small
- Medium
- Large
-
-
- ),
+ render: () => (
+
+
+ Primary
+ Secondary
+ Outline
+ Ghost
+ Danger
+
+
+ Small
+ Medium
+ Large
+
+
+ ),
};
diff --git a/src/components/Button/Button.test.tsx b/src/components/Button/Button.test.tsx
index af7a8e8..c1d4755 100644
--- a/src/components/Button/Button.test.tsx
+++ b/src/components/Button/Button.test.tsx
@@ -10,100 +10,100 @@ const render = (ui: React.ReactElement) => {
};
describe("Button", () => {
- it("renders with children", () => {
- render(Click me );
+ it("renders with text", () => {
+ render( );
expect(
screen.getByRole("button", { name: /click me/i }),
).toBeInTheDocument();
});
it("applies primary variant by default", () => {
- render(Primary );
+ render( );
const button = screen.getByRole("button");
expect(button).toHaveClass("bg-[var(--btn-bg)]");
});
it("applies secondary variant correctly", () => {
- render(Secondary );
+ render( );
const button = screen.getByRole("button");
expect(button).toHaveClass("bg-[var(--btn-bg)]");
expect(button).toHaveClass("text-[var(--btn-text)]");
});
it("applies outline variant correctly", () => {
- render(Outline );
+ render( );
const button = screen.getByRole("button");
expect(button).toHaveClass("border-2");
expect(button).toHaveClass("border-[var(--btn-border)]");
});
it("applies ghost variant correctly", () => {
- render(Ghost );
+ render( );
const button = screen.getByRole("button");
expect(button).toHaveClass("text-[var(--btn-text)]");
});
it("applies danger variant correctly", () => {
- render(Danger );
+ render( );
const button = screen.getByRole("button");
expect(button).toHaveClass("bg-[var(--btn-bg)]");
});
it("applies small size correctly", () => {
- render(Small );
+ render( );
const button = screen.getByRole("button");
expect(button).toHaveClass("h-8");
});
it("applies medium size by default", () => {
- render(Medium );
+ render( );
const button = screen.getByRole("button");
expect(button).toHaveClass("h-10");
});
it("applies large size correctly", () => {
- render(Large );
+ render( );
const button = screen.getByRole("button");
- expect(button).toHaveClass("h-11");
+ expect(button).toHaveClass("h-12");
});
it("handles click events", async () => {
const handleClick = vi.fn();
const user = userEvent.setup();
- render(Click me );
+ render( );
await user.click(screen.getByRole("button"));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it("disables button when disabled prop is true", () => {
- render(Disabled );
+ render( );
const button = screen.getByRole("button");
expect(button).toBeDisabled();
});
it("shows loading state", () => {
- render(Loading );
+ render( );
const button = screen.getByRole("button");
expect(button).toHaveAttribute("aria-busy", "true");
expect(screen.getByText(/loading/i)).toBeInTheDocument();
});
it("disables button when loading", () => {
- render(Loading );
+ render( );
const button = screen.getByRole("button");
expect(button).toBeDisabled();
});
it("forwards ref correctly", () => {
const ref = vi.fn();
- render(Button );
+ render( );
expect(ref).toHaveBeenCalled();
});
it("applies custom className", () => {
- render(Custom );
+ render( );
const button = screen.getByRole("button");
expect(button).toHaveClass("custom-class");
});
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index 745b627..0ae8788 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -1,7 +1,10 @@
+// src/components/Button/Button.tsx
import { forwardRef } from "react";
import { cn } from "@/utils";
import { useTheme } from "@/theme";
import { type ButtonProps, type ButtonSize } from "./Button.types";
+import { Spinner } from "@/assets/Icons/Spinner";
+import { withOpacity } from "@/utils/Utils";
const iconSizes: Record = {
sm: "h-3.5 w-3.5",
@@ -9,30 +12,6 @@ const iconSizes: Record = {
lg: "h-5 w-5",
};
-const Spinner = ({ size }: { size: ButtonSize }) => (
-
-
-
-
-);
-
const Button = forwardRef(
(
{
@@ -41,7 +20,14 @@ const Button = forwardRef(
size = "md",
isLoading = false,
disabled,
- children,
+ text,
+ prefixIcon,
+ suffixIcon,
+ type = "button",
+ "aria-label": ariaLabel,
+ "aria-describedby": ariaDescribedBy,
+ "aria-pressed": ariaPressed,
+ onClick,
...props
},
ref,
@@ -49,7 +35,7 @@ const Button = forwardRef(
const { theme } = useTheme();
const baseStyles =
- "inline-flex items-center justify-center gap-2 font-medium transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50";
+ "inline-flex items-center justify-center gap-2 font-medium transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 disabled:pointer-events-none";
const variants = {
primary:
@@ -65,71 +51,133 @@ const Button = forwardRef(
};
const sizes = {
- sm: "h-8 px-3 text-sm",
- md: "h-10 px-4 text-sm",
- lg: "h-11 px-6 text-base",
+ sm: "h-8 px-3 text-sm min-w-[4rem]",
+ md: "h-10 px-4 text-sm min-w-[5rem]",
+ lg: "h-12 px-6 text-base min-w-[6rem]",
};
+ // Adjust padding for icon-only buttons
+ const iconOnlyStyles =
+ !text && (prefixIcon || suffixIcon)
+ ? {
+ sm: "h-8 w-8 px-0 min-w-0",
+ md: "h-10 w-10 px-0 min-w-0",
+ lg: "h-12 w-12 px-0 min-w-0",
+ }
+ : null;
+
// Theme-derived style variables
const themeStyles: Record = {
"--radius": theme.radius,
- "--btn-bg": "",
- "--btn-hover": "",
- "--btn-active": "",
+ "--btn-bg": theme.colors.primary[600],
+ "--btn-hover": theme.colors.primary[700],
+ "--btn-active": theme.colors.primary[800],
"--btn-ring": theme.colors.primary[500],
"--btn-text": theme.colors.semantic.foreground,
- "--btn-border": theme.colors.semantic.border,
+ "--btn-border": theme.colors.primary[600],
};
- if (variant === "primary") {
- themeStyles["--btn-bg"] = theme.colors.primary[600];
- themeStyles["--btn-hover"] = theme.colors.primary[700];
- themeStyles["--btn-active"] = theme.colors.primary[800];
- themeStyles["--btn-ring"] = theme.colors.primary[500];
- } else if (variant === "secondary") {
- themeStyles["--btn-bg"] = theme.colors.secondary[200];
- themeStyles["--btn-hover"] = theme.colors.secondary[300];
- themeStyles["--btn-active"] = theme.colors.secondary[400];
- themeStyles["--btn-text"] = theme.colors.secondary[900];
- themeStyles["--btn-ring"] = theme.colors.secondary[500];
- } else if (variant === "outline") {
- themeStyles["--btn-hover"] = theme.colors.semantic.muted;
- themeStyles["--btn-active"] = theme.colors.secondary[200];
- themeStyles["--btn-ring"] = theme.colors.semantic.ring;
- } else if (variant === "ghost") {
- themeStyles["--btn-hover"] = theme.colors.semantic.muted;
- themeStyles["--btn-active"] = theme.colors.secondary[200];
- themeStyles["--btn-ring"] = theme.colors.semantic.ring;
- } else if (variant === "danger") {
- themeStyles["--btn-bg"] = theme.colors.error[600];
- themeStyles["--btn-hover"] = theme.colors.error[700];
- themeStyles["--btn-active"] = theme.colors.error[800];
- themeStyles["--btn-ring"] = theme.colors.error[500];
+ // Apply variant-specific colors
+ switch (variant) {
+ case "primary":
+ themeStyles["--btn-bg"] = theme.colors.primary[600];
+ themeStyles["--btn-hover"] = theme.colors.primary[700];
+ themeStyles["--btn-active"] = theme.colors.primary[800];
+ themeStyles["--btn-ring"] = theme.colors.primary[500];
+ break;
+ case "secondary":
+ themeStyles["--btn-bg"] = theme.colors.secondary[100];
+ themeStyles["--btn-hover"] = theme.colors.secondary[200];
+ themeStyles["--btn-active"] = theme.colors.secondary[300];
+ themeStyles["--btn-text"] = theme.colors.secondary[900];
+ themeStyles["--btn-ring"] = theme.colors.secondary[500];
+ break;
+ case "outline":
+ themeStyles["--btn-border"] = theme.colors.primary[600];
+ themeStyles["--btn-text"] = theme.colors.primary[600];
+ themeStyles["--btn-hover"] = withOpacity(
+ theme.colors.primary[600],
+ 0.1,
+ );
+ themeStyles["--btn-active"] = withOpacity(
+ theme.colors.primary[600],
+ 0.2,
+ );
+ themeStyles["--btn-ring"] = theme.colors.primary[500];
+ break;
+ case "ghost":
+ themeStyles["--btn-text"] = theme.colors.semantic.foreground;
+ themeStyles["--btn-hover"] = withOpacity(
+ theme.colors.semantic.foreground,
+ 0.08,
+ );
+ themeStyles["--btn-active"] = withOpacity(
+ theme.colors.semantic.foreground,
+ 0.16,
+ );
+ themeStyles["--btn-ring"] = theme.colors.primary[500];
+ break;
+ case "danger":
+ themeStyles["--btn-bg"] = theme.colors.error[600];
+ themeStyles["--btn-hover"] = theme.colors.error[700];
+ themeStyles["--btn-active"] = theme.colors.error[800];
+ themeStyles["--btn-ring"] = theme.colors.error[500];
+ break;
}
+ // Determine if button needs an accessible label
+ const isIconOnly = !text && (prefixIcon || suffixIcon);
+ const needsAriaLabel = (isLoading && !text) || (isIconOnly && !ariaLabel);
+ const computedAriaLabel =
+ ariaLabel || (needsAriaLabel ? "Button" : undefined);
+
return (
{isLoading ? (
<>
-
- Loading
+
+ {text && {text} }
>
) : (
- children
+ <>
+ {prefixIcon && (
+
+ {prefixIcon}
+
+ )}
+ {text && {text} }
+ {suffixIcon && (
+
+ {suffixIcon}
+
+ )}
+ >
)}
);
diff --git a/src/components/Button/Button.types.tsx b/src/components/Button/Button.types.tsx
index 1da534f..58efa8c 100644
--- a/src/components/Button/Button.types.tsx
+++ b/src/components/Button/Button.types.tsx
@@ -1,12 +1,34 @@
-import { type ButtonHTMLAttributes, type ReactNode } from 'react';
+import { type ButtonHTMLAttributes, type ReactNode } from "react";
-export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
-export type ButtonSize = 'sm' | 'md' | 'lg';
+export type ButtonVariant =
+ | "primary"
+ | "secondary"
+ | "outline"
+ | "ghost"
+ | "danger";
+export type ButtonSize = "sm" | "md" | "lg";
export interface ButtonProps extends ButtonHTMLAttributes {
- variant?: ButtonVariant;
- size?: ButtonSize;
- isLoading?: boolean;
- children: ReactNode;
+ // Content
+ text?: string;
-}
\ No newline at end of file
+ // Visual variants
+ variant?: ButtonVariant;
+ size?: ButtonSize;
+
+ // Icons
+ prefixIcon?: ReactNode;
+ suffixIcon?: ReactNode;
+
+ // States
+ isLoading?: boolean;
+ disabled?: boolean;
+
+ // Interaction
+ onClick?: (event: React.MouseEvent) => void;
+
+ // Accessibility
+ "aria-label"?: string;
+ "aria-describedby"?: string;
+ "aria-pressed"?: boolean;
+}
diff --git a/src/components/Modal/Modal.stories.tsx b/src/components/Modal/Modal.stories.tsx
index 23c7b31..7f2ef22 100644
--- a/src/components/Modal/Modal.stories.tsx
+++ b/src/components/Modal/Modal.stories.tsx
@@ -2,6 +2,7 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { useState } from "react";
import { Modal } from "./Modal";
+import { Button } from "../Button";
const meta: Meta = {
title: "Components/Modal",
@@ -50,12 +51,7 @@ const ModalWrapper = (args: any) => {
return (
<>
- setIsOpen(true)}
- className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
- >
- Open Modal
-
+ setIsOpen(true)}>Open Modal
setIsOpen(false)} />
>
);
@@ -72,9 +68,7 @@ export const Default: Story = {
This is a modal dialog. You can close it by clicking the close button,
clicking outside, or pressing the Escape key.
-
- Action Button
-
+ Action Button
),
},
@@ -87,30 +81,10 @@ export const Sizes: Story = {
return (
-
setActiveModal("sm")}
- className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
- >
- Small
-
-
setActiveModal("md")}
- className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
- >
- Medium
-
-
setActiveModal("lg")}
- className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
- >
- Large
-
-
setActiveModal("xl")}
- className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
- >
- Extra Large
-
+
setActiveModal("sm")}>Small
+
setActiveModal("md")}>Medium
+
setActiveModal("lg")}>Large
+
setActiveModal("xl")}>Extra Large
- setIsOpen(true)}
- className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
- >
+ setIsOpen(true)}>
Delete Item
-
+
setIsOpen(false)}
@@ -253,18 +224,12 @@ export const ConfirmationDialog: Story = {
undone.
- setIsOpen(false)}
- className="px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300"
- >
+ setIsOpen(false)}>
Cancel
-
-
+
+
Delete
-
+
@@ -286,12 +251,7 @@ export const FormModal: Story = {
return (
<>
- setIsOpen(true)}
- className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
- >
- Add New Item
-
+ setIsOpen(true)}>Add New Item
setIsOpen(false)}
@@ -319,19 +279,14 @@ export const FormModal: Story = {
/>
- setIsOpen(false)}
- className="px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300"
>
Cancel
-
-
- Submit
-
+
+ Submit
From a81be3154ce0309240cdf50ef7234f0936092699 Mon Sep 17 00:00:00 2001
From: ABILASH <114130954+Abilash-19@users.noreply.github.com>
Date: Mon, 16 Feb 2026 23:41:17 +0530
Subject: [PATCH 02/14] feat: new playground
---
src/App.tsx | 557 +++++++++++++++++++++++++++++++++-----
src/theme/DefaultTheme.ts | 14 +-
src/utils/utils.ts | 37 +++
3 files changed, 526 insertions(+), 82 deletions(-)
create mode 100644 src/utils/utils.ts
diff --git a/src/App.tsx b/src/App.tsx
index b3a72e6..eb5b4b7 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,11 +2,482 @@ import { useState } from "react";
import { Button } from "@/components/Button";
import { Input } from "@/components/Input";
import { Modal } from "@/components/Modal";
+import { Typography } from "@/components/Typography";
import { ThemeProvider, darkTheme } from "@/theme";
+import { useTheme } from "@/theme";
import "@/theme/index.css";
-function App() {
+// Inner component that can access theme context
+function PlaygroundContent() {
+ const { theme } = useTheme();
const [isModalOpen, setIsModalOpen] = useState(false);
+ const [inputValue, setInputValue] = useState("");
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [emailError, setEmailError] = useState("");
+
+ const handleEmailChange = (e: React.ChangeEvent) => {
+ setEmail(e.target.value);
+ if (emailError) setEmailError("");
+ };
+
+ const validateEmail = () => {
+ if (!email.includes("@")) {
+ setEmailError("Please enter a valid email address");
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+ UI Component Library
+
+
+ Interactive playground for testing components
+
+
+
+
+
+
+ {/* Main Content */}
+
+
+ {/* Buttons Section */}
+
+
+
+ Buttons
+
+
+ Interactive buttons with multiple variants and states
+
+
+
+
+ {/* Variants */}
+
+
+ Variants
+
+
+ console.log("Primary")}
+ />
+
+
+
+
+
+
+
+ {/* Sizes */}
+
+
+ {/* States */}
+
+
+ {/* With Actions */}
+
+
+ Interactive Examples
+
+
+ setIsModalOpen(true)}
+ />
+
+ console.log("Button clicked!", { inputValue })
+ }
+ />
+
+
+
+
+
+ {/* Inputs Section */}
+
+
+
+ Input Fields
+
+
+ Form inputs with validation and different states
+
+
+
+
+
+
+ {/* Modal Section */}
+
+
+
+ Modals & Dialogs
+
+
+ Accessible modal dialogs with overlay
+
+
+
+
+ setIsModalOpen(true)}
+ />
+
+
+
+ {/* Typography Section */}
+
+
+
+ Typography Scale
+
+
+ All available typography variants
+
+
+
+
+ Display 1
+ Display 2
+ Heading 1
+ Heading 2
+ Heading 3
+ Heading 4
+ Heading 5
+ Heading 6
+ Subtitle 1
+ Subtitle 2
+
+ Body 1 - Regular paragraph text
+
+
+ Body 2 - Smaller paragraph text
+
+ Caption text
+
+
+
+ {/* Form Example */}
+
+
+
+ Complete Form Example
+
+
+ Combining multiple components together
+
+
+
+
+
+
+
+
+ {/* Modal */}
+
setIsModalOpen(false)}
+ title="Confirmation Dialog"
+ >
+
+
+ This is an example modal dialog. It demonstrates how the modal
+ component works with the current theme.
+
+
+
+ 💡 Pro tip: Modals automatically trap focus and
+ can be closed by clicking outside or pressing the Escape key.
+
+
+
+ setIsModalOpen(false)}
+ />
+ {
+ console.log("Confirmed!");
+ setIsModalOpen(false);
+ }}
+ />
+
+
+
+
+ );
+}
+
+function App() {
const [isDarkMode, setIsDarkMode] = useState(false);
const purpleTheme = {
@@ -31,82 +502,18 @@ function App() {
return (
-
-
-
-
-
- UI Library - {isDarkMode ? "Dark" : "Purple"} Theme{" "}
- {isDarkMode ? "🌙" : "💜"}
-
- setIsDarkMode(!isDarkMode)}
- >
- Switch to {isDarkMode ? "Light" : "Dark"} Mode
-
-
-
-
-
- Buttons
-
-
- console.log("Primary clicked")}>
- Primary
-
- Secondary
- Outline
- Ghost
- Danger
- Loading
-
-
-
-
-
-
-
- Modals
-
- setIsModalOpen(true)}>Open Modal
-
-
+
+ {/* Theme Switcher - Fixed Position */}
+
+ setIsDarkMode(!isDarkMode)}
+ />
-
setIsModalOpen(false)}
- title="Example Modal"
- >
-
-
- This is an example modal with purple theme. Notice the accent
- color!
-
-
- setIsModalOpen(false)}>
- Cancel
-
- setIsModalOpen(false)}>Confirm
-
-
-
+
);
diff --git a/src/theme/DefaultTheme.ts b/src/theme/DefaultTheme.ts
index 9df846d..ef12778 100644
--- a/src/theme/DefaultTheme.ts
+++ b/src/theme/DefaultTheme.ts
@@ -82,15 +82,15 @@ export const defaultTheme: ThemeConfig = {
950: "#083344",
},
semantic: {
- background: "#ffffff",
- foreground: "#0f172a",
- muted: "#f1f5f9",
- mutedForeground: "#64748b",
- border: "#e2e8f0",
- ring: "#2563eb",
+ background: "#ffff", // Soft cream (warm white)
+ foreground: "#1a1a1a", // Rich charcoal (softer than pure black)
+ muted: "#f5f3f0", // Warm light beige
+ mutedForeground: "#78716c", // Warm gray (stone)
+ border: "#e7e5e4", // Subtle warm border
+ ring: "#2563eb", // Keep blue for consistency
},
},
- radius: "0.25rem", // 8px - single value for all components
+ radius: "0.25rem",
fontFamily:
'Rubik, Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
};
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
new file mode 100644
index 0000000..b393ce8
--- /dev/null
+++ b/src/utils/utils.ts
@@ -0,0 +1,37 @@
+// src/utils/color.ts
+
+/**
+ * Add opacity/alpha to a hex color
+ * @param color - Hex color (e.g., "#2563eb")
+ * @param opacity - Opacity value (0-1)
+ * @returns Hex color with alpha (e.g., "#2563eb80")
+ */
+export function withOpacity(color: string, opacity: number): string {
+ // Remove # if present
+ const hex = color.replace("#", "");
+
+ // Clamp opacity between 0 and 1
+ const alpha = Math.max(0, Math.min(1, opacity));
+
+ // Convert to 0-255 and then to hex
+ const alphaHex = Math.round(alpha * 255)
+ .toString(16)
+ .padStart(2, "0");
+
+ return `#${hex}${alphaHex}`;
+}
+
+/**
+ * Convert hex to rgba string
+ * @param color - Hex color
+ * @param opacity - Opacity (0-1)
+ * @returns rgba string (e.g., "rgba(37, 99, 235, 0.5)")
+ */
+export function hexToRgba(color: string, opacity: number): string {
+ const hex = color.replace("#", "");
+ const r = parseInt(hex.substring(0, 2), 16);
+ const g = parseInt(hex.substring(2, 4), 16);
+ const b = parseInt(hex.substring(4, 6), 16);
+
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
+}
From 19df2f867411c2c7a74856b23a7a5b8fda910b5a Mon Sep 17 00:00:00 2001
From: ABILASH <114130954+Abilash-19@users.noreply.github.com>
Date: Mon, 16 Feb 2026 23:47:13 +0530
Subject: [PATCH 03/14] fix: util import
---
src/components/Button/Button.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index 0ae8788..25478e4 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -4,7 +4,7 @@ import { cn } from "@/utils";
import { useTheme } from "@/theme";
import { type ButtonProps, type ButtonSize } from "./Button.types";
import { Spinner } from "@/assets/Icons/Spinner";
-import { withOpacity } from "@/utils/Utils";
+import { withOpacity } from "@/utils/utils";
const iconSizes: Record
= {
sm: "h-3.5 w-3.5",
From 7d43b55631381ac13e0a1817001e2236b779e6f4 Mon Sep 17 00:00:00 2001
From: ABILASH <114130954+Abilash-19@users.noreply.github.com>
Date: Mon, 16 Feb 2026 23:58:39 +0530
Subject: [PATCH 04/14] fix: removed dependbot
---
.github/dependabot.yml | 16 ----------------
1 file changed, 16 deletions(-)
delete mode 100644 .github/dependabot.yml
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 475ff71..0000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-version: 2
-updates:
- - package-ecosystem: 'npm'
- directory: '/'
- schedule:
- interval: 'weekly'
- day: 'monday'
- commit-message:
- prefix: 'chore'
- include: 'scope'
- open-pull-requests-limit: 10
- reviewers:
- - 'your-username'
- labels:
- - 'dependencies'
- - 'automated'
From 634b877083d3a7d7e60aacf309235727b0e342f1 Mon Sep 17 00:00:00 2001
From: ABILASH <114130954+Abilash-19@users.noreply.github.com>
Date: Wed, 18 Feb 2026 20:23:52 +0530
Subject: [PATCH 05/14] refactor: chnage the button text to children and
removed icon props
---
src/App.tsx | 44 ++++++++++++------------
src/assets/Icons/Dummy.tsx | 19 ++++++++++
src/components/Button/Button.stories.tsx | 27 +++++++++------
src/components/Button/Button.tsx | 37 ++++++--------------
src/components/Button/Button.types.tsx | 2 +-
5 files changed, 69 insertions(+), 60 deletions(-)
create mode 100644 src/assets/Icons/Dummy.tsx
diff --git a/src/App.tsx b/src/App.tsx
index eb5b4b7..d516613 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -7,7 +7,7 @@ import { ThemeProvider, darkTheme } from "@/theme";
import { useTheme } from "@/theme";
import "@/theme/index.css";
-// Inner component that can access theme context
+// Inner component that can access theme conchildren
function PlaygroundContent() {
const { theme } = useTheme();
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -96,13 +96,13 @@ function PlaygroundContent() {
console.log("Primary")}
/>
-
-
-
-
+
+
+
+
@@ -117,9 +117,9 @@ function PlaygroundContent() {
Sizes
-
-
-
+
+
+
@@ -134,8 +134,8 @@ function PlaygroundContent() {
States
-
-
+
+
@@ -153,12 +153,12 @@ function PlaygroundContent() {
setIsModalOpen(true)}
/>
console.log("Button clicked!", { inputValue })
}
@@ -319,7 +319,7 @@ function PlaygroundContent() {
setIsModalOpen(true)}
/>
@@ -356,12 +356,12 @@ function PlaygroundContent() {
Subtitle 1
Subtitle 2
- Body 1 - Regular paragraph text
+ Body 1 - Regular paragraph children
- Body 2 - Smaller paragraph text
+ Body 2 - Smaller paragraph children
- Caption text
+ Caption children
@@ -414,11 +414,11 @@ function PlaygroundContent() {
required
/>
-
+
{
setEmail("");
setPassword("");
@@ -460,11 +460,11 @@ function PlaygroundContent() {
setIsModalOpen(false)}
/>
{
console.log("Confirmed!");
setIsModalOpen(false);
@@ -508,7 +508,7 @@ function App() {
setIsDarkMode(!isDarkMode)}
/>
diff --git a/src/assets/Icons/Dummy.tsx b/src/assets/Icons/Dummy.tsx
new file mode 100644
index 0000000..35fb645
--- /dev/null
+++ b/src/assets/Icons/Dummy.tsx
@@ -0,0 +1,19 @@
+export const DummyIcon = () => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx
index 6310817..6f3cebc 100644
--- a/src/components/Button/Button.stories.tsx
+++ b/src/components/Button/Button.stories.tsx
@@ -1,5 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { Button } from "./Button";
+import { DummyIcon } from "../../assets/Icons/Dummy";
const meta: Meta = {
title: "Components/Button",
@@ -31,73 +32,79 @@ type Story = StoryObj;
export const Primary: Story = {
args: {
- text: "Primary Button",
+ children: "Primary Button",
variant: "primary",
},
};
export const Secondary: Story = {
args: {
- text: "Secondary Button",
+ children: "Secondary Button",
variant: "secondary",
},
};
export const Outline: Story = {
args: {
- text: "Outline Button",
+ children: "Outline Button",
variant: "outline",
},
};
export const Ghost: Story = {
args: {
- text: "Ghost Button",
+ children: "Ghost Button",
variant: "ghost",
},
};
export const Danger: Story = {
args: {
- text: "Danger Button",
+ children: "Danger Button",
variant: "danger",
},
};
export const Small: Story = {
args: {
- text: "Small Button",
+ children: "Small Button",
size: "sm",
},
};
export const Medium: Story = {
args: {
- text: "Medium Button",
+ children: "Medium Button",
size: "md",
},
};
export const Large: Story = {
args: {
- text: "Large Button",
+ children: "Large Button",
size: "lg",
},
};
export const Loading: Story = {
args: {
- text: "Loading Button",
+ children: "Loading Button",
isLoading: true,
},
};
export const Disabled: Story = {
args: {
- text: "Disabled Button",
+ children: "Disabled Button",
disabled: true,
},
};
+export const IconOnly: Story = {
+ args: {
+ children: ,
+ size: "md",
+ },
+};
export const AllVariants: Story = {
render: () => (
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index 25478e4..054559d 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -20,7 +20,7 @@ const Button = forwardRef(
size = "md",
isLoading = false,
disabled,
- text,
+ children,
prefixIcon,
suffixIcon,
type = "button",
@@ -51,14 +51,14 @@ const Button = forwardRef(
};
const sizes = {
- sm: "h-8 px-3 text-sm min-w-[4rem]",
- md: "h-10 px-4 text-sm min-w-[5rem]",
- lg: "h-12 px-6 text-base min-w-[6rem]",
+ sm: "h-8 px-3 text-sm",
+ md: "h-10 px-4 text-sm",
+ lg: "h-12 px-6 text-base",
};
// Adjust padding for icon-only buttons
const iconOnlyStyles =
- !text && (prefixIcon || suffixIcon)
+ !children && (prefixIcon || suffixIcon)
? {
sm: "h-8 w-8 px-0 min-w-0",
md: "h-10 w-10 px-0 min-w-0",
@@ -126,8 +126,9 @@ const Button = forwardRef(
}
// Determine if button needs an accessible label
- const isIconOnly = !text && (prefixIcon || suffixIcon);
- const needsAriaLabel = (isLoading && !text) || (isIconOnly && !ariaLabel);
+ const isIconOnly = !children && (prefixIcon || suffixIcon);
+ const needsAriaLabel =
+ (isLoading && !children) || (isIconOnly && !ariaLabel);
const computedAriaLabel =
ariaLabel || (needsAriaLabel ? "Button" : undefined);
@@ -156,28 +157,10 @@ const Button = forwardRef(
{isLoading ? (
<>
- {text && {text} }
+ {children && {children} }
>
) : (
- <>
- {prefixIcon && (
-
- {prefixIcon}
-
- )}
- {text && {text} }
- {suffixIcon && (
-
- {suffixIcon}
-
- )}
- >
+ children
)}
);
diff --git a/src/components/Button/Button.types.tsx b/src/components/Button/Button.types.tsx
index 58efa8c..8f1d33d 100644
--- a/src/components/Button/Button.types.tsx
+++ b/src/components/Button/Button.types.tsx
@@ -10,7 +10,7 @@ export type ButtonSize = "sm" | "md" | "lg";
export interface ButtonProps extends ButtonHTMLAttributes
{
// Content
- text?: string;
+ children?: ReactNode;
// Visual variants
variant?: ButtonVariant;
From 80f7741238baad962dde14ab4d37e65703bb55d4 Mon Sep 17 00:00:00 2001
From: ABILASH <114130954+Abilash-19@users.noreply.github.com>
Date: Thu, 19 Feb 2026 00:08:58 +0530
Subject: [PATCH 06/14] refactor(theme): migrate to three-layer semantic token
architecture
---
src/App.tsx | 280 ++++++++++--------
src/assets/Icons/CloseIcon.tsx | 4 +-
src/components/Button/Button.tsx | 81 +++--
src/components/Input/Input.tsx | 104 ++++---
src/components/Modal/Modal.tsx | 50 +---
src/components/Typography/Typography.tsx | 8 +-
.../Typography/Typography.types.tsx | 4 +
src/theme/DarkTheme.ts | 89 +++++-
tailwind.config.js | 268 +----------------
9 files changed, 364 insertions(+), 524 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index d516613..8ac7048 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,11 +3,21 @@ import { Button } from "@/components/Button";
import { Input } from "@/components/Input";
import { Modal } from "@/components/Modal";
import { Typography } from "@/components/Typography";
-import { ThemeProvider, darkTheme } from "@/theme";
+import { ThemeProvider } from "@/theme";
import { useTheme } from "@/theme";
import "@/theme/index.css";
-// Inner component that can access theme conchildren
+function ThemeToggle() {
+ const { toggleColorMode, theme } = useTheme();
+ return (
+
+
+ {theme.colorMode === "light" ? "Switch to Dark" : "Switch to Light"}
+
+
+ );
+}
+
function PlaygroundContent() {
const { theme } = useTheme();
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -27,20 +37,25 @@ function PlaygroundContent() {
}
};
+ const sectionStyles = {
+ backgroundColor: theme.tokens.surface,
+ "--ring-color": theme.tokens.border,
+ } as React.CSSProperties;
+
return (