diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 0000000..0250327 --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,24 @@ +# Workflow name +name: "Chromatic Deployment" + +# Event for the workflow +on: push + +# List of jobs +jobs: + test: + # Operating System + runs-on: ubuntu-latest + # Job steps + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - run: npm + #๐Ÿ‘‡ Adds Chromatic as a step in the workflow + - uses: chromaui/action@v1 + # Options required for Chromatic's GitHub Action + with: + #๐Ÿ‘‡ Chromatic projectToken, see https://storybook.js.org/tutorials/intro-to-storybook/react/en/deploy/ to obtain it + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 9d8301d..3ee7cd9 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,9 @@ - ํ‚ค๋ณด๋“œ๋งŒ์œผ๋กœ ์ถ”์ฒœ ๊ฒ€์ƒ‰์–ด๋“ค๋กœ ์ด๋™ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ตฌํ˜„ - ๊ฒ€์ƒ‰์ฐฝ์—์„œ onKeyDown ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ event.key ๊ฐ’์ด ArrowDown, ArrowUp์ผ ๊ฒฝ์šฐ selectedIndex๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ฒŒ ํ–ˆ๊ณ  ์ถ”์ฒœ ๊ฒ€์ƒ‰์–ด์˜ index์™€ selectedIndex๊ฐ€ ๊ฐ™์„ ๋•Œ background-color๊ฐ€ ๋ณ€๊ฒฝ๋˜๋„๋ก ๊ตฌํ˜„. + +### ๊ฒ€์ƒ‰์ฐฝ +======= ### ๊ฒ€์ƒ‰์ฐฝ ![2024-05-1010 09 48-ezgif com-video-to-gif-converter](https://github.com/Leeseunghwan7305/Infinite_Challenge_FE/assets/78102507/378ec5b8-452d-4c64-8147-4f8a909deea7) diff --git a/package-lock.json b/package-lock.json index ec487dd..4474517 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@tanstack/react-query": "^5.34.2", "@tanstack/react-query-devtools": "^5.35.1", "axios": "^1.6.8", + "chromatic": "^11.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.23.0", @@ -6255,7 +6256,6 @@ "version": "11.3.2", "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-11.3.2.tgz", "integrity": "sha512-0PuHl49VvBMoDHEfmNjC/bim9YYNhWF3axTZlFuatC0avwr2Xw4GDqJDG9fArEWN8oM8VtYHkE9D7qc87dmz2w==", - "dev": true, "bin": { "chroma": "dist/bin.js", "chromatic": "dist/bin.js", diff --git a/package.json b/package.json index 3823366..bcc653b 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,14 @@ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" + "build-storybook": "storybook build", + "chromatic": "npx chromatic --project-token=chpt_bc436d25f0df202" }, "dependencies": { "@tanstack/react-query": "^5.34.2", "@tanstack/react-query-devtools": "^5.35.1", "axios": "^1.6.8", + "chromatic": "^11.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.23.0", diff --git a/src/common/Nav.stories.ts b/src/common/Nav.stories.ts new file mode 100644 index 0000000..4b6e52a --- /dev/null +++ b/src/common/Nav.stories.ts @@ -0,0 +1,34 @@ +// Nav.stories.tsx +import { Meta, StoryObj } from "@storybook/react"; +import Nav from "./Nav"; +import { fn } from "@storybook/test"; + +const meta = { + title: "Common/Nav", + component: Nav, + parameters: { + layout: "centered", + }, + tages: ["autodocs"], + // decorators: [ + // (Story) => ( + //
+ // + //
+ // ), + // ], + args: { onClick: fn() }, +} as Meta; + +export default meta; + +type Story = StoryObj; + +export const Basic: Story = {}; diff --git a/src/components/main/DropBox.stories.tsx b/src/components/main/DropBox.stories.tsx index e69de29..b61159e 100644 --- a/src/components/main/DropBox.stories.tsx +++ b/src/components/main/DropBox.stories.tsx @@ -0,0 +1,89 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { fn } from "@storybook/test"; +import DropBox from "./DropBox"; + +const meta = { + title: "Components/DropBox", + component: DropBox, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + // argTypes: { + // type: "์ถ”์ฒœ๊ฒ€์ƒ‰์–ด", + // }, + args: { onClick: fn() }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} as Meta; + +export default meta; +type Story = StoryObj; + +export const RecommendedSearch: Story = { + args: { + type: "์ถ”์ฒœ๊ฒ€์ƒ‰์–ด", + searchLists: [ + { name: "Search Term 1", id: 1 }, + { name: "Search Term 2", id: 2 }, + { name: "Search Term 3", id: 3 }, + ], + selectedIndex: -1, + }, +}; + +export const RecentSearch: Story = { + args: { + type: "์ตœ๊ทผ๊ฒ€์ƒ‰์–ด", + searchLists: [ + { name: "Search Term 1", id: 1 }, + { name: "Search Term 2", id: 2 }, + { name: "Search Term 3", id: 3 }, + ], + selectedIndex: -1, + }, +}; + +// import React, { useState } from "react"; +// import { Story } from "@storybook/react"; +// import DropBox, { DropBoxProps } from "./DropBox"; + +// export default { +// title: "Components/DropBox", +// component: DropBox, +// }; + +// const Template: Story = (args) => { +// const [value, setValue] = useState(""); +// return ; +// }; + +// export const RecommendedSearch = Template.bind({}); +// RecommendedSearch.args = { +// type: "์ถ”์ฒœ๊ฒ€์ƒ‰์–ด", +// searchLists: [ +// { name: "Search Term 1", id: 1 }, +// { name: "Search Term 2", id: 2 }, +// { name: "Search Term 3", id: 3 }, +// ], +// selectedIndex: -1, +// }; + +// export const RecentSearch = Template.bind({}); +// RecentSearch.args = { +// type: "์ตœ๊ทผ๊ฒ€์ƒ‰์–ด", +// searchLists: [], +// selectedIndex: -1, +// }; diff --git a/src/components/main/NoResult.stories.tsx b/src/components/main/NoResult.stories.tsx new file mode 100644 index 0000000..ec7aa2c --- /dev/null +++ b/src/components/main/NoResult.stories.tsx @@ -0,0 +1,33 @@ +import { Meta, StoryObj } from "@storybook/react"; +import NoResult from "./NoResult"; + +const meta = { + title: "Components/NoResult", + component: NoResult, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} as Meta; + +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + title: "๊ฒ€์ƒ‰๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", + description: "๊ฒ€์ƒ‰์–ด๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•ด์ฃผ์„ธ์š”.", + }, +}; diff --git a/src/components/main/ResultList.stories.tsx b/src/components/main/ResultList.stories.tsx new file mode 100644 index 0000000..85d4a16 --- /dev/null +++ b/src/components/main/ResultList.stories.tsx @@ -0,0 +1,58 @@ +import { Meta, StoryObj } from "@storybook/react"; +import ResultList from "./ResultList"; +import { ResultListType } from "@/src/types/searchResult"; +import { fn } from "@storybook/test"; + +const meta = { + title: "Components/ResultList", + component: ResultList, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + args: { onClick: fn() }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} as Meta; + +export default meta; + +type Story = StoryObj; + +const sampleResults: ResultListType = { + from_type: 1, + url: "http://example.com", + id: 123, + ct_id: "NCT123456", + locations: [{ city: "San Francisco" }], + phases: ["Phase 1", "Phase 2"], + minimum_age_display: "18 Years", + maximum_age_display: "65 Years", + title: "Study of Something Important", + start_date: "2021-01-01", + completion_date: "2023-01-01", + lead_sponsor_name: "Big Pharma Inc.", + brief_summary: "This study investigates something important.", + gender: "All", + is_sponsor: true, + survey_id: null, + is_new: true, + created_at: "2020-12-31", +}; + +export const Basic: Story = { + args: { + searchResult: sampleResults, + toggleFavorites: () => {}, + location: "main", + }, +}; diff --git a/src/components/main/SearchBar.stories.tsx b/src/components/main/SearchBar.stories.tsx new file mode 100644 index 0000000..0627ea8 --- /dev/null +++ b/src/components/main/SearchBar.stories.tsx @@ -0,0 +1,61 @@ +import { Meta, StoryObj } from "@storybook/react"; +import SearchBar from "./SearchBar"; +import { useState } from "react"; +import { fn, userEvent, within } from "@storybook/test"; + +const meta = { + title: "Components/SearchBar", + component: SearchBar, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + decorators: [ + (Story) => ( +
+ +
+ ), + ], + args: { onClick: fn() }, +} as Meta; + +export default meta; + +type Story = StoryObj; + +const Render = (args: typeof meta) => { + const [value, setValue] = useState(""); + const onChange = (e: React.ChangeEvent) => { + setValue(e.target.value); + }; + const refetch = () => {}; + + return ( + + ); +}; + +export const Basic: Story = { + render: () => , + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const input = canvas.getAllByRole("textbox")[0]; + + await userEvent.type(input, "๊ฐ‘์ƒ์„ ", { + delay: 100, + }); + await userEvent.click(input); + }, +}; diff --git a/src/components/main/SearchBar.tsx b/src/components/main/SearchBar.tsx index d71f610..17c4312 100644 --- a/src/components/main/SearchBar.tsx +++ b/src/components/main/SearchBar.tsx @@ -91,7 +91,6 @@ const Input = styled.input` border: none; font-size: 16px; background-color: transparent; - pointer-events: none; input::placeholder { color: #bbbbbb; } diff --git a/src/stories/Button.stories.ts b/src/stories/Button.stories.ts deleted file mode 100644 index 2bfb143..0000000 --- a/src/stories/Button.stories.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -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", - }, -}; - -export const Warning: Story = { - args: { - primary: true, - label: "Delete now", - backgroundColor: "red", - }, -}; diff --git a/src/stories/Button.tsx b/src/stories/Button.tsx deleted file mode 100644 index 12ade3a..0000000 --- a/src/stories/Button.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import "./button.css"; - -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 80c71d0..0000000 --- a/src/stories/Header.stories.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -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 c06836b..0000000 --- a/src/stories/Header.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Button } from "./Button"; -import "./header.css"; - -type User = { - name: string; -}; - -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 f7a0681..0000000 --- a/src/stories/Page.stories.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { within, userEvent, expect } 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 interaction 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 e117483..0000000 --- a/src/stories/Page.tsx +++ /dev/null @@ -1,73 +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 dc91dc7..0000000 --- a/src/stories/button.css +++ /dev/null @@ -1,30 +0,0 @@ -.storybook-button { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-weight: 700; - border: 0; - border-radius: 3em; - cursor: pointer; - display: inline-block; - line-height: 1; -} -.storybook-button--primary { - color: white; - background-color: #1ea7fd; -} -.storybook-button--secondary { - color: #333; - background-color: transparent; - box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; -} -.storybook-button--small { - font-size: 12px; - padding: 10px 16px; -} -.storybook-button--medium { - font-size: 14px; - padding: 11px 20px; -} -.storybook-button--large { - font-size: 16px; - padding: 12px 24px; -} diff --git a/src/stories/header.css b/src/stories/header.css deleted file mode 100644 index d9a7052..0000000 --- a/src/stories/header.css +++ /dev/null @@ -1,32 +0,0 @@ -.storybook-header { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - padding: 15px 20px; - display: flex; - align-items: center; - justify-content: space-between; -} - -.storybook-header svg { - display: inline-block; - vertical-align: top; -} - -.storybook-header h1 { - font-weight: 700; - font-size: 20px; - line-height: 1; - margin: 6px 0 6px 10px; - display: inline-block; - vertical-align: top; -} - -.storybook-header button + button { - margin-left: 10px; -} - -.storybook-header .welcome { - color: #333; - font-size: 14px; - margin-right: 10px; -} diff --git a/src/stories/page.css b/src/stories/page.css deleted file mode 100644 index 098dad1..0000000 --- a/src/stories/page.css +++ /dev/null @@ -1,69 +0,0 @@ -.storybook-page { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 24px; - padding: 48px 20px; - margin: 0 auto; - max-width: 600px; - color: #333; -} - -.storybook-page h2 { - font-weight: 700; - font-size: 32px; - line-height: 1; - margin: 0 0 4px; - display: inline-block; - vertical-align: top; -} - -.storybook-page p { - margin: 1em 0; -} - -.storybook-page a { - text-decoration: none; - color: #1ea7fd; -} - -.storybook-page ul { - padding-left: 30px; - margin: 1em 0; -} - -.storybook-page li { - margin-bottom: 8px; -} - -.storybook-page .tip { - display: inline-block; - border-radius: 1em; - font-size: 11px; - line-height: 12px; - font-weight: 700; - background: #e7fdd8; - color: #66bf3c; - padding: 4px 12px; - margin-right: 10px; - vertical-align: top; -} - -.storybook-page .tip-wrapper { - font-size: 13px; - line-height: 20px; - margin-top: 40px; - margin-bottom: 40px; -} - -.storybook-page .tip-wrapper svg { - display: inline-block; - height: 12px; - width: 12px; - margin-right: 4px; - vertical-align: top; - margin-top: 3px; -} - -.storybook-page .tip-wrapper svg path { - fill: #1ea7fd; -}