diff --git a/src/shared/ui/Button/Button.stories.ts b/src/shared/ui/Button/Button.stories.ts new file mode 100644 index 00000000..4fef3bbd --- /dev/null +++ b/src/shared/ui/Button/Button.stories.ts @@ -0,0 +1,53 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { fn } from "storybook/test"; + +import Button from "./Button"; + +const meta = { + title: "Components/Button", + component: Button, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + variant: { + control: { type: "radio" }, + options: ["primary", "secondary", "tertiary"], + }, + size: { + control: { type: "radio" }, + options: ["sm", "md", "lg"], + }, + disabled: { + control: "boolean", + }, + }, + args: { + onClick: fn(), + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + variant: "primary", + children: "Primary", + }, +}; + +export const Secondary: Story = { + args: { + variant: "secondary", + children: "Secondary", + }, +}; + +export const Tertiary: Story = { + args: { + variant: "tertiary", + children: "Tertiary", + }, +}; diff --git a/src/shared/ui/Button/Button.tsx b/src/shared/ui/Button/Button.tsx new file mode 100644 index 00000000..59a77b40 --- /dev/null +++ b/src/shared/ui/Button/Button.tsx @@ -0,0 +1,63 @@ +import React from "react"; +import cn from "@/shared/lib/cn"; + +type ButtonVariant = "primary" | "secondary" | "tertiary"; +type ButtonSize = "sm" | "md" | "lg"; +type VariantStyle = { + default: string; + disabled: string; +}; + +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: ButtonVariant; + size?: ButtonSize; +} + +const sizeClassMap: Record = { + sm: "text-base w-[335px] h-[50px]", + md: "text-base w-[440px] h-[55px]", + lg: "text-lg w-[640px] h-[65px]", +}; + +const variantClassMap: Record = { + primary: { + default: "bg-linear-to-r from-[#5097fa] to-[#5363ff] text-[#F1F1F5]", + disabled: "bg-[#353542] text-[#6E6E82]", + }, + secondary: { + default: "bg-transparent text-[#5097FA] border border-[#5097FA]", + disabled: "bg-transparent text-[#6E6E82] border border-[#353542]", + }, + tertiary: { + default: "bg-transparent text-[#9FA6B2] border border-[#9FA6B2]", + disabled: "bg-transparent text-[#6E6E82] border border-[#353542]", + }, +}; + +const Button = ({ + variant = "primary", + size = "sm", + disabled = false, + className, + children, + ...props +}: ButtonProps) => { + const baseClasses = "font-semibold leading-none rounded-lg"; + + const mergedClasses = cn( + baseClasses, + sizeClassMap[size], + disabled + ? [variantClassMap[variant].disabled, "cursor-not-allowed"] + : [variantClassMap[variant].default, "cursor-pointer"], + className, + ); + + return ( + + ); +}; + +export default Button; diff --git a/src/stories/Button.stories.ts b/src/stories/Button.stories.ts deleted file mode 100644 index 0218e291..00000000 --- a/src/stories/Button.stories.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/nextjs-vite"; - -import { fn } from "storybook/test"; - -import { Button } from "./Button"; - -// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export -const meta = { - title: "Example/Button", - component: Button, - parameters: { - // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout - layout: "centered", - }, - // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs - tags: ["autodocs"], - // More on argTypes: https://storybook.js.org/docs/api/argtypes - argTypes: { - backgroundColor: { control: "color" }, - }, - // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args - args: { onClick: fn() }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args -export const Primary: Story = { - args: { - primary: true, - label: "Button", - }, -}; - -export const Secondary: Story = { - args: { - label: "Button", - }, -}; - -export const Large: Story = { - args: { - size: "large", - label: "Button", - }, -}; - -export const Small: Story = { - args: { - size: "small", - label: "Button", - }, -}; diff --git a/src/stories/Button.tsx b/src/stories/Button.tsx deleted file mode 100644 index 0773d460..00000000 --- a/src/stories/Button.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import "./button.css"; - -export interface ButtonProps { - /** Is this the principal call to action on the page? */ - primary?: boolean; - /** What background color to use */ - backgroundColor?: string; - /** How large should the button be? */ - size?: "small" | "medium" | "large"; - /** Button contents */ - label: string; - /** Optional click handler */ - onClick?: () => void; -} - -/** Primary UI component for user interaction */ -export const Button = ({ - primary = false, - size = "medium", - backgroundColor, - label, - ...props -}: ButtonProps) => { - const mode = primary - ? "storybook-button--primary" - : "storybook-button--secondary"; - return ( - - ); -}; diff --git a/src/stories/Header.stories.ts b/src/stories/Header.stories.ts deleted file mode 100644 index 6f985bcd..00000000 --- a/src/stories/Header.stories.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/nextjs-vite"; - -import { fn } from "storybook/test"; - -import { Header } from "./Header"; - -const meta = { - title: "Example/Header", - component: Header, - // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs - tags: ["autodocs"], - parameters: { - // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout - layout: "fullscreen", - }, - args: { - onLogin: fn(), - onLogout: fn(), - onCreateAccount: fn(), - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const LoggedIn: Story = { - args: { - user: { - name: "Jane Doe", - }, - }, -}; - -export const LoggedOut: Story = {}; diff --git a/src/stories/Header.tsx b/src/stories/Header.tsx deleted file mode 100644 index d3f3ccf0..00000000 --- a/src/stories/Header.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Button } from "./Button"; -import "./header.css"; - -type User = { - name: string; -}; - -export interface HeaderProps { - user?: User; - onLogin?: () => void; - onLogout?: () => void; - onCreateAccount?: () => void; -} - -export const Header = ({ - user, - onLogin, - onLogout, - onCreateAccount, -}: HeaderProps) => ( -
-
-
- - - - - - - -

Acme

-
-
- {user ? ( - <> - - Welcome, {user.name}! - -
-
-
-); diff --git a/src/stories/Page.stories.ts b/src/stories/Page.stories.ts deleted file mode 100644 index f32018b4..00000000 --- a/src/stories/Page.stories.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/nextjs-vite"; - -import { expect, userEvent, within } from "storybook/test"; - -import { Page } from "./Page"; - -const meta = { - title: "Example/Page", - component: Page, - parameters: { - // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout - layout: "fullscreen", - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const LoggedOut: Story = {}; - -// More on component testing: https://storybook.js.org/docs/writing-tests/interaction-testing -export const LoggedIn: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const loginButton = canvas.getByRole("button", { name: /Log in/i }); - await expect(loginButton).toBeInTheDocument(); - await userEvent.click(loginButton); - await expect(loginButton).not.toBeInTheDocument(); - - const logoutButton = canvas.getByRole("button", { name: /Log out/i }); - await expect(logoutButton).toBeInTheDocument(); - }, -}; diff --git a/src/stories/Page.tsx b/src/stories/Page.tsx deleted file mode 100644 index a294b193..00000000 --- a/src/stories/Page.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React from "react"; - -import { Header } from "./Header"; -import "./page.css"; - -type User = { - name: string; -}; - -export const Page: React.FC = () => { - const [user, setUser] = React.useState(); - - return ( -
-
setUser({ name: "Jane Doe" })} - onLogout={() => setUser(undefined)} - onCreateAccount={() => setUser({ name: "Jane Doe" })} - /> - -
-

Pages in Storybook

-

- We recommend building UIs with a{" "} - - component-driven - {" "} - process starting with atomic components and ending with pages. -

-

- Render pages with mock data. This makes it easy to build and review - page states without needing to navigate to them in your app. Here are - some handy patterns for managing page data in Storybook: -

-
    -
  • - Use a higher-level connected component. Storybook helps you compose - such data from the "args" of child component stories -
  • -
  • - Assemble data in the page component from your services. You can mock - these services out using Storybook. -
  • -
-

- Get a guided tutorial on component-driven development at{" "} - - Storybook tutorials - - . Read more in the{" "} - - docs - - . -

-
- Tip Adjust the width of the canvas with - the{" "} - - - - - - Viewports addon in the toolbar -
-
-
- ); -}; diff --git a/src/stories/button.css b/src/stories/button.css deleted file mode 100644 index 4e3620b0..00000000 --- a/src/stories/button.css +++ /dev/null @@ -1,30 +0,0 @@ -.storybook-button { - display: inline-block; - cursor: pointer; - border: 0; - border-radius: 3em; - font-weight: 700; - line-height: 1; - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} -.storybook-button--primary { - background-color: #555ab9; - color: white; -} -.storybook-button--secondary { - box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; - background-color: transparent; - color: #333; -} -.storybook-button--small { - padding: 10px 16px; - font-size: 12px; -} -.storybook-button--medium { - padding: 11px 20px; - font-size: 14px; -} -.storybook-button--large { - padding: 12px 24px; - font-size: 16px; -} diff --git a/src/stories/header.css b/src/stories/header.css deleted file mode 100644 index 5efd46c2..00000000 --- a/src/stories/header.css +++ /dev/null @@ -1,32 +0,0 @@ -.storybook-header { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - padding: 15px 20px; - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -.storybook-header svg { - display: inline-block; - vertical-align: top; -} - -.storybook-header h1 { - display: inline-block; - vertical-align: top; - margin: 6px 0 6px 10px; - font-weight: 700; - font-size: 20px; - line-height: 1; -} - -.storybook-header button + button { - margin-left: 10px; -} - -.storybook-header .welcome { - margin-right: 10px; - color: #333; - font-size: 14px; -} diff --git a/src/stories/page.css b/src/stories/page.css deleted file mode 100644 index 77c81d2d..00000000 --- a/src/stories/page.css +++ /dev/null @@ -1,68 +0,0 @@ -.storybook-page { - margin: 0 auto; - padding: 48px 20px; - max-width: 600px; - color: #333; - font-size: 14px; - line-height: 24px; - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -.storybook-page h2 { - display: inline-block; - vertical-align: top; - margin: 0 0 4px; - font-weight: 700; - font-size: 32px; - line-height: 1; -} - -.storybook-page p { - margin: 1em 0; -} - -.storybook-page a { - color: inherit; -} - -.storybook-page ul { - margin: 1em 0; - padding-left: 30px; -} - -.storybook-page li { - margin-bottom: 8px; -} - -.storybook-page .tip { - display: inline-block; - vertical-align: top; - margin-right: 10px; - border-radius: 1em; - background: #e7fdd8; - padding: 4px 12px; - color: #357a14; - font-weight: 700; - font-size: 11px; - line-height: 12px; -} - -.storybook-page .tip-wrapper { - margin-top: 40px; - margin-bottom: 40px; - font-size: 13px; - line-height: 20px; -} - -.storybook-page .tip-wrapper svg { - display: inline-block; - vertical-align: top; - margin-top: 3px; - margin-right: 4px; - width: 12px; - height: 12px; -} - -.storybook-page .tip-wrapper svg path { - fill: #1ea7fd; -}