diff --git a/apps/storybook/.gitignore b/apps/storybook/.gitignore new file mode 100644 index 00000000..48852aba --- /dev/null +++ b/apps/storybook/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +storybook-static + diff --git a/apps/storybook/.storybook/main.ts b/apps/storybook/.storybook/main.ts new file mode 100644 index 00000000..6c28546b --- /dev/null +++ b/apps/storybook/.storybook/main.ts @@ -0,0 +1,32 @@ +import type { StorybookConfig } from '@storybook/react-vite'; +import { mergeConfig } from 'vite'; +import path from 'path'; + +const config: StorybookConfig = { + stories: ['../stories/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + ], + framework: { + name: '@storybook/react-vite', + options: {}, + }, + docs: { + autodocs: 'tag', + }, + async viteFinal(config) { + return mergeConfig(config, { + resolve: { + alias: { + '@lib': path.resolve(__dirname, '../../../packages/design-system/src/lib'), + '@components': path.resolve(__dirname, '../../../packages/design-system/src/components'), + }, + }, + }); + }, +}; + +export default config; + diff --git a/apps/storybook/.storybook/preview-head.html b/apps/storybook/.storybook/preview-head.html new file mode 100644 index 00000000..3ffbc9b7 --- /dev/null +++ b/apps/storybook/.storybook/preview-head.html @@ -0,0 +1,15 @@ + + + + + diff --git a/apps/storybook/.storybook/preview.tsx b/apps/storybook/.storybook/preview.tsx new file mode 100644 index 00000000..dec35bec --- /dev/null +++ b/apps/storybook/.storybook/preview.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import type { Preview } from "@storybook/react"; +import "@react-shop/design-system/src/styles/global.css"; + +const preview: Preview = { + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + backgrounds: { + default: "light", + values: [ + { name: "light", value: "#ffffff" }, + { name: "dark", value: "#1a1a1a" }, + ], + }, + }, +}; + +export default preview; diff --git a/apps/storybook/README.md b/apps/storybook/README.md new file mode 100644 index 00000000..dcffee06 --- /dev/null +++ b/apps/storybook/README.md @@ -0,0 +1,59 @@ +# Storybook - Design System Documentation + +This is the Storybook documentation app for `@react-shop/design-system`. + +## Getting Started + +```bash +# Install dependencies (from root) +pnpm install + +# Start Storybook +cd apps/storybook +pnpm dev +``` + +Storybook will be available at `http://localhost:6006` + +## Building + +```bash +pnpm build +``` + +## Writing Stories + +Create stories in the `stories/` directory: + +```typescript +// stories/Button.stories.tsx +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from '@react-shop/design-system'; + +const meta = { + title: 'Atoms/Button', + component: Button, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + children: 'Button', + variant: 'solid', + }, +}; +``` + +## Structure + +``` +stories/ +├── Atoms/ → Basic components (Button, Text, Input) +├── Molecules/ → Composite components (Card, Rating) +├── Organisms/ → Complex components (Modal, ProductCard) +└── Layout/ → Layout components (Header, Footer) +``` + diff --git a/apps/storybook/package.json b/apps/storybook/package.json new file mode 100644 index 00000000..989fe16b --- /dev/null +++ b/apps/storybook/package.json @@ -0,0 +1,32 @@ +{ + "name": "storybook", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "storybook dev -p 6006", + "build": "storybook build", + "preview": "storybook preview" + }, + "dependencies": { + "@react-shop/design-system": "workspace:*", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@storybook/addon-essentials": "^8.0.0", + "@storybook/addon-interactions": "^8.0.0", + "@storybook/addon-links": "^8.0.0", + "@storybook/blocks": "^8.0.0", + "@storybook/react": "^8.0.0", + "@storybook/react-vite": "^8.0.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32", + "storybook": "^8.0.0", + "tailwindcss": "^3.4.0", + "tsconfig": "workspace:*", + "typescript": "^5.0.0", + "vite": "^5.0.0" + } +} diff --git a/apps/storybook/postcss.config.js b/apps/storybook/postcss.config.js new file mode 100644 index 00000000..c21c0763 --- /dev/null +++ b/apps/storybook/postcss.config.js @@ -0,0 +1,7 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; + diff --git a/apps/storybook/stories/Atoms/Avatar.stories.tsx b/apps/storybook/stories/Atoms/Avatar.stories.tsx new file mode 100644 index 00000000..59bbcc02 --- /dev/null +++ b/apps/storybook/stories/Atoms/Avatar.stories.tsx @@ -0,0 +1,53 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Avatar } from '@react-shop/design-system'; + +const meta = { + title: 'Atoms/Avatar', + component: Avatar, + tags: ['autodocs'], + argTypes: { + size: { + control: 'select', + options: ['sm', 'md', 'lg', 'xl'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + src: 'https://i.pravatar.cc/150?img=1', + alt: 'User Avatar', + }, +}; + +export const WithFallback: Story = { + args: { + src: undefined, + fallback: 'JD', + }, +}; + +export const Sizes: Story = { + render: () => ( +
+ + + + +
+ ), +}; + +export const Fallbacks: Story = { + render: () => ( +
+ + + +
+ ), +}; + diff --git a/apps/storybook/stories/Atoms/Badge.stories.tsx b/apps/storybook/stories/Atoms/Badge.stories.tsx new file mode 100644 index 00000000..d49cac3f --- /dev/null +++ b/apps/storybook/stories/Atoms/Badge.stories.tsx @@ -0,0 +1,64 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Badge } from '@react-shop/design-system'; + +const meta = { + title: 'Atoms/Badge', + component: Badge, + tags: ['autodocs'], + argTypes: { + variant: { + control: 'select', + options: ['default', 'primary', 'success', 'error', 'warning'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + children: 'Badge', + }, +}; + +export const Primary: Story = { + args: { + children: 'Primary', + variant: 'primary', + }, +}; + +export const Success: Story = { + args: { + children: 'Success', + variant: 'success', + }, +}; + +export const Error: Story = { + args: { + children: 'Error', + variant: 'error', + }, +}; + +export const Warning: Story = { + args: { + children: 'Warning', + variant: 'warning', + }, +}; + +export const AllVariants: Story = { + render: () => ( +
+ Default + Primary + Success + Error + Warning +
+ ), +}; + diff --git a/apps/storybook/stories/Atoms/Button.stories.tsx b/apps/storybook/stories/Atoms/Button.stories.tsx new file mode 100644 index 00000000..c1b8bf03 --- /dev/null +++ b/apps/storybook/stories/Atoms/Button.stories.tsx @@ -0,0 +1,84 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from '@react-shop/design-system'; + +const meta = { + title: 'Atoms/Button', + component: Button, + tags: ['autodocs'], + argTypes: { + variant: { + control: 'select', + options: ['solid', 'outline', 'ghost', 'link'], + }, + size: { + control: 'select', + options: ['sm', 'md', 'lg'], + }, + fullWidth: { + control: 'boolean', + }, + disabled: { + control: 'boolean', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Solid: Story = { + args: { + children: 'Solid Button', + variant: 'solid', + }, +}; + +export const Outline: Story = { + args: { + children: 'Outline Button', + variant: 'outline', + }, +}; + +export const Ghost: Story = { + args: { + children: 'Ghost Button', + variant: 'ghost', + }, +}; + +export const Link: Story = { + args: { + children: 'Link Button', + variant: 'link', + }, +}; + +export const Small: Story = { + args: { + children: 'Small Button', + size: 'sm', + }, +}; + +export const Large: Story = { + args: { + children: 'Large Button', + size: 'lg', + }, +}; + +export const FullWidth: Story = { + args: { + children: 'Full Width Button', + fullWidth: true, + }, +}; + +export const Disabled: Story = { + args: { + children: 'Disabled Button', + disabled: true, + }, +}; + diff --git a/apps/storybook/stories/Atoms/Card.stories.tsx b/apps/storybook/stories/Atoms/Card.stories.tsx new file mode 100644 index 00000000..29065ab6 --- /dev/null +++ b/apps/storybook/stories/Atoms/Card.stories.tsx @@ -0,0 +1,61 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Card, CardHeader, CardContent, CardFooter } from '@react-shop/design-system'; + +const meta = { + title: 'Atoms/Card', + component: Card, + tags: ['autodocs'], + argTypes: { + variant: { + control: 'select', + options: ['elevated', 'outlined'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Elevated: Story = { + render: () => ( + + +

Card Title

+

Card subtitle

+
+ +

This is the card content. It can contain any elements you want.

+
+ + + +
+ ), +}; + +export const Outlined: Story = { + render: () => ( + + +

Outlined Card

+

With border

+
+ +

This card has an outlined variant with a visible border.

+
+
+ ), +}; + +export const SimpleCard: Story = { + render: () => ( + + +

Simple card with just content

+
+
+ ), +}; + diff --git a/apps/storybook/stories/Atoms/Divider.stories.tsx b/apps/storybook/stories/Atoms/Divider.stories.tsx new file mode 100644 index 00000000..336b4492 --- /dev/null +++ b/apps/storybook/stories/Atoms/Divider.stories.tsx @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Divider } from '@react-shop/design-system'; + +const meta = { + title: 'Atoms/Divider', + component: Divider, + tags: ['autodocs'], + argTypes: { + orientation: { + control: 'select', + options: ['horizontal', 'vertical'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Horizontal: Story = { + render: () => ( +
+

Content above divider

+ +

Content below divider

+
+ ), +}; + +export const Vertical: Story = { + render: () => ( +
+ Left + + Middle + + Right +
+ ), +}; + diff --git a/apps/storybook/stories/Atoms/Heading.stories.tsx b/apps/storybook/stories/Atoms/Heading.stories.tsx new file mode 100644 index 00000000..bb624c60 --- /dev/null +++ b/apps/storybook/stories/Atoms/Heading.stories.tsx @@ -0,0 +1,59 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Heading } from '@react-shop/design-system'; + +const meta = { + title: 'Atoms/Heading', + component: Heading, + tags: ['autodocs'], + argTypes: { + as: { + control: 'select', + options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], + }, + size: { + control: 'select', + options: ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const H1: Story = { + args: { + as: 'h1', + size: '4xl', + children: 'Heading Level 1', + }, +}; + +export const H2: Story = { + args: { + as: 'h2', + size: '3xl', + children: 'Heading Level 2', + }, +}; + +export const H3: Story = { + args: { + as: 'h3', + size: '2xl', + children: 'Heading Level 3', + }, +}; + +export const AllSizes: Story = { + render: () => ( +
+ Extra Large Heading (4xl) + Large Heading (3xl) + Medium-Large Heading (2xl) + Medium Heading (xl) + Small-Medium Heading (lg) + Small Heading (md) +
+ ), +}; + diff --git a/apps/storybook/stories/Atoms/Skeleton.stories.tsx b/apps/storybook/stories/Atoms/Skeleton.stories.tsx new file mode 100644 index 00000000..1ec70aad --- /dev/null +++ b/apps/storybook/stories/Atoms/Skeleton.stories.tsx @@ -0,0 +1,49 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Skeleton } from '@react-shop/design-system'; + +const meta = { + title: 'Atoms/Skeleton', + component: Skeleton, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + className: 'h-4 w-full', + }, +}; + +export const Circle: Story = { + args: { + className: 'h-12 w-12 rounded-full', + }, +}; + +export const Card: Story = { + render: () => ( +
+ +
+ + + +
+
+ ), +}; + +export const UserProfile: Story = { + render: () => ( +
+ +
+ + +
+
+ ), +}; + diff --git a/apps/storybook/stories/Atoms/Text.stories.tsx b/apps/storybook/stories/Atoms/Text.stories.tsx new file mode 100644 index 00000000..e3bb8dce --- /dev/null +++ b/apps/storybook/stories/Atoms/Text.stories.tsx @@ -0,0 +1,66 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Text } from '@react-shop/design-system'; + +const meta = { + title: 'Atoms/Text', + component: Text, + tags: ['autodocs'], + argTypes: { + size: { + control: 'select', + options: ['xs', 'sm', 'base', 'lg', 'xl'], + }, + weight: { + control: 'select', + options: ['light', 'normal', 'medium', 'semibold', 'bold'], + }, + align: { + control: 'select', + options: ['left', 'center', 'right'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + children: 'This is default text', + }, +}; + +export const Sizes: Story = { + render: () => ( +
+ Extra small text (xs) + Small text (sm) + Base text (default) + Large text (lg) + Extra large text (xl) +
+ ), +}; + +export const Weights: Story = { + render: () => ( +
+ Light weight text + Normal weight text (default) + Medium weight text + Semibold weight text + Bold weight text +
+ ), +}; + +export const Alignment: Story = { + render: () => ( +
+ Left aligned text + Center aligned text + Right aligned text +
+ ), +}; + diff --git a/apps/storybook/stories/Introduction.mdx b/apps/storybook/stories/Introduction.mdx new file mode 100644 index 00000000..2b483c59 --- /dev/null +++ b/apps/storybook/stories/Introduction.mdx @@ -0,0 +1,83 @@ +import { Meta } from '@storybook/blocks'; + + + +# React Shop Design System + +Welcome to the React Shop Design System documentation. + +## Overview + +This design system provides a comprehensive set of reusable components for building modern e-commerce applications. + +## Component Categories + +### 🎨 Design System/Theme +Explore our color palette, typography, spacing, and other design tokens that power the design system. + +### ⚛️ Atoms +Basic building blocks that cannot be broken down further: +- Avatar, Badge, Box, Button, Card +- Container, Divider, Flex, Grid +- Heading, Icon, Input, Skeleton +- Stack, Text + +### 🧩 Molecules +Composite components built from atoms: +- PriceDisplay, Rating, Select, Toast + +### 🏗️ Organisms +Complex components combining multiple molecules: +- Modal, ProductCard + +### 📐 Layout +Page layout components: +- CartIcon, Footer, Header, Logo +- MobileMenu, Navigation, SearchBar, UserMenu + +## Design Principles + +- **Framework Agnostic**: Pure presentational components without framework-specific logic +- **Type Safe**: Full TypeScript support with comprehensive prop types +- **Accessible**: WCAG 2.1 AA compliant +- **Themeable**: Powered by Tailwind CSS with customizable design tokens +- **Modular**: Import only what you need +- **Composable**: Build complex UIs from simple components + +## Getting Started + +1. **Explore Components**: Browse categories in the sidebar +2. **Interactive Controls**: Modify props in real-time using the Controls panel +3. **View Source**: Check the code examples for each story +4. **Dark Mode**: Toggle between light and dark backgrounds +5. **Responsive**: Test components at different viewport sizes + +## Usage + +```typescript +import { Button, Card, ProductCard } from '@react-shop/design-system'; + +function MyComponent() { + return ( + + {}} + /> + + + ); +} +``` + +## Theme Tokens + +Visit the **Design System/Theme** section to explore: +- Color palettes (Brand, Primary, Success, Error, Warning) +- Typography scale and font weights +- Spacing system +- Border radius values +- Shadow depths + diff --git a/apps/storybook/stories/Layout/CartIcon.stories.tsx b/apps/storybook/stories/Layout/CartIcon.stories.tsx new file mode 100644 index 00000000..4e4e13ec --- /dev/null +++ b/apps/storybook/stories/Layout/CartIcon.stories.tsx @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { CartIcon } from '@react-shop/design-system'; + +const meta = { + title: 'Layout/CartIcon', + component: CartIcon, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Empty: Story = { + args: { + itemCount: 0, + onClick: () => console.log('Cart clicked'), + }, +}; + +export const WithItems: Story = { + args: { + itemCount: 3, + onClick: () => console.log('Cart clicked'), + }, +}; + +export const ManyItems: Story = { + args: { + itemCount: 99, + onClick: () => console.log('Cart clicked'), + }, +}; + +export const OverLimit: Story = { + args: { + itemCount: 999, + onClick: () => console.log('Cart clicked'), + }, +}; + diff --git a/apps/storybook/stories/Layout/Logo.stories.tsx b/apps/storybook/stories/Layout/Logo.stories.tsx new file mode 100644 index 00000000..d3b053b4 --- /dev/null +++ b/apps/storybook/stories/Layout/Logo.stories.tsx @@ -0,0 +1,59 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Logo } from '@react-shop/design-system'; + +const meta = { + title: 'Layout/Logo', + component: Logo, + tags: ['autodocs'], + argTypes: { + size: { + control: 'select', + options: ['sm', 'md', 'lg'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: {}, +}; + +export const Small: Story = { + args: { + size: 'sm', + }, +}; + +export const Medium: Story = { + args: { + size: 'md', + }, +}; + +export const Large: Story = { + args: { + size: 'lg', + }, +}; + +export const AllSizes: Story = { + render: () => ( +
+
+

Small

+ +
+
+

Medium (Default)

+ +
+
+

Large

+ +
+
+ ), +}; + diff --git a/apps/storybook/stories/Layout/MobileMenu.stories.tsx b/apps/storybook/stories/Layout/MobileMenu.stories.tsx new file mode 100644 index 00000000..d608ef3a --- /dev/null +++ b/apps/storybook/stories/Layout/MobileMenu.stories.tsx @@ -0,0 +1,74 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { MobileMenu } from '@react-shop/design-system'; +import { useState } from 'react'; + +const meta = { + title: 'Layout/MobileMenu', + component: MobileMenu, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const mockLinks = [ + { label: 'Home', href: '/', isActive: true }, + { label: 'Products', href: '/products', isActive: false }, + { label: 'Categories', href: '/categories', isActive: false }, + { label: 'About', href: '/about', isActive: false }, +]; + +const MockLink = ({ href, children, className, onClick }: any) => ( + { e.preventDefault(); onClick?.(); }}> + {children} + +); + +export const LoggedOut: Story = { + render: () => { + const [isOpen, setIsOpen] = useState(false); + return ( + <> + + setIsOpen(false)} + user={null} + links={mockLinks} + onLogin={() => console.log('Login')} + onLogout={() => console.log('Logout')} + LinkComponent={MockLink} + /> + + ); + }, +}; + +export const LoggedIn: Story = { + render: () => { + const [isOpen, setIsOpen] = useState(false); + return ( + <> + + setIsOpen(false)} + user={{ + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@example.com', + }} + links={mockLinks} + onLogin={() => console.log('Login')} + onLogout={() => console.log('Logout')} + LinkComponent={MockLink} + /> + + ); + }, +}; + diff --git a/apps/storybook/stories/Layout/Navigation.stories.tsx b/apps/storybook/stories/Layout/Navigation.stories.tsx new file mode 100644 index 00000000..ec996518 --- /dev/null +++ b/apps/storybook/stories/Layout/Navigation.stories.tsx @@ -0,0 +1,43 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Navigation } from '@react-shop/design-system'; + +const meta = { + title: 'Layout/Navigation', + component: Navigation, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const mockLinks = [ + { label: 'Home', href: '/', isActive: true }, + { label: 'Products', href: '/products', isActive: false }, + { label: 'Categories', href: '/categories', isActive: false }, + { label: 'About', href: '/about', isActive: false }, +]; + +const MockLink = ({ href, children, className }: any) => ( + e.preventDefault()}> + {children} + +); + +export const Default: Story = { + args: { + links: mockLinks, + LinkComponent: MockLink, + }, +}; + +export const WithActiveLink: Story = { + args: { + links: [ + { label: 'Home', href: '/', isActive: false }, + { label: 'Products', href: '/products', isActive: true }, + { label: 'Categories', href: '/categories', isActive: false }, + ], + LinkComponent: MockLink, + }, +}; + diff --git a/apps/storybook/stories/Layout/SearchBar.stories.tsx b/apps/storybook/stories/Layout/SearchBar.stories.tsx new file mode 100644 index 00000000..d88e33d2 --- /dev/null +++ b/apps/storybook/stories/Layout/SearchBar.stories.tsx @@ -0,0 +1,45 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { SearchBar } from '@react-shop/design-system'; +import { useState } from 'react'; + +const meta = { + title: 'Layout/SearchBar', + component: SearchBar, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => { + const [query, setQuery] = useState(''); + return ( +
+ console.log('Search:', query)} + placeholder="Search products..." + /> +
+ ); + }, +}; + +export const WithValue: Story = { + render: () => { + const [query, setQuery] = useState('wireless headphones'); + return ( +
+ console.log('Search:', query)} + placeholder="Search products..." + /> +
+ ); + }, +}; + diff --git a/apps/storybook/stories/Layout/UserMenu.stories.tsx b/apps/storybook/stories/Layout/UserMenu.stories.tsx new file mode 100644 index 00000000..b95e7592 --- /dev/null +++ b/apps/storybook/stories/Layout/UserMenu.stories.tsx @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { UserMenu } from '@react-shop/design-system'; + +const meta = { + title: 'Layout/UserMenu', + component: UserMenu, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const LoggedOut: Story = { + args: { + user: null, + onLogin: () => console.log('Login clicked'), + onLogout: () => console.log('Logout clicked'), + }, +}; + +export const LoggedIn: Story = { + args: { + user: { + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@example.com', + }, + onLogin: () => console.log('Login clicked'), + onLogout: () => console.log('Logout clicked'), + }, +}; + +export const WithShortName: Story = { + args: { + user: { + firstName: 'Jo', + lastName: 'Do', + email: 'jo@example.com', + }, + onLogin: () => console.log('Login clicked'), + onLogout: () => console.log('Logout clicked'), + }, +}; + diff --git a/apps/storybook/stories/Molecules/PriceDisplay.stories.tsx b/apps/storybook/stories/Molecules/PriceDisplay.stories.tsx new file mode 100644 index 00000000..afe818a1 --- /dev/null +++ b/apps/storybook/stories/Molecules/PriceDisplay.stories.tsx @@ -0,0 +1,52 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { PriceDisplay } from '@react-shop/design-system'; + +const meta = { + title: 'Molecules/PriceDisplay', + component: PriceDisplay, + tags: ['autodocs'], + argTypes: { + size: { + control: 'select', + options: ['sm', 'md', 'lg'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + price: 99.99, + currency: '$', + }, +}; + +export const WithComparePrice: Story = { + args: { + price: 79.99, + comparePrice: 99.99, + currency: '$', + }, +}; + +export const Sizes: Story = { + render: () => ( +
+ + + +
+ ), +}; + +export const WithDiscount: Story = { + render: () => ( +
+ + +
+ ), +}; + diff --git a/apps/storybook/stories/Molecules/Rating.stories.tsx b/apps/storybook/stories/Molecules/Rating.stories.tsx new file mode 100644 index 00000000..e8ada2e5 --- /dev/null +++ b/apps/storybook/stories/Molecules/Rating.stories.tsx @@ -0,0 +1,78 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Rating } from '@react-shop/design-system'; +import { useState } from 'react'; + +const meta = { + title: 'Molecules/Rating', + component: Rating, + tags: ['autodocs'], + argTypes: { + size: { + control: 'select', + options: ['sm', 'md', 'lg'], + }, + readonly: { + control: 'boolean', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + value: 3.5, + readonly: true, + }, +}; + +export const Interactive: Story = { + render: () => { + const [rating, setRating] = useState(0); + return ( +
+ +

Selected rating: {rating}

+
+ ); + }, +}; + +export const Readonly: Story = { + render: () => ( +
+
+ +

Perfect (5.0)

+
+
+ +

Great (4.0)

+
+
+ +

Good (3.5)

+
+
+ +

Fair (2.0)

+
+
+ +

Poor (1.0)

+
+
+ ), +}; + +export const Sizes: Story = { + render: () => ( +
+ + + +
+ ), +}; + diff --git a/apps/storybook/stories/Molecules/Toast.stories.tsx b/apps/storybook/stories/Molecules/Toast.stories.tsx new file mode 100644 index 00000000..b5a3b37e --- /dev/null +++ b/apps/storybook/stories/Molecules/Toast.stories.tsx @@ -0,0 +1,85 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Toast } from '@react-shop/design-system'; + +const meta = { + title: 'Molecules/Toast', + component: Toast, + tags: ['autodocs'], + argTypes: { + variant: { + control: 'select', + options: ['success', 'error', 'warning', 'info'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Success: Story = { + args: { + variant: 'success', + title: 'Success', + message: 'Your changes have been saved successfully.', + onClose: () => console.log('Toast closed'), + }, +}; + +export const Error: Story = { + args: { + variant: 'error', + title: 'Error', + message: 'Something went wrong. Please try again.', + onClose: () => console.log('Toast closed'), + }, +}; + +export const Warning: Story = { + args: { + variant: 'warning', + title: 'Warning', + message: 'Please review your information before continuing.', + onClose: () => console.log('Toast closed'), + }, +}; + +export const Info: Story = { + args: { + variant: 'info', + title: 'Info', + message: 'Your session will expire in 5 minutes.', + onClose: () => console.log('Toast closed'), + }, +}; + +export const AllVariants: Story = { + render: () => ( +
+ {}} + /> + {}} + /> + {}} + /> + {}} + /> +
+ ), +}; + diff --git a/apps/storybook/stories/Organisms/Modal.stories.tsx b/apps/storybook/stories/Organisms/Modal.stories.tsx new file mode 100644 index 00000000..e7cd5787 --- /dev/null +++ b/apps/storybook/stories/Organisms/Modal.stories.tsx @@ -0,0 +1,110 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Modal } from '@react-shop/design-system'; +import { useState } from 'react'; + +const meta = { + title: 'Organisms/Modal', + component: Modal, + tags: ['autodocs'], + argTypes: { + size: { + control: 'select', + options: ['sm', 'md', 'lg', 'xl'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => { + const [isOpen, setIsOpen] = useState(false); + return ( + <> + + setIsOpen(false)} + title="Modal Title" + > +

This is the modal content. You can add any content here.

+
+ + ); + }, +}; + +export const WithActions: Story = { + render: () => { + const [isOpen, setIsOpen] = useState(false); + return ( + <> + + setIsOpen(false)} + title="Confirm Action" + footer={ +
+ + +
+ } + > +

Are you sure you want to proceed with this action?

+
+ + ); + }, +}; + +export const Sizes: Story = { + render: () => { + const [size, setSize] = useState<'sm' | 'md' | 'lg' | 'xl' | null>(null); + return ( + <> +
+ + + + +
+ {size && ( + setSize(null)} + title={`${size.toUpperCase()} Modal`} + size={size} + > +

This is a {size} sized modal.

+
+ )} + + ); + }, +}; + diff --git a/apps/storybook/stories/Organisms/ProductCard.stories.tsx b/apps/storybook/stories/Organisms/ProductCard.stories.tsx new file mode 100644 index 00000000..85599426 --- /dev/null +++ b/apps/storybook/stories/Organisms/ProductCard.stories.tsx @@ -0,0 +1,95 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ProductCard } from '@react-shop/design-system'; + +const meta = { + title: 'Organisms/ProductCard', + component: ProductCard, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=500&h=500&fit=crop', + title: 'Premium Wireless Headphones', + price: 299.99, + currency: '$', + rating: 4.5, + onAddToCart: () => console.log('Add to cart clicked'), + }, +}; + +export const WithDiscount: Story = { + args: { + image: 'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=500&h=500&fit=crop', + title: 'Classic Watch', + price: 149.99, + comparePrice: 199.99, + discount: 25, + currency: '$', + rating: 4.8, + badge: 'Sale', + onAddToCart: () => console.log('Add to cart clicked'), + }, +}; + +export const OutOfStock: Story = { + args: { + image: 'https://images.unsplash.com/photo-1572635196237-14b3f281503f?w=500&h=500&fit=crop', + title: 'Designer Sunglasses', + price: 199.99, + currency: '$', + rating: 4.2, + badge: 'Out of Stock', + onAddToCart: () => console.log('Add to cart clicked'), + }, +}; + +export const NewProduct: Story = { + args: { + image: 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=500&h=500&fit=crop', + title: 'Running Shoes', + price: 129.99, + currency: '$', + rating: 5, + badge: 'New', + onAddToCart: () => console.log('Add to cart clicked'), + }, +}; + +export const Grid: Story = { + render: () => ( +
+ {}} + /> + {}} + /> + {}} + /> +
+ ), +}; + diff --git a/apps/storybook/stories/Theme.stories.tsx b/apps/storybook/stories/Theme.stories.tsx new file mode 100644 index 00000000..b79fed86 --- /dev/null +++ b/apps/storybook/stories/Theme.stories.tsx @@ -0,0 +1,272 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Design System/Theme', + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const ColorSwatch = ({ name, value }: { name: string; value: string }) => ( +
+
+
+

{name}

+

{value}

+
+
+); + +export const Colors: Story = { + render: () => ( +
+
+

Brand Colors

+
+ + + + + + + + + + + +
+
+ +
+

Primary Colors

+
+ + + + + + + + + + +
+
+ +
+

Success Colors

+
+ + + + + + + + + + +
+
+ +
+

Error Colors

+
+ + + + + + + + + + +
+
+ +
+

Warning Colors

+
+ + + + + + + + + + +
+
+
+ ), +}; + +export const Typography: Story = { + render: () => ( +
+
+

Font Sizes

+
+
+ 2xs + The quick brown fox jumps over the lazy dog (0.625rem) +
+
+ xs + The quick brown fox jumps over the lazy dog (0.75rem) +
+
+ sm + The quick brown fox jumps over the lazy dog (0.875rem) +
+
+ base + The quick brown fox jumps over the lazy dog (1rem) +
+
+ lg + The quick brown fox jumps over the lazy dog (1.125rem) +
+
+ xl + The quick brown fox jumps over the lazy dog (1.25rem) +
+
+ 2xl + The quick brown fox jumps over the lazy dog (1.5rem) +
+
+ 3xl + The quick brown fox jumps (1.875rem) +
+
+ 4xl + The quick brown (2.25rem) +
+
+
+ +
+

Font Weights

+
+

Font Light (300) - The quick brown fox jumps over the lazy dog

+

Font Normal (400) - The quick brown fox jumps over the lazy dog

+

Font Medium (500) - The quick brown fox jumps over the lazy dog

+

Font Semibold (600) - The quick brown fox jumps over the lazy dog

+

Font Bold (700) - The quick brown fox jumps over the lazy dog

+
+
+
+ ), +}; + +export const Spacing: Story = { + render: () => ( +
+
+

Spacing Scale

+
+ {[0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64].map((space) => ( +
+ {space} +
+ {space * 4}px / {space * 0.25}rem +
+ ))} +
+
+
+ ), +}; + +export const BorderRadius: Story = { + render: () => ( +
+

Border Radius

+
+
+
+ none (0px) +
+
+
+ sm (0.125rem) +
+
+
+ base (0.25rem) +
+
+
+ md (0.375rem) +
+
+
+ lg (0.5rem) +
+
+
+ xl (0.75rem) +
+
+
+ 2xl (1rem) +
+
+
+ full (9999px) +
+
+
+ ), +}; + +export const Shadows: Story = { + render: () => ( +
+

Shadows

+
+
+
+ sm +
+
+
+
+ base +
+
+
+
+ md +
+
+
+
+ lg +
+
+
+
+ xl +
+
+
+
+ 2xl +
+
+
+
+ ), +}; + diff --git a/apps/storybook/tailwind.config.ts b/apps/storybook/tailwind.config.ts new file mode 100644 index 00000000..36243e2f --- /dev/null +++ b/apps/storybook/tailwind.config.ts @@ -0,0 +1,99 @@ +import type { Config } from 'tailwindcss'; + +const config: Config = { + content: [ + './stories/**/*.{js,ts,jsx,tsx,mdx}', + '../../packages/design-system/src/**/*.{js,ts,jsx,tsx}', + ], + theme: { + extend: { + colors: { + // Brand colors + brand: { + 50: '#f0f9ff', + 100: '#e0f2fe', + 200: '#bae6fd', + 300: '#7dd3fc', + 400: '#38bdf8', + 500: '#0ea5e9', + 600: '#0284c7', + 700: '#0369a1', + 800: '#075985', + 900: '#0c4a6e', + 950: '#082f49', + }, + // Semantic colors + primary: { + DEFAULT: '#0284c7', + 50: '#f0f9ff', + 100: '#e0f2fe', + 200: '#bae6fd', + 300: '#7dd3fc', + 400: '#38bdf8', + 500: '#0ea5e9', + 600: '#0284c7', + 700: '#0369a1', + 800: '#075985', + 900: '#0c4a6e', + }, + success: { + DEFAULT: '#22c55e', + 50: '#f0fdf4', + 100: '#dcfce7', + 200: '#bbf7d0', + 300: '#86efac', + 400: '#4ade80', + 500: '#22c55e', + 600: '#16a34a', + 700: '#15803d', + 800: '#166534', + 900: '#14532d', + }, + error: { + DEFAULT: '#ef4444', + 50: '#fef2f2', + 100: '#fee2e2', + 200: '#fecaca', + 300: '#fca5a5', + 400: '#f87171', + 500: '#ef4444', + 600: '#dc2626', + 700: '#b91c1c', + 800: '#991b1b', + 900: '#7f1d1d', + }, + warning: { + DEFAULT: '#f59e0b', + 50: '#fffbeb', + 100: '#fef3c7', + 200: '#fde68a', + 300: '#fcd34d', + 400: '#fbbf24', + 500: '#f59e0b', + 600: '#d97706', + 700: '#b45309', + 800: '#92400e', + 900: '#78350f', + }, + }, + fontFamily: { + sans: ['var(--font-poppins)', 'system-ui', 'sans-serif'], + heading: ['var(--font-poppins)', 'system-ui', 'sans-serif'], + }, + fontSize: { + '2xs': '0.625rem', + }, + spacing: { + '128': '32rem', + '144': '36rem', + }, + boxShadow: { + '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + }, + }, + }, + plugins: [], +}; + +export default config; + diff --git a/apps/storybook/tsconfig.json b/apps/storybook/tsconfig.json new file mode 100644 index 00000000..bff5c615 --- /dev/null +++ b/apps/storybook/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../packages/tsconfig/react-library.json", + "compilerOptions": { + "jsx": "react-jsx", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "target": "ES2020", + "baseUrl": ".", + "paths": { + "@/*": ["./stories/*"] + } + }, + "include": ["stories", ".storybook"], + "exclude": ["node_modules", "dist", "storybook-static"] +} + diff --git a/apps/storybook/vite.config.ts b/apps/storybook/vite.config.ts new file mode 100644 index 00000000..296f6fc0 --- /dev/null +++ b/apps/storybook/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite'; +import path from 'path'; + +export default defineConfig({ + resolve: { + alias: { + '@lib/utils': path.resolve(__dirname, '../../packages/design-system/src/lib/utils.ts'), + '@components': path.resolve(__dirname, '../../packages/design-system/src/components'), + }, + }, +}); + diff --git a/apps/web/FEATURES.md b/apps/web/FEATURES.md index 4bc89694..41bf6ab5 100644 --- a/apps/web/FEATURES.md +++ b/apps/web/FEATURES.md @@ -90,9 +90,9 @@ ### Authentication -- [ ] Login page -- [ ] Register page -- [ ] Forgot password +- [x] Login page +- [x] Register page +- [x] Forgot password - [ ] Reset password - [ ] Email verification - [ ] Social login (Google, GitHub) @@ -141,11 +141,11 @@ ### Navigation -- [ ] Header with logo, search, cart, user menu -- [ ] Mobile responsive menu (hamburger) +- [x] Header with logo, search, cart, user menu +- [x] Mobile responsive menu (hamburger) - [ ] Mega menu for categories - [ ] Breadcrumbs -- [ ] Footer with links, social media, newsletter +- [x] Footer with links, social media, newsletter ### Layout Components @@ -222,9 +222,9 @@ ### Phase 1 - Foundation (Week 1-2) 1. ✅ Setup SDK, Design System, Fonts -2. [ ] Authentication pages (login, register, forgot password) -3. [ ] Layout (header, footer, navigation) -4. [ ] Home page structure +2. ✅ Authentication pages (login, register, forgot password) +3. ✅ Layout (header, footer, navigation) +4. ✅ Home page structure ### Phase 2 - Core Shopping (Week 3-4) diff --git a/apps/web/app/(auth)/forgot-password/page.tsx b/apps/web/app/(auth)/forgot-password/page.tsx new file mode 100644 index 00000000..4da56200 --- /dev/null +++ b/apps/web/app/(auth)/forgot-password/page.tsx @@ -0,0 +1,7 @@ +import { ForgotPasswordScreen } from "@/app/modules/auth/screens"; + +export default function ForgotPasswordPage() { + return ; +} + + diff --git a/apps/web/app/(auth)/layout.tsx b/apps/web/app/(auth)/layout.tsx new file mode 100644 index 00000000..641600ca --- /dev/null +++ b/apps/web/app/(auth)/layout.tsx @@ -0,0 +1,9 @@ +import { AuthLayout } from "@/app/modules/layout/screens"; + +export default function AuthLayoutWrapper({ + children, +}: { + children: React.ReactNode; +}) { + return {children}; +} diff --git a/apps/web/app/(auth)/login/page.tsx b/apps/web/app/(auth)/login/page.tsx new file mode 100644 index 00000000..c6fc1041 --- /dev/null +++ b/apps/web/app/(auth)/login/page.tsx @@ -0,0 +1,5 @@ +import { LoginScreen } from "@/app/modules/auth/screens"; + +export default function LoginPage() { + return ; +} diff --git a/apps/web/app/(auth)/register/page.tsx b/apps/web/app/(auth)/register/page.tsx new file mode 100644 index 00000000..8d2958f0 --- /dev/null +++ b/apps/web/app/(auth)/register/page.tsx @@ -0,0 +1,5 @@ +import { RegisterScreen } from "@/app/modules/auth/screens"; + +export default function RegisterPage() { + return ; +} diff --git a/apps/web/app/(main)/layout.tsx b/apps/web/app/(main)/layout.tsx new file mode 100644 index 00000000..b136cb8e --- /dev/null +++ b/apps/web/app/(main)/layout.tsx @@ -0,0 +1,9 @@ +import { MainLayout } from "@/app/modules/layout/screens"; + +export default function MainLayoutWrapper({ + children, +}: { + children: React.ReactNode; +}) { + return {children}; +} diff --git a/apps/web/app/page.tsx b/apps/web/app/(main)/page.tsx similarity index 66% rename from apps/web/app/page.tsx rename to apps/web/app/(main)/page.tsx index cd6eb2ed..d5ee8da6 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/(main)/page.tsx @@ -10,6 +10,8 @@ import { Badge, Skeleton, ProductCard, + Flex, + Stack, } from "@react-shop/design-system"; import { useProducts } from "@react-shop/sdk"; @@ -19,12 +21,12 @@ export default function HomePage() { return ( {/* Hero Section */} -
+
New Arrivals - + Welcome to React Ecommerce - + Discover the best products at unbeatable prices
{/* Products Section */} -
- +
+ Featured Products @@ -48,30 +50,29 @@ export default function HomePage() { )} {isLoading && ( - + {[...Array(8)].map((_, i) => ( - - + + ))} )} {products && products.length > 0 && ( - + {products.map((product) => ( ))} @@ -85,60 +86,47 @@ export default function HomePage() {
{/* Test Design System Components */} -
- +
+ Design System Test - + Buttons -
+ -
+
Badges -
+ Solid Subtle Outline -
+
Typography -
+ Small text Medium text Large text -
+
); } + diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index bda70aa6..099ced74 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -11,8 +11,9 @@ const poppins = Poppins({ }); export const metadata: Metadata = { - title: "React Ecommerce - Shop the Best Products", - description: "Modern ecommerce store with the best products and deals", + title: "React Shop - Best Online Shopping Experience", + description: + "Shop the latest products at unbeatable prices. Free shipping, easy returns, and 24/7 customer support.", }; const apiConfig = { diff --git a/apps/web/app/modules/auth/README.md b/apps/web/app/modules/auth/README.md new file mode 100644 index 00000000..7f09c99f --- /dev/null +++ b/apps/web/app/modules/auth/README.md @@ -0,0 +1,130 @@ +# Auth Module + +This module contains all authentication-related logic for the web application. + +## Structure + +``` +/app/modules/auth/ +├── components/ # Reusable form components +│ ├── FormField.tsx # Generic form field with validation +│ ├── LoginForm.tsx # Login form component +│ ├── RegisterForm.tsx # Registration form component +│ ├── ForgotPasswordForm.tsx # Forgot password form +│ └── index.ts # Barrel export +├── screens/ # Container components with business logic +│ ├── LoginScreen.tsx # Login screen container +│ ├── RegisterScreen.tsx # Register screen container +│ ├── ForgotPasswordScreen.tsx # Forgot password screen +│ └── index.ts # Barrel export +└── README.md # This file +``` + +## Architecture Pattern + +### Pages (Minimal) +- Located in `/app/(auth)/*/page.tsx` +- Only imports and renders the screen component +- No business logic + +### Screens (Containers) +- Located in `/app/modules/auth/screens/` +- Contains business logic (API calls, routing, state management) +- Handles success/error states +- Uses SDK hooks (useLogin, useRegister, etc.) +- Renders forms and layout components + +### Components (Presentational) +- Located in `/app/modules/auth/components/` +- Pure presentational components +- Receives data and callbacks via props +- No direct API calls or routing +- Reusable across different screens + +## Usage Example + +### Page (Minimal) +```tsx +// app/(auth)/login/page.tsx +import { LoginScreen } from "@/app/modules/auth/screens"; + +export default function LoginPage() { + return ; +} +``` + +### Screen (Container) +```tsx +// app/modules/auth/screens/LoginScreen.tsx +"use client"; + +import { useRouter } from "next/navigation"; +import { useLogin } from "@react-shop/sdk"; +import { LoginForm } from "../components"; + +export function LoginScreen() { + const router = useRouter(); + const { mutate: login, isPending, error } = useLogin(); + + const handleSubmit = (data) => { + login(data, { + onSuccess: () => router.push("/"), + }); + }; + + return ; +} +``` + +### Component (Presentational) +```tsx +// app/modules/auth/components/LoginForm.tsx +"use client"; + +import { useForm, FormProvider } from "react-hook-form"; + +interface LoginFormProps { + onSubmit: (data: LoginFormData) => void; + isLoading?: boolean; +} + +export function LoginForm({ onSubmit, isLoading }: LoginFormProps) { + const methods = useForm(); + + return ( + +
+ {/* Form fields */} +
+
+ ); +} +``` + +## Benefits + +1. **Separation of Concerns**: Pages, screens, and components have clear responsibilities +2. **Reusability**: Components can be reused across different screens +3. **Testability**: Each layer can be tested independently +4. **Maintainability**: Changes are isolated to specific layers +5. **Scalability**: Easy to add new features following the same pattern + +## Adding New Auth Features + +1. Create form component in `/components/` if needed +2. Create screen container in `/screens/` +3. Create page in `/app/(auth)/*/page.tsx` +4. Export components from index files + +Example: +```bash +# 1. Create form component +/app/modules/auth/components/ResetPasswordForm.tsx + +# 2. Create screen container +/app/modules/auth/screens/ResetPasswordScreen.tsx + +# 3. Create page +/app/(auth)/reset-password/page.tsx +``` + diff --git a/apps/web/app/modules/auth/components/ForgotPasswordForm.tsx b/apps/web/app/modules/auth/components/ForgotPasswordForm.tsx new file mode 100644 index 00000000..1b2e4189 --- /dev/null +++ b/apps/web/app/modules/auth/components/ForgotPasswordForm.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { useForm, FormProvider } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { Button, Stack } from "@react-shop/design-system"; +import { FormField } from "./FormField"; +import Link from "next/link"; + +const forgotPasswordSchema = z.object({ + email: z.string().email("Please enter a valid email address"), +}); + +export type ForgotPasswordFormData = z.infer; + +interface ForgotPasswordFormProps { + onSubmit: (data: ForgotPasswordFormData) => void; + isLoading?: boolean; +} + +export function ForgotPasswordForm({ + onSubmit, + isLoading = false, +}: ForgotPasswordFormProps) { + const methods = useForm({ + resolver: zodResolver(forgotPasswordSchema), + defaultValues: { + email: "", + }, + }); + + return ( + +
+ + + + + + + + + +
+
+ ); +} + diff --git a/apps/web/app/modules/auth/components/FormField.tsx b/apps/web/app/modules/auth/components/FormField.tsx new file mode 100644 index 00000000..513d92a4 --- /dev/null +++ b/apps/web/app/modules/auth/components/FormField.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { Input, Text, Stack } from "@react-shop/design-system"; +import { useFormContext } from "react-hook-form"; + +interface FormFieldProps { + name: string; + label: string; + type?: string; + placeholder?: string; + required?: boolean; +} + +export function FormField({ + name, + label, + type = "text", + placeholder, + required = false, +}: FormFieldProps) { + const { + register, + formState: { errors }, + } = useFormContext(); + + const error = errors[name]?.message as string | undefined; + + return ( + + + + {error && ( + + {error} + + )} + + ); +} + diff --git a/apps/web/app/modules/auth/components/LoginForm.tsx b/apps/web/app/modules/auth/components/LoginForm.tsx new file mode 100644 index 00000000..b9f57839 --- /dev/null +++ b/apps/web/app/modules/auth/components/LoginForm.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { useForm, FormProvider } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { Button, Stack, Flex, Text } from "@react-shop/design-system"; +import { FormField } from "./FormField"; +import Link from "next/link"; + +const loginSchema = z.object({ + email: z.string().email("Please enter a valid email address"), + password: z.string().min(6, "Password must be at least 6 characters"), +}); + +export type LoginFormData = z.infer; + +interface LoginFormProps { + onSubmit: (data: LoginFormData) => void; + isLoading?: boolean; +} + +export function LoginForm({ onSubmit, isLoading = false }: LoginFormProps) { + const methods = useForm({ + resolver: zodResolver(loginSchema), + defaultValues: { + email: "", + password: "", + }, + }); + + return ( + +
+ + + + + + + + + Forgot password? + + + + + + + + + Don't have an account? + + + + Sign up + + + + +
+
+ ); +} + diff --git a/apps/web/app/modules/auth/components/RegisterForm.tsx b/apps/web/app/modules/auth/components/RegisterForm.tsx new file mode 100644 index 00000000..3fed828d --- /dev/null +++ b/apps/web/app/modules/auth/components/RegisterForm.tsx @@ -0,0 +1,126 @@ +"use client"; + +import { useForm, FormProvider } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { Button, Stack, Flex, Text } from "@react-shop/design-system"; +import { FormField } from "./FormField"; +import Link from "next/link"; + +const registerSchema = z + .object({ + firstName: z.string().min(2, "First name must be at least 2 characters"), + lastName: z.string().min(2, "Last name must be at least 2 characters"), + email: z.string().email("Please enter a valid email address"), + password: z.string().min(8, "Password must be at least 8 characters"), + confirmPassword: z.string(), + }) + .refine((data) => data.password === data.confirmPassword, { + message: "Passwords don't match", + path: ["confirmPassword"], + }); + +export type RegisterFormData = z.infer; + +interface RegisterFormProps { + onSubmit: (data: Omit) => void; + isLoading?: boolean; +} + +export function RegisterForm({ + onSubmit, + isLoading = false, +}: RegisterFormProps) { + const methods = useForm({ + resolver: zodResolver(registerSchema), + defaultValues: { + firstName: "", + lastName: "", + email: "", + password: "", + confirmPassword: "", + }, + }); + + const handleSubmit = (data: RegisterFormData) => { + const { confirmPassword, ...registerData } = data; + onSubmit(registerData); + }; + + return ( + +
+ + + + + + + + + + + + + + + + + + + + + Already have an account? + + + + Sign in + + + + +
+
+ ); +} + diff --git a/apps/web/app/modules/auth/components/index.ts b/apps/web/app/modules/auth/components/index.ts new file mode 100644 index 00000000..dabaaf10 --- /dev/null +++ b/apps/web/app/modules/auth/components/index.ts @@ -0,0 +1,8 @@ +export { FormField } from "./FormField"; +export { LoginForm } from "./LoginForm"; +export type { LoginFormData } from "./LoginForm"; +export { RegisterForm } from "./RegisterForm"; +export type { RegisterFormData } from "./RegisterForm"; +export { ForgotPasswordForm } from "./ForgotPasswordForm"; +export type { ForgotPasswordFormData } from "./ForgotPasswordForm"; + diff --git a/apps/web/app/modules/auth/screens/ForgotPasswordScreen.tsx b/apps/web/app/modules/auth/screens/ForgotPasswordScreen.tsx new file mode 100644 index 00000000..5e4ee9bd --- /dev/null +++ b/apps/web/app/modules/auth/screens/ForgotPasswordScreen.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { useState } from "react"; +import { Container, Card, Heading, Text, Stack, Button } from "@react-shop/design-system"; +import { ForgotPasswordForm, ForgotPasswordFormData } from "../components"; +import Link from "next/link"; + +export function ForgotPasswordScreen() { + const [isSuccess, setIsSuccess] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const handleSubmit = async (data: ForgotPasswordFormData) => { + setIsLoading(true); + + // TODO: Implement forgot password API call when backend is ready + console.log("Forgot password:", data); + + // Simulate API call + await new Promise((resolve) => setTimeout(resolve, 1000)); + + setIsLoading(false); + setIsSuccess(true); + }; + + if (isSuccess) { + return ( + + + + + + Check Your Email + + + We've sent password reset instructions to your email address. + Please check your inbox and follow the link to reset your + password. + + + + + + + + + + ); + } + + return ( + + + + + + Forgot Password? + + + Enter your email address and we'll send you instructions to reset + your password. + + + + + + + + ); +} + diff --git a/apps/web/app/modules/auth/screens/LoginScreen.tsx b/apps/web/app/modules/auth/screens/LoginScreen.tsx new file mode 100644 index 00000000..3a76613e --- /dev/null +++ b/apps/web/app/modules/auth/screens/LoginScreen.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { Container, Card, Heading, Text, Stack } from "@react-shop/design-system"; +import { useLogin } from "@react-shop/sdk"; +import { LoginForm, LoginFormData } from "../components"; + +export function LoginScreen() { + const router = useRouter(); + const { mutate: login, isPending, error } = useLogin(); + + const handleSubmit = (data: LoginFormData) => { + login(data, { + onSuccess: () => { + router.push("/"); + }, + }); + }; + + return ( + + + + + + Welcome Back + + + Sign in to your account to continue + + + + {error && ( + + + {error instanceof Error + ? error.message + : "Login failed. Please try again."} + + + )} + + + + + + ); +} + diff --git a/apps/web/app/modules/auth/screens/RegisterScreen.tsx b/apps/web/app/modules/auth/screens/RegisterScreen.tsx new file mode 100644 index 00000000..f2662a75 --- /dev/null +++ b/apps/web/app/modules/auth/screens/RegisterScreen.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { Container, Card, Heading, Text, Stack } from "@react-shop/design-system"; +import { useRegister } from "@react-shop/sdk"; +import { RegisterForm, RegisterFormData } from "../components"; + +export function RegisterScreen() { + const router = useRouter(); + const { mutate: register, isPending, error } = useRegister(); + + const handleSubmit = (data: Omit) => { + register(data, { + onSuccess: () => { + router.push("/"); + }, + }); + }; + + return ( + + + + + + Create Account + + + Sign up to start shopping + + + + {error && ( + + + {error instanceof Error + ? error.message + : "Registration failed. Please try again."} + + + )} + + + + + + ); +} + diff --git a/apps/web/app/modules/auth/screens/index.ts b/apps/web/app/modules/auth/screens/index.ts new file mode 100644 index 00000000..e9fcf1ab --- /dev/null +++ b/apps/web/app/modules/auth/screens/index.ts @@ -0,0 +1,4 @@ +export { LoginScreen } from "./LoginScreen"; +export { RegisterScreen } from "./RegisterScreen"; +export { ForgotPasswordScreen } from "./ForgotPasswordScreen"; + diff --git a/apps/web/app/modules/layout/README.md b/apps/web/app/modules/layout/README.md new file mode 100644 index 00000000..729ef83c --- /dev/null +++ b/apps/web/app/modules/layout/README.md @@ -0,0 +1,167 @@ +# Layout Module + +This module contains all layout-related components for the web application including header, footer, and navigation. + +## Structure + +``` +/app/modules/layout/ +├── components/ # Presentational components +│ ├── Header.tsx # Main header with navigation +│ ├── Footer.tsx # Footer with links and newsletter +│ ├── Logo.tsx # Brand logo component +│ ├── SearchBar.tsx # Product search input +│ ├── Navigation.tsx # Desktop navigation links +│ ├── CartIcon.tsx # Shopping cart icon with badge +│ ├── UserMenu.tsx # User dropdown menu +│ ├── MobileMenu.tsx # Mobile hamburger menu +│ └── index.ts # Barrel export +├── screens/ # Container components +│ ├── MainLayout.tsx # Main layout wrapper with header/footer +│ └── index.ts # Barrel export +└── README.md # This file +``` + +## Architecture Pattern + +### Route Groups +- `(main)/` - Pages with header and footer +- `(auth)/` - Auth pages with minimal layout + +### Components + +#### Header +- Sticky header with logo, search, navigation, cart, and user menu +- Responsive design with mobile menu +- Integrates with authentication state + +#### Footer +- Company info and social links +- Quick links (Shop, Support) +- Newsletter signup form +- Copyright and legal links + +#### Navigation +- Desktop: Horizontal nav bar +- Mobile: Hamburger menu with slide-out drawer + +#### UserMenu +- Guest: Sign In / Sign Up buttons +- Authenticated: User avatar with dropdown + - Dashboard + - Orders + - Profile + - Wishlist + - Sign Out + +#### CartIcon +- Shopping cart icon with item count badge +- Links to cart page + +## Usage + +### Main Layout (with Header & Footer) +```tsx +// app/(main)/layout.tsx +import { MainLayout } from "@/app/modules/layout/screens"; + +export default function MainLayoutWrapper({ children }) { + return {children}; +} +``` + +### Auth Layout (minimal) +```tsx +// app/(auth)/layout.tsx +export default function AuthLayout({ children }) { + return
{children}
; +} +``` + +## Features + +### Header Features +- ✅ Sticky positioning +- ✅ Logo with home link +- ✅ Search bar (desktop and mobile) +- ✅ Desktop navigation menu +- ✅ Shopping cart icon with badge +- ✅ User authentication state +- ✅ User dropdown menu +- ✅ Mobile hamburger menu +- ✅ Responsive design + +### Footer Features +- ✅ Company information +- ✅ Social media links (Facebook, Twitter, Instagram) +- ✅ Quick links (Shop, Support) +- ✅ Newsletter signup form +- ✅ Copyright notice +- ✅ Legal links (Privacy, Terms) +- ✅ Responsive grid layout + +### Mobile Menu Features +- ✅ Slide-out drawer from right +- ✅ Overlay backdrop +- ✅ User profile section +- ✅ Navigation links +- ✅ Authentication buttons +- ✅ Sign out button +- ✅ Smooth animations + +## State Management + +The MainLayout screen handles: +- User authentication state (via `useMe` hook) +- Logout functionality (via `useLogout` hook) +- Cart item count (TODO: integrate with cart context) + +## Responsive Breakpoints + +- Mobile: `< 768px` - Mobile menu, stacked search +- Tablet: `768px - 1024px` - Partial desktop layout +- Desktop: `> 1024px` - Full desktop layout with all features + +## Customization + +### Adding New Navigation Links +Edit `components/Navigation.tsx`: +```tsx +const navLinks = [ + { href: "/", label: "Home" }, + { href: "/products", label: "Products" }, + // Add your link here +]; +``` + +### Changing Logo +Edit `components/Logo.tsx` to use your custom logo image or design. + +### Footer Links +Edit `components/Footer.tsx` to modify footer sections and links. + +## Integration Points + +### Authentication +- Uses `useMe()` from SDK to get current user +- Uses `useLogout()` from SDK for sign out +- Redirects to `/login` and `/register` pages + +### Cart +- Currently shows static cart count (0) +- TODO: Integrate with cart context/hook when implemented + +### Search +- Form submits to `/search?q={query}` +- TODO: Implement search results page + +## Future Enhancements + +- [ ] Cart context integration for real-time cart count +- [ ] Wishlist count badge +- [ ] Notifications dropdown +- [ ] Mega menu for categories +- [ ] Breadcrumbs component +- [ ] Back to top button +- [ ] Sticky cart button on mobile + diff --git a/apps/web/app/modules/layout/components/FooterWrapper.tsx b/apps/web/app/modules/layout/components/FooterWrapper.tsx new file mode 100644 index 00000000..0c950601 --- /dev/null +++ b/apps/web/app/modules/layout/components/FooterWrapper.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { Footer as DSFooter } from "@react-shop/design-system"; + +// Wrapper component to match the expected Link interface +const LinkWrapper = ({ + href, + className, + target, + rel, + children, +}: { + href: string; + className?: string; + target?: string; + rel?: string; + children: React.ReactNode; +}) => ( + + {children} + +); + +export function FooterWrapper() { + const [newsletterEmail, setNewsletterEmail] = useState(""); + + const handleNewsletterSubmit = (e: React.FormEvent) => { + e.preventDefault(); + // TODO: Implement newsletter signup + console.log("Newsletter signup:", newsletterEmail); + setNewsletterEmail(""); + }; + + return ( + + ); +} + diff --git a/apps/web/app/modules/layout/components/HeaderWrapper.tsx b/apps/web/app/modules/layout/components/HeaderWrapper.tsx new file mode 100644 index 00000000..248ccf8b --- /dev/null +++ b/apps/web/app/modules/layout/components/HeaderWrapper.tsx @@ -0,0 +1,151 @@ +"use client"; + +import { useState, useRef, useEffect } from "react"; +import Link from "next/link"; +import { usePathname, useRouter } from "next/navigation"; +import { + Header as DSHeader, + Logo as DSLogo, + SearchBar as DSSearchBar, + Navigation as DSNavigation, + CartIcon as DSCartIcon, + UserMenu as DSUserMenu, + MobileMenu as DSMobileMenu, + type User, + type NavLink, +} from "@react-shop/design-system"; + +// Wrapper component to match the expected Link interface +const LinkWrapper = ({ + href, + className, + onClick, + children, +}: { + href: string; + className?: string; + onClick?: () => void; + children: React.ReactNode; +}) => ( + + {children} + +); + +interface HeaderWrapperProps { + user?: User | null; + cartItemCount?: number; + onLogout?: () => void; +} + +const NAV_LINKS: NavLink[] = [ + { href: "/", label: "Home" }, + { href: "/products", label: "Products" }, + { href: "/categories", label: "Categories" }, + { href: "/deals", label: "Deals" }, +]; + +export function HeaderWrapper({ + user, + cartItemCount, + onLogout, +}: HeaderWrapperProps) { + const pathname = usePathname(); + const router = useRouter(); + + // Search state + const [searchQuery, setSearchQuery] = useState(""); + + // User menu state + const [isUserMenuOpen, setIsUserMenuOpen] = useState(false); + const userMenuRef = useRef(null); + + // Mobile menu state + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + + // Handle search submit + const handleSearchSubmit = (query: string) => { + router.push(`/search?q=${encodeURIComponent(query)}`); + }; + + // Handle user menu click outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + userMenuRef.current && + !userMenuRef.current.contains(event.target as Node) + ) { + setIsUserMenuOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + // Handle mobile menu body scroll + useEffect(() => { + if (isMobileMenuOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "unset"; + } + + return () => { + document.body.style.overflow = "unset"; + }; + }, [isMobileMenuOpen]); + + // Handle logout with router push + const handleLogout = () => { + onLogout?.(); + router.push("/"); + }; + + return ( + } + SearchBarComponent={ + + } + NavigationComponent={ + + } + CartIconComponent={ + + } + UserMenuComponent={ + setIsUserMenuOpen(!isUserMenuOpen)} + onClose={() => setIsUserMenuOpen(false)} + LinkComponent={LinkWrapper} + menuRef={userMenuRef} + /> + } + MobileMenuComponent={ + setIsMobileMenuOpen(!isMobileMenuOpen)} + onClose={() => setIsMobileMenuOpen(false)} + navLinks={NAV_LINKS} + currentPath={pathname} + LinkComponent={LinkWrapper} + /> + } + /> + ); +} + diff --git a/apps/web/app/modules/layout/components/index.ts b/apps/web/app/modules/layout/components/index.ts new file mode 100644 index 00000000..65889264 --- /dev/null +++ b/apps/web/app/modules/layout/components/index.ts @@ -0,0 +1,3 @@ +export { HeaderWrapper } from "./HeaderWrapper"; +export { FooterWrapper } from "./FooterWrapper"; + diff --git a/apps/web/app/modules/layout/screens/AuthLayout.tsx b/apps/web/app/modules/layout/screens/AuthLayout.tsx new file mode 100644 index 00000000..4ed39cb4 --- /dev/null +++ b/apps/web/app/modules/layout/screens/AuthLayout.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { HeaderWrapper } from "@/app/modules/layout/components"; +import { useMe, useLogout } from "@react-shop/sdk"; + +interface AuthLayoutProps { + children: React.ReactNode; +} + +export function AuthLayout({ children }: AuthLayoutProps) { + const { data: user } = useMe(); + const { mutate: logout } = useLogout(); + + // TODO: Get cart item count from cart context/hook when implemented + const cartItemCount = 0; + + const handleLogout = () => { + logout(); + }; + + return ( +
+ +
{children}
+
+ ); +} + diff --git a/apps/web/app/modules/layout/screens/MainLayout.tsx b/apps/web/app/modules/layout/screens/MainLayout.tsx new file mode 100644 index 00000000..464d7d24 --- /dev/null +++ b/apps/web/app/modules/layout/screens/MainLayout.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { HeaderWrapper, FooterWrapper } from "@/app/modules/layout/components"; +import { useMe, useLogout } from "@react-shop/sdk"; + +interface MainLayoutProps { + children: React.ReactNode; +} + +export function MainLayout({ children }: MainLayoutProps) { + const { data: user } = useMe(); + const { mutate: logout } = useLogout(); + + // TODO: Get cart item count from cart context/hook when implemented + const cartItemCount = 0; + + const handleLogout = () => { + logout(); + }; + + return ( +
+ +
{children}
+ +
+ ); +} diff --git a/apps/web/app/modules/layout/screens/index.ts b/apps/web/app/modules/layout/screens/index.ts new file mode 100644 index 00000000..80791835 --- /dev/null +++ b/apps/web/app/modules/layout/screens/index.ts @@ -0,0 +1,3 @@ +export { MainLayout } from "./MainLayout"; +export { AuthLayout } from "./AuthLayout"; + diff --git a/apps/web/next.config.js b/apps/web/next.config.js index ce448ab0..a22ffa5c 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -14,6 +14,7 @@ const nextConfig = { webpack: (config) => { config.resolve.alias = { ...config.resolve.alias, + '@components': path.resolve(__dirname, '../../packages/design-system/src/components'), '@lib': path.resolve(__dirname, '../../packages/design-system/src/lib'), '@entities': path.resolve(__dirname, '../../packages/sdk/src/entities'), '@providers': path.resolve(__dirname, '../../packages/sdk/src/providers'), diff --git a/packages/design-system/.storybook/main.ts b/packages/design-system/.storybook/main.ts deleted file mode 100644 index 11d601fd..00000000 --- a/packages/design-system/.storybook/main.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { StorybookConfig } from '@storybook/react-vite'; - -const config: StorybookConfig = { - stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], - addons: [ - '@storybook/addon-links', - '@storybook/addon-essentials', - '@storybook/addon-interactions', - ], - framework: { - name: '@storybook/react-vite', - options: {}, - }, - docs: { - autodocs: 'tag', - }, -}; - -export default config; - diff --git a/packages/design-system/.storybook/preview.tsx b/packages/design-system/.storybook/preview.tsx deleted file mode 100644 index eaba3ff5..00000000 --- a/packages/design-system/.storybook/preview.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { Preview } from '@storybook/react'; -import '../styled-system/styles.css'; - -const preview: Preview = { - parameters: { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - backgrounds: { - default: 'light', - values: [ - { name: 'light', value: '#ffffff' }, - { name: 'dark', value: '#1a1a1a' }, - ], - }, - }, -}; - -export default preview; - diff --git a/packages/design-system/package.json b/packages/design-system/package.json index a3904505..ddd712f8 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -5,17 +5,9 @@ "types": "./src/index.tsx", "license": "MIT", "scripts": { - "lint": "TIMING=1 eslint \"**/*.ts*\"", - "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" + "lint": "TIMING=1 eslint \"**/*.ts*\"" }, "devDependencies": { - "@storybook/addon-essentials": "^8.0.0", - "@storybook/addon-interactions": "^8.0.0", - "@storybook/addon-links": "^8.0.0", - "@storybook/blocks": "^8.0.0", - "@storybook/react": "^8.0.0", - "@storybook/react-vite": "^8.0.0", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "autoprefixer": "^10.4.16", @@ -23,11 +15,9 @@ "eslint-config-custom": "workspace:*", "postcss": "^8.4.32", "react": "^18.2.0", - "storybook": "^8.0.0", "tailwindcss": "^3.4.0", "tsconfig": "workspace:*", - "typescript": "^5.0.0", - "vite": "^5.0.0" + "typescript": "^5.0.0" }, "dependencies": { "clsx": "^2.1.0", diff --git a/packages/design-system/src/components/Atoms/Icon/Icon.tsx b/packages/design-system/src/components/Atoms/Icon/Icon.tsx index eb227f69..4c452857 100644 --- a/packages/design-system/src/components/Atoms/Icon/Icon.tsx +++ b/packages/design-system/src/components/Atoms/Icon/Icon.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { tv, type VariantProps } from 'tailwind-variants'; -import { Icon as LucideIcon, type IconNode } from 'lucide-react'; -import { cn } from '@lib/utils'; +import { type LucideIcon } from 'lucide-react'; +import { cn } from '../../../lib/utils'; const icon = tv({ base: 'inline-flex items-center justify-center shrink-0', @@ -30,16 +30,16 @@ const icon = tv({ export type IconVariants = VariantProps; -export interface IconProps extends IconVariants, Omit, 'ref'> { - icon: IconNode; +export interface IconProps extends Omit, Omit, 'ref' | 'color'> { + icon: React.ComponentType; + color?: IconVariants['color']; } export const Icon = React.forwardRef( - ({ icon: iconNode, size, color, className, ...props }, ref) => { + ({ icon: IconComponent, size, color, className, ...props }, ref) => { return ( - diff --git a/packages/design-system/src/components/Atoms/Text/Text.tsx b/packages/design-system/src/components/Atoms/Text/Text.tsx index 827f4d63..6958e092 100644 --- a/packages/design-system/src/components/Atoms/Text/Text.tsx +++ b/packages/design-system/src/components/Atoms/Text/Text.tsx @@ -43,17 +43,17 @@ const text = tv({ export type TextVariants = VariantProps; -export interface TextProps extends React.HTMLAttributes, TextVariants { +export interface TextProps extends Omit, 'color'>, TextVariants { as?: 'p' | 'span' | 'div' | 'label'; } -export const Text = React.forwardRef( +export const Text = React.forwardRef( ({ className, as: Component = 'p', size, weight, align, color, ...props }, ref) => { return ( ); } diff --git a/packages/design-system/src/components/Atoms/index.ts b/packages/design-system/src/components/Atoms/index.ts new file mode 100644 index 00000000..aa93d478 --- /dev/null +++ b/packages/design-system/src/components/Atoms/index.ts @@ -0,0 +1,15 @@ +export * from "./Flex"; +export * from "./Avatar"; +export * from "./Badge"; +export * from "./Box"; +export * from "./Button"; +export * from "./Card"; +export * from "./Container"; +export * from "./Divider"; +export * from "./Grid"; +export * from "./Heading"; +export * from "./Icon"; +export * from "./Input"; +export * from "./Skeleton"; +export * from "./Stack"; +export * from "./Text"; diff --git a/packages/design-system/src/components/Layout/CartIcon/CartIcon.tsx b/packages/design-system/src/components/Layout/CartIcon/CartIcon.tsx new file mode 100644 index 00000000..35aabddf --- /dev/null +++ b/packages/design-system/src/components/Layout/CartIcon/CartIcon.tsx @@ -0,0 +1,36 @@ +import { Badge } from "../../Atoms"; +import type { CartIconProps } from "./types"; + +export function CartIcon({ itemCount = 0, LinkComponent }: CartIconProps) { + return ( + +
+ + + + {itemCount > 0 && ( +
+ + {itemCount > 99 ? "99+" : itemCount} + +
+ )} +
+
+ ); +} + diff --git a/packages/design-system/src/components/Layout/CartIcon/index.ts b/packages/design-system/src/components/Layout/CartIcon/index.ts new file mode 100644 index 00000000..a35cc53a --- /dev/null +++ b/packages/design-system/src/components/Layout/CartIcon/index.ts @@ -0,0 +1 @@ +export { CartIcon } from "./CartIcon"; diff --git a/packages/design-system/src/components/Layout/CartIcon/types.ts b/packages/design-system/src/components/Layout/CartIcon/types.ts new file mode 100644 index 00000000..0e116120 --- /dev/null +++ b/packages/design-system/src/components/Layout/CartIcon/types.ts @@ -0,0 +1,9 @@ +export interface CartIconProps { + itemCount?: number; + LinkComponent: React.ComponentType<{ + href: string; + className?: string; + children: React.ReactNode; + }>; +} + diff --git a/packages/design-system/src/components/Layout/Footer/Footer.tsx b/packages/design-system/src/components/Layout/Footer/Footer.tsx new file mode 100644 index 00000000..d1e27ae4 --- /dev/null +++ b/packages/design-system/src/components/Layout/Footer/Footer.tsx @@ -0,0 +1,201 @@ +import * as React from "react"; +import { Container } from "@components/Atoms/Container/Container"; +import { Grid } from "@components/Atoms/Grid/Grid"; +import { Stack } from "@components/Atoms/Stack/Stack"; +import { Text } from "@components/Atoms/Text/Text"; +import { Heading } from "@components/Atoms/Heading/Heading"; +import { Input } from "@components/Atoms/Input/Input"; +import { Button } from "@components/Atoms/Button/Button"; +import { Flex } from "@components/Atoms/Flex/Flex"; +import type { FooterProps } from "./types"; + +export function Footer({ + newsletterEmail, + onNewsletterEmailChange, + onNewsletterSubmit, + LinkComponent, +}: FooterProps) { + return ( +
+ + {/* Main Footer Content */} +
+ + {/* Company Info */} + + + React Shop + + + Your one-stop shop for quality products at unbeatable prices. + + + + + + + + + + + + + + + + + + + + + {/* Shop Links */} + + + Shop + + + + All Products + + + Categories + + + Deals & Offers + + + New Arrivals + + + + + {/* Support Links */} + + + Support + + + + Contact Us + + + FAQ + + + Shipping Info + + + Returns + + + + + {/* Newsletter */} + + + Newsletter + + + Subscribe to get special offers and updates. + +
+ ) => + onNewsletterEmailChange( + (e.target as HTMLInputElement).value + ) + } + required + className="bg-gray-800 border-gray-700 text-white placeholder:text-gray-500" + /> + +
+
+
+
+ + {/* Bottom Footer */} +
+ + + © {new Date().getFullYear()} React Shop. All rights reserved. + + + + Privacy Policy + + + Terms of Service + + + +
+
+
+ ); +} diff --git a/packages/design-system/src/components/Layout/Footer/index.ts b/packages/design-system/src/components/Layout/Footer/index.ts new file mode 100644 index 00000000..e49f0fc1 --- /dev/null +++ b/packages/design-system/src/components/Layout/Footer/index.ts @@ -0,0 +1,2 @@ +export { Footer } from "./Footer"; + diff --git a/packages/design-system/src/components/Layout/Footer/types.ts b/packages/design-system/src/components/Layout/Footer/types.ts new file mode 100644 index 00000000..29a30a2d --- /dev/null +++ b/packages/design-system/src/components/Layout/Footer/types.ts @@ -0,0 +1,13 @@ +export interface FooterProps { + newsletterEmail: string; + onNewsletterEmailChange: (email: string) => void; + onNewsletterSubmit: (e: React.FormEvent) => void; + LinkComponent: React.ComponentType<{ + href: string; + className?: string; + target?: string; + rel?: string; + children: React.ReactNode; + }>; +} + diff --git a/packages/design-system/src/components/Layout/Header/Header.tsx b/packages/design-system/src/components/Layout/Header/Header.tsx new file mode 100644 index 00000000..afee5657 --- /dev/null +++ b/packages/design-system/src/components/Layout/Header/Header.tsx @@ -0,0 +1,45 @@ +import { Container, Flex } from "../../Atoms"; +import type { HeaderProps } from "./types"; + +export function Header({ + LogoComponent, + SearchBarComponent, + NavigationComponent, + CartIconComponent, + UserMenuComponent, + MobileMenuComponent, +}: HeaderProps) { + return ( +
+ + + {/* Logo */} + {LogoComponent} + + {/* Desktop Navigation */} + {NavigationComponent} + + {/* Search Bar (hidden on mobile) */} +
+ {SearchBarComponent} +
+ + {/* Right Actions */} + + {/* Cart Icon */} + {CartIconComponent} + + {/* Desktop User Menu */} +
{UserMenuComponent}
+ + {/* Mobile Menu */} + {MobileMenuComponent} +
+
+ + {/* Mobile Search Bar */} +
{SearchBarComponent}
+
+
+ ); +} diff --git a/packages/design-system/src/components/Layout/Header/index.ts b/packages/design-system/src/components/Layout/Header/index.ts new file mode 100644 index 00000000..c940126c --- /dev/null +++ b/packages/design-system/src/components/Layout/Header/index.ts @@ -0,0 +1 @@ +export { Header } from "./Header"; diff --git a/packages/design-system/src/components/Layout/Header/types.ts b/packages/design-system/src/components/Layout/Header/types.ts new file mode 100644 index 00000000..15d0c632 --- /dev/null +++ b/packages/design-system/src/components/Layout/Header/types.ts @@ -0,0 +1,9 @@ +export interface HeaderProps { + LogoComponent: React.ReactNode; + SearchBarComponent: React.ReactNode; + NavigationComponent: React.ReactNode; + CartIconComponent: React.ReactNode; + UserMenuComponent: React.ReactNode; + MobileMenuComponent: React.ReactNode; +} + diff --git a/packages/design-system/src/components/Layout/Logo/Logo.tsx b/packages/design-system/src/components/Layout/Logo/Logo.tsx new file mode 100644 index 00000000..f19bbc06 --- /dev/null +++ b/packages/design-system/src/components/Layout/Logo/Logo.tsx @@ -0,0 +1,16 @@ +import { Heading } from "../../Atoms"; +import type { LogoProps } from "./types"; + +export function Logo({ LinkComponent }: LogoProps) { + return ( + +
+ R +
+ + React Shop + +
+ ); +} + diff --git a/packages/design-system/src/components/Layout/Logo/index.ts b/packages/design-system/src/components/Layout/Logo/index.ts new file mode 100644 index 00000000..9ba2b804 --- /dev/null +++ b/packages/design-system/src/components/Layout/Logo/index.ts @@ -0,0 +1,2 @@ +export { Logo } from "./Logo"; + diff --git a/packages/design-system/src/components/Layout/Logo/types.ts b/packages/design-system/src/components/Layout/Logo/types.ts new file mode 100644 index 00000000..169c4130 --- /dev/null +++ b/packages/design-system/src/components/Layout/Logo/types.ts @@ -0,0 +1,8 @@ +export interface LogoProps { + LinkComponent: React.ComponentType<{ + href: string; + className?: string; + children: React.ReactNode; + }>; +} + diff --git a/packages/design-system/src/components/Layout/MobileMenu/MobileMenu.tsx b/packages/design-system/src/components/Layout/MobileMenu/MobileMenu.tsx new file mode 100644 index 00000000..9eb89c35 --- /dev/null +++ b/packages/design-system/src/components/Layout/MobileMenu/MobileMenu.tsx @@ -0,0 +1,218 @@ +import { Button, Text, Stack, Divider } from "../../Atoms"; +import { cn } from "../../../lib/utils"; +import type { MobileMenuProps } from "./types"; + +export function MobileMenu({ + user, + onLogout, + isOpen, + onToggle, + onClose, + navLinks, + currentPath, + LinkComponent, +}: MobileMenuProps) { + return ( + <> + {/* Hamburger Button */} + + + {/* Mobile Menu Overlay */} + {isOpen && ( +
+ )} + + {/* Mobile Menu Drawer */} +
+
+ {/* Header */} +
+
+ + Menu + + +
+
+ + {/* User Section */} + {user ? ( +
+
+
+ + {user.firstName?.[0] || ""} + {user.lastName?.[0] || ""} + +
+
+ + {user.firstName || ""} {user.lastName || ""} + + + {user.email} + +
+
+
+ ) : ( +
+ + + + + + + + +
+ )} + + {/* Navigation Links */} + + + {/* Logout Button */} + {user && ( +
+ +
+ )} +
+
+ + ); +} diff --git a/packages/design-system/src/components/Layout/MobileMenu/index.ts b/packages/design-system/src/components/Layout/MobileMenu/index.ts new file mode 100644 index 00000000..e96bf198 --- /dev/null +++ b/packages/design-system/src/components/Layout/MobileMenu/index.ts @@ -0,0 +1 @@ +export { MobileMenu } from "./MobileMenu"; diff --git a/packages/design-system/src/components/Layout/MobileMenu/types.ts b/packages/design-system/src/components/Layout/MobileMenu/types.ts new file mode 100644 index 00000000..dfac0e77 --- /dev/null +++ b/packages/design-system/src/components/Layout/MobileMenu/types.ts @@ -0,0 +1,27 @@ +export interface NavLink { + href: string; + label: string; +} + +export interface User { + firstName: string | null; + lastName: string | null; + email: string; +} + +export interface MobileMenuProps { + user?: User | null; + onLogout?: () => void; + isOpen: boolean; + onToggle: () => void; + onClose: () => void; + navLinks: NavLink[]; + currentPath: string; + LinkComponent: React.ComponentType<{ + href: string; + className?: string; + onClick?: () => void; + children: React.ReactNode; + }>; +} + diff --git a/packages/design-system/src/components/Layout/Navigation/Navigation.tsx b/packages/design-system/src/components/Layout/Navigation/Navigation.tsx new file mode 100644 index 00000000..75590c56 --- /dev/null +++ b/packages/design-system/src/components/Layout/Navigation/Navigation.tsx @@ -0,0 +1,34 @@ +import { Text } from "../../Atoms"; +import { cn } from "../../../lib/utils"; +import type { NavigationProps } from "./types"; + +export function Navigation({ links, currentPath, LinkComponent }: NavigationProps) { + return ( + + ); +} + diff --git a/packages/design-system/src/components/Layout/Navigation/index.ts b/packages/design-system/src/components/Layout/Navigation/index.ts new file mode 100644 index 00000000..c4d18f12 --- /dev/null +++ b/packages/design-system/src/components/Layout/Navigation/index.ts @@ -0,0 +1,2 @@ +export { Navigation } from "./Navigation"; + diff --git a/packages/design-system/src/components/Layout/Navigation/types.ts b/packages/design-system/src/components/Layout/Navigation/types.ts new file mode 100644 index 00000000..efb71cc8 --- /dev/null +++ b/packages/design-system/src/components/Layout/Navigation/types.ts @@ -0,0 +1,15 @@ +export interface NavLink { + href: string; + label: string; +} + +export interface NavigationProps { + links: NavLink[]; + currentPath: string; + LinkComponent: React.ComponentType<{ + href: string; + className?: string; + children: React.ReactNode; + }>; +} + diff --git a/packages/design-system/src/components/Layout/SearchBar/SearchBar.tsx b/packages/design-system/src/components/Layout/SearchBar/SearchBar.tsx new file mode 100644 index 00000000..545e724a --- /dev/null +++ b/packages/design-system/src/components/Layout/SearchBar/SearchBar.tsx @@ -0,0 +1,29 @@ +import { Input } from "../../Atoms"; +import type { SearchBarProps } from "./types"; + +export function SearchBar({ + value, + onChange, + onSubmit, + placeholder = "Search products...", +}: SearchBarProps) { + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (value.trim()) { + onSubmit(value); + } + }; + + return ( +
+ onChange(e.target.value)} + className="w-full" + /> +
+ ); +} + diff --git a/packages/design-system/src/components/Layout/SearchBar/index.ts b/packages/design-system/src/components/Layout/SearchBar/index.ts new file mode 100644 index 00000000..47d8459c --- /dev/null +++ b/packages/design-system/src/components/Layout/SearchBar/index.ts @@ -0,0 +1,2 @@ +export { SearchBar } from "./SearchBar"; + diff --git a/packages/design-system/src/components/Layout/SearchBar/types.ts b/packages/design-system/src/components/Layout/SearchBar/types.ts new file mode 100644 index 00000000..0813539d --- /dev/null +++ b/packages/design-system/src/components/Layout/SearchBar/types.ts @@ -0,0 +1,7 @@ +export interface SearchBarProps { + value: string; + onChange: (value: string) => void; + onSubmit: (query: string) => void; + placeholder?: string; +} + diff --git a/packages/design-system/src/components/Layout/UserMenu/UserMenu.tsx b/packages/design-system/src/components/Layout/UserMenu/UserMenu.tsx new file mode 100644 index 00000000..93ff8f7d --- /dev/null +++ b/packages/design-system/src/components/Layout/UserMenu/UserMenu.tsx @@ -0,0 +1,118 @@ +import { Button, Text, Stack } from "../../Atoms"; +import type { UserMenuProps } from "./types"; + +export function UserMenu({ + user, + onLogout, + isOpen, + onToggle, + onClose, + LinkComponent, + menuRef, +}: UserMenuProps) { + if (!user) { + return ( +
+ + + + + + +
+ ); + } + + return ( +
+ + + {isOpen && ( +
+
+ + {user.firstName || ""} {user.lastName || ""} + + + {user.email} + +
+ + + + Dashboard + + + Orders + + + Profile + + + Wishlist + + + +
+ +
+
+ )} +
+ ); +} + diff --git a/packages/design-system/src/components/Layout/UserMenu/index.ts b/packages/design-system/src/components/Layout/UserMenu/index.ts new file mode 100644 index 00000000..98b0032c --- /dev/null +++ b/packages/design-system/src/components/Layout/UserMenu/index.ts @@ -0,0 +1 @@ +export { UserMenu } from "./UserMenu"; diff --git a/packages/design-system/src/components/Layout/UserMenu/types.ts b/packages/design-system/src/components/Layout/UserMenu/types.ts new file mode 100644 index 00000000..ab0f4e99 --- /dev/null +++ b/packages/design-system/src/components/Layout/UserMenu/types.ts @@ -0,0 +1,21 @@ +export interface User { + firstName: string | null; + lastName: string | null; + email: string; +} + +export interface UserMenuProps { + user?: User | null; + onLogout?: () => void; + isOpen: boolean; + onToggle: () => void; + onClose: () => void; + LinkComponent: React.ComponentType<{ + href: string; + className?: string; + onClick?: () => void; + children: React.ReactNode; + }>; + menuRef?: React.RefObject; +} + diff --git a/packages/design-system/src/components/Layout/index.ts b/packages/design-system/src/components/Layout/index.ts new file mode 100644 index 00000000..1c046105 --- /dev/null +++ b/packages/design-system/src/components/Layout/index.ts @@ -0,0 +1,17 @@ +export { Header } from "./Header"; +export { Footer } from "./Footer"; +export { Logo } from "./Logo"; +export { SearchBar } from "./SearchBar"; +export { Navigation } from "./Navigation"; +export { CartIcon } from "./CartIcon"; +export { UserMenu } from "./UserMenu"; +export { MobileMenu } from "./MobileMenu"; + +export type { HeaderProps } from "./Header/types"; +export type { FooterProps } from "./Footer/types"; +export type { LogoProps } from "./Logo/types"; +export type { SearchBarProps } from "./SearchBar/types"; +export type { NavigationProps, NavLink } from "./Navigation/types"; +export type { CartIconProps } from "./CartIcon/types"; +export type { UserMenuProps, User } from "./UserMenu/types"; +export type { MobileMenuProps } from "./MobileMenu/types"; diff --git a/packages/design-system/src/components/Molecules/Rating/Rating.tsx b/packages/design-system/src/components/Molecules/Rating/Rating.tsx index abeaba16..6b624b03 100644 --- a/packages/design-system/src/components/Molecules/Rating/Rating.tsx +++ b/packages/design-system/src/components/Molecules/Rating/Rating.tsx @@ -19,7 +19,7 @@ const rating = tv({ export type RatingVariants = VariantProps; -export interface RatingProps extends React.HTMLAttributes, RatingVariants { +export interface RatingProps extends Omit, 'onChange'>, RatingVariants { value: number; max?: number; showValue?: boolean; diff --git a/packages/design-system/src/index.tsx b/packages/design-system/src/index.tsx index a874119e..5651ada5 100644 --- a/packages/design-system/src/index.tsx +++ b/packages/design-system/src/index.tsx @@ -28,5 +28,8 @@ export * from "./components/Molecules/Toast/Toast"; export * from "./components/Organisms/Modal/Modal"; export * from "./components/Organisms/ProductCard/ProductCard"; +// Layout +export * from "./components/Layout"; + // Global Styles - should be imported in the app root // import '@react-shop/design-system/src/styles/global.css'; diff --git a/packages/design-system/tsconfig.json b/packages/design-system/tsconfig.json index 9d3c48ed..2f90a323 100644 --- a/packages/design-system/tsconfig.json +++ b/packages/design-system/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "lib": ["ES2020", "DOM", "DOM.Iterable"], "baseUrl": ".", "paths": { "@/*": ["./src/*"], @@ -8,7 +9,8 @@ "@molecules/*": ["./src/components/Molecules/*"], "@organisms/*": ["./src/components/Organisms/*"], "@theme/*": ["./src/theme/*"], - "@lib/*": ["./src/lib/*"] + "@lib/*": ["./src/lib/*"], + "@react-shop/design-system": ["./src/index.tsx"] } }, "extends": "../tsconfig/react-library.json", diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 884732ad..0ddee8f5 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -61,6 +61,13 @@ export const createApiClient = (config: ApiClientConfig): AxiosInstance => { // If error is 401 and we haven't tried to refresh yet if (error.response?.status === 401 && !originalRequest._retry) { + const refreshToken = getStoredRefreshToken(); + + // If no refresh token exists, user was never logged in - just reject + if (!refreshToken) { + return Promise.reject(error); + } + if (isRefreshing) { // If already refreshing, queue this request return new Promise((resolve, reject) => { @@ -77,8 +84,6 @@ export const createApiClient = (config: ApiClientConfig): AxiosInstance => { originalRequest._retry = true; isRefreshing = true; - const refreshToken = getStoredRefreshToken(); - try { // Try to refresh the token const response = await axios.post(`${config.baseURL}/api/auth/refresh`, { @@ -103,7 +108,7 @@ export const createApiClient = (config: ApiClientConfig): AxiosInstance => { // Retry the original request return client(originalRequest); } catch (refreshError) { - // Refresh failed, clear tokens and redirect + // Refresh failed, clear tokens and redirect to login processQueue(refreshError); isRefreshing = false; clearStoredToken(); diff --git a/packages/sdk/src/entities/Product.ts b/packages/sdk/src/entities/Product.ts index 10a99c16..abe8cb9c 100644 --- a/packages/sdk/src/entities/Product.ts +++ b/packages/sdk/src/entities/Product.ts @@ -82,14 +82,8 @@ export interface UpdateProductInput { isFeatured?: boolean; } -// Import Category and Tag types -export interface Category { - id: string; - name: string; - description: string | null; - createdAt: Date; - updatedAt: Date; -} +// Import Category and Tag types from their respective files +import type { Category } from './Category'; export interface Tag { id: string; diff --git a/packages/sdk/src/services/mutations/useCategoryMutations.ts b/packages/sdk/src/services/mutations/useCategoryMutations.ts index cebdec02..3aa05f39 100644 --- a/packages/sdk/src/services/mutations/useCategoryMutations.ts +++ b/packages/sdk/src/services/mutations/useCategoryMutations.ts @@ -1,6 +1,6 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useApiClient } from '../../providers/ApiProvider'; -import type { Category, CreateCategoryInput, UpdateCategoryInput } from '../../entities'; +import type { Category, CreateCategoryInput, UpdateCategoryInput } from '../../entities/Category'; export function useCreateCategory() { const { client } = useApiClient(); diff --git a/packages/sdk/src/services/queries/auth/useMe/index.ts b/packages/sdk/src/services/queries/auth/useMe/index.ts index d4bdff72..415d8ac7 100644 --- a/packages/sdk/src/services/queries/auth/useMe/index.ts +++ b/packages/sdk/src/services/queries/auth/useMe/index.ts @@ -2,14 +2,17 @@ import { useQuery } from '@tanstack/react-query'; import { useApiClient } from '@providers/ApiProvider'; import { useMeKey } from './key'; import { fetchMe } from './request'; +import { getStoredToken } from '../../../../client'; export function useMe() { const { client } = useApiClient(); + const hasToken = !!getStoredToken(); return useQuery({ queryKey: useMeKey(), queryFn: () => fetchMe(client), retry: false, + enabled: hasToken, // Only fetch if user has a token }); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8742069e..67f9a441 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -190,6 +190,64 @@ importers: specifier: ^5.0.0 version: 5.9.3 + apps/storybook: + dependencies: + '@react-shop/design-system': + specifier: workspace:* + version: link:../../packages/design-system + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + devDependencies: + '@storybook/addon-essentials': + specifier: ^8.0.0 + version: 8.6.14(@types/react@18.3.27)(storybook@8.6.15(prettier@3.7.4)) + '@storybook/addon-interactions': + specifier: ^8.0.0 + version: 8.6.14(storybook@8.6.15(prettier@3.7.4)) + '@storybook/addon-links': + specifier: ^8.0.0 + version: 8.6.15(react@18.3.1)(storybook@8.6.15(prettier@3.7.4)) + '@storybook/blocks': + specifier: ^8.0.0 + version: 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4)) + '@storybook/react': + specifier: ^8.0.0 + version: 8.6.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4))(typescript@5.9.3) + '@storybook/react-vite': + specifier: ^8.0.0 + version: 8.6.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.54.0)(storybook@8.6.15(prettier@3.7.4))(typescript@5.9.3)(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.23.0)(terser@5.44.1)) + '@types/react': + specifier: ^18.2.0 + version: 18.3.27 + '@types/react-dom': + specifier: ^18.2.0 + version: 18.3.7(@types/react@18.3.27) + autoprefixer: + specifier: ^10.4.16 + version: 10.4.23(postcss@8.5.6) + postcss: + specifier: ^8.4.32 + version: 8.5.6 + storybook: + specifier: ^8.0.0 + version: 8.6.15(prettier@3.7.4) + tailwindcss: + specifier: ^3.4.0 + version: 3.4.19(tsx@4.21.0) + tsconfig: + specifier: workspace:* + version: link:../../packages/tsconfig + typescript: + specifier: ^5.0.0 + version: 5.9.3 + vite: + specifier: ^5.0.0 + version: 5.4.21(@types/node@20.19.27)(lightningcss@1.23.0)(terser@5.44.1) + apps/web: dependencies: '@hookform/resolvers': @@ -261,7 +319,7 @@ importers: version: 0.263.1(react@18.3.1) react-dom: specifier: ^18.2.0 - version: 19.2.3(react@18.3.1) + version: 18.3.1(react@18.3.1) tailwind-merge: specifier: ^2.6.0 version: 2.6.0 @@ -269,24 +327,6 @@ importers: specifier: ^0.1.20 version: 0.1.20(tailwindcss@3.4.19(tsx@4.21.0)) devDependencies: - '@storybook/addon-essentials': - specifier: ^8.0.0 - version: 8.6.14(@types/react@18.3.27)(storybook@8.6.15(prettier@3.7.4)) - '@storybook/addon-interactions': - specifier: ^8.0.0 - version: 8.6.14(storybook@8.6.15(prettier@3.7.4)) - '@storybook/addon-links': - specifier: ^8.0.0 - version: 8.6.15(react@18.3.1)(storybook@8.6.15(prettier@3.7.4)) - '@storybook/blocks': - specifier: ^8.0.0 - version: 8.6.14(react-dom@19.2.3(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4)) - '@storybook/react': - specifier: ^8.0.0 - version: 8.6.15(react-dom@19.2.3(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4))(typescript@5.9.3) - '@storybook/react-vite': - specifier: ^8.0.0 - version: 8.6.15(react-dom@19.2.3(react@18.3.1))(react@18.3.1)(rollup@4.54.0)(storybook@8.6.15(prettier@3.7.4))(typescript@5.9.3)(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.23.0)(terser@5.44.1)) '@types/react': specifier: ^18.2.0 version: 18.3.27 @@ -298,7 +338,7 @@ importers: version: 10.4.23(postcss@8.5.6) eslint: specifier: ^9.0.0 - version: 9.39.2(jiti@1.21.7) + version: 9.39.2(jiti@2.6.1) eslint-config-custom: specifier: workspace:* version: link:../eslint-config-custom @@ -308,9 +348,6 @@ importers: react: specifier: ^18.2.0 version: 18.3.1 - storybook: - specifier: ^8.0.0 - version: 8.6.15(prettier@3.7.4) tailwindcss: specifier: ^3.4.0 version: 3.4.19(tsx@4.21.0) @@ -320,9 +357,6 @@ importers: typescript: specifier: ^5.0.0 version: 5.9.3 - vite: - specifier: ^5.0.0 - version: 5.4.21(@types/node@20.19.27)(lightningcss@1.23.0)(terser@5.44.1) packages/eslint-config-custom: dependencies: @@ -900,12 +934,6 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - '@esbuild/aix-ppc64@0.20.2': - resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -918,12 +946,6 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.20.2': - resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} @@ -936,12 +958,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.20.2': - resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.21.5': resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} @@ -954,12 +970,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.20.2': - resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} @@ -972,12 +982,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.20.2': - resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} @@ -990,12 +994,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.20.2': - resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} @@ -1008,12 +1006,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.20.2': - resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} @@ -1026,12 +1018,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.20.2': - resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} @@ -1044,12 +1030,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.20.2': - resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} @@ -1062,12 +1042,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.20.2': - resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} @@ -1080,12 +1054,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.20.2': - resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} @@ -1098,12 +1066,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.20.2': - resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} @@ -1116,12 +1078,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.20.2': - resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} @@ -1134,12 +1090,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.20.2': - resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} @@ -1152,12 +1102,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.20.2': - resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} @@ -1170,12 +1114,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.20.2': - resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} @@ -1188,12 +1126,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.20.2': - resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} @@ -1212,12 +1144,6 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.20.2': - resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} @@ -1236,12 +1162,6 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.20.2': - resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} @@ -1260,12 +1180,6 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.20.2': - resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} @@ -1278,12 +1192,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.20.2': - resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} @@ -1296,12 +1204,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.20.2': - resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} @@ -1314,12 +1216,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.20.2': - resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} @@ -3733,11 +3629,6 @@ packages: peerDependencies: esbuild: '>=0.12 <1' - esbuild@0.20.2: - resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} - engines: {node: '>=12'} - hasBin: true - esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -5801,6 +5692,11 @@ packages: resolution: {integrity: sha512-hlSJDQ2synMPKFZOsKo9Hi8WWZTC7POR8EmWvTSjow+VDgKzkmjQvFm2fk0tmRw+f0vTOIYKlarR0iL4996pdg==} engines: {node: '>=16.14.0'} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + react-dom@19.2.3: resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} peerDependencies: @@ -5986,6 +5882,9 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -7754,153 +7653,102 @@ snapshots: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.20.2': - optional: true - '@esbuild/aix-ppc64@0.21.5': optional: true '@esbuild/aix-ppc64@0.27.2': optional: true - '@esbuild/android-arm64@0.20.2': - optional: true - '@esbuild/android-arm64@0.21.5': optional: true '@esbuild/android-arm64@0.27.2': optional: true - '@esbuild/android-arm@0.20.2': - optional: true - '@esbuild/android-arm@0.21.5': optional: true '@esbuild/android-arm@0.27.2': optional: true - '@esbuild/android-x64@0.20.2': - optional: true - '@esbuild/android-x64@0.21.5': optional: true '@esbuild/android-x64@0.27.2': optional: true - '@esbuild/darwin-arm64@0.20.2': - optional: true - '@esbuild/darwin-arm64@0.21.5': optional: true '@esbuild/darwin-arm64@0.27.2': optional: true - '@esbuild/darwin-x64@0.20.2': - optional: true - '@esbuild/darwin-x64@0.21.5': optional: true '@esbuild/darwin-x64@0.27.2': optional: true - '@esbuild/freebsd-arm64@0.20.2': - optional: true - '@esbuild/freebsd-arm64@0.21.5': optional: true '@esbuild/freebsd-arm64@0.27.2': optional: true - '@esbuild/freebsd-x64@0.20.2': - optional: true - '@esbuild/freebsd-x64@0.21.5': optional: true '@esbuild/freebsd-x64@0.27.2': optional: true - '@esbuild/linux-arm64@0.20.2': - optional: true - '@esbuild/linux-arm64@0.21.5': optional: true '@esbuild/linux-arm64@0.27.2': optional: true - '@esbuild/linux-arm@0.20.2': - optional: true - '@esbuild/linux-arm@0.21.5': optional: true '@esbuild/linux-arm@0.27.2': optional: true - '@esbuild/linux-ia32@0.20.2': - optional: true - '@esbuild/linux-ia32@0.21.5': optional: true '@esbuild/linux-ia32@0.27.2': optional: true - '@esbuild/linux-loong64@0.20.2': - optional: true - '@esbuild/linux-loong64@0.21.5': optional: true '@esbuild/linux-loong64@0.27.2': optional: true - '@esbuild/linux-mips64el@0.20.2': - optional: true - '@esbuild/linux-mips64el@0.21.5': optional: true '@esbuild/linux-mips64el@0.27.2': optional: true - '@esbuild/linux-ppc64@0.20.2': - optional: true - '@esbuild/linux-ppc64@0.21.5': optional: true '@esbuild/linux-ppc64@0.27.2': optional: true - '@esbuild/linux-riscv64@0.20.2': - optional: true - '@esbuild/linux-riscv64@0.21.5': optional: true '@esbuild/linux-riscv64@0.27.2': optional: true - '@esbuild/linux-s390x@0.20.2': - optional: true - '@esbuild/linux-s390x@0.21.5': optional: true '@esbuild/linux-s390x@0.27.2': optional: true - '@esbuild/linux-x64@0.20.2': - optional: true - '@esbuild/linux-x64@0.21.5': optional: true @@ -7910,9 +7758,6 @@ snapshots: '@esbuild/netbsd-arm64@0.27.2': optional: true - '@esbuild/netbsd-x64@0.20.2': - optional: true - '@esbuild/netbsd-x64@0.21.5': optional: true @@ -7922,9 +7767,6 @@ snapshots: '@esbuild/openbsd-arm64@0.27.2': optional: true - '@esbuild/openbsd-x64@0.20.2': - optional: true - '@esbuild/openbsd-x64@0.21.5': optional: true @@ -7934,47 +7776,30 @@ snapshots: '@esbuild/openharmony-arm64@0.27.2': optional: true - '@esbuild/sunos-x64@0.20.2': - optional: true - '@esbuild/sunos-x64@0.21.5': optional: true '@esbuild/sunos-x64@0.27.2': optional: true - '@esbuild/win32-arm64@0.20.2': - optional: true - '@esbuild/win32-arm64@0.21.5': optional: true '@esbuild/win32-arm64@0.27.2': optional: true - '@esbuild/win32-ia32@0.20.2': - optional: true - '@esbuild/win32-ia32@0.21.5': optional: true '@esbuild/win32-ia32@0.27.2': optional: true - '@esbuild/win32-x64@0.20.2': - optional: true - '@esbuild/win32-x64@0.21.5': optional: true '@esbuild/win32-x64@0.27.2': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@1.21.7))': - dependencies: - eslint: 9.39.2(jiti@1.21.7) - eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': dependencies: eslint: 9.39.2(jiti@2.6.1) @@ -8305,11 +8130,11 @@ snapshots: - encoding - supports-color - '@mdx-js/react@3.1.1(@types/react@18.3.27)(react@18.3.1)': + '@mdx-js/react@3.1.1(@types/react@18.3.27)(react@19.2.3)': dependencies: '@types/mdx': 2.0.13 '@types/react': 18.3.27 - react: 18.3.1 + react: 19.2.3 '@mrleebo/prisma-ast@0.12.1': dependencies: @@ -9065,12 +8890,12 @@ snapshots: '@storybook/addon-docs@8.6.14(@types/react@18.3.27)(storybook@8.6.15(prettier@3.7.4))': dependencies: - '@mdx-js/react': 3.1.1(@types/react@18.3.27)(react@18.3.1) - '@storybook/blocks': 8.6.14(react-dom@19.2.3(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4)) + '@mdx-js/react': 3.1.1(@types/react@18.3.27)(react@19.2.3) + '@storybook/blocks': 8.6.14(react-dom@18.3.1(react@18.3.1))(react@19.2.3)(storybook@8.6.15(prettier@3.7.4)) '@storybook/csf-plugin': 8.6.14(storybook@8.6.15(prettier@3.7.4)) - '@storybook/react-dom-shim': 8.6.14(react-dom@19.2.3(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4)) - react: 18.3.1 - react-dom: 19.2.3(react@18.3.1) + '@storybook/react-dom-shim': 8.6.14(react-dom@18.3.1(react@18.3.1))(react@19.2.3)(storybook@8.6.15(prettier@3.7.4)) + react: 19.2.3 + react-dom: 18.3.1(react@19.2.3) storybook: 8.6.15(prettier@3.7.4) ts-dedent: 2.2.0 transitivePeerDependencies: @@ -9135,14 +8960,23 @@ snapshots: memoizerific: 1.11.3 storybook: 8.6.15(prettier@3.7.4) - '@storybook/blocks@8.6.14(react-dom@19.2.3(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4))': + '@storybook/blocks@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4))': dependencies: - '@storybook/icons': 1.6.0(react-dom@19.2.3(react@18.3.1))(react@18.3.1) + '@storybook/icons': 1.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) storybook: 8.6.15(prettier@3.7.4) ts-dedent: 2.2.0 optionalDependencies: react: 18.3.1 - react-dom: 19.2.3(react@18.3.1) + react-dom: 18.3.1(react@18.3.1) + + '@storybook/blocks@8.6.14(react-dom@18.3.1(react@18.3.1))(react@19.2.3)(storybook@8.6.15(prettier@3.7.4))': + dependencies: + '@storybook/icons': 1.6.0(react-dom@18.3.1(react@18.3.1))(react@19.2.3) + storybook: 8.6.15(prettier@3.7.4) + ts-dedent: 2.2.0 + optionalDependencies: + react: 19.2.3 + react-dom: 18.3.1(react@18.3.1) '@storybook/builder-vite@8.6.15(storybook@8.6.15(prettier@3.7.4))(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.23.0)(terser@5.44.1))': dependencies: @@ -9161,8 +8995,8 @@ snapshots: '@storybook/theming': 8.6.15(storybook@8.6.15(prettier@3.7.4)) better-opn: 3.0.2 browser-assert: 1.2.1 - esbuild: 0.20.2 - esbuild-register: 3.6.0(esbuild@0.20.2) + esbuild: 0.21.5 + esbuild-register: 3.6.0(esbuild@0.21.5) jsdoc-type-pratt-parser: 4.8.0 process: 0.11.10 recast: 0.23.11 @@ -9189,10 +9023,15 @@ snapshots: '@storybook/global@5.0.0': {} - '@storybook/icons@1.6.0(react-dom@19.2.3(react@18.3.1))(react@18.3.1)': + '@storybook/icons@1.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: react: 18.3.1 - react-dom: 19.2.3(react@18.3.1) + react-dom: 18.3.1(react@18.3.1) + + '@storybook/icons@1.6.0(react-dom@18.3.1(react@18.3.1))(react@19.2.3)': + dependencies: + react: 19.2.3 + react-dom: 18.3.1(react@18.3.1) '@storybook/instrumenter@8.6.14(storybook@8.6.15(prettier@3.7.4))': dependencies: @@ -9208,29 +9047,29 @@ snapshots: dependencies: storybook: 8.6.15(prettier@3.7.4) - '@storybook/react-dom-shim@8.6.14(react-dom@19.2.3(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4))': + '@storybook/react-dom-shim@8.6.14(react-dom@18.3.1(react@18.3.1))(react@19.2.3)(storybook@8.6.15(prettier@3.7.4))': dependencies: - react: 18.3.1 - react-dom: 19.2.3(react@18.3.1) + react: 19.2.3 + react-dom: 18.3.1(react@18.3.1) storybook: 8.6.15(prettier@3.7.4) - '@storybook/react-dom-shim@8.6.15(react-dom@19.2.3(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4))': + '@storybook/react-dom-shim@8.6.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4))': dependencies: react: 18.3.1 - react-dom: 19.2.3(react@18.3.1) + react-dom: 18.3.1(react@18.3.1) storybook: 8.6.15(prettier@3.7.4) - '@storybook/react-vite@8.6.15(react-dom@19.2.3(react@18.3.1))(react@18.3.1)(rollup@4.54.0)(storybook@8.6.15(prettier@3.7.4))(typescript@5.9.3)(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.23.0)(terser@5.44.1))': + '@storybook/react-vite@8.6.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.54.0)(storybook@8.6.15(prettier@3.7.4))(typescript@5.9.3)(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.23.0)(terser@5.44.1))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.9.3)(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.23.0)(terser@5.44.1)) '@rollup/pluginutils': 5.3.0(rollup@4.54.0) '@storybook/builder-vite': 8.6.15(storybook@8.6.15(prettier@3.7.4))(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.23.0)(terser@5.44.1)) - '@storybook/react': 8.6.15(react-dom@19.2.3(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4))(typescript@5.9.3) + '@storybook/react': 8.6.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4))(typescript@5.9.3) find-up: 5.0.0 magic-string: 0.30.10 react: 18.3.1 react-docgen: 7.1.1 - react-dom: 19.2.3(react@18.3.1) + react-dom: 18.3.1(react@18.3.1) resolve: 1.22.11 storybook: 8.6.15(prettier@3.7.4) tsconfig-paths: 4.2.0 @@ -9240,16 +9079,16 @@ snapshots: - supports-color - typescript - '@storybook/react@8.6.15(react-dom@19.2.3(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4))(typescript@5.9.3)': + '@storybook/react@8.6.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4))(typescript@5.9.3)': dependencies: '@storybook/components': 8.6.15(storybook@8.6.15(prettier@3.7.4)) '@storybook/global': 5.0.0 '@storybook/manager-api': 8.6.15(storybook@8.6.15(prettier@3.7.4)) '@storybook/preview-api': 8.6.15(storybook@8.6.15(prettier@3.7.4)) - '@storybook/react-dom-shim': 8.6.15(react-dom@19.2.3(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4)) + '@storybook/react-dom-shim': 8.6.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@3.7.4)) '@storybook/theming': 8.6.15(storybook@8.6.15(prettier@3.7.4)) react: 18.3.1 - react-dom: 19.2.3(react@18.3.1) + react-dom: 18.3.1(react@18.3.1) storybook: 8.6.15(prettier@3.7.4) optionalDependencies: typescript: 5.9.3 @@ -10804,39 +10643,13 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild-register@3.6.0(esbuild@0.20.2): + esbuild-register@3.6.0(esbuild@0.21.5): dependencies: debug: 4.4.3 - esbuild: 0.20.2 + esbuild: 0.21.5 transitivePeerDependencies: - supports-color - esbuild@0.20.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.20.2 - '@esbuild/android-arm': 0.20.2 - '@esbuild/android-arm64': 0.20.2 - '@esbuild/android-x64': 0.20.2 - '@esbuild/darwin-arm64': 0.20.2 - '@esbuild/darwin-x64': 0.20.2 - '@esbuild/freebsd-arm64': 0.20.2 - '@esbuild/freebsd-x64': 0.20.2 - '@esbuild/linux-arm': 0.20.2 - '@esbuild/linux-arm64': 0.20.2 - '@esbuild/linux-ia32': 0.20.2 - '@esbuild/linux-loong64': 0.20.2 - '@esbuild/linux-mips64el': 0.20.2 - '@esbuild/linux-ppc64': 0.20.2 - '@esbuild/linux-riscv64': 0.20.2 - '@esbuild/linux-s390x': 0.20.2 - '@esbuild/linux-x64': 0.20.2 - '@esbuild/netbsd-x64': 0.20.2 - '@esbuild/openbsd-x64': 0.20.2 - '@esbuild/sunos-x64': 0.20.2 - '@esbuild/win32-arm64': 0.20.2 - '@esbuild/win32-ia32': 0.20.2 - '@esbuild/win32-x64': 0.20.2 - esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -10910,8 +10723,8 @@ snapshots: '@typescript-eslint/parser': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@9.39.2(jiti@2.6.1)) @@ -10930,8 +10743,8 @@ snapshots: '@typescript-eslint/parser': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@2.6.1)) @@ -10960,7 +10773,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -10971,22 +10784,22 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -10997,7 +10810,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -11094,47 +10907,6 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.2(jiti@1.21.7): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7)) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.1 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.3 - '@eslint/js': 9.39.2 - '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.7.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - optionalDependencies: - jiti: 1.21.7 - transitivePeerDependencies: - - supports-color - eslint@9.39.2(jiti@2.6.1): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) @@ -13272,10 +13044,17 @@ snapshots: transitivePeerDependencies: - supports-color - react-dom@19.2.3(react@18.3.1): + react-dom@18.3.1(react@18.3.1): dependencies: + loose-envify: 1.4.0 react: 18.3.1 - scheduler: 0.27.0 + scheduler: 0.23.2 + + react-dom@18.3.1(react@19.2.3): + dependencies: + loose-envify: 1.4.0 + react: 19.2.3 + scheduler: 0.23.2 react-dom@19.2.3(react@19.2.3): dependencies: @@ -13488,6 +13267,10 @@ snapshots: safer-buffer@2.1.2: {} + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + scheduler@0.27.0: {} schema-utils@3.3.0: diff --git a/turbo.json b/turbo.json index 0c90c3d7..a798de5e 100644 --- a/turbo.json +++ b/turbo.json @@ -1,6 +1,6 @@ { "$schema": "https://turbo.build/schema.json", - "pipeline": { + "tasks": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", ".next/**"]