diff --git a/package.json b/package.json index 7b69783..e56ea6e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "format:check": "prettier --check .", "lint": "eslint .", "preview": "vite preview", + "prepare": "husky", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "chromatic": "npx chromatic --project-token=chpt_bd8d0a3ce41cca0", @@ -81,4 +82,4 @@ "vite-tsconfig-paths": "^6.1.1", "vitest": "^4.1.5" } -} \ No newline at end of file +} diff --git a/src/index.css b/src/index.css index 0295c40..4854b10 100644 --- a/src/index.css +++ b/src/index.css @@ -2,38 +2,67 @@ /* 1. Fallback tokens (match SemanticTokens EXACTLY) */ :root { + /* Background */ --color-bg-primary: #ffffff; --color-bg-secondary: #f5f5f5; + --color-bg-tertiary: #eeede9; + + /* Text */ --color-text-primary: #000000; --color-text-secondary: #555555; - --color-border: #e5e5e5; - --color-danger: #ef4444; - --color-success: #22c55e; + --color-text-tertiary: #888780; + + /* Border */ + --color-border-primary: rgba(0, 0, 0, 0.32); + --color-border-secondary: rgba(0, 0, 0, 0.2); + --color-border-tertiary: rgba(0, 0, 0, 0.1); + + /* Danger */ + --color-bg-danger: #fde8e8; + --color-text-danger: #b91c1c; + --color-border-danger: #f5c2c2; + + /* Warning */ + --color-bg-warning: #fef3c7; + --color-text-warning: #92400e; + --color-border-warning: #fde68a; + + /* Success */ + --color-bg-success: #e6f4ea; + --color-text-success: #166534; + --color-border-success: #bbf7d0; + /* Info */ + --color-bg-info: #e8f1fb; + --color-text-info: #1e3a8a; + --color-border-info: #bfdbfe; + + /* Radius */ --radius-sm: 4px; --radius-md: 8px; --radius-lg: 12px; + /* Shadow */ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); /* Status Backgrounds */ ---color-bg-danger: #fde8e8; ---color-bg-warning: #fef3c7; ---color-bg-success: #e6f4ea; ---color-bg-info: #e8f1fb; - -/* Status Text */ ---color-text-danger: #b91c1c; ---color-text-warning: #92400e; ---color-text-success: #166534; ---color-text-info: #1e3a8a; - -/* Status Borders */ ---color-border-danger: #f5c2c2; ---color-border-warning: #fde68a; ---color-border-success: #bbf7d0; ---color-border-info: #bfdbfe; + --color-bg-danger: #fde8e8; + --color-bg-warning: #fef3c7; + --color-bg-success: #e6f4ea; + --color-bg-info: #e8f1fb; + + /* Status Text */ + --color-text-danger: #b91c1c; + --color-text-warning: #92400e; + --color-text-success: #166534; + --color-text-info: #1e3a8a; + + /* Status Borders */ + --color-border-danger: #f5c2c2; + --color-border-warning: #fde68a; + --color-border-success: #bbf7d0; + --color-border-info: #bfdbfe; } /* 2. Tailwind v4 bridge (CRITICAL) */ @@ -48,7 +77,7 @@ --color-text-secondary: var(--color-text-secondary); --color-text-tertiary: var(--color-text-tertiary); - /* Borders */ + /* Border */ --color-border-primary: var(--color-border-primary); --color-border-secondary: var(--color-border-secondary); --color-border-tertiary: var(--color-border-tertiary); @@ -73,17 +102,17 @@ } /* Disable transitions on first load (prevents flash) */ -html[data-theme-loaded="false"], -html[data-theme-loaded="false"] * { +html[data-theme-loaded='false'], +html[data-theme-loaded='false'] * { transition: none !important; } /* Enable smooth transitions AFTER theme is applied */ -html[data-theme-loaded="true"], -html[data-theme-loaded="true"] * { +html[data-theme-loaded='true'], +html[data-theme-loaded='true'] * { transition: background-color 0.25s ease, color 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease; -} \ No newline at end of file +} diff --git a/src/shared/Badge/Badge.stories.tsx b/src/shared/Badge/Badge.stories.tsx index a44286f..288fb63 100644 --- a/src/shared/Badge/Badge.stories.tsx +++ b/src/shared/Badge/Badge.stories.tsx @@ -1,5 +1,5 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Badge } from '.'; +import type { Meta, StoryObj } from '@storybook/react-vite' +import { Badge } from '.' const meta = { title: 'Shared/Primitives/Badge', @@ -31,74 +31,74 @@ const meta = { control: 'boolean', }, }, -} satisfies Meta; +} satisfies Meta -export default meta; +export default meta -type Story = StoryObj; +type Story = StoryObj export const Default: Story = { args: { children: 'Default', variant: 'default', }, -}; +} export const Pending: Story = { args: { children: 'Pending', variant: 'pending', }, -}; +} export const Escalated: Story = { args: { children: 'Escalated', variant: 'escalated', }, -}; +} export const Resolved: Story = { args: { children: 'Resolved', variant: 'resolved', }, -}; +} export const Dismissed: Story = { args: { children: 'Dismissed', variant: 'dismissed', }, -}; +} export const Info: Story = { args: { children: 'Info', variant: 'info', }, -}; +} export const Success: Story = { args: { children: 'Success', variant: 'success', }, -}; +} export const Warning: Story = { args: { children: 'Warning', variant: 'warning', }, -}; +} export const Danger: Story = { args: { children: 'Danger', variant: 'danger', }, -}; +} export const Small: Story = { args: { @@ -106,7 +106,7 @@ export const Small: Story = { variant: 'pending', size: 'sm', }, -}; +} export const Medium: Story = { args: { @@ -114,7 +114,7 @@ export const Medium: Story = { variant: 'pending', size: 'md', }, -}; +} export const Dot: Story = { args: { @@ -122,4 +122,4 @@ export const Dot: Story = { dot: true, size: 'md', }, -}; \ No newline at end of file +} diff --git a/src/shared/Badge/Badge.tsx b/src/shared/Badge/Badge.tsx index acb63bf..941e5b8 100644 --- a/src/shared/Badge/Badge.tsx +++ b/src/shared/Badge/Badge.tsx @@ -1,37 +1,28 @@ -import React from 'react'; -import { cva, type VariantProps } from 'class-variance-authority'; +import React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' const badge = cva( 'inline-flex items-center gap-1 font-medium rounded-full border transition-opacity', { variants: { variant: { - default: - 'bg-bg-secondary text-text-secondary border-border-secondary', + default: 'bg-bg-secondary text-text-secondary border-border-secondary', - warning: - 'bg-bg-warning text-text-warning border-border-warning', + warning: 'bg-bg-warning text-text-warning border-border-warning', - danger: - 'bg-bg-danger text-text-danger border-border-danger', + danger: 'bg-bg-danger text-text-danger border-border-danger', - success: - 'bg-bg-success text-text-success border-border-success', + success: 'bg-bg-success text-text-success border-border-success', - pending: - 'bg-bg-warning text-text-warning border-border-warning', + pending: 'bg-bg-warning text-text-warning border-border-warning', - escalated: - 'bg-bg-danger text-text-danger border-border-danger', + escalated: 'bg-bg-danger text-text-danger border-border-danger', - resolved: - 'bg-bg-success text-text-success border-border-success', + resolved: 'bg-bg-success text-text-success border-border-success', - dismissed: - 'bg-bg-secondary text-text-tertiary border-border-secondary', + dismissed: 'bg-bg-secondary text-text-tertiary border-border-secondary', - info: - 'bg-bg-info text-text-info border-border-info', + info: 'bg-bg-info text-text-info border-border-info', }, size: { @@ -45,27 +36,16 @@ const badge = cva( size: 'md', }, } -); +) export interface BadgeProps - extends React.HTMLAttributes, - VariantProps { - children?: React.ReactNode; - dot?: boolean; + extends React.HTMLAttributes, VariantProps { + children?: React.ReactNode + dot?: boolean } -export function Badge({ - children, - variant, - size, - dot = false, - className, - ...props -}: BadgeProps) { - const dotSize = - size === 'sm' - ? 'w-1.5 h-1.5' - : 'w-2 h-2'; +export function Badge({ children, variant, size, dot = false, className, ...props }: BadgeProps) { + const dotSize = size === 'sm' ? 'w-1.5 h-1.5' : 'w-2 h-2' return ( - {dot ? ( - - ) : ( - children - )} + {dot ? : children} - ); -} \ No newline at end of file + ) +} diff --git a/src/shared/Button/Button.stories.tsx b/src/shared/Button/Button.stories.tsx new file mode 100644 index 0000000..2b0717b --- /dev/null +++ b/src/shared/Button/Button.stories.tsx @@ -0,0 +1,127 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Button } from '.'; + +const meta = { + title: 'Shared/Primitives/Button', + component: Button, + tags: ['autodocs'], + + argTypes: { + variant: { + control: 'select', + options: [ + 'primary', + 'secondary', + 'danger', + 'warning', + 'ghost', + 'info', + ], + }, + + size: { + control: 'select', + options: ['sm', 'md', 'lg'], + }, + + isLoading: { + control: 'boolean', + }, + + isDisabled: { + control: 'boolean', + }, + + fullWidth: { + control: 'boolean', + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + args: { + children: 'Primary Button', + variant: 'primary', + }, +}; + +export const Secondary: Story = { + args: { + children: 'Secondary Button', + variant: 'secondary', + }, +}; + +export const Danger: Story = { + args: { + children: 'Danger Button', + variant: 'danger', + }, +}; + +export const Warning: Story = { + args: { + children: 'Warning Button', + variant: 'warning', + }, +}; + +export const Ghost: Story = { + args: { + children: 'Ghost Button', + variant: 'ghost', + }, +}; + +export const Info: Story = { + args: { + children: 'Info Button', + variant: 'info', + }, +}; + +export const Small: Story = { + args: { + children: 'Small Button', + size: 'sm', + }, +}; + +export const Medium: Story = { + args: { + children: 'Medium Button', + size: 'md', + }, +}; + +export const Large: Story = { + args: { + children: 'Large Button', + size: 'lg', + }, +}; + +export const Loading: Story = { + args: { + children: 'Loading...', + isLoading: true, + }, +}; + +export const Disabled: Story = { + args: { + children: 'Disabled Button', + isDisabled: true, + }, +}; + +export const FullWidth: Story = { + args: { + children: 'Full Width Button', + fullWidth: true, + }, +}; \ No newline at end of file diff --git a/src/shared/Button/Button.tsx b/src/shared/Button/Button.tsx new file mode 100644 index 0000000..5c93819 --- /dev/null +++ b/src/shared/Button/Button.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; + +const button = cva( + [ + 'inline-flex items-center justify-center gap-2', + 'rounded-md font-medium', + 'transition-all', + 'active:scale-[0.98]', + 'focus:outline-none', + 'disabled:opacity-50', + 'disabled:cursor-not-allowed disabled:pointer-events-none', + ], + { + variants: { + variant: { + primary: + 'bg-text-info text-bg-primary border border-border-info', + + secondary: + 'bg-bg-secondary text-text-primary border border-border-secondary', + + danger: + 'bg-bg-danger text-text-danger border border-border-danger', + + warning: + 'bg-bg-warning text-text-warning border border-border-warning', + + info: + 'bg-bg-info text-text-info border border-border-info', + + ghost: + 'bg-transparent text-text-secondary border border-transparent', + }, + + size: { + sm: 'px-3 py-1.5 text-sm', + md: 'px-4 py-2 text-sm', + lg: 'px-5 py-3 text-base', + }, + + fullWidth: { + true: 'w-full', + }, + }, + + defaultVariants: { + variant: 'primary', + size: 'md', + fullWidth: false, + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + children: React.ReactNode; + + isLoading?: boolean; + isDisabled?: boolean; + + leftIcon?: React.ReactNode; + rightIcon?: React.ReactNode; + + testId?: string; +} + +export function Button({ + children, + + variant, + size, + fullWidth, + + className, + + isLoading = false, + isDisabled = false, + + leftIcon, + rightIcon, + + testId, + + ...props +}: ButtonProps) { + const disabled = isDisabled || isLoading; + + return ( + + ); +} \ No newline at end of file diff --git a/src/shared/Button/index.ts b/src/shared/Button/index.ts new file mode 100644 index 0000000..a957269 --- /dev/null +++ b/src/shared/Button/index.ts @@ -0,0 +1,2 @@ +export { Button } from './Button' +export type { ButtonProps } from './Button' \ No newline at end of file