diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 00000000..1d3cfed0 --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,42 @@ +name: 'Chromatic Deployment' + +on: + pull_request: + branches: + - develop + +permissions: write-all + +jobs: + chromatic: + name: 'Run Chromatic' + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Dependencies + run: yarn + + - name: Run Chromatic + id: chromatic + uses: chromaui/action@latest + with: + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} + onlyChanged: true + autoAcceptChanges: true + + - name: Create comment PR + uses: thollander/actions-comment-pull-request@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN}} + with: + comment_tag: ${{ github.event.pull_request.number }}-storybook + message: | + πŸͺ· Storybook 확인 πŸͺ· + πŸ”— ${{ steps.chromatic.outputs.storybookUrl }} + edit_mode: update diff --git a/.gitignore b/.gitignore index f940a995..93c98991 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ dist-ssr *.sw? *storybook.log +.env diff --git a/.storybook/main.ts b/.storybook/main.ts index 768eb03e..ba187032 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,15 +1,16 @@ -import type { StorybookConfig } from "@storybook/react-vite"; +import type { StorybookConfig } from '@storybook/react-vite'; const config: StorybookConfig = { - stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], addons: [ - "@storybook/addon-onboarding", - "@storybook/addon-essentials", - "@chromatic-com/storybook", - "@storybook/addon-interactions", + '@storybook/addon-onboarding', + '@storybook/addon-essentials', + '@chromatic-com/storybook', + '@storybook/addon-interactions', + 'storybook-addon-react-router-v6', ], framework: { - name: "@storybook/react-vite", + name: '@storybook/react-vite', options: {}, }, }; diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 00000000..82ee1ca8 --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,3 @@ + diff --git a/.storybook/preview.ts b/.storybook/preview.ts deleted file mode 100644 index 37914b18..00000000 --- a/.storybook/preview.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { Preview } from "@storybook/react"; - -const preview: Preview = { - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, -}; - -export default preview; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 00000000..d70c025d --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,38 @@ +import type { Preview } from '@storybook/react'; +import React from 'react'; +import { withRouter } from 'storybook-addon-react-router-v6'; +import '../src/styles/fonts.css'; +import '../src/styles/global.css'; +import '../src/styles/reset.css'; + +const preview: Preview = { + parameters: { + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + reactRouter: { + routePath: '/', + }, + }, + }, + decorators: [ + (Story) => ( + <> + + + + ), + withRouter, + ], +}; + +export default preview; diff --git a/index.html b/index.html index e4b78eae..0d8908fb 100644 --- a/index.html +++ b/index.html @@ -9,5 +9,8 @@
+ diff --git a/package.json b/package.json index 3a4e2fde..0831fe45 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,9 @@ "@vanilla-extract/vite-plugin": "^4.0.19", "axios": "^1.7.9", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-router-dom": "^7.1.1", + "storybook-addon-react-router-v6": "^2.0.15" }, "devDependencies": { "@chromatic-com/storybook": "^3.2.3", @@ -33,11 +35,13 @@ "@storybook/react-vite": "^8.4.7", "@storybook/test": "^8.4.7", "@tanstack/react-query-devtools": "^5.62.11", + "@types/navermaps": "^3.7.8", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", "@typescript-eslint/eslint-plugin": "^8.19.0", "@typescript-eslint/parser": "^8.19.0", "@vitejs/plugin-react": "^4.3.4", + "chromatic": "^11.22.1", "eslint": "^9.17.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^18.0.0", @@ -52,6 +56,7 @@ "globals": "^15.14.0", "prettier": "^3.4.2", "storybook": "^8.4.7", + "storybook-addon-react-router-v6": "^2.0.15", "typescript": "~5.6.2", "typescript-eslint": "^8.18.2", "vite": "^6.0.5", diff --git a/src/App.tsx b/src/App.tsx index bb37647d..50ec30ce 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,11 @@ +import { Outlet } from 'react-router-dom'; + const App = () => { - return
; + return ( +
+ +
+ ); }; export default App; diff --git a/src/assets/images/home_card_look.png b/src/assets/images/home_card_look.png new file mode 100644 index 00000000..87748743 Binary files /dev/null and b/src/assets/images/home_card_look.png differ diff --git a/src/assets/images/home_card_map.png b/src/assets/images/home_card_map.png new file mode 100644 index 00000000..d3a90191 Binary files /dev/null and b/src/assets/images/home_card_map.png differ diff --git a/src/assets/images/icn_map.png b/src/assets/images/icn_map.png new file mode 100644 index 00000000..789e8b10 Binary files /dev/null and b/src/assets/images/icn_map.png differ diff --git a/src/assets/images/img_gray_light_leaf_large.png b/src/assets/images/img_gray_light_leaf_large.png new file mode 100644 index 00000000..9aa3a88e Binary files /dev/null and b/src/assets/images/img_gray_light_leaf_large.png differ diff --git a/src/assets/images/img_pink_light_smile.png b/src/assets/images/img_pink_light_smile.png new file mode 100644 index 00000000..55af7a46 Binary files /dev/null and b/src/assets/images/img_pink_light_smile.png differ diff --git a/src/assets/images/img_yellow_light_smile.png b/src/assets/images/img_yellow_light_smile.png new file mode 100644 index 00000000..3f07931d Binary files /dev/null and b/src/assets/images/img_yellow_light_smile.png differ diff --git a/src/assets/svgs/icn_arrow_black_left.svg b/src/assets/svgs/icn_arrow_black_left.svg new file mode 100644 index 00000000..eb1139e6 --- /dev/null +++ b/src/assets/svgs/icn_arrow_black_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_arrow_gray_down.svg b/src/assets/svgs/icn_arrow_gray_down.svg new file mode 100644 index 00000000..3e98d924 --- /dev/null +++ b/src/assets/svgs/icn_arrow_gray_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_arrow_gray_right.svg b/src/assets/svgs/icn_arrow_gray_right.svg new file mode 100644 index 00000000..82f7530d --- /dev/null +++ b/src/assets/svgs/icn_arrow_gray_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_arrow_gray_up.svg b/src/assets/svgs/icn_arrow_gray_up.svg new file mode 100644 index 00000000..ea1053ee --- /dev/null +++ b/src/assets/svgs/icn_arrow_gray_up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_back_black_left.svg b/src/assets/svgs/icn_back_black_left.svg new file mode 100644 index 00000000..b75113bb --- /dev/null +++ b/src/assets/svgs/icn_back_black_left.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/svgs/icn_close_large_gray.svg b/src/assets/svgs/icn_close_large_gray.svg new file mode 100644 index 00000000..61adf0b9 --- /dev/null +++ b/src/assets/svgs/icn_close_large_gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_close_small_gray.svg b/src/assets/svgs/icn_close_small_gray.svg new file mode 100644 index 00000000..802d50be --- /dev/null +++ b/src/assets/svgs/icn_close_small_gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_divider.svg b/src/assets/svgs/icn_divider.svg new file mode 100644 index 00000000..58dd9728 --- /dev/null +++ b/src/assets/svgs/icn_divider.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_double_arrow_lightgray_down.svg b/src/assets/svgs/icn_double_arrow_lightgray_down.svg new file mode 100644 index 00000000..127b91c8 --- /dev/null +++ b/src/assets/svgs/icn_double_arrow_lightgray_down.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svgs/icn_filter.svg b/src/assets/svgs/icn_filter.svg new file mode 100644 index 00000000..28ed39e5 --- /dev/null +++ b/src/assets/svgs/icn_filter.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svgs/icn_flower_gray.svg b/src/assets/svgs/icn_flower_gray.svg new file mode 100644 index 00000000..36f53046 --- /dev/null +++ b/src/assets/svgs/icn_flower_gray.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/svgs/icn_flower_pink.svg b/src/assets/svgs/icn_flower_pink.svg new file mode 100644 index 00000000..a0fed754 --- /dev/null +++ b/src/assets/svgs/icn_flower_pink.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/svgs/icn_home.svg b/src/assets/svgs/icn_home.svg new file mode 100644 index 00000000..9f7ed6c7 --- /dev/null +++ b/src/assets/svgs/icn_home.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_insta.svg b/src/assets/svgs/icn_insta.svg new file mode 100644 index 00000000..54c99fca --- /dev/null +++ b/src/assets/svgs/icn_insta.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svgs/icn_kakao_logo.svg b/src/assets/svgs/icn_kakao_logo.svg new file mode 100644 index 00000000..a76f1b76 --- /dev/null +++ b/src/assets/svgs/icn_kakao_logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_large_heart_pink.svg b/src/assets/svgs/icn_large_heart_pink.svg new file mode 100644 index 00000000..5caffe2e --- /dev/null +++ b/src/assets/svgs/icn_large_heart_pink.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_linearrow_large_right.svg b/src/assets/svgs/icn_linearrow_large_right.svg new file mode 100644 index 00000000..f276e7f2 --- /dev/null +++ b/src/assets/svgs/icn_linearrow_large_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_linearrow_small_left.svg b/src/assets/svgs/icn_linearrow_small_left.svg new file mode 100644 index 00000000..88f35d6b --- /dev/null +++ b/src/assets/svgs/icn_linearrow_small_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_linearrow_small_right.svg b/src/assets/svgs/icn_linearrow_small_right.svg new file mode 100644 index 00000000..dbbe1cb8 --- /dev/null +++ b/src/assets/svgs/icn_linearrow_small_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_mypage.svg b/src/assets/svgs/icn_mypage.svg new file mode 100644 index 00000000..6f4fab57 --- /dev/null +++ b/src/assets/svgs/icn_mypage.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/svgs/icn_o.svg b/src/assets/svgs/icn_o.svg new file mode 100644 index 00000000..5c6d0f39 --- /dev/null +++ b/src/assets/svgs/icn_o.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_paste.svg b/src/assets/svgs/icn_paste.svg new file mode 100644 index 00000000..98712b69 --- /dev/null +++ b/src/assets/svgs/icn_paste.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_reset.svg b/src/assets/svgs/icn_reset.svg new file mode 100644 index 00000000..10158b2f --- /dev/null +++ b/src/assets/svgs/icn_reset.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_search_large_black.svg b/src/assets/svgs/icn_search_large_black.svg new file mode 100644 index 00000000..93ec7c55 --- /dev/null +++ b/src/assets/svgs/icn_search_large_black.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/svgs/icn_search_medium_gray.svg b/src/assets/svgs/icn_search_medium_gray.svg new file mode 100644 index 00000000..75d31913 --- /dev/null +++ b/src/assets/svgs/icn_search_medium_gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_small_heart.svg b/src/assets/svgs/icn_small_heart.svg new file mode 100644 index 00000000..47d0453a --- /dev/null +++ b/src/assets/svgs/icn_small_heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_wish.svg b/src/assets/svgs/icn_wish.svg new file mode 100644 index 00000000..cee57a3e --- /dev/null +++ b/src/assets/svgs/icn_wish.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icn_x.svg b/src/assets/svgs/icn_x.svg new file mode 100644 index 00000000..9e6bf9a1 --- /dev/null +++ b/src/assets/svgs/icn_x.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svgs/index.ts b/src/assets/svgs/index.ts index a71af172..cbeb4799 100644 --- a/src/assets/svgs/index.ts +++ b/src/assets/svgs/index.ts @@ -1 +1,67 @@ -// index.ts +import IcnArrowBlackLeft from './icn_arrow_black_left.svg'; +import IcnArrowGrayDown from './icn_arrow_gray_down.svg'; +import IcnArrowGrayRight from './icn_arrow_gray_right.svg'; +import IcnArrowGrayUp from './icn_arrow_gray_up.svg'; +import IcnBackBlackLeft from './icn_back_black_left.svg'; +import IcnCloseLargeGray from './icn_close_large_gray.svg'; +import IcnCloseSmallGray from './icn_close_small_gray.svg'; +import IcnDivider from './icn_divider.svg'; +import IcnDoubleArrowDown from './icn_double_arrow_lightgray_down.svg'; +import IcnFilter from './icn_filter.svg'; +import IcnFlowerGray from './icn_flower_gray.svg'; +import IcnFlowerPink from './icn_flower_pink.svg'; +import IcnHome from './icn_home.svg'; +import IcnInsta from './icn_insta.svg'; +import IcnKakaoLogo from './icn_kakao_logo.svg'; +import IcnLargeHeartPink from './icn_large_heart_pink.svg'; +import IcnLineArrowLargeRight from './icn_linearrow_large_right.svg'; +import IcnLineArrowSmallLeft from './icn_linearrow_small_left.svg'; +import IcnLineArrowSmallRight from './icn_linearrow_small_right.svg'; +import IcnMyPage from './icn_mypage.svg'; +import IcnO from './icn_o.svg'; +import IcnPaste from './icn_paste.svg'; +import IcnReset from './icn_reset.svg'; +import IcnSearchLargeBlack from './icn_search_large_black.svg'; +import IcnSearchMediumGray from './icn_search_medium_gray.svg'; +import IcnSmallHeart from './icn_small_heart.svg'; +import IcnWish from './icn_wish.svg'; +import IcnX from './icn_x.svg'; +import Logo from './logo.svg'; +import SmallLogo from './logo_small.svg'; +import XSmallLogo from './logo_xsmall.svg'; + +const Icon = { + Logo, + SmallLogo, + XSmallLogo, + IcnArrowBlackLeft, + IcnArrowGrayDown, + IcnArrowGrayRight, + IcnArrowGrayUp, + IcnCloseLargeGray, + IcnCloseSmallGray, + IcnFilter, + IcnHome, + IcnInsta, + IcnLargeHeartPink, + IcnLineArrowLargeRight, + IcnLineArrowSmallLeft, + IcnLineArrowSmallRight, + IcnMyPage, + IcnO, + IcnPaste, + IcnReset, + IcnSearchLargeBlack, + IcnSearchMediumGray, + IcnSmallHeart, + IcnWish, + IcnX, + IcnBackBlackLeft, + IcnFlowerPink, + IcnFlowerGray, + IcnDivider, + IcnKakaoLogo, + IcnDoubleArrowDown, +}; + +export default Icon; diff --git a/src/assets/svgs/logo.svg b/src/assets/svgs/logo.svg new file mode 100644 index 00000000..90d7271d --- /dev/null +++ b/src/assets/svgs/logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/svgs/logo_small.svg b/src/assets/svgs/logo_small.svg new file mode 100644 index 00000000..53b78472 --- /dev/null +++ b/src/assets/svgs/logo_small.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/svgs/logo_xsmall.svg b/src/assets/svgs/logo_xsmall.svg new file mode 100644 index 00000000..027fdcc4 --- /dev/null +++ b/src/assets/svgs/logo_xsmall.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/components/card/lookCard/LookCard.tsx b/src/components/card/lookCard/LookCard.tsx new file mode 100644 index 00000000..19964488 --- /dev/null +++ b/src/components/card/lookCard/LookCard.tsx @@ -0,0 +1,46 @@ +import ImgHookCard from '@assets/images/home_card_look.png'; +import BasicBtn from '@components/common/button/basicBtn/BasicBtn'; +import { useNavigate } from 'react-router-dom'; + +import * as styles from './lookCard.css'; + +interface LookCardProps { + name: string; +} + +const LookCard = ({ name }: LookCardProps) => { + const navigate = useNavigate(); + + const handleClick = () => { + navigate('/look'); // μΆ”ν›„ λ³€κ²½μ˜ˆμ • + }; + + return ( +
+
+ + {name} λ‹˜μ€ +
+ μ–΄λ–€ ν…œν”ŒμŠ€ν…Œμ΄λ₯Ό +
+ μ›ν•˜μ‹œλ‚˜μš”? +
+
+ +
+
+
+ ); +}; + +export default LookCard; diff --git a/src/components/card/lookCard/lookCard.css.ts b/src/components/card/lookCard/lookCard.css.ts new file mode 100644 index 00000000..598b3d16 --- /dev/null +++ b/src/components/card/lookCard/lookCard.css.ts @@ -0,0 +1,22 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const cardWrapper = style({ + backgroundSize: 'cover', + backgroundPosition: 'center', + width: '33.5rem', + height: '33.1rem', + borderRadius: 8, + padding: '2rem', +}); + +export const textBox = style({ + ...theme.FONTS.b0R22, + display: 'flex', + flexDirection: 'column', + gap: '1.2rem', +}); + +export const name = style({ + ...theme.FONTS.h0Sb22, +}); diff --git a/src/components/card/mapCard/LocBtn.tsx b/src/components/card/mapCard/LocBtn.tsx new file mode 100644 index 00000000..0e72bd60 --- /dev/null +++ b/src/components/card/mapCard/LocBtn.tsx @@ -0,0 +1,19 @@ +import btnContainer from '@components/card/mapCard/locBtn.css'; +import BasicBtn from '@components/common/button/basicBtn/BasicBtn'; + +interface LocBtnProps { + region: string; + top: number; + left: number; + onClick?: () => void; +} + +const LocBtn = ({ region, top, left, onClick }: LocBtnProps) => { + return ( +
+ +
+ ); +}; + +export default LocBtn; diff --git a/src/components/card/mapCard/Map.tsx b/src/components/card/mapCard/Map.tsx new file mode 100644 index 00000000..dee63191 --- /dev/null +++ b/src/components/card/mapCard/Map.tsx @@ -0,0 +1,15 @@ +import LocBtn from '@components/card/mapCard/LocBtn'; +import mapStyle from '@components/card/mapCard/map.css'; +import REGION_INFOS from '@constants/regionInfos'; + +const Map = () => { + return ( +
+ {Object.entries(REGION_INFOS).map(([region, { top, left }]) => ( + + ))} +
+ ); +}; + +export default Map; diff --git a/src/components/card/mapCard/MapCard.tsx b/src/components/card/mapCard/MapCard.tsx new file mode 100644 index 00000000..8ac04da6 --- /dev/null +++ b/src/components/card/mapCard/MapCard.tsx @@ -0,0 +1,19 @@ +import Icon from '@assets/svgs'; +import Map from '@components/card/mapCard/Map'; +import React from 'react'; + +import * as styles from './mapCard.css'; + +const MapCard = () => { + return ( +
+
+

μ›ν•˜λŠ” 지역을 κ³¨λΌλ³΄μ„Έμš”!

+ +
+ +
+ ); +}; + +export default MapCard; diff --git a/src/components/card/mapCard/locBtn.css.ts b/src/components/card/mapCard/locBtn.css.ts new file mode 100644 index 00000000..7bbbbfc0 --- /dev/null +++ b/src/components/card/mapCard/locBtn.css.ts @@ -0,0 +1,7 @@ +import { style } from '@vanilla-extract/css'; + +const btnContainer = style({ + position: 'absolute', +}); + +export default btnContainer; diff --git a/src/components/card/mapCard/map.css.ts b/src/components/card/mapCard/map.css.ts new file mode 100644 index 00000000..de7d9713 --- /dev/null +++ b/src/components/card/mapCard/map.css.ts @@ -0,0 +1,12 @@ +import { style } from '@vanilla-extract/css'; + +const mapStyle = style({ + width: '28.9rem', + height: '40.8rem', + backgroundImage: 'url(src/assets/images/home_card_map.png)', + backgroundSize: 'cover', + backgroundPosition: 'center', + position: 'relative', +}); + +export default mapStyle; diff --git a/src/components/card/mapCard/mapCard.css.ts b/src/components/card/mapCard/mapCard.css.ts new file mode 100644 index 00000000..be20818d --- /dev/null +++ b/src/components/card/mapCard/mapCard.css.ts @@ -0,0 +1,24 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const mapWrapper = style({ + borderRadius: 8, + backgroundColor: theme.COLORS.green6, + width: '33.5rem', + padding: '2.4rem 0 2.8rem 0', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', +}); + +export const titleBox = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', +}); + +export const title = style({ + ...theme.FONTS.b6M16, + color: theme.COLORS.gray9, +}); diff --git a/src/components/card/popularCard/PopularCard.tsx b/src/components/card/popularCard/PopularCard.tsx new file mode 100644 index 00000000..c3c5919f --- /dev/null +++ b/src/components/card/popularCard/PopularCard.tsx @@ -0,0 +1,66 @@ +import Icon from '@assets/svgs'; +import RankBtn from '@components/card/popularCard/RankBtn'; +import { useState } from 'react'; + +import * as styles from './popularCard.css'; + +interface PopularCardProps { + ranking: number; + templeName: string; + templeLoc: string; + templeImg: string; + tag: string; + onClick: () => void; + isLiked?: boolean; +} + +const PopularCard = ({ + ranking, + templeName, + templeLoc, + templeImg, + tag, + onClick, + isLiked = false, +}: PopularCardProps) => { + const [liked, setLiked] = useState(isLiked); + + const handleLikeClick = (e: React.MouseEvent) => { + e.stopPropagation(); + setLiked((prev) => !prev); + }; + + return ( +
{ + if (e.key === 'Enter' || e.key === ' ') { + onClick(); + } + }}> +
+
+ +
+
+
+ {templeName} +
+ {templeLoc} + + #{tag} +
+
+ +
+
+
+ ); +}; + +export default PopularCard; diff --git a/src/components/card/popularCard/RankBtn.tsx b/src/components/card/popularCard/RankBtn.tsx new file mode 100644 index 00000000..8c82e3fe --- /dev/null +++ b/src/components/card/popularCard/RankBtn.tsx @@ -0,0 +1,11 @@ +import rankBox from '@components/card/popularCard/rankBtn.css'; + +interface RankBtnProps { + ranking: number; +} + +const RankBtn = ({ ranking }: RankBtnProps) => { + return {ranking}; +}; + +export default RankBtn; diff --git a/src/components/card/popularCard/popularCard.css.ts b/src/components/card/popularCard/popularCard.css.ts new file mode 100644 index 00000000..6247ed42 --- /dev/null +++ b/src/components/card/popularCard/popularCard.css.ts @@ -0,0 +1,58 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const cardWrapper = style({ + width: '33.5rem', + cursor: 'pointer', +}); + +export const cardContainer = style({ + display: 'flex', + flexDirection: 'column', + gap: '0.8rem', +}); + +export const templeInfoBox = style({ + display: 'flex', + flexDirection: 'column', +}); + +export const templeName = style({ + ...theme.FONTS.h3Sb18, + textAlign: 'left', +}); + +export const bottomBox = style({ + display: 'flex', + color: theme.COLORS.gray8, + ...theme.FONTS.b9R15, + gap: '1rem', + alignItems: 'center', +}); + +export const likeBtn = style({ + padding: '1rem', +}); + +export const imgBox = style({ + height: '13.7rem', + borderRadius: 8, + display: 'flex', + justifyContent: 'flex-end', + color: theme.COLORS.white, + overflow: 'hidden', + backgroundSize: 'cover', + backgroundPosition: 'center', +}); + +export const bottomContainer = style({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'flex-start', + paddingTop: '0.8rem', +}); + +export const bottomWrapper = style({ + display: 'flex', + justifyContent: 'space-between', +}); diff --git a/src/components/card/popularCard/rankBtn.css.ts b/src/components/card/popularCard/rankBtn.css.ts new file mode 100644 index 00000000..d44d7bdb --- /dev/null +++ b/src/components/card/popularCard/rankBtn.css.ts @@ -0,0 +1,16 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +const rankBox = style({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '3.4rem', + height: '3.4rem', + borderRadius: '0 0 8px 8px', + backgroundColor: theme.COLORS.black60, + ...theme.FONTS.c2R14, + marginRight: '2rem', +}); + +export default rankBox; diff --git a/src/components/card/reviewCard/cardInfo/CardInfo.tsx b/src/components/card/reviewCard/cardInfo/CardInfo.tsx new file mode 100644 index 00000000..49739786 --- /dev/null +++ b/src/components/card/reviewCard/cardInfo/CardInfo.tsx @@ -0,0 +1,17 @@ +import * as styles from './cardInfo.css'; + +interface CardInfoProps { + reviewName: string | null; + reviewDate: string; +} + +const CardInfo = ({ reviewName, reviewDate }: CardInfoProps) => { + return ( +
+

{reviewName || '넀이버 λΈ”λ‘œκ·Έ'}

+

{reviewDate}

+
+ ); +}; + +export default CardInfo; diff --git a/src/components/card/reviewCard/cardInfo/cardInfo.css.ts b/src/components/card/reviewCard/cardInfo/cardInfo.css.ts new file mode 100644 index 00000000..54e2a3c0 --- /dev/null +++ b/src/components/card/reviewCard/cardInfo/cardInfo.css.ts @@ -0,0 +1,20 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const cardInfo = style({ + display: 'flex', + justifyContent: 'space-between', + + width: '100%', + + ...theme.FONTS.c6R13, + color: theme.COLORS.gray5, +}); + +export const reviewerName = style({ + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + textAlign: 'left', + width: '12.6rem', +}); diff --git a/src/components/card/reviewCard/reviewCard/ReviewCard.tsx b/src/components/card/reviewCard/reviewCard/ReviewCard.tsx new file mode 100644 index 00000000..3e61257b --- /dev/null +++ b/src/components/card/reviewCard/reviewCard/ReviewCard.tsx @@ -0,0 +1,49 @@ +import * as styles from './reviewCard.css'; +import CardInfo from '../cardInfo/CardInfo'; + +interface ReviewCardProps { + reviewTitle: string; + reviewDate: string; + reviewName: string | null; + reviewLink: string; + reviewDescription?: string | null; + blogImage: string | null; + size: 'small' | 'large'; +} + +const ReviewCard = ({ + reviewTitle, + reviewDate, + reviewName, + reviewLink, + reviewDescription, + blogImage, + size, +}: ReviewCardProps) => { + const handleButtonClick = () => { + window.location.href = reviewLink; + }; + + return ( + + ); +}; + +export default ReviewCard; diff --git a/src/components/card/reviewCard/reviewCard/reviewCard.css.ts b/src/components/card/reviewCard/reviewCard/reviewCard.css.ts new file mode 100644 index 00000000..a8a97a83 --- /dev/null +++ b/src/components/card/reviewCard/reviewCard/reviewCard.css.ts @@ -0,0 +1,111 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; +import { recipe } from '@vanilla-extract/recipes'; + +export const cardContainer = recipe({ + base: { + overflow: 'hidden', + border: `1px solid ${theme.COLORS.gray2}`, + backgroundColor: theme.COLORS.white, + selectors: { + '&:hover': { + filter: 'brightness(96%)', + }, + }, + }, + variants: { + size: { + small: { + width: '24rem', + height: '20rem', + borderRadius: 5, + }, + large: { + width: '33.5rem', + height: '23.2rem', + borderRadius: 4, + }, + }, + }, +}); + +export const cardImage = recipe({ + base: { + objectFit: 'cover', + overflow: 'hidden', + backgroundSize: 'cover', + backgroundPosition: 'center', + width: '100%', + }, + variants: { + size: { + small: { + height: '11.5rem', + }, + large: { + height: '11.8rem', + }, + }, + }, +}); + +export const cardContent = recipe({ + base: { + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + padding: '0 1.2rem', + gap: '1.2rem', + }, + variants: { + size: { + small: { + height: '8.5rem', + }, + large: { + height: '11.4rem', + }, + }, + }, +}); + +export const cardTitle = recipe({ + base: { + overflow: 'hidden', + textOverflow: 'ellipsis', + textAlign: 'left', + }, + variants: { + size: { + small: { + display: '-webkit-box', + WebkitBoxOrient: 'vertical', + WebkitLineClamp: 2, + height: '4.2rem', + marginTop: '0.6rem', + ...theme.FONTS.c1Sb15, + }, + large: { + marginTop: '1rem', + marginBottom: '0.4rem', + whiteSpace: 'nowrap', + width: '31.1rem', + ...theme.FONTS.h3Sb18, + }, + }, + }, +}); + +export const cardBody = style({ + display: '-webkit-box', + WebkitBoxOrient: 'vertical', + WebkitLineClamp: 2, + height: '3.6rem', + + overflow: 'hidden', + textOverflow: 'ellipsis', + textAlign: 'left', + + ...theme.FONTS.c2R14, + color: theme.COLORS.gray8, +}); diff --git a/src/components/card/templeStayCard/InfoSection.tsx b/src/components/card/templeStayCard/InfoSection.tsx new file mode 100644 index 00000000..b9f158f7 --- /dev/null +++ b/src/components/card/templeStayCard/InfoSection.tsx @@ -0,0 +1,30 @@ +import Tag from '@components/common/tag/Tag'; + +import * as styles from './infoSection.css'; + +interface InfoSectionProps { + templeName: string; + templestayName: string; + tag: string; + region: string; + type: string; +} + +const InfoSection = ({ templeName, templestayName, tag, region, type }: InfoSectionProps) => { + return ( +
+
+

#{tag}

+

+ {templeName} {templestayName} +

+
+
+ + +
+
+ ); +}; + +export default InfoSection; diff --git a/src/components/card/templeStayCard/TempleStayCard.tsx b/src/components/card/templeStayCard/TempleStayCard.tsx new file mode 100644 index 00000000..d462617a --- /dev/null +++ b/src/components/card/templeStayCard/TempleStayCard.tsx @@ -0,0 +1,69 @@ +import InfoSection from '@components/card/templeStayCard/InfoSection'; +import FlowerIcon from '@components/common/icon/flowerIcon/FlowerIcon'; +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import * as styles from './templeStayCard.css'; + +interface TempleStayCardProps { + id: number; + templeName: string; + templestayName: string; + tag: string; + region: string; + type: string; + imgUrl: string; + liked: boolean; + layout: 'vertical' | 'horizontal'; +} + +const TempleStayCard = ({ + templeName, + templestayName, + tag, + region, + type, + imgUrl, + liked, + layout, +}: TempleStayCardProps) => { + const [isWished, setIsWished] = useState(liked); + const navigate = useNavigate(); + const isHorizontal = layout === 'horizontal'; + + const onClickWishBtn = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsWished((prev) => !prev); + }; + const handleCardClick = () => { + navigate('/'); + }; + + return ( +
+
+ {templeName + +
+ + +
+ ); +}; + +export default TempleStayCard; diff --git a/src/components/card/templeStayCard/infoSection.css.ts b/src/components/card/templeStayCard/infoSection.css.ts new file mode 100644 index 00000000..7796602e --- /dev/null +++ b/src/components/card/templeStayCard/infoSection.css.ts @@ -0,0 +1,44 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const infoBox = style({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-around', + width: '16.3rem', +}); + +export const hashTag = style({ + width: '100%', + height: '1.7rem', + + color: theme.COLORS.gray5, + ...theme.FONTS.c5M13, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', +}); + +export const title = style({ + width: '100%', + height: '4.9rem', + alignContent: 'top', + + color: theme.COLORS.black, + ...theme.FONTS.h5Sb16, + + overflow: 'hidden', + display: '-webkit-box', + WebkitBoxOrient: 'vertical', + WebkitLineClamp: 2, + textOverflow: 'ellipsis', +}); + +export const tagBox = style({ + display: 'flex', + gap: '0.6rem', + width: '100%', + height: '2.1rem', + + marginTop: '0.8rem', +}); diff --git a/src/components/card/templeStayCard/templeStayCard.css.ts b/src/components/card/templeStayCard/templeStayCard.css.ts new file mode 100644 index 00000000..4c388ab1 --- /dev/null +++ b/src/components/card/templeStayCard/templeStayCard.css.ts @@ -0,0 +1,76 @@ +import { style } from '@vanilla-extract/css'; + +export const verticalContainer = style({ + display: 'flex', + flexDirection: 'column', + gap: '0.8rem', + width: '16.3rem', + height: '27.7rem', + cursor: 'pointer', +}); + +export const horizontalContainer = style({ + position: 'relative', + display: 'flex', + gap: '1.2rem', + width: '33.5rem', + height: '13.5rem', + + borderRadius: '4px', + cursor: 'pointer', +}); + +export const horizontalImgSection = style({ + width: '12rem', + height: '13.5rem', +}); + +export const verticalImgSection = style({ + position: 'relative', + width: '16.3rem', + height: '17.2rem', +}); + +export const verticalImage = style({ + width: '100%', + height: '100%', + objectFit: 'cover', + borderRadius: '4px', + + transition: '0.15s ease-out', + + selectors: { + [`${verticalContainer}:hover &`]: { + filter: 'brightness(88%)', + }, + }, +}); + +export const horizontalImage = style({ + width: '100%', + height: '100%', + objectFit: 'cover', + borderRadius: '4px 0 0 4px', + + transition: '0.15s ease-out', + + selectors: { + [`${horizontalContainer}:hover &`]: { + filter: 'brightness(88%)', + }, + }, +}); + +export const wishBtn = style({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + position: 'absolute', + bottom: '0', + right: '0', + + width: '4rem', + height: '4rem', + + zIndex: '2', +}); diff --git a/src/components/card/templeStayCard/wishCardList/WishCardList.tsx b/src/components/card/templeStayCard/wishCardList/WishCardList.tsx new file mode 100644 index 00000000..7ae11ed3 --- /dev/null +++ b/src/components/card/templeStayCard/wishCardList/WishCardList.tsx @@ -0,0 +1,40 @@ +import React from 'react'; + +import TempleStayCard from '../TempleStayCard'; +import container from './wishCardList.css'; + +interface WishCardListProps { + data: { + id: number; + templeName: string; + templestayName: string; + tag: string; + region: string; + type: string; + imgUrl: string; + liked: boolean; + }[]; + layout: 'vertical' | 'horizontal'; +} +const WishCardList = ({ data, layout = 'vertical' }: WishCardListProps) => { + return ( +
+ {data.map((temple) => ( + + ))} +
+ ); +}; + +export default WishCardList; diff --git a/src/components/card/templeStayCard/wishCardList/wishCardList.css.ts b/src/components/card/templeStayCard/wishCardList/wishCardList.css.ts new file mode 100644 index 00000000..0c498eff --- /dev/null +++ b/src/components/card/templeStayCard/wishCardList/wishCardList.css.ts @@ -0,0 +1,13 @@ +import { style } from '@vanilla-extract/css'; + +const container = style({ + marginTop: '1rem', + marginBottom: '6rem', + display: 'grid', + gridTemplateColumns: '1fr 1fr', + justifyContent: 'space-between', + gap: '4.4rem 0.9rem', + width: '33.5rem', +}); + +export default container; diff --git a/src/components/carousel/curationCarousel/CurationCarousel.tsx b/src/components/carousel/curationCarousel/CurationCarousel.tsx new file mode 100644 index 00000000..0505e04f --- /dev/null +++ b/src/components/carousel/curationCarousel/CurationCarousel.tsx @@ -0,0 +1,39 @@ +import CurationCard from '@components/curation/curationCard/CurationCard'; +import CURATION_INFO from '@constants/curationInfo'; +import useCarousel from '@hooks/useCarousel'; +import registDragEvent from '@utils/registDragEvent'; +import React from 'react'; + +import * as styles from './curationCarousel.css'; + +const CurationCarousel = () => { + const { carouselRef, transformStyle, handleDragChange, handleDragEnd } = useCarousel({ + itemCount: CURATION_INFO.length, + moveDistance: 295, + }); + + return ( +
+
+
+ {CURATION_INFO.map((data, index) => ( + + ))} +
+
+ ); +}; + +export default CurationCarousel; diff --git a/src/components/carousel/curationCarousel/curationCarousel.css.ts b/src/components/carousel/curationCarousel/curationCarousel.css.ts new file mode 100644 index 00000000..31513d96 --- /dev/null +++ b/src/components/carousel/curationCarousel/curationCarousel.css.ts @@ -0,0 +1,23 @@ +import { style } from '@vanilla-extract/css'; + +export const emptyBox = style({ + width: '2rem', + height: '100%', + flexShrink: 0, +}); + +export const carouselWrapper = style({ + width: '37.5rem', + overflow: 'hidden', + display: 'flex', + marginLeft: '-2rem', +}); + +export const carouselContainer = style({ + display: 'flex', + gap: '1rem', +}); + +export const carouselItem = style({ + flexShrink: 0, +}); diff --git a/src/components/carousel/popularCarousel/CarouselIndex.tsx b/src/components/carousel/popularCarousel/CarouselIndex.tsx new file mode 100644 index 00000000..ece63382 --- /dev/null +++ b/src/components/carousel/popularCarousel/CarouselIndex.tsx @@ -0,0 +1,22 @@ +import indexStyle, { indexContainer } from '@components/carousel/popularCarousel/carouselIndex.css'; +import React from 'react'; + +interface CarouselIndexProps { + total: number; + currentIndex: number; +} + +const CarouselIndex = ({ total, currentIndex }: CarouselIndexProps) => { + return ( +
+ {Array.from({ length: total }).map((_, index) => ( +
+ ))} +
+ ); +}; + +export default CarouselIndex; diff --git a/src/components/carousel/popularCarousel/PopularCarousel.tsx b/src/components/carousel/popularCarousel/PopularCarousel.tsx new file mode 100644 index 00000000..f867aefb --- /dev/null +++ b/src/components/carousel/popularCarousel/PopularCarousel.tsx @@ -0,0 +1,74 @@ +import PopularCard from '@components/card/popularCard/PopularCard'; +import useCarousel from '@hooks/useCarousel'; +import registDragEvent from '@utils/registDragEvent'; +import React from 'react'; + +import * as styles from './popularCarousel.css'; + +const stayData = { + rankings: [ + { + ranking: 1, + id: 1, + templeName: 'λŒ€μ›μ‚¬(보성)', + tag: 'λ°©κΈ‹λ°©κΈ‹', + region: '전남', + liked: true, + imgUrl: 'http://noms.templestay.com/images//RsImage/L_13774.png', + }, + { + ranking: 2, + id: 2, + templeName: 'μˆ˜μ›μ‚¬', + tag: 'λ°©κΈ‹λ°©κΈ‹', + region: 'κ²½κΈ°', + liked: true, + imgUrl: 'http://noms.templestay.com/images//RsImage/L_13774.png', + }, + { + ranking: 3, + id: 3, + templeName: '용ν₯사', + tag: 'λ°©κΈ‹λ°©κΈ‹', + region: '전남', + liked: false, + imgUrl: 'http://noms.templestay.com/images//RsImage/L_13774.png', + }, + ], +}; + +const PopularCarousel = () => { + const { carouselRef, transformStyle, handleDragChange, handleDragEnd } = useCarousel({ + itemCount: stayData.rankings.length, + moveDistance: 355, + }); + + return ( +
+
+ {stayData.rankings.map((data) => ( + { + alert(`${data.templeName} 클릭됨!`); + }} + /> + ))} +
+
+ ); +}; + +export default PopularCarousel; diff --git a/src/components/carousel/popularCarousel/carouselIndex.css.ts b/src/components/carousel/popularCarousel/carouselIndex.css.ts new file mode 100644 index 00000000..f6f11dd7 --- /dev/null +++ b/src/components/carousel/popularCarousel/carouselIndex.css.ts @@ -0,0 +1,30 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; +import { recipe } from '@vanilla-extract/recipes'; + +const indexStyle = recipe({ + base: { + width: '0.6rem', + height: '0.6rem', + borderRadius: '100%', + backgroundColor: theme.COLORS.gray3, + }, + variants: { + state: { + active: { + backgroundColor: theme.COLORS.gray11, + }, + inactive: {}, + }, + }, + defaultVariants: { + state: 'inactive', + }, +}); + +export const indexContainer = style({ + display: 'flex', + gap: '0.4rem', +}); + +export default indexStyle; diff --git a/src/components/carousel/popularCarousel/popularCarousel.css.ts b/src/components/carousel/popularCarousel/popularCarousel.css.ts new file mode 100644 index 00000000..9b1df44f --- /dev/null +++ b/src/components/carousel/popularCarousel/popularCarousel.css.ts @@ -0,0 +1,22 @@ +import { style } from '@vanilla-extract/css'; + +export const carouselWrapper = style({ + width: '33.5rem', + overflow: 'hidden', + display: 'flex', +}); + +export const carouselContainer = style({ + display: 'flex', + gap: '2rem', +}); + +export const carouselItem = style({ + flexShrink: 0, +}); + +export const emptyBox = style({ + width: '2rem', + height: '100%', + flexShrink: 0, +}); diff --git a/src/components/common/button/arrowBtn/ArrowBtn.tsx b/src/components/common/button/arrowBtn/ArrowBtn.tsx new file mode 100644 index 00000000..71686293 --- /dev/null +++ b/src/components/common/button/arrowBtn/ArrowBtn.tsx @@ -0,0 +1,16 @@ +import Icon from '@assets/svgs'; +import BtnBox from '@components/common/button/arrowBtn/arrowBtn.css'; + +interface ArrowBtnProps { + onClick: () => void; +} + +const ArrowBtn = ({ onClick }: ArrowBtnProps) => { + return ( + + ); +}; + +export default ArrowBtn; diff --git a/src/components/common/button/arrowBtn/arrowBtn.css.ts b/src/components/common/button/arrowBtn/arrowBtn.css.ts new file mode 100644 index 00000000..9caa6069 --- /dev/null +++ b/src/components/common/button/arrowBtn/arrowBtn.css.ts @@ -0,0 +1,15 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +const BtnBox = style({ + width: '4.4rem', + height: '4.4rem', + backgroundColor: theme.COLORS.white, + boxShadow: theme.COLORS.boxArrowBtnDropshadow, + borderRadius: '12px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}); + +export default BtnBox; diff --git a/src/components/common/button/basicBtn/BasicBtn.tsx b/src/components/common/button/basicBtn/BasicBtn.tsx new file mode 100644 index 00000000..80081e96 --- /dev/null +++ b/src/components/common/button/basicBtn/BasicBtn.tsx @@ -0,0 +1,37 @@ +import Icon from '@assets/svgs'; +import buttonStyle from '@components/common/button/basicBtn/basicBtn.css'; + +interface ButtonProps { + variant?: 'primary' | 'grayOutlined' | 'blackOutlined' | 'lightGrayOutlined' | 'green'; + size?: 'large' | 'medium' | 'small'; + label: string; + leftIcon?: keyof typeof Icon; + rightIcon?: keyof typeof Icon; + onClick?: () => void; + isActive?: boolean; +} + +const BasicBtn = ({ + variant = 'primary', + size = 'medium', + label, + leftIcon, + rightIcon, + onClick, + isActive = false, +}: ButtonProps) => { + const SelectedLeftIcon = leftIcon ? Icon[leftIcon] : null; + const SelectedRightIcon = rightIcon ? Icon[rightIcon] : null; + + return ( + + ); +}; + +export default BasicBtn; diff --git a/src/components/common/button/basicBtn/basicBtn.css.ts b/src/components/common/button/basicBtn/basicBtn.css.ts new file mode 100644 index 00000000..d5490654 --- /dev/null +++ b/src/components/common/button/basicBtn/basicBtn.css.ts @@ -0,0 +1,148 @@ +import theme from '@styles/theme.css'; +import { recipe } from '@vanilla-extract/recipes'; + +const buttonStyle = recipe({ + base: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + borderRadius: '40px', + transition: 'background-color 0.2s, color 0.2s, border 0.2s', // μž„μ‹œλ‘œ 넣어놓은 transition + boxSizing: 'border-box', + }, + + variants: { + color: { + primary: { + backgroundColor: theme.COLORS.white, + color: theme.COLORS.green5, + border: `1px solid ${theme.COLORS.green3}`, + + selectors: { + '&:hover': { + backgroundColor: theme.COLORS.green1, + color: theme.COLORS.green5, + border: `1px solid ${theme.COLORS.green4}`, + }, + }, + }, + grayOutlined: { + backgroundColor: theme.COLORS.white, + color: theme.COLORS.gray7, + border: `1px solid ${theme.COLORS.gray2}`, + + selectors: { + '&:hover': { + backgroundColor: theme.COLORS.gray1, + color: theme.COLORS.gray7, + border: `1px solid ${theme.COLORS.gray1}`, + }, + }, + }, + blackOutlined: { + backgroundColor: theme.COLORS.white, + color: theme.COLORS.gray10, + border: `1px solid ${theme.COLORS.gray3}`, + + selectors: { + '&:hover': { + color: theme.COLORS.gray10, + border: `1px solid ${theme.COLORS.gray7}`, + }, + }, + }, + lightGrayOutlined: { + backgroundColor: theme.COLORS.white, + color: theme.COLORS.gray10, + border: `1px solid ${theme.COLORS.gray3}`, + + selectors: { + '&:hover': { + backgroundColor: theme.COLORS.gray2, + border: `1px solid ${theme.COLORS.gray3}`, + }, + }, + }, + green: { + backgroundColor: theme.COLORS.green4, + color: theme.COLORS.white, + + selectors: { + '&:hover': { + backgroundColor: theme.COLORS.primary600, + }, + }, + }, + }, + + size: { + large: { + height: '4.1rem', + padding: '1rem 1.4rem', + ...theme.FONTS.b8M15, + gap: '0.4rem', + }, + medium: { + height: '3.6rem', + padding: '0.8rem 1.2rem', + ...theme.FONTS.c4M14, + gap: '0.2rem', + }, + small: { + height: '3rem', + padding: '0.6rem 0.8rem', + ...theme.FONTS.c2R14, + gap: '0.1rem', + }, + }, + + active: { + true: {}, + false: {}, + }, + }, + + compoundVariants: [ + { + variants: { color: 'primary', active: true }, + style: { + backgroundColor: theme.COLORS.green4, + color: theme.COLORS.white, + }, + }, + { + variants: { color: 'grayOutlined', active: true }, + style: { + border: `1px solid ${theme.COLORS.gray8}`, + color: theme.COLORS.gray11, + }, + }, + { + variants: { color: 'blackOutlined', active: true }, + style: { + border: `1px solid ${theme.COLORS.gray11}`, + color: theme.COLORS.gray11, + }, + }, + { + variants: { color: 'lightGrayOutlined', active: true }, + style: { + border: `1px solid ${theme.COLORS.gray7}`, + color: theme.COLORS.gray10, + }, + }, + { + variants: { color: 'blackOutlined', size: 'medium' }, + style: { + height: '3.7rem', + ...theme.FONTS.b8M15, + }, + }, + ], + + defaultVariants: { + active: false, + }, +}); + +export default buttonStyle; diff --git a/src/components/common/button/buttonBar/ButtonBar.tsx b/src/components/common/button/buttonBar/ButtonBar.tsx new file mode 100644 index 00000000..99afd2d2 --- /dev/null +++ b/src/components/common/button/buttonBar/ButtonBar.tsx @@ -0,0 +1,40 @@ +import buttonBarContainer from '@components/common/button/buttonBar/buttonBar.css'; +import FlowerBtn from '@components/common/button/flowerBtn/FlowerBtn'; +import PageBottomBtn from '@components/common/button/pageBottomBtn/PageBottomBtn'; +import TextBtn from '@components/common/button/textBtn/TextBtn'; +import { useState } from 'react'; + +interface ButtonBarProps { + type: 'reset' | 'wish'; + label: string; +} + +const ButtonBar = ({ type, label }: ButtonBarProps) => { + const [isActive, setIsActive] = useState(false); + + const onClickLefthBtn = () => { + setIsActive((prev) => !prev); + }; + + const renderLeftButton = () => + type === 'wish' ? ( + + ) : ( + + ); + + return ( +
+ {renderLeftButton()} + {}} /> +
+ ); +}; + +export default ButtonBar; diff --git a/src/components/common/button/buttonBar/buttonBar.css.ts b/src/components/common/button/buttonBar/buttonBar.css.ts new file mode 100644 index 00000000..8d239c4b --- /dev/null +++ b/src/components/common/button/buttonBar/buttonBar.css.ts @@ -0,0 +1,15 @@ +import { style } from '@vanilla-extract/css'; + +const buttonBarContainer = style({ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + width: '37.5rem', + height: '7.2rem', + padding: '1rem 2rem', + boxSizing: 'border-box', + + boxShadow: `0px -4px 16px 0px rgba(0, 0, 0, 0.05)`, +}); + +export default buttonBarContainer; diff --git a/src/components/common/button/flowerBtn/FlowerBtn.tsx b/src/components/common/button/flowerBtn/FlowerBtn.tsx new file mode 100644 index 00000000..87a9152d --- /dev/null +++ b/src/components/common/button/flowerBtn/FlowerBtn.tsx @@ -0,0 +1,29 @@ +import FlowerIcon from '@components/common/icon/FlowerIcon'; + +import * as styles from './flowerBtn.css'; + +interface FlowerBtnProps { + isRightIcn?: boolean; + isLeftIcn?: boolean; + label: string; + isActive: boolean; + onClick: () => void; +} + +const FlowerBtn = ({ + isRightIcn = false, + isLeftIcn = false, + label, + isActive, + onClick, +}: FlowerBtnProps) => { + return ( + + ); +}; + +export default FlowerBtn; diff --git a/src/components/common/button/flowerBtn/flowerBtn.css.ts b/src/components/common/button/flowerBtn/flowerBtn.css.ts new file mode 100644 index 00000000..e93d8a70 --- /dev/null +++ b/src/components/common/button/flowerBtn/flowerBtn.css.ts @@ -0,0 +1,30 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; +import { recipe } from '@vanilla-extract/recipes'; + +export const FlowerBtnStyle = style({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '4.4rem', + gap: '0.3rem', + + padding: '0 0.3rem', + boxSizing: 'border-box', +}); + +export const textStyle = recipe({ + base: { + ...theme.FONTS.b8M15, + color: theme.COLORS.gray7, + }, + + variants: { + active: { + false: {}, + true: { + color: theme.COLORS.gray10, + }, + }, + }, +}); diff --git a/src/components/common/button/infoBtn/InfoBtn.tsx b/src/components/common/button/infoBtn/InfoBtn.tsx new file mode 100644 index 00000000..190a900b --- /dev/null +++ b/src/components/common/button/infoBtn/InfoBtn.tsx @@ -0,0 +1,19 @@ +import Icon from '@assets/svgs'; +import buttonStyle from '@components/common/button/infoBtn/infoBtn.css'; + +interface InfoBtnProps { + label: string; + onClick: () => void; + hasDivider?: boolean; +} + +const InfoBtn = ({ label, onClick, hasDivider = false }: InfoBtnProps) => { + return ( + + ); +}; + +export default InfoBtn; diff --git a/src/components/common/button/infoBtn/infoBtn.css.ts b/src/components/common/button/infoBtn/infoBtn.css.ts new file mode 100644 index 00000000..2b6de608 --- /dev/null +++ b/src/components/common/button/infoBtn/infoBtn.css.ts @@ -0,0 +1,27 @@ +import theme from '@styles/theme.css'; +import { recipe } from '@vanilla-extract/recipes'; + +const buttonStyle = recipe({ + base: { + backgroundColor: 'inherit', + ...theme.FONTS.h5Sb16, + display: 'flex', + justifyContent: 'space-between', + width: '31.7rem', + padding: '1.6rem 2.3rem 1.6rem 0', + borderRadius: 8, + }, + variants: { + hasDivider: { + true: { + borderBottom: `1px solid ${theme.COLORS.gray2}`, + }, + false: {}, + }, + }, + defaultVariants: { + hasDivider: false, + }, +}); + +export default buttonStyle; diff --git a/src/components/common/button/kakaoBtn/KakaoBtn.tsx b/src/components/common/button/kakaoBtn/KakaoBtn.tsx new file mode 100644 index 00000000..ef5c9f29 --- /dev/null +++ b/src/components/common/button/kakaoBtn/KakaoBtn.tsx @@ -0,0 +1,18 @@ +import Icon from '@assets/svgs'; +import loginBtn from '@components/common/button/kakaoBtn/kakaoBtn.css'; +import React from 'react'; + +const KakaoBtn = () => { + const handleLogin = () => { + window.location.href = 'http://127.0.0.1:8080/oauth2/authorization/kakao'; + }; + + return ( + + ); +}; + +export default KakaoBtn; diff --git a/src/components/common/button/kakaoBtn/kakaoBtn.css.ts b/src/components/common/button/kakaoBtn/kakaoBtn.css.ts new file mode 100644 index 00000000..78fb2d9d --- /dev/null +++ b/src/components/common/button/kakaoBtn/kakaoBtn.css.ts @@ -0,0 +1,17 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const loginBtn = style({ + display: 'flex', + gap: '1rem', + alignItems: 'center', + justifyContent: 'center', + padding: '1.6rem', + width: '33.5rem', + backgroundColor: theme.COLORS.kakao, + borderRadius: 8, + + ...theme.FONTS.h4Sb17, +}); + +export default loginBtn; diff --git a/src/components/common/button/onboardingBtn/OnboardingBtn.tsx b/src/components/common/button/onboardingBtn/OnboardingBtn.tsx new file mode 100644 index 00000000..dc239c3d --- /dev/null +++ b/src/components/common/button/onboardingBtn/OnboardingBtn.tsx @@ -0,0 +1,24 @@ +import Icon from '@assets/svgs'; + +import { OnboardingBtnStyle, btnContentStyle } from './onboardingBtn.css'; + +interface OnboardingBtnProps { + btnText: string; + isActive?: boolean; + leftIcon?: keyof typeof Icon; +} + +const OnboardingBtn = ({ btnText, leftIcon, isActive = false }: OnboardingBtnProps) => { + const SelectedLeftIcon = leftIcon ? Icon[leftIcon] : null; + + return ( + + ); +}; + +export default OnboardingBtn; diff --git a/src/components/common/button/onboardingBtn/onboardingBtn.css.ts b/src/components/common/button/onboardingBtn/onboardingBtn.css.ts new file mode 100644 index 00000000..d24ad95e --- /dev/null +++ b/src/components/common/button/onboardingBtn/onboardingBtn.css.ts @@ -0,0 +1,40 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; +import { recipe } from '@vanilla-extract/recipes'; + +export const OnboardingBtnStyle = recipe({ + base: { + ...theme.FONTS.c1Sb15, + borderRadius: 40, + width: '15.5rem', + height: '5.2rem', + border: '1px solid', + }, + variants: { + active: { + true: { + backgroundColor: theme.COLORS.green1, + borderColor: theme.COLORS.primary200, + color: theme.COLORS.primary200, + '& svg path': { + stroke: theme.COLORS.primary200, + }, + '& svg circle': { + stroke: theme.COLORS.primary200, + }, + }, + false: { + backgroundColor: theme.COLORS.gray1, + borderColor: theme.COLORS.gray2, + color: theme.COLORS.gray10, + }, + }, + }, +}); + +export const btnContentStyle = style({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '0.8rem', +}); diff --git a/src/components/common/button/pageBottomBtn/PageBottomBtn.tsx b/src/components/common/button/pageBottomBtn/PageBottomBtn.tsx new file mode 100644 index 00000000..c9f1e406 --- /dev/null +++ b/src/components/common/button/pageBottomBtn/PageBottomBtn.tsx @@ -0,0 +1,23 @@ +import bottomBtnStyle from './pageBottomBtn.css'; + +interface PageBottomBtnProps { + btnText: string; + size: 'small' | 'large'; + isDisabled?: boolean; + onClick: () => void; +} + +const PageBottomBtn = ({ btnText, size, isDisabled = false, onClick }: PageBottomBtnProps) => { + const className = bottomBtnStyle({ + size, + isDisabled, + }); + + return ( + + ); +}; + +export default PageBottomBtn; diff --git a/src/components/common/button/pageBottomBtn/pageBottomBtn.css.ts b/src/components/common/button/pageBottomBtn/pageBottomBtn.css.ts new file mode 100644 index 00000000..1104b1f5 --- /dev/null +++ b/src/components/common/button/pageBottomBtn/pageBottomBtn.css.ts @@ -0,0 +1,43 @@ +import theme from '@styles/theme.css'; +import { recipe } from '@vanilla-extract/recipes'; + +const bottomBtnStyle = recipe({ + base: { + ...theme.FONTS.h5Sb16, + borderRadius: 8, + height: '5.2rem', + color: theme.COLORS.white, + }, + variants: { + size: { + small: { + width: '23.6rem', + }, + large: { + width: '33.5rem', + }, + }, + isDisabled: { + true: { + backgroundColor: theme.COLORS.gray5, + cursor: 'not-allowed', + }, + false: { + backgroundColor: theme.COLORS.primary400, + cursor: 'pointer', + }, + }, + }, + compoundVariants: [ + { + variants: { isDisabled: false }, + style: { + ':hover': { + backgroundColor: theme.COLORS.primary600, + }, + }, + }, + ], +}); + +export default bottomBtnStyle; diff --git a/src/components/common/button/pageBtn/PageBtn.tsx b/src/components/common/button/pageBtn/PageBtn.tsx new file mode 100644 index 00000000..80556c41 --- /dev/null +++ b/src/components/common/button/pageBtn/PageBtn.tsx @@ -0,0 +1,19 @@ +import pageBtnStyles from './pageBtn.css'; + +interface PageBtnProps { + pageIndex: number; + currentPageNum: number; + onClick: () => void; +} + +const PageBtn = ({ pageIndex, currentPageNum, onClick }: PageBtnProps) => { + return ( + + ); +}; + +export default PageBtn; diff --git a/src/components/common/button/pageBtn/pageBtn.css.ts b/src/components/common/button/pageBtn/pageBtn.css.ts new file mode 100644 index 00000000..4b73e1f0 --- /dev/null +++ b/src/components/common/button/pageBtn/pageBtn.css.ts @@ -0,0 +1,23 @@ +import theme from '@styles/theme.css'; +import { styleVariants } from '@vanilla-extract/css'; + +const commonPageNumStyles = { + width: '2.5rem', + height: '2.5rem', + ...theme.FONTS.c2R14, +}; + +const pageBtnStyles = styleVariants({ + current: { + ...commonPageNumStyles, + color: theme.COLORS.white, + borderRadius: 24, + backgroundColor: theme.COLORS.gray10, + }, + default: { + ...commonPageNumStyles, + color: theme.COLORS.gray10, + }, +}); + +export default pageBtnStyles; diff --git a/src/components/common/button/textBtn/TextBtn.tsx b/src/components/common/button/textBtn/TextBtn.tsx new file mode 100644 index 00000000..0efd72f9 --- /dev/null +++ b/src/components/common/button/textBtn/TextBtn.tsx @@ -0,0 +1,34 @@ +import Icon from '@assets/svgs'; + +import * as styles from './textBtn.css'; + +interface TextBtnProps { + clicked?: boolean; + size?: 'small' | 'medium'; + leftIcon?: keyof typeof Icon; + rightIcon?: keyof typeof Icon; + text: string; + onClick: () => void; +} + +const TextBtn = ({ + clicked = false, + size = 'small', + text, + leftIcon, + rightIcon, + onClick, +}: TextBtnProps) => { + const LeftIconComponent = leftIcon ? Icon[leftIcon] : null; + const RightIconComponent = rightIcon ? Icon[rightIcon] : null; + + return ( + + ); +}; + +export default TextBtn; diff --git a/src/components/common/button/textBtn/textBtn.css.ts b/src/components/common/button/textBtn/textBtn.css.ts new file mode 100644 index 00000000..974a8490 --- /dev/null +++ b/src/components/common/button/textBtn/textBtn.css.ts @@ -0,0 +1,64 @@ +import theme from '@styles/theme.css'; +import { recipe } from '@vanilla-extract/recipes'; + +export const iconStyle = recipe({ + base: { + color: 'currentColor', + }, + variants: { + size: { + small: { + width: '1.3rem', + height: '1.3rem', + }, + medium: { + width: '2rem', + height: '2rem', + }, + }, + }, +}); + +export const textBtnStyle = recipe({ + base: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '0.3rem', + color: theme.COLORS.gray5, + }, + + variants: { + clicked: { + false: {}, + true: {}, + }, + + size: { + small: { height: '3.3rem', color: theme.COLORS.gray5, ...theme.FONTS.c6R13 }, + medium: { + height: '4.4rem', + color: theme.COLORS.gray7, + padding: '0 0.8rem', + ...theme.FONTS.b8M15, + selectors: { + '&:active': { + color: theme.COLORS.gray10, + }, + }, + }, + }, + }, + + compoundVariants: [ + { + variants: { + clicked: true, + size: 'small', + }, + style: { + color: theme.COLORS.gray9, + }, + }, + ], +}); diff --git a/src/components/common/button/underlinedBtn/UnderlinedBtn.tsx b/src/components/common/button/underlinedBtn/UnderlinedBtn.tsx new file mode 100644 index 00000000..4e9cec60 --- /dev/null +++ b/src/components/common/button/underlinedBtn/UnderlinedBtn.tsx @@ -0,0 +1,17 @@ +import ButtonStyle from '@components/common/button/underlinedBtn/underlinedBtn.css'; + +interface UnderlinedBtnProps { + label: string; + onClick: () => void; + isActive: boolean; +} + +const UnderlinedBtn = ({ label, onClick, isActive = false }: UnderlinedBtnProps) => { + return ( + + ); +}; + +export default UnderlinedBtn; diff --git a/src/components/common/button/underlinedBtn/underlinedBtn.css.ts b/src/components/common/button/underlinedBtn/underlinedBtn.css.ts new file mode 100644 index 00000000..45f91a7b --- /dev/null +++ b/src/components/common/button/underlinedBtn/underlinedBtn.css.ts @@ -0,0 +1,24 @@ +import theme from '@styles/theme.css'; +import { recipe } from '@vanilla-extract/recipes'; + +const ButtonStyle = recipe({ + base: { + cursor: 'pointer', + padding: '10px 0', + ...theme.FONTS.h5Sb16, + }, + variants: { + isActive: { + true: { + color: theme.COLORS.black, + boxShadow: `0 1px 0 0 ${theme.COLORS.black}`, + }, + false: { + color: theme.COLORS.gray9, + boxShadow: 'none', + }, + }, + }, +}); + +export default ButtonStyle; diff --git a/src/components/common/detailInfo/DetailInfo.tsx b/src/components/common/detailInfo/DetailInfo.tsx new file mode 100644 index 00000000..6bc4f54b --- /dev/null +++ b/src/components/common/detailInfo/DetailInfo.tsx @@ -0,0 +1,17 @@ +import * as styles from './detailInfo.css'; + +interface DetailInfoProps { + title: string; + content: string; +} + +const DetailInfo = ({ title, content }: DetailInfoProps) => { + return ( +
+ {title} + {content} +
+ ); +}; + +export default DetailInfo; diff --git a/src/components/common/detailInfo/detailInfo.css.ts b/src/components/common/detailInfo/detailInfo.css.ts new file mode 100644 index 00000000..99545dbc --- /dev/null +++ b/src/components/common/detailInfo/detailInfo.css.ts @@ -0,0 +1,25 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const infoBox = style({ + width: '33.5rem', + padding: '1.2rem 1.4rem', + display: 'flex', + gap: '0.8rem', + borderRadius: '4px', + backgroundColor: theme.COLORS.gray1, +}); + +export const title = style({ + ...theme.FONTS.c3Sb14, + color: theme.COLORS.gray11, +}); + +export const content = style({ + ...theme.FONTS.c2R14, + color: theme.COLORS.gray10, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + wordBreak: 'break-all', + overflow: 'hidden', +}); diff --git a/src/components/common/empty/wishEmpty/WishEmpty.tsx b/src/components/common/empty/wishEmpty/WishEmpty.tsx new file mode 100644 index 00000000..5542deba --- /dev/null +++ b/src/components/common/empty/wishEmpty/WishEmpty.tsx @@ -0,0 +1,24 @@ +import emptyImage from '@assets/images/img_gray_light_leaf_large.png'; +import PageBottomBtn from '@components/common/button/pageBottomBtn/PageBottomBtn'; +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +import * as styles from './wishEmpty.css'; + +const WishEmpty = () => { + const navigate = useNavigate(); + const handleButtonClick = () => { + navigate('/'); + }; + return ( +
+
+

μœ„μ‹œλ¦¬μŠ€νŠΈκ°€ λΉ„μ–΄μžˆμ–΄μš”

+ μœ„μ‹œλ¦¬μŠ€νŠΈ λΉ„μ–΄μžˆμŒ +
+ +
+ ); +}; + +export default WishEmpty; diff --git a/src/components/common/empty/wishEmpty/wishEmpty.css.ts b/src/components/common/empty/wishEmpty/wishEmpty.css.ts new file mode 100644 index 00000000..9f8c35f1 --- /dev/null +++ b/src/components/common/empty/wishEmpty/wishEmpty.css.ts @@ -0,0 +1,29 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const container = style({ + height: '30.2rem', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '6.2rem', + marginTop: '11.4rem', +}); + +export const boxStyle = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '3.8rem', + width: '17.8rem', +}); + +export const textStyle = style({ + ...theme.FONTS.h3Sb18, + color: theme.COLORS.gray7, +}); + +export const imgStyle = style({ + width: '16.1rem', + height: '12.5rem', +}); diff --git a/src/components/common/icon/FlowerIcon.tsx b/src/components/common/icon/FlowerIcon.tsx new file mode 100644 index 00000000..eb0f19a1 --- /dev/null +++ b/src/components/common/icon/FlowerIcon.tsx @@ -0,0 +1,11 @@ +import Icon from '@assets/svgs'; + +interface FlowerIconProps { + isActive?: boolean; +} + +const FlowerIcon = ({ isActive = false }: FlowerIconProps) => { + return <>{isActive ? : }; +}; + +export default FlowerIcon; diff --git a/src/components/common/icon/flowerIcon/FlowerIcon.tsx b/src/components/common/icon/flowerIcon/FlowerIcon.tsx new file mode 100644 index 00000000..eb0f19a1 --- /dev/null +++ b/src/components/common/icon/flowerIcon/FlowerIcon.tsx @@ -0,0 +1,11 @@ +import Icon from '@assets/svgs'; + +interface FlowerIconProps { + isActive?: boolean; +} + +const FlowerIcon = ({ isActive = false }: FlowerIconProps) => { + return <>{isActive ? : }; +}; + +export default FlowerIcon; diff --git a/src/components/common/pageName/PageName.tsx b/src/components/common/pageName/PageName.tsx new file mode 100644 index 00000000..28b0bb9a --- /dev/null +++ b/src/components/common/pageName/PageName.tsx @@ -0,0 +1,29 @@ +import Icon from '@assets/svgs'; +import React from 'react'; + +import * as PageNameStyle from './pageName.css'; + +interface PageNameProps { + onLeftClick: () => void; + title: string; + onRightClick?: () => void; + isLikeBtn?: boolean; +} + +const PageName = ({ onLeftClick, title, onRightClick, isLikeBtn = true }: PageNameProps) => { + return ( + + ); +}; + +export default PageName; diff --git a/src/components/common/pageName/pageName.css.ts b/src/components/common/pageName/pageName.css.ts new file mode 100644 index 00000000..e919d7fe --- /dev/null +++ b/src/components/common/pageName/pageName.css.ts @@ -0,0 +1,29 @@ +import theme from '@styles/theme.css'; +import { style, globalStyle } from '@vanilla-extract/css'; + +export const container = style({ + display: 'grid', + gridTemplateColumns: '4rem 1fr 4rem', + width: '33.5rem', + height: '4rem', + alignItems: 'center', +}); + +export const buttonLayout = style({ + width: '4rem', + height: '4rem', +}); + +globalStyle(`${buttonLayout} svg`, { + width: '2.4rem', + height: '2.4rem', +}); + +export const titleStyle = style({ + gridColumn: '2', + textAlign: 'center', + width: '20.3rem', + margin: '0 auto', + ...theme.FONTS.h3Sb18, + color: theme.COLORS.black, +}); diff --git a/src/components/common/pagination/Pagination.tsx b/src/components/common/pagination/Pagination.tsx new file mode 100644 index 00000000..48682295 --- /dev/null +++ b/src/components/common/pagination/Pagination.tsx @@ -0,0 +1,63 @@ +import Icon from '@assets/svgs'; +import PAGINATION_UNIT from '@constants/constants'; +import React from 'react'; + +import * as styles from './pagination.css'; +import PageBtn from '../button/pageBtn/PageBtn'; + +interface PaginationProps { + currentPage: number; + totalPages: number; + onPageChange: (page: number) => void; + color: 'gray' | 'white'; +} + +const Pagination = ({ currentPage, totalPages, onPageChange, color }: PaginationProps) => { + const calculatePageRange = () => { + const rangeStart = Math.floor((currentPage - 1) / PAGINATION_UNIT) * PAGINATION_UNIT + 1; + const rangeEnd = Math.min(rangeStart + PAGINATION_UNIT - 1, totalPages); + return { rangeStart, rangeEnd }; + }; + + const { rangeStart, rangeEnd } = calculatePageRange(); + + const isLeftDisabled = rangeStart === 1; + const isRightDisabled = rangeEnd === totalPages; + + const renderPageNumbers = () => { + const pages = Array.from({ length: rangeEnd - rangeStart + 1 }, (_, i) => rangeStart + i); + + return pages.map((page) => ( + onPageChange(page)} + /> + )); + }; + + return ( + + ); +}; + +export default Pagination; diff --git a/src/components/common/pagination/pagination.css.ts b/src/components/common/pagination/pagination.css.ts new file mode 100644 index 00000000..a819f801 --- /dev/null +++ b/src/components/common/pagination/pagination.css.ts @@ -0,0 +1,69 @@ +import theme from '@styles/theme.css'; +import { style, styleVariants } from '@vanilla-extract/css'; +import { recipe } from '@vanilla-extract/recipes'; + +export const paginationContainer = style({ + display: 'flex', + alignItems: 'center', + gap: '0.3rem', + height: '3.7rem', + padding: '0.6rem 0.8rem', + borderRadius: '2.4rem', +}); + +export const containerColors = styleVariants({ + gray: { + backgroundColor: theme.COLORS.gray1, + }, + white: { + backgroundColor: theme.COLORS.white, + }, +}); + +export const arrowStyle = recipe({ + base: { + width: '3.6rem', + display: 'flex', + cursor: 'pointer', + selectors: { + '&:disabled': { + cursor: 'not-allowed', + }, + }, + }, + variants: { + direction: { + left: { + paddingLeft: '0.4rem', + paddingRight: '1.6rem', + }, + right: { + paddingLeft: '1.6rem', + paddingRight: '0.4rem', + }, + }, + isDisabled: { + true: { + cursor: 'not-allowed', + }, + false: {}, + }, + }, +}); + +export const iconStyle = style({ + color: theme.COLORS.gray10, + transition: 'color 0.2s ease-in-out', +}); + +export const disabledIcon = style({ + color: theme.COLORS.gray4, +}); + +export const dotStyle = style({ + width: '2.6rem', + height: '2.6rem', + padding: '0 0.8rem', + ...theme.FONTS.c2R14, + color: theme.COLORS.gray10, +}); diff --git a/src/components/common/progressBar/ProgressBar.tsx b/src/components/common/progressBar/ProgressBar.tsx new file mode 100644 index 00000000..265fc69a --- /dev/null +++ b/src/components/common/progressBar/ProgressBar.tsx @@ -0,0 +1,29 @@ +import Icon from '@assets/svgs'; +import React from 'react'; + +import * as ProgressBarStyle from './progressBar.css'; + +interface ProgressBarProps { + currentStep: number; + totalSteps: number; + onBackClick: () => void; +} + +const ProgressBar = ({ currentStep, totalSteps, onBackClick }: ProgressBarProps) => { + const progressPercentage = (currentStep / totalSteps) * 100; + + return ( +
+ +
+
+
+
+ ); +}; + +export default ProgressBar; diff --git a/src/components/common/progressBar/progressBar.css.ts b/src/components/common/progressBar/progressBar.css.ts new file mode 100644 index 00000000..d3e21796 --- /dev/null +++ b/src/components/common/progressBar/progressBar.css.ts @@ -0,0 +1,31 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const container = style({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'start', + width: '37.5rem', + height: '6.4rem', + paddingTop: '1.2rem', +}); + +export const backButton = style({ + width: '4rem', + height: '4rem', + marginLeft: '1.2rem', +}); + +export const barContainer = style({ + width: '100%', + height: '0.2rem', + backgroundColor: theme.COLORS.gray1, + overflow: 'hidden', +}); + +export const barStyle = style({ + height: '100%', + backgroundColor: theme.COLORS.primary400, + transition: 'width 0.3s ease-in-out', +}); diff --git a/src/components/common/tag/Tag.tsx b/src/components/common/tag/Tag.tsx new file mode 100644 index 00000000..b8422ec7 --- /dev/null +++ b/src/components/common/tag/Tag.tsx @@ -0,0 +1,12 @@ +import tagBox from '@components/common/tag/tag.css'; + +interface TagProps { + label: string; + color: 'brown' | 'blue' | 'gray'; +} + +const Tag = ({ label, color }: TagProps) => { + return

{label}

; +}; + +export default Tag; diff --git a/src/components/common/tag/tag.css.ts b/src/components/common/tag/tag.css.ts new file mode 100644 index 00000000..ae35eb88 --- /dev/null +++ b/src/components/common/tag/tag.css.ts @@ -0,0 +1,29 @@ +import theme from '@styles/theme.css'; +import { recipe } from '@vanilla-extract/recipes'; + +const tagBox = recipe({ + base: { + padding: '0.3rem 0.8rem', + borderRadius: '4px', + ...theme.FONTS.c7R12, + }, + + variants: { + color: { + brown: { + backgroundColor: theme.COLORS.brown1, + color: theme.COLORS.brown2, + }, + blue: { + backgroundColor: theme.COLORS.blue1, + color: theme.COLORS.blue2, + }, + gray: { + backgroundColor: theme.COLORS.black60, + color: theme.COLORS.white, + }, + }, + }, +}); + +export default tagBox; diff --git a/src/components/common/tapBar/TapBar.tsx b/src/components/common/tapBar/TapBar.tsx new file mode 100644 index 00000000..cfb1de93 --- /dev/null +++ b/src/components/common/tapBar/TapBar.tsx @@ -0,0 +1,32 @@ +import UnderlinedBtn from '@components/common/button/underlinedBtn/UnderlinedBtn'; +import tapBarContainer from '@components/common/tapBar/tapBar.css'; +import { TapType, TAPS } from '@constants/taps'; +import { useState } from 'react'; + +interface TapBarProps { + type: TapType; +} + +const TapBar = ({ type }: TapBarProps) => { + const taplist = TAPS[type]; + const [activeIndex, setActiveIndex] = useState(0); + + const handleTabClick = (index: number) => { + setActiveIndex(index); + }; + + return ( +
+ {taplist.map((label, index) => ( + handleTabClick(index)} + /> + ))} +
+ ); +}; + +export default TapBar; diff --git a/src/components/common/tapBar/tapBar.css.ts b/src/components/common/tapBar/tapBar.css.ts new file mode 100644 index 00000000..2c07c2a0 --- /dev/null +++ b/src/components/common/tapBar/tapBar.css.ts @@ -0,0 +1,15 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +const tapBarContainer = style({ + display: 'flex', + justifyContent: 'space-between', + + width: '37.5rem', + height: '4.2rem', + padding: '0 2rem', + + boxShadow: theme.COLORS.filerDropshadow, +}); + +export default tapBarContainer; diff --git a/src/components/curation/curationCard/CurationCard.tsx b/src/components/curation/curationCard/CurationCard.tsx new file mode 100644 index 00000000..f93bb4e2 --- /dev/null +++ b/src/components/curation/curationCard/CurationCard.tsx @@ -0,0 +1,24 @@ +import * as styles from './curationCard.css'; + +interface CurationCardProps { + bgImage: string; + title: string; + subtitle: string; + onClick: () => void; +} + +const CurationCard = ({ bgImage, title, subtitle, onClick }: CurationCardProps) => { + return ( + + ); +}; + +export default CurationCard; diff --git a/src/components/curation/curationCard/curationCard.css.ts b/src/components/curation/curationCard/curationCard.css.ts new file mode 100644 index 00000000..05c70c9e --- /dev/null +++ b/src/components/curation/curationCard/curationCard.css.ts @@ -0,0 +1,31 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const cardContainer = style({ + width: '28.5rem', + height: '26.8rem', + borderRadius: '8px', + overflow: 'hidden', + backgroundSize: 'cover', + backgroundPosition: 'center', + display: 'flex', + alignItems: 'flex-end', + flexShrink: 0, + + padding: '1.6rem', + color: theme.COLORS.white, +}); + +export const textbox = style({ + display: 'flex', + flexDirection: 'column', + textAlign: 'start', +}); + +export const title = style({ + ...theme.FONTS.h3Sb18, +}); + +export const subtitle = style({ + ...theme.FONTS.b9R15, +}); diff --git a/src/components/detailTitle/DetailTitle.tsx b/src/components/detailTitle/DetailTitle.tsx new file mode 100644 index 00000000..55c34c24 --- /dev/null +++ b/src/components/detailTitle/DetailTitle.tsx @@ -0,0 +1,30 @@ +import { titleContainerStyle, titleStyle, buttonStyle } from './detailTitle.css'; + +interface DetailTitleProps { + title: string; + isTotal?: boolean; + onClick?: () => void; + size?: 'small' | 'medium'; + rigntBtnLabel?: string; +} + +const DetailTitle = ({ + title, + isTotal = false, + onClick, + size = 'medium', + rigntBtnLabel = '젠체보기', +}: DetailTitleProps) => { + return ( +
+

{title}

+ {isTotal && ( + + )} +
+ ); +}; + +export default DetailTitle; diff --git a/src/components/detailTitle/detailTitle.css.ts b/src/components/detailTitle/detailTitle.css.ts new file mode 100644 index 00000000..505b78ad --- /dev/null +++ b/src/components/detailTitle/detailTitle.css.ts @@ -0,0 +1,29 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; +import { recipe } from '@vanilla-extract/recipes'; + +export const titleContainerStyle = style({ + width: '33.5rem', + height: '3.3rem', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', +}); + +export const buttonStyle = style({ + ...theme.FONTS.c6R13, + color: theme.COLORS.gray8, +}); + +export const titleStyle = recipe({ + variants: { + size: { + small: { + ...theme.FONTS.h5Sb16, + }, + medium: { + ...theme.FONTS.h2Sb20, + }, + }, + }, +}); diff --git a/src/components/footer/Footer.tsx b/src/components/footer/Footer.tsx new file mode 100644 index 00000000..51ed7294 --- /dev/null +++ b/src/components/footer/Footer.tsx @@ -0,0 +1,49 @@ +import Icon from '@assets/svgs'; + +import * as styles from './footer.css'; + +const Footer = () => { + return ( + + ); +}; + +export default Footer; diff --git a/src/components/footer/footer.css.ts b/src/components/footer/footer.css.ts new file mode 100644 index 00000000..c827a1a4 --- /dev/null +++ b/src/components/footer/footer.css.ts @@ -0,0 +1,59 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const footerContainer = style({ + width: '37.5rem', + height: '18.7rem', + padding: '2rem', + marginLeft: '-2rem', + position: 'absolute', + bottom: 0, + backgroundColor: theme.COLORS.gray1, +}); + +export const topBox = style({ + display: 'flex', + flexDirection: 'column', + gap: '0.4rem', + marginBottom: '4.8rem', +}); + +export const topBoxText = style({ + ...theme.FONTS.f3R10, + color: theme.COLORS.gray6, +}); + +export const infoContainer = style({ + width: '2.7rem', + height: '4.4rem', + ...theme.FONTS.f2Sb10, +}); + +export const contentContainer = style({ + display: 'flex', + alignItems: 'center', + marginTop: '0.6rem', + marginBottom: '1rem', + gap: '0.6rem', +}); + +export const contentTitle = style({ + ...theme.FONTS.f2Sb10, + color: theme.COLORS.gray6, + + width: '2.7rem', +}); + +export const contentBody = style({ + ...theme.FONTS.f3R10, + color: theme.COLORS.gray6, +}); + +export const bottomContainer = style({ + display: 'flex', + alignItems: 'center', + gap: '0.8rem', + + ...theme.FONTS.f1Sb11, + color: theme.COLORS.gray11, +}); diff --git a/src/components/header/Header.tsx b/src/components/header/Header.tsx new file mode 100644 index 00000000..2ba6a604 --- /dev/null +++ b/src/components/header/Header.tsx @@ -0,0 +1,22 @@ +import Icon from '@assets/svgs'; + +import * as styles from './header.css'; + +const Header = () => { + const handleClick = () => {}; + + return ( +
+ + +
+ ); +}; + +export default Header; diff --git a/src/components/header/header.css.ts b/src/components/header/header.css.ts new file mode 100644 index 00000000..75ed307b --- /dev/null +++ b/src/components/header/header.css.ts @@ -0,0 +1,24 @@ +import { style } from '@vanilla-extract/css'; + +export const headerContainer = style({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + + width: '33.1rem', + height: '4rem', +}); + +export const iconBox = style({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '1.6rem', + + width: '12rem', + height: '4rem', +}); + +export const iconStyle = style({ + cursor: 'pointer', +}); diff --git a/src/components/schedule/ScheduleCard.tsx b/src/components/schedule/ScheduleCard.tsx new file mode 100644 index 00000000..008fe011 --- /dev/null +++ b/src/components/schedule/ScheduleCard.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import * as styles from './scheduleCard.css'; + +interface ScheduleCardProps { + day: string; + programs: { [time: string]: string }; +} + +const ScheduleCard = ({ day, programs }: ScheduleCardProps) => { + return ( +
+

{day}

+
    + {Object.entries(programs).map(([time, description], index) => ( +
  • + {time} + {description} +
  • + ))} +
+
+ ); +}; + +export default ScheduleCard; diff --git a/src/components/schedule/scheduleCard.css.ts b/src/components/schedule/scheduleCard.css.ts new file mode 100644 index 00000000..09d60449 --- /dev/null +++ b/src/components/schedule/scheduleCard.css.ts @@ -0,0 +1,43 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const cardContainer = style({ + display: 'flex', + width: '33.5rem', + padding: '0.6rem 1.4rem', + flexDirection: 'column', + alignItems: 'flex-start', + borderRadius: 8, + backgroundColor: theme.COLORS.gray1, +}); + +export const dayStyle = style({ + ...theme.FONTS.c1Sb15, + color: theme.COLORS.black, + width: '30.7rem', + height: '4.1rem', + paddingTop: '0.8rem', + borderBottom: `0.1rem solid ${theme.COLORS.gray3}`, +}); + +export const programItem = style({ + display: 'flex', + gap: '1.2rem', + padding: '0.6rem 0', + width: '30.7rem', + borderBottom: `0.1rem solid ${theme.COLORS.gray3}`, +}); + +export const timeStyle = style({ + ...theme.FONTS.b9R15, + color: theme.COLORS.gray10, + width: '9rem', + fontFeatureSettings: "'tnum'", + fontVariantNumeric: 'tabular-nums', +}); + +export const description = style({ + ...theme.FONTS.b8M15, + color: theme.COLORS.black, + width: '20.5rem', +}); diff --git a/src/components/search/recentBtn/RecentBtnBox.tsx b/src/components/search/recentBtn/RecentBtnBox.tsx new file mode 100644 index 00000000..758c18de --- /dev/null +++ b/src/components/search/recentBtn/RecentBtnBox.tsx @@ -0,0 +1,24 @@ +import BasicBtn from '@components/common/button/basicBtn/BasicBtn'; +import * as styles from '@components/search/recentBtn/recentBtnBox.css'; + +const RecentBtnBox = ({ data }: { data: { id: number; content: string }[] }) => { + return ( +
+ {data.length > 0 ? ( + data.map((item) => ( + + )) + ) : ( +

졜근 검색 내역이 μ—†μ–΄μš”

+ )} +
+ ); +}; + +export default RecentBtnBox; diff --git a/src/components/search/recentBtn/recentBtnBox.css.ts b/src/components/search/recentBtn/recentBtnBox.css.ts new file mode 100644 index 00000000..0c71bfe9 --- /dev/null +++ b/src/components/search/recentBtn/recentBtnBox.css.ts @@ -0,0 +1,25 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const recentBtnBox = style({ + display: 'flex', + gap: '0.8rem', + marginTop: '0.8rem', + paddingRight: '2rem', + + overflowX: 'auto', + whiteSpace: 'nowrap', + + selectors: { + '&::-webkit-scrollbar': { + display: 'none', + }, + }, +}); + +export const emptyResult = style({ + margin: '3.2rem 0 0 10rem', + + color: theme.COLORS.gray7, + ...theme.FONTS.c2R14, +}); diff --git a/src/components/search/searchBar/SearchBar.tsx b/src/components/search/searchBar/SearchBar.tsx new file mode 100644 index 00000000..abf65c92 --- /dev/null +++ b/src/components/search/searchBar/SearchBar.tsx @@ -0,0 +1,37 @@ +import Icon from '@assets/svgs'; +import { useState } from 'react'; + +import * as styles from './searchBar.css'; + +const SearchBar = () => { + const [inputValue, setInputValue] = useState(''); + + const handleChangeInput = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + }; + + const handleClearInput = () => { + setInputValue(''); + }; + + return ( +
+
+
+ +
+ +
+
+ +
+
+ ); +}; + +export default SearchBar; diff --git a/src/components/search/searchBar/searchBar.css.ts b/src/components/search/searchBar/searchBar.css.ts new file mode 100644 index 00000000..c3e3c714 --- /dev/null +++ b/src/components/search/searchBar/searchBar.css.ts @@ -0,0 +1,43 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const searchBarContainer = style({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + gap: '0.6rem', + + width: '29.5rem', + height: '4.4rem', + padding: '1rem 0.8rem', + borderRadius: 8, + backgroundColor: theme.COLORS.gray1, + + ...theme.FONTS.b8M15, + color: theme.COLORS.black, +}); + +export const searchBarLayout = style({ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + gap: '0.6rem', + + width: '24.9rem', + height: '2.4rem', +}); + +export const inputStyle = style({ + width: '21.9rem', + backgroundColor: 'inherit', + + selectors: { + '&::placeholder': { + color: theme.COLORS.gray5, + }, + }, +}); + +export const pointer = style({ + cursor: 'pointer', +}); diff --git a/src/components/search/searchHeader/SearchHeader.tsx b/src/components/search/searchHeader/SearchHeader.tsx new file mode 100644 index 00000000..32c73a2f --- /dev/null +++ b/src/components/search/searchHeader/SearchHeader.tsx @@ -0,0 +1,14 @@ +import Icon from '@assets/svgs'; +import SearchBar from '@components/search/searchBar/SearchBar'; +import searchHeaderStyle from '@components/search/searchHeader/searchHeader.css'; + +const SearchHeader = () => { + return ( +
+ + +
+ ); +}; + +export default SearchHeader; diff --git a/src/components/search/searchHeader/searchHeader.css.ts b/src/components/search/searchHeader/searchHeader.css.ts new file mode 100644 index 00000000..281bc1a2 --- /dev/null +++ b/src/components/search/searchHeader/searchHeader.css.ts @@ -0,0 +1,14 @@ +import { style } from '@vanilla-extract/css'; + +const searchHeaderStyle = style({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: '0.8rem', + width: '37.5rem', + height: '4.4rem', + + marginBottom: '2rem', +}); + +export default searchHeaderStyle; diff --git a/src/components/templeDetail/naverMap/MapContainer.tsx b/src/components/templeDetail/naverMap/MapContainer.tsx new file mode 100644 index 00000000..b493f15f --- /dev/null +++ b/src/components/templeDetail/naverMap/MapContainer.tsx @@ -0,0 +1,43 @@ +import smallMarker from '@assets/images/icn_map.png'; +import { useEffect } from 'react'; + +interface MapContainerProps { + latitude: number; + longitude: number; + size: 'small' | 'large'; +} + +const MapContainer = ({ latitude, longitude, size }: MapContainerProps) => { + useEffect(() => { + const { naver } = window; + + const isSmall = size === 'small'; + + const mapOptions = { + center: new naver.maps.LatLng(latitude, longitude), + zoom: isSmall ? 16 : 19, + disableDoubleClickZoom: isSmall, + scrollWheel: !isSmall, + draggable: !isSmall, + pinchZoom: !isSmall, + keyboardShortcuts: !isSmall, + }; + + const map = new naver.maps.Map('map', mapOptions); + + new naver.maps.Marker({ + position: new naver.maps.LatLng(latitude, longitude), + map, + icon: { + url: smallMarker, + size: isSmall ? new naver.maps.Size(53, 53) : new naver.maps.Size(63, 63), + scaledSize: isSmall ? new naver.maps.Size(53, 53) : new naver.maps.Size(63, 63), + anchor: isSmall ? new naver.maps.Point(26.5, 26.5) : new naver.maps.Size(31.5, 31.5), + }, + }); + }, [latitude, longitude, size]); + + return
; +}; + +export default MapContainer; diff --git a/src/components/templeDetail/naverMap/largeMap/LargeMap.tsx b/src/components/templeDetail/naverMap/largeMap/LargeMap.tsx new file mode 100644 index 00000000..db121bbe --- /dev/null +++ b/src/components/templeDetail/naverMap/largeMap/LargeMap.tsx @@ -0,0 +1,30 @@ +import ArrowBtn from '@components/common/button/arrowBtn/ArrowBtn'; +import useNavigateTo from '@hooks/useNavigateTo'; +import { useLocation } from 'react-router-dom'; + +import * as styles from './largeMap.css'; +import MapContainer from '../MapContainer'; + +const LargeMap = () => { + const handleToBack = useNavigateTo(-1); + const location = useLocation(); + const params = new URLSearchParams(location.search); + const latitudeString = params.get('latitude'); + const longitudeString = params.get('longitude'); + + const latitude = latitudeString ? parseFloat(latitudeString) : 0; + const longitude = longitudeString ? parseFloat(longitudeString) : 0; + + return ( + <> +
+
+ +
+ +
+ + ); +}; + +export default LargeMap; diff --git a/src/components/templeDetail/naverMap/largeMap/largeMap.css.ts b/src/components/templeDetail/naverMap/largeMap/largeMap.css.ts new file mode 100644 index 00000000..0846dfec --- /dev/null +++ b/src/components/templeDetail/naverMap/largeMap/largeMap.css.ts @@ -0,0 +1,15 @@ +import { style } from '@vanilla-extract/css'; + +export const largeMapContainer = style({ + width: '100%', + height: '100vh', + margin: '-1.2rem -2rem 0', + position: 'relative', +}); + +export const arrowBtn = style({ + position: 'absolute', + top: '5.9rem', + left: '2rem', + zIndex: 1, +}); diff --git a/src/components/templeDetail/naverMap/smallMap/SmallMap.tsx b/src/components/templeDetail/naverMap/smallMap/SmallMap.tsx new file mode 100644 index 00000000..f5f02755 --- /dev/null +++ b/src/components/templeDetail/naverMap/smallMap/SmallMap.tsx @@ -0,0 +1,40 @@ +import TextBtn from '@components/common/button/textBtn/TextBtn'; +import DetailTitle from '@components/detailTitle/DetailTitle'; +import useNavigateTo from '@hooks/useNavigateTo'; + +import * as styles from './smallMap.css'; +import MapContainer from '../MapContainer'; + +const mapData = { + address: 'μ„œμšΈ μš©μ‚°κ΅¬ 청파둜 387', + latitude: 37.55433, + longitude: 126.9686, +}; + +const SmallMap = () => { + const copyToClipboard = () => { + navigator.clipboard.writeText(mapData.address); + alert('μ£Όμ†Œκ°€ λ³΅μ‚¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€.'); + }; + + const navigateToHome = useNavigateTo( + `/detail/map?latitude=${mapData.latitude}&longitude=${mapData.longitude}`, + ); + + return ( + <> + +
+
+

{mapData.address}

+ +
+ +
+ + ); +}; + +export default SmallMap; diff --git a/src/components/templeDetail/naverMap/smallMap/smallMap.css.ts b/src/components/templeDetail/naverMap/smallMap/smallMap.css.ts new file mode 100644 index 00000000..40524d71 --- /dev/null +++ b/src/components/templeDetail/naverMap/smallMap/smallMap.css.ts @@ -0,0 +1,25 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const mapContainerStyle = style({ + display: 'flex', + flexDirection: 'column', + gap: '0.8rem', +}); + +export const addressDetailStyle = style({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + + width: '33.5rem', + height: '3.4rem', + + ...theme.FONTS.b9R15, + color: theme.COLORS.gray11, +}); + +export const mapStyle = style({ + width: '33.5rem', + height: '21.2rem', +}); diff --git a/src/components/templeDetail/templeInfo/contentCollapse/ContentCollapse.tsx b/src/components/templeDetail/templeInfo/contentCollapse/ContentCollapse.tsx new file mode 100644 index 00000000..7432e27e --- /dev/null +++ b/src/components/templeDetail/templeInfo/contentCollapse/ContentCollapse.tsx @@ -0,0 +1,23 @@ +import Icon from '@assets/svgs'; + +import * as styles from './contentCollapse.css'; + +interface ContetnCollapseProps { + leftIcon: keyof typeof Icon; + text: string; + onClick: () => void; +} + +const ContentCollapse = ({ leftIcon, text, onClick }: ContetnCollapseProps) => { + const LeftIconComponent = Icon[leftIcon]; + return ( +
+ +
+ ); +}; + +export default ContentCollapse; diff --git a/src/components/templeDetail/templeInfo/contentCollapse/contentCollapse.css.ts b/src/components/templeDetail/templeInfo/contentCollapse/contentCollapse.css.ts new file mode 100644 index 00000000..91f25718 --- /dev/null +++ b/src/components/templeDetail/templeInfo/contentCollapse/contentCollapse.css.ts @@ -0,0 +1,18 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const contentCollapseContainer = style({ + display: 'flex', + justifyContent: 'center', + width: '33.5rem', + height: '4.5rem', +}); + +export const collapseButtonBox = style({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '0.3rem', + ...theme.FONTS.b8M15, + color: theme.COLORS.gray7, +}); diff --git a/src/components/templeDetail/templeInfo/templeInfo.css.ts b/src/components/templeDetail/templeInfo/templeInfo.css.ts new file mode 100644 index 00000000..ac5d6614 --- /dev/null +++ b/src/components/templeDetail/templeInfo/templeInfo.css.ts @@ -0,0 +1,37 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const templeInfoContainer = style({ + display: 'flex', + flexDirection: 'column', + gap: '0.4rem', +}); + +export const templeInfoBoxStyle = style({ + display: 'flex', + flexDirection: 'column', + width: '33.5rem', + minHeight: '9rem', + gap: '0.2rem', +}); + +export const templeInfoTitle = style({ + ...theme.FONTS.h5Sb16, + color: theme.COLORS.gray10, +}); + +export const templeInfoContent = style({ + display: '-webkit-box', + WebkitBoxOrient: 'vertical', + WebkitLineClamp: 3, + textOverflow: 'ellipsis', + overflow: 'hidden', + minHeight: '6.6rem', + ...theme.FONTS.b7R16, + color: theme.COLORS.gray10, +}); + +export const expandedContent = style({ + WebkitLineClamp: 'unset', + overflow: 'visible', +}); diff --git a/src/components/templeDetail/templeInfo/templeInfo.tsx b/src/components/templeDetail/templeInfo/templeInfo.tsx new file mode 100644 index 00000000..439335f7 --- /dev/null +++ b/src/components/templeDetail/templeInfo/templeInfo.tsx @@ -0,0 +1,41 @@ +import DetailTitle from '@components/detailTitle/DetailTitle'; +import useExpandHook from '@hooks/useExpandHook/useExpandHook'; +import { useRef } from 'react'; + +import ContentCollapse from './contentCollapse/ContentCollapse'; +import * as styles from './templeInfo.css'; + +const TEMPLEINFODATA = { + introduction: [ + 'μ°¨ ν•œ μž”μ˜ 행볡 (νœ΄μ‹ν˜•)', + 'μƒˆμ†Œλ¦¬λ₯Ό λ“€μœΌλ©° μž μ‹œ μˆ²κΈΈμ„ κ±°λ‹κ±°λ‚˜μƒˆμ†Œλ¦¬λ₯Ό λ“€μœΌλ©° μž μ‹œ μˆ²κΈΈμ„ κ±°λ‹κ±°λ‚˜μƒˆμ†Œλ¦¬λ₯Ό 길을 κ±°λ‹κ±°λ‚˜μƒˆμ†Œλ¦¬λ₯Ό 길을 κ±°λ‹κ±°λ‚˜μƒˆμ†Œλ¦¬λ₯Ό', + ], +}; + +const TempleInfo = () => { + const contentRef = useRef(null); + const { isAppeared, isExpanded, handleToggleExpand } = useExpandHook(contentRef); + + return ( +
+ +
+

{TEMPLEINFODATA.introduction[0]}

+

+ {TEMPLEINFODATA.introduction[1]} +

+ {isAppeared && ( + + )} +
+
+ ); +}; + +export default TempleInfo; diff --git a/src/components/userInfo/userInfo.css.ts b/src/components/userInfo/userInfo.css.ts new file mode 100644 index 00000000..62cb4385 --- /dev/null +++ b/src/components/userInfo/userInfo.css.ts @@ -0,0 +1,13 @@ +import { style } from '@vanilla-extract/css'; + +const infoContainerStyle = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '3rem', + height: '56.8rem', + paddingTop: '1rem', + width: '33.5rem', +}); + +export default infoContainerStyle; diff --git a/src/components/userInfo/userInfo.tsx b/src/components/userInfo/userInfo.tsx new file mode 100644 index 00000000..c2d86490 --- /dev/null +++ b/src/components/userInfo/userInfo.tsx @@ -0,0 +1,42 @@ +import USER_INFO from '@constants/userInfo'; + +import infoContainerStyle from './userInfo.css'; +import AccountActions from './userInfoContent/accountAction/AccountAction'; +import HelpSection from './userInfoContent/helpContent/HelpContent'; +import TopInfo from './userInfoContent/nameContent/NameContent'; +import MemberInfo from './userInfoContent/userDetailInfo.tsx/UserDetailInfo'; +import UserInfoSection from './userInfoContent/userInfoSection/userInfoSection'; + +const UserInfo = () => { + const handleNoticeClick = () => { + alert('notice click'); + }; + const handleQuestionClick = () => { + alert('question click'); + }; + const handleLogoutClick = () => { + alert('logout click'); + }; + const handleDeleteClick = () => { + alert('delete click'); + }; + + return ( +
+ + + + + + + + +
+ ); +}; + +export default UserInfo; diff --git a/src/components/userInfo/userInfoContent/accountAction/AccountAction.tsx b/src/components/userInfo/userInfoContent/accountAction/AccountAction.tsx new file mode 100644 index 00000000..64085cb8 --- /dev/null +++ b/src/components/userInfo/userInfoContent/accountAction/AccountAction.tsx @@ -0,0 +1,20 @@ +import * as styles from './accountAction.css'; + +interface HelpSectionProps { + onLogoutClick: () => void; + onDeleteClick: () => void; +} + +const AccountActions = ({ onLogoutClick, onDeleteClick }: HelpSectionProps) => ( +
+ +
+ +
+); + +export default AccountActions; diff --git a/src/components/userInfo/userInfoContent/accountAction/accountAction.css.ts b/src/components/userInfo/userInfoContent/accountAction/accountAction.css.ts new file mode 100644 index 00000000..4d39ac62 --- /dev/null +++ b/src/components/userInfo/userInfoContent/accountAction/accountAction.css.ts @@ -0,0 +1,22 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const accountActionBoxStyle = style({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '1.8rem', +}); + +export const accountActionButtonStyle = style({ + width: '4.6rem', + height: '3.3rem', + ...theme.FONTS.c6R13, + color: theme.COLORS.gray5, +}); + +export const smallDivider = style({ + width: '0.1rem', + height: '1.4rem', + backgroundColor: theme.COLORS.gray5, +}); diff --git a/src/components/userInfo/userInfoContent/helpContent/HelpContent.tsx b/src/components/userInfo/userInfoContent/helpContent/HelpContent.tsx new file mode 100644 index 00000000..be04efe3 --- /dev/null +++ b/src/components/userInfo/userInfoContent/helpContent/HelpContent.tsx @@ -0,0 +1,17 @@ +import InfoBtn from '@components/common/button/infoBtn/InfoBtn'; + +import userHelpStyle from './helpContent.css'; + +interface HelpSectionProps { + onNoticeClick: () => void; + onQuestionClick: () => void; +} + +const HelpSection = ({ onNoticeClick, onQuestionClick }: HelpSectionProps) => ( + +); + +export default HelpSection; diff --git a/src/components/userInfo/userInfoContent/helpContent/helpContent.css.ts b/src/components/userInfo/userInfoContent/helpContent/helpContent.css.ts new file mode 100644 index 00000000..0f3f70e3 --- /dev/null +++ b/src/components/userInfo/userInfoContent/helpContent/helpContent.css.ts @@ -0,0 +1,15 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +const userHelpStyle = style({ + display: 'flex', + padding: '0 0 0 1.8rem', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'flex-start', + borderRadius: 8, + + backgroundColor: theme.COLORS.gray1, +}); + +export default userHelpStyle; diff --git a/src/components/userInfo/userInfoContent/nameContent/NameContent.tsx b/src/components/userInfo/userInfoContent/nameContent/NameContent.tsx new file mode 100644 index 00000000..6467613a --- /dev/null +++ b/src/components/userInfo/userInfoContent/nameContent/NameContent.tsx @@ -0,0 +1,18 @@ +import * as styles from './nameContent.css'; + +interface TopInfoProps { + nickname: string; + email: string; +} + +const TopInfo = ({ nickname, email }: TopInfoProps) => ( +
+
+

{nickname}

+

λ‹˜

+
+

{email}

+
+); + +export default TopInfo; diff --git a/src/components/userInfo/userInfoContent/nameContent/nameContent.css.ts b/src/components/userInfo/userInfoContent/nameContent/nameContent.css.ts new file mode 100644 index 00000000..565f6e15 --- /dev/null +++ b/src/components/userInfo/userInfoContent/nameContent/nameContent.css.ts @@ -0,0 +1,26 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const topInfoStyle = style({ + display: 'flex', + width: '33.5rem', + padding: '1.8rem', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'flex-start', + gap: '0.4rem', + + borderRadius: 8, + backgroundColor: theme.COLORS.gray1, +}); + +export const userNameStyle = style({ + display: 'flex', + gap: '0.2rem', + ...theme.FONTS.h2Sb20, +}); + +export const userEmailStyle = style({ + ...theme.FONTS.c2R14, + color: theme.COLORS.gray8, +}); diff --git a/src/components/userInfo/userInfoContent/userDetailInfo.tsx/UserDetailInfo.tsx b/src/components/userInfo/userInfoContent/userDetailInfo.tsx/UserDetailInfo.tsx new file mode 100644 index 00000000..30b2e19a --- /dev/null +++ b/src/components/userInfo/userInfoContent/userDetailInfo.tsx/UserDetailInfo.tsx @@ -0,0 +1,28 @@ +import * as styles from './userDetailInfo.css'; + +interface MemberInfoProps { + ageRange: string; + gender: string; + religion: string; +} + +const MemberInfo = ({ ageRange, gender, religion }: MemberInfoProps) => ( +
+
+

μ—°λ ΉλŒ€

+

{ageRange}

+
+
+
+

성별

+

{gender}

+
+
+
+

쒅ꡐ

+

{religion}

+
+
+); + +export default MemberInfo; diff --git a/src/components/userInfo/userInfoContent/userDetailInfo.tsx/userDetailInfo.css.ts b/src/components/userInfo/userInfoContent/userDetailInfo.tsx/userDetailInfo.css.ts new file mode 100644 index 00000000..0563c517 --- /dev/null +++ b/src/components/userInfo/userInfoContent/userDetailInfo.tsx/userDetailInfo.css.ts @@ -0,0 +1,35 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const infoDetailContainer = style({ + display: 'flex', + padding: '1.6rem 2.4rem', + justifyContent: 'center', + alignItems: 'center', + gap: '2rem', + borderRadius: 8, + backgroundColor: theme.COLORS.gray1, +}); + +export const infoDetailBox = style({ + display: 'flex', + width: '6.8rem', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + gap: '0.8rem', +}); + +export const infoDetailTitle = style({ + ...theme.FONTS.c2R14, +}); + +export const infoDetailData = style({ + ...theme.FONTS.h5Sb16, +}); + +export const divider = style({ + width: '0.1rem', + height: '5.3rem', + backgroundColor: theme.COLORS.gray2, +}); diff --git a/src/components/userInfo/userInfoContent/userInfoSection/userInfoSection.css.ts b/src/components/userInfo/userInfoContent/userInfoSection/userInfoSection.css.ts new file mode 100644 index 00000000..16fe864b --- /dev/null +++ b/src/components/userInfo/userInfoContent/userInfoSection/userInfoSection.css.ts @@ -0,0 +1,16 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const userInfoStyle = style({ + display: 'flex', + width: '33.5rem', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'flex-start', + gap: '1.2rem', +}); + +export const infoStepStyle = style({ + ...theme.FONTS.c2R14, + color: theme.COLORS.gray11, +}); diff --git a/src/components/userInfo/userInfoContent/userInfoSection/userInfoSection.tsx b/src/components/userInfo/userInfoContent/userInfoSection/userInfoSection.tsx new file mode 100644 index 00000000..e7b88e4f --- /dev/null +++ b/src/components/userInfo/userInfoContent/userInfoSection/userInfoSection.tsx @@ -0,0 +1,15 @@ +import * as styles from './userInfoSection.css'; + +interface UserInfoSectionProps { + title: string; + children: React.ReactNode; +} + +const UserInfoSection = ({ title, children }: UserInfoSectionProps) => ( +
+

{title}

+
{children}
+
+); + +export default UserInfoSection; diff --git a/src/constants/constants.ts b/src/constants/constants.ts new file mode 100644 index 00000000..640cb1a7 --- /dev/null +++ b/src/constants/constants.ts @@ -0,0 +1,3 @@ +const PAGINATION_UNIT = 5; + +export default PAGINATION_UNIT; diff --git a/src/constants/curationInfo.ts b/src/constants/curationInfo.ts new file mode 100644 index 00000000..07c9b33e --- /dev/null +++ b/src/constants/curationInfo.ts @@ -0,0 +1,28 @@ +const CURATION_INFO = [ + { + bgImage: 'https://img.danawa.com/images/descFiles/6/110/5109431_agiLaciMHn_1659098198501.jpeg', + title: '1번', + subtitle: 'subtitle', + onClick: () => alert('1번 νλ ˆμ΄μ…˜ 클릭'), + }, + { + bgImage: 'https://img.danawa.com/images/descFiles/6/110/5109431_agiLaciMHn_1659098198501.jpeg', + title: '2번', + subtitle: 'subtitle', + onClick: () => alert('2번 νλ ˆμ΄μ…˜ 클릭'), + }, + { + bgImage: 'https://img.danawa.com/images/descFiles/6/110/5109431_agiLaciMHn_1659098198501.jpeg', + title: '3번', + subtitle: 'subtitle', + onClick: () => alert('3번 νλ ˆμ΄μ…˜ 클릭'), + }, + { + bgImage: 'https://img.danawa.com/images/descFiles/6/110/5109431_agiLaciMHn_1659098198501.jpeg', + title: '4번', + subtitle: 'subtitle', + onClick: () => alert('4번 νλ ˆμ΄μ…˜ 클릭'), + }, +]; + +export default CURATION_INFO; diff --git a/src/constants/loginInfos.ts b/src/constants/loginInfos.ts new file mode 100644 index 00000000..99600dcc --- /dev/null +++ b/src/constants/loginInfos.ts @@ -0,0 +1,16 @@ +const LOGIN_INFOS = { + my: { + title: 'λ§ˆμ΄νŽ˜μ΄μ§€', + text: 'μ ˆλ‘œκ°€λ₯Ό 200% 즐기기 μœ„ν•΄\n둜그인 ν•˜λŸ¬κ°€κΈ°', + imageSrc: 'src/assets/images/img_yellow_light_smile.png', + alt: 'λ§ˆμ΄νŽ˜μ΄μ§€ 둜그인', + }, + wish: { + title: 'μœ„μ‹œλ¦¬μŠ€νŠΈ', + text: 'λ‚˜λ§Œμ˜ μœ„μ‹œλ¦¬μŠ€νŠΈλ₯Ό\nλ§Œλ“€κ³  μ‹Άλ‹€λ©΄ λ‘œκ·ΈμΈμ„ ν•΄μ£Όμ„Έμš”', + imageSrc: 'src/assets/images/img_pink_light_smile.png', + alt: 'μœ„μ‹œλ¦¬μŠ€νŠΈ 둜그인', + }, +}; + +export default LOGIN_INFOS; diff --git a/src/constants/regionInfos.ts b/src/constants/regionInfos.ts new file mode 100644 index 00000000..7462a482 --- /dev/null +++ b/src/constants/regionInfos.ts @@ -0,0 +1,18 @@ +const REGION_INFOS = { + μ„œμšΈ: { top: 40, left: 90 }, + 인천: { top: 50, left: 20 }, + κ²½κΈ°: { top: 88, left: 98 }, + 강원: { top: 48, left: 180 }, + 좩뢁: { top: 116, left: 148 }, + 좩남: { top: 136, left: 74 }, + 경뢁: { top: 137, left: 222 }, + 경남: { top: 238, left: 166 }, + 전뢁: { top: 188, left: 102 }, + κ΄‘μ£Ό: { top: 234, left: 76 }, + 전남: { top: 286, left: 66 }, + λŒ€κ΅¬: { top: 179, left: 196 }, + λΆ€μ‚°: { top: 235, left: 232 }, + 제주: { top: 357, left: 10 }, +}; + +export default REGION_INFOS; diff --git a/src/constants/taps.ts b/src/constants/taps.ts new file mode 100644 index 00000000..614161f9 --- /dev/null +++ b/src/constants/taps.ts @@ -0,0 +1,6 @@ +export const TAPS = { + filter: ['지역', 'μœ ν˜•', 'λͺ©μ ', 'μ²΄ν—˜', '가격', '기타'], + detail: ['리뷰', 'ν”„λ‘œκ·Έλž¨ 일정', '가격', 'ν…œν”ŒμŠ€ν…Œμ΄ 정보', '지도'], +} as const; + +export type TapType = keyof typeof TAPS; diff --git a/src/constants/userInfo.ts b/src/constants/userInfo.ts new file mode 100644 index 00000000..85c222ef --- /dev/null +++ b/src/constants/userInfo.ts @@ -0,0 +1,9 @@ +const USER_INFO = { + nickname: 'κΉ€νƒœμš±', + email: 'taewook@naver.com', + ageRange: '20λŒ€', + gender: 'female', + religion: '무ꡐ', +} as const; + +export default USER_INFO; diff --git a/src/hooks/useCarousel.ts b/src/hooks/useCarousel.ts new file mode 100644 index 00000000..66116847 --- /dev/null +++ b/src/hooks/useCarousel.ts @@ -0,0 +1,45 @@ +import { useState, useRef } from 'react'; + +interface UseCarouselProps { + itemCount: number; // μΊλŸ¬μ…€ ν•­λͺ© 개수 + moveDistance: number; // 이동 거리 +} + +const useCarousel = ({ itemCount, moveDistance }: UseCarouselProps) => { + const [currentIndex, setCurrentIndex] = useState(0); + const [transX, setTransX] = useState(0); + const carouselRef = useRef(null); + + const handleDragChange = (deltaX: number) => { + setTransX(deltaX); + }; + + const handleDragEnd = (deltaX: number) => { + const maxIndex = itemCount - 1; + + if (deltaX < -100) { + // μ™Όμͺ½μœΌλ‘œ λ“œλž˜κ·Έ + setCurrentIndex(currentIndex + 1 > maxIndex ? maxIndex : currentIndex + 1); + } else if (deltaX > 100) { + // 였λ₯Έμͺ½μœΌλ‘œ λ“œλž˜κ·Έ + setCurrentIndex(currentIndex - 1 < 0 ? 0 : currentIndex - 1); + } + + setTransX(0); + }; + + const transformStyle = { + transform: `translateX(${-currentIndex * moveDistance + transX}px)`, + transition: 'transform 200ms ease-in-out 0s', + }; + + return { + currentIndex, + carouselRef, + transformStyle, + handleDragChange, + handleDragEnd, + }; +}; + +export default useCarousel; diff --git a/src/hooks/useExpandHook/useExpandHook.tsx b/src/hooks/useExpandHook/useExpandHook.tsx new file mode 100644 index 00000000..adb2cf95 --- /dev/null +++ b/src/hooks/useExpandHook/useExpandHook.tsx @@ -0,0 +1,25 @@ +import { useState, useEffect, RefObject } from 'react'; + +const useExpandHook = (contentRef: RefObject) => { + const [isAppeared, setIsAppeared] = useState(false); + const [isExpanded, setIsExpanded] = useState(false); + + useEffect(() => { + const contentElement = contentRef.current; + if (contentElement) { + setIsAppeared(contentElement.scrollHeight > contentElement.clientHeight); + } + }, [contentRef]); + + const handleToggleExpand = () => { + setIsExpanded((prev) => !prev); + }; + + return { + isAppeared, + isExpanded, + handleToggleExpand, + }; +}; + +export default useExpandHook; diff --git a/src/hooks/useNavigateTo.tsx b/src/hooks/useNavigateTo.tsx new file mode 100644 index 00000000..05eba498 --- /dev/null +++ b/src/hooks/useNavigateTo.tsx @@ -0,0 +1,17 @@ +import { useNavigate } from 'react-router-dom'; + +const useNavigateTo = (routePage: string | number) => { + const navigate = useNavigate(); + + const navigateToPage = () => { + if (typeof routePage === 'string') { + navigate(routePage); + } else if (typeof routePage === 'number') { + navigate(routePage); + } + }; + + return navigateToPage; +}; + +export default useNavigateTo; diff --git a/src/main.tsx b/src/main.tsx index fd12bb29..0923b4c8 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,18 +2,19 @@ import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; +import Router from 'src/router/Router.tsx'; + import '@styles/reset.css.ts'; import '@styles/global.css.ts'; import '@styles/fonts.css.ts'; -import App from './App.tsx'; import queryClient from './queryClient.ts'; createRoot(document.getElementById('root')!).render( - + , ); diff --git a/src/pages/ErrorPage.tsx b/src/pages/ErrorPage.tsx new file mode 100644 index 00000000..927fc735 --- /dev/null +++ b/src/pages/ErrorPage.tsx @@ -0,0 +1,5 @@ +const ErrorPage = () => { + return
ErrorPage
; +}; + +export default ErrorPage; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx new file mode 100644 index 00000000..b2f0bf4e --- /dev/null +++ b/src/pages/HomePage.tsx @@ -0,0 +1,5 @@ +const HomePage = () => { + return
HomePage
; +}; + +export default HomePage; diff --git a/src/pages/WishPage.tsx b/src/pages/WishPage.tsx new file mode 100644 index 00000000..21f40d1f --- /dev/null +++ b/src/pages/WishPage.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const WishPage = () => { + return
WishPage
; +}; + +export default WishPage; diff --git a/src/pages/loginPage/LoginPage.tsx b/src/pages/loginPage/LoginPage.tsx new file mode 100644 index 00000000..84a2602d --- /dev/null +++ b/src/pages/loginPage/LoginPage.tsx @@ -0,0 +1,32 @@ +import KakaoBtn from '@components/common/button/kakaoBtn/KakaoBtn'; +import PageName from '@components/common/pageName/PageName'; +import LOGIN_INFOS from '@constants/loginInfos'; +import React from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +import * as styles from './loginPage.css'; + +type LoginType = 'my' | 'wish'; + +const LoginPage = () => { + const location = useLocation(); + const navigate = useNavigate(); + + const type: LoginType = location.state?.type || 'my'; + + const { title, text, imageSrc, alt } = LOGIN_INFOS[type]; + + return ( +
+ navigate(-1)} isLikeBtn={false} /> +
+

{text}

+ + {alt} +
+ +
+ ); +}; + +export default LoginPage; diff --git a/src/pages/loginPage/loginPage.css.ts b/src/pages/loginPage/loginPage.css.ts new file mode 100644 index 00000000..ca121741 --- /dev/null +++ b/src/pages/loginPage/loginPage.css.ts @@ -0,0 +1,37 @@ +import theme from '@styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const logoContainer = style({ + display: 'flex', + flexDirection: 'column', + gap: '1.5rem', + justifyContent: 'center', + alignItems: 'center', +}); + +export const textStyle = style({ + ...theme.FONTS.h3Sb18, + whiteSpace: 'pre-line', + textAlign: 'center', +}); + +export const loginWrapper = style({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + width: '33.5rem', +}); + +export const contentWrapper = style({ + margin: '8.4rem 0 9.3rem 0', + display: 'flex', + flexDirection: 'column', + gap: '0.4rem', +}); + +export const imgStyle = style({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', +}); diff --git a/src/pages/myPage/MyPage.tsx b/src/pages/myPage/MyPage.tsx new file mode 100644 index 00000000..614f7293 --- /dev/null +++ b/src/pages/myPage/MyPage.tsx @@ -0,0 +1,17 @@ +import PageName from '@components/common/pageName/PageName'; +import Footer from '@components/footer/Footer'; +import UserInfo from '@components/userInfo/userInfo'; +import useNavigateTo from '@hooks/useNavigateTo'; + +const MyPage = () => { + const handleToBack = useNavigateTo(-1); + return ( +
+ + +
+
+ ); +}; + +export default MyPage; diff --git a/src/pages/searchPage/SearchPage.tsx b/src/pages/searchPage/SearchPage.tsx new file mode 100644 index 00000000..e4963519 --- /dev/null +++ b/src/pages/searchPage/SearchPage.tsx @@ -0,0 +1,34 @@ +import DetailTitle from '@components/detailTitle/DetailTitle'; +import RecentBtnBox from '@components/search/recentBtn/RecentBtnBox'; +import SearchHeader from '@components/search/searchHeader/SearchHeader'; + +import * as styles from './searchPage.css'; + +const recentData = [ + { id: 1, content: 'λΆˆγ…‡μ‚¬' }, + { id: 2, content: '봉인사' }, + { id: 3, content: 'μˆ™κ΅¬μ‚¬' }, + { id: 4, content: 'λΆˆγ…‡μ‚¬' }, + { id: 5, content: '봉인사' }, + { id: 6, content: 'μˆ™κ΅¬μ‚¬' }, + { id: 7, content: 'λΆˆγ…‡μ‚¬' }, + { id: 8, content: 'λΆˆγ…‡μ‚¬' }, + { id: 9, content: '봉인사' }, + { id: 10, content: 'μˆ™κ΅¬μ‚¬' }, +]; + +const SearchPage = () => { + return ( + <> + +
+ +
+
+ +
+ + ); +}; + +export default SearchPage; diff --git a/src/pages/searchPage/searchPage.css.ts b/src/pages/searchPage/searchPage.css.ts new file mode 100644 index 00000000..99425212 --- /dev/null +++ b/src/pages/searchPage/searchPage.css.ts @@ -0,0 +1,9 @@ +import { style } from '@vanilla-extract/css'; + +export const paddingStyle = style({ + padding: '0 2rem', +}); + +export const leftPaddingStyle = style({ + paddingLeft: '2rem', +}); diff --git a/src/pages/wishList/WishListPage.tsx b/src/pages/wishList/WishListPage.tsx new file mode 100644 index 00000000..4194e9e9 --- /dev/null +++ b/src/pages/wishList/WishListPage.tsx @@ -0,0 +1,139 @@ +import WishCardList from '@components/card/templeStayCard/wishCardList/WishCardList'; +import WishEmpty from '@components/common/empty/wishEmpty/WishEmpty'; +import PageName from '@components/common/pageName/PageName'; +import Pagination from '@components/common/pagination/Pagination'; +import useNavigateTo from '@hooks/useNavigateTo'; +import React, { useState } from 'react'; + +import container from './wishListPage.css'; + +const mockData = { + page: 3, + pageSize: 10, + totalPages: 13, + wishlist: [ + { + id: 1, + templeName: 'λŒ€μ›μ‚¬(보성)', + templestayName: 'μ°¨ ν•œ μž”μ˜ 행볡', + tag: 'νƒ±νˆ¬κ°€ λ‹€λ…€κ°„', + region: '전남', + type: 'νœ΄μ‹ν˜•', + imgUrl: 'https://github.com/user-attachments/assets/e018d4af-d61e-42a1-90d9-96351d653124', + liked: true, + }, + { + id: 2, + templeName: 'μˆ˜μ›μ‚¬', + templestayName: 'μ§€κΈˆ ν–‰λ³΅ν•˜κΈ°', + tag: 'μ—°μ˜ˆμΈμ΄ λ‹€λ…€κ°„', + region: 'κ²½κΈ°', + type: 'μ²΄ν—˜ν˜•', + imgUrl: 'https://github.com/user-attachments/assets/e018d4af-d61e-42a1-90d9-96351d653124', + liked: true, + }, + { + id: 3, + templeName: '봉은사', + templestayName: 'λͺ…상 μ°¨λ‹΄ ν…œν”ŒμŠ€ν…Œμ΄', + tag: 'μ—°μ˜ˆμΈμ΄ λ‹€λ…€κ°„', + region: 'μ„œμšΈ', + type: 'μ²΄ν—˜ν˜•', + imgUrl: 'https://github.com/user-attachments/assets/e018d4af-d61e-42a1-90d9-96351d653124', + liked: true, + }, + { + id: 4, + templeName: 'λŒ€μ›μ‚¬(보성)', + templestayName: 'μ°¨ ν•œ μž”μ˜ 행볡', + tag: 'νƒ±νˆ¬κ°€ λ‹€λ…€κ°„', + region: '전남', + type: 'νœ΄μ‹ν˜•', + imgUrl: 'https://github.com/user-attachments/assets/e018d4af-d61e-42a1-90d9-96351d653124', + liked: true, + }, + { + id: 5, + templeName: 'μˆ˜μ›μ‚¬', + templestayName: 'μ§€κΈˆ ν–‰λ³΅ν•˜κΈ°', + tag: 'μ—°μ˜ˆμΈμ΄ λ‹€λ…€κ°„', + region: 'κ²½κΈ°', + type: 'μ²΄ν—˜ν˜•', + imgUrl: 'https://github.com/user-attachments/assets/e018d4af-d61e-42a1-90d9-96351d653124', + liked: true, + }, + { + id: 6, + templeName: '봉은사', + templestayName: 'λͺ…상 μ°¨λ‹΄ ν…œν”ŒμŠ€ν…Œμ΄', + tag: 'μ—°μ˜ˆμΈμ΄ λ‹€λ…€κ°„', + region: 'μ„œμšΈ', + type: 'μ²΄ν—˜ν˜•', + imgUrl: 'https://github.com/user-attachments/assets/e018d4af-d61e-42a1-90d9-96351d653124', + liked: true, + }, + { + id: 7, + templeName: 'λŒ€μ›μ‚¬(보성)', + templestayName: 'μ°¨ ν•œ μž”μ˜ 행볡', + tag: 'νƒ±νˆ¬κ°€ λ‹€λ…€κ°„', + region: '전남', + type: 'νœ΄μ‹ν˜•', + imgUrl: 'https://github.com/user-attachments/assets/e018d4af-d61e-42a1-90d9-96351d653124', + liked: true, + }, + { + id: 8, + templeName: 'μˆ˜μ›μ‚¬', + templestayName: 'μ§€κΈˆ ν–‰λ³΅ν•˜κΈ°', + tag: 'μ—°μ˜ˆμΈμ΄ λ‹€λ…€κ°„', + region: 'κ²½κΈ°', + type: 'μ²΄ν—˜ν˜•', + imgUrl: 'https://github.com/user-attachments/assets/e018d4af-d61e-42a1-90d9-96351d653124', + liked: true, + }, + { + id: 9, + templeName: '봉은사', + templestayName: 'λͺ…상 μ°¨λ‹΄ ν…œν”ŒμŠ€ν…Œμ΄', + tag: 'μ—°μ˜ˆμΈμ΄ λ‹€λ…€κ°„', + region: 'μ„œμšΈ', + type: 'μ²΄ν—˜ν˜•', + imgUrl: 'https://github.com/user-attachments/assets/e018d4af-d61e-42a1-90d9-96351d653124', + liked: true, + }, + ], +}; + +const WishListPage = () => { + const [currentPage, setCurrentPage] = useState(mockData.page); + const [wishlist] = useState(mockData.wishlist); + + const handleToBack = useNavigateTo(-1); + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + return ( +
+ + {wishlist.length === 0 ? ( + + ) : ( + <> +
+ +
+ + + )} +
+ ); +}; + +export default WishListPage; diff --git a/src/pages/wishList/wishListPage.css.ts b/src/pages/wishList/wishListPage.css.ts new file mode 100644 index 00000000..95a9b141 --- /dev/null +++ b/src/pages/wishList/wishListPage.css.ts @@ -0,0 +1,11 @@ +import { style } from '@vanilla-extract/css'; + +const container = style({ + minHeight: '100vh', + paddingBottom: '4.4rem', + display: 'grid', + gridTemplateRows: 'auto 1fr auto', + justifyItems: 'center', +}); + +export default container; diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts new file mode 100644 index 00000000..6c20ff5d --- /dev/null +++ b/src/react-app-env.d.ts @@ -0,0 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +interface Window { + naver: any; +} diff --git a/src/router/PrivateRoute.tsx b/src/router/PrivateRoute.tsx new file mode 100644 index 00000000..b184cf23 --- /dev/null +++ b/src/router/PrivateRoute.tsx @@ -0,0 +1,20 @@ +import { ReactNode } from 'react'; +import { Navigate } from 'react-router-dom'; + +interface PrivateRouteProps { + children: ReactNode; + redirectPath: string; + state?: { type: 'my' | 'wish' }; +} + +const PrivateRoute = ({ children, redirectPath, state }: PrivateRouteProps) => { + const isAuthenticated = localStorage.getItem('accessToken'); + + if (!isAuthenticated) { + return ; + } + + return <>{children}; +}; + +export default PrivateRoute; diff --git a/src/router/Router.tsx b/src/router/Router.tsx new file mode 100644 index 00000000..c24f02aa --- /dev/null +++ b/src/router/Router.tsx @@ -0,0 +1,67 @@ +import LoginPage from '@pages/loginPage/LoginPage'; +import WishListPage from '@pages/wishList/WishListPage'; +import { lazy, Suspense } from 'react'; +import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom'; +import App from 'src/App'; +import PrivateRoute from 'src/router/PrivateRoute'; + +const HomePage = lazy(() => import('@pages/HomePage')); +const SearchPage = lazy(() => import('@pages/searchPage/SearchPage')); +const ErrorPage = lazy(() => import('@pages/ErrorPage')); +const MyPage = lazy(() => import('@pages/myPage/MyPage')); +const WishPage = lazy(() => import('@pages/WishPage')); + +const router = createBrowserRouter([ + { + path: '/', + element: , + errorElement: , + children: [ + { + index: true, + element: , + }, + { + path: 'search', + element: , + }, + { + path: 'myPage', + element: ( + + + + ), + }, + { + path: 'wish', + element: ( + + + + ), + }, + { + path: 'login', + element: , + }, + { + path: 'wishList', + element: , + }, + ], + }, + { + path: '*', + element: , + }, +]); + +// TODO: λ‘œλ”© ν™”λ©΄ μΆ”κ°€ +const Router = () => ( + λ‘œλ”© ν™”λ©΄ μΆ”κ°€
}> + + +); + +export default Router; diff --git a/src/stories/ArrowBtn.stories.ts b/src/stories/ArrowBtn.stories.ts new file mode 100644 index 00000000..32c97dc7 --- /dev/null +++ b/src/stories/ArrowBtn.stories.ts @@ -0,0 +1,25 @@ +import ArrowBtn from '@components/common/button/arrowBtn/ArrowBtn'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/Button/ArrowBtn', + component: ArrowBtn, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + onClick: { + action: 'clicked', + }, + }, + args: { + onClick: () => alert('click !'), + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/BasicBtn.stories.ts b/src/stories/BasicBtn.stories.ts new file mode 100644 index 00000000..e2347ec0 --- /dev/null +++ b/src/stories/BasicBtn.stories.ts @@ -0,0 +1,97 @@ +import Icon from '@assets/svgs'; +import BasicBtn from '@components/common/button/basicBtn/BasicBtn'; +import type { Meta, StoryObj } from '@storybook/react'; +import { ButtonHTMLAttributes } from 'react'; + +interface ButtonProps extends ButtonHTMLAttributes { + variant?: 'primary' | 'grayOutlined' | 'blackOutlined' | 'lightGrayOutlined'; + size?: 'large' | 'medium' | 'small'; + label: string; + leftIcon?: keyof typeof Icon; + rightIcon?: keyof typeof Icon; + isActive: boolean; +} + +const meta = { + title: 'Common/Button/BasicBtn', + component: BasicBtn, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + variant: { + control: { type: 'radio' }, + options: ['primary', 'grayOutlined', 'blackOutlined', 'lightGrayOutlined', 'green'], + }, + size: { + control: { type: 'radio' }, + options: ['large', 'medium', 'small'], + }, + + label: { + control: { type: 'text' }, + }, + isActive: { + control: { type: 'boolean' }, + }, + leftIcon: { + control: { type: 'select' }, + options: Object.keys(Icon), + }, + rightIcon: { + control: { type: 'select' }, + options: Object.keys(Icon), + }, + }, + args: { + variant: 'primary', + size: 'medium', + label: 'Button', + isActive: false, + leftIcon: 'IcnCloseLargeGray', + rightIcon: 'IcnCloseLargeGray', + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +const createButtonStory = ( + variant: ButtonProps['variant'], + label: string, + leftIcon?: keyof typeof Icon, + rightIcon?: keyof typeof Icon, +) => ({ + args: { + variant, + label, + leftIcon, + rightIcon, + }, + argsType: { + variant: { + control: false, + }, + leftIcon: { + control: false, + }, + rightIcon: { + control: false, + }, + }, +}); + +export const Primary: Story = createButtonStory('primary', 'Primary Button', 'IcnCloseLargeGray'); + +export const grayOutlined: Story = createButtonStory( + 'grayOutlined', + 'GrayOutlined Button', + undefined, + 'IcnCloseLargeGray', +); + +export const blackOutlined: Story = createButtonStory('blackOutlined', 'BlackOutlined Button'); diff --git a/src/stories/Button.stories.ts b/src/stories/Button.stories.ts deleted file mode 100644 index 2a05e01b..00000000 --- a/src/stories/Button.stories.ts +++ /dev/null @@ -1,53 +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', - }, -}; diff --git a/src/stories/Button.tsx b/src/stories/Button.tsx deleted file mode 100644 index 1255d294..00000000 --- a/src/stories/Button.tsx +++ /dev/null @@ -1,34 +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/ButtonBar.stories.ts b/src/stories/ButtonBar.stories.ts new file mode 100644 index 00000000..8b1cbd9d --- /dev/null +++ b/src/stories/ButtonBar.stories.ts @@ -0,0 +1,27 @@ +import ButtonBar from '@components/common/button/buttonBar/ButtonBar'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/Button/ButtonBar', + component: ButtonBar, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + type: { + control: { type: 'select' }, + options: ['reset', 'wish'], + }, + }, + args: { + type: 'wish', + label: 'μ˜ˆμ•½ν•˜κΈ°', + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/CarouselIndex.stories.ts b/src/stories/CarouselIndex.stories.ts new file mode 100644 index 00000000..12937cd9 --- /dev/null +++ b/src/stories/CarouselIndex.stories.ts @@ -0,0 +1,29 @@ +import CarouselIndex from '@components/carousel/popularCarousel/CarouselIndex'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Carousel/PopularCarousel/CarouselIndex', + component: CarouselIndex, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + total: { + control: { type: 'number' }, + }, + currentIndex: { + control: { type: 'number' }, + }, + }, + args: { + total: 3, + currentIndex: 2, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/Configure.mdx b/src/stories/Configure.mdx deleted file mode 100644 index 6a537304..00000000 --- a/src/stories/Configure.mdx +++ /dev/null @@ -1,364 +0,0 @@ -import { Meta } from "@storybook/blocks"; - -import Github from "./assets/github.svg"; -import Discord from "./assets/discord.svg"; -import Youtube from "./assets/youtube.svg"; -import Tutorials from "./assets/tutorials.svg"; -import Styling from "./assets/styling.png"; -import Context from "./assets/context.png"; -import Assets from "./assets/assets.png"; -import Docs from "./assets/docs.png"; -import Share from "./assets/share.png"; -import FigmaPlugin from "./assets/figma-plugin.png"; -import Testing from "./assets/testing.png"; -import Accessibility from "./assets/accessibility.png"; -import Theming from "./assets/theming.png"; -import AddonLibrary from "./assets/addon-library.png"; - -export const RightArrow = () => - - - - - -
-
- # Configure your project - - Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community. -
-
-
- A wall of logos representing different styling technologies -

Add styling and CSS

-

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

- Learn more -
-
- An abstraction representing the composition of data for a component -

Provide context and mocking

-

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

- Learn more -
-
- A representation of typography and image assets -
-

Load assets and resources

-

To link static files (like fonts) to your projects and stories, use the - `staticDirs` configuration option to specify folders to load when - starting Storybook.

- Learn more -
-
-
-
-
-
- # Do more with Storybook - - Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs. -
- -
-
-
- A screenshot showing the autodocs tag being set, pointing a docs page being generated -

Autodocs

-

Auto-generate living, - interactive reference documentation from your components and stories.

- Learn more -
-
- A browser window showing a Storybook being published to a chromatic.com URL -

Publish to Chromatic

-

Publish your Storybook to review and collaborate with your entire team.

- Learn more -
-
- Windows showing the Storybook plugin in Figma -

Figma Plugin

-

Embed your stories into Figma to cross-reference the design and live - implementation in one place.

- Learn more -
-
- Screenshot of tests passing and failing -

Testing

-

Use stories to test a component in all its variations, no matter how - complex.

- Learn more -
-
- Screenshot of accessibility tests passing and failing -

Accessibility

-

Automatically test your components for a11y issues as you develop.

- Learn more -
-
- Screenshot of Storybook in light and dark mode -

Theming

-

Theme Storybook's UI to personalize it to your project.

- Learn more -
-
-
-
-
-
-

Addons

-

Integrate your tools with Storybook to connect workflows.

- Discover all addons -
-
- Integrate your tools with Storybook to connect workflows. -
-
- -
-
- Github logo - Join our contributors building the future of UI development. - - Star on GitHub -
-
- Discord logo -
- Get support and chat with frontend developers. - - Join Discord server -
-
-
- Youtube logo -
- Watch tutorials, feature previews and interviews. - - Watch on YouTube -
-
-
- A book -

Follow guided walkthroughs on for key workflows.

- - Discover tutorials -
-
- - diff --git a/src/stories/CurationCard.stories.ts b/src/stories/CurationCard.stories.ts new file mode 100644 index 00000000..c07e35da --- /dev/null +++ b/src/stories/CurationCard.stories.ts @@ -0,0 +1,37 @@ +import CurationCard from '@components/curation/curationCard/CurationCard'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Curation/CurationCard', + component: CurationCard, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + bgImage: { + control: { type: 'text' }, + }, + title: { + control: { type: 'text' }, + }, + subtitle: { + control: { type: 'text' }, + }, + onClick: { + action: 'clicked', + }, + }, + args: { + bgImage: 'https://img.danawa.com/images/descFiles/6/110/5109431_agiLaciMHn_1659098198501.jpeg', + title: '고양이 μžˆλŠ” 절 λ΄€μ–΄?', + subtitle: 'μš©λ¬Έμ‚¬μ— μžˆλŠ” 고양이 μ’€ 봐. 귀엽지?', + onClick: () => alert('click !'), + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/CurationCarousel.stories.ts b/src/stories/CurationCarousel.stories.ts new file mode 100644 index 00000000..3fb4eddb --- /dev/null +++ b/src/stories/CurationCarousel.stories.ts @@ -0,0 +1,17 @@ +import CurationCarousel from '@components/carousel/curationCarousel/CurationCarousel'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Carousel/CurationCarousel', + component: CurationCarousel, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/DetailInfo.stories.ts b/src/stories/DetailInfo.stories.ts new file mode 100644 index 00000000..f80e34b7 --- /dev/null +++ b/src/stories/DetailInfo.stories.ts @@ -0,0 +1,29 @@ +import DetailInfo from '@components/common/detailInfo/DetailInfo'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/DetailInfo', + component: DetailInfo, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + title: { + control: { type: 'text' }, + }, + content: { + control: { type: 'text' }, + }, + }, + args: { + title: 'title', + content: 'body', + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/DetailTitle.stories.ts b/src/stories/DetailTitle.stories.ts new file mode 100644 index 00000000..ea868a76 --- /dev/null +++ b/src/stories/DetailTitle.stories.ts @@ -0,0 +1,47 @@ +import DetailTitle from '@components/detailTitle/DetailTitle'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/DetailTitle', + component: DetailTitle, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + title: { + control: { type: 'text' }, + description: 'Title text displayed in the component', + }, + isTotal: { + control: { type: 'boolean' }, + description: 'Determines if the "전체보기" button is shown', + }, + onClick: { + action: 'clicked', + description: 'Function executed when the button is clicked', + }, + }, + args: { + title: 'Default Title', + isTotal: false, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const ReviewButton: Story = { + args: { + title: '리뷰', + isTotal: true, + onClick: () => alert('μ „μ²΄λ³΄κΈ°λ‘œ 이동'), + }, +}; + +export const WithoutButton: Story = { + args: { + title: 'ν…œν”ŒμŠ€ν…Œμ΄ 정보', + }, +}; diff --git a/src/stories/FlowerBtn.stories.ts b/src/stories/FlowerBtn.stories.ts new file mode 100644 index 00000000..c4742044 --- /dev/null +++ b/src/stories/FlowerBtn.stories.ts @@ -0,0 +1,38 @@ +import FlowerBtn from '@components/common/button/flowerBtn/FlowerBtn'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/Button/FlowerBtn', + component: FlowerBtn, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + label: { + control: { type: 'text' }, + }, + isRightIcn: { + control: { type: 'boolean' }, + }, + isLeftIcn: { + control: { type: 'boolean' }, + }, + isActive: { + control: { type: 'boolean' }, + }, + }, + args: { + label: 'μ°œν•˜κΈ°', + isRightIcn: true, + isLeftIcn: true, + isActive: false, + onClick: () => {}, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/Footer.stories.ts b/src/stories/Footer.stories.ts new file mode 100644 index 00000000..edcb4e3d --- /dev/null +++ b/src/stories/Footer.stories.ts @@ -0,0 +1,17 @@ +import Footer from '@components/footer/Footer'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/Footer', + component: Footer, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/Header.stories.ts b/src/stories/Header.stories.ts index 80c71d0f..f07f34be 100644 --- a/src/stories/Header.stories.ts +++ b/src/stories/Header.stories.ts @@ -1,33 +1,16 @@ +import Header from '@components/header/Header'; import type { Meta, StoryObj } from '@storybook/react'; -import { fn } from '@storybook/test'; - -import { Header } from './Header'; - const meta = { - title: 'Example/Header', + title: 'Common/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(), + layout: 'centered', }, + tags: ['autodocs'], } satisfies Meta; export default meta; -type Story = StoryObj; -export const LoggedIn: Story = { - args: { - user: { - name: 'Jane Doe', - }, - }, -}; +type Story = StoryObj; -export const LoggedOut: Story = {}; +export const Default: Story = {}; diff --git a/src/stories/Header.tsx b/src/stories/Header.tsx deleted file mode 100644 index d05ed4f6..00000000 --- a/src/stories/Header.tsx +++ /dev/null @@ -1,54 +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/InfoBtn.stories.ts b/src/stories/InfoBtn.stories.ts new file mode 100644 index 00000000..81ca12db --- /dev/null +++ b/src/stories/InfoBtn.stories.ts @@ -0,0 +1,33 @@ +import InfoBtn from '@components/common/button/infoBtn/InfoBtn'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/Button/InfoBtn', + component: InfoBtn, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + label: { + control: { type: 'text' }, + }, + onClick: { + action: 'clicked', + }, + hasDivider: { + action: 'boolean', + }, + }, + args: { + label: '곡지사항', + onClick: () => alert('click !'), + hasDivider: true, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/KakaoBtn.stories.ts b/src/stories/KakaoBtn.stories.ts new file mode 100644 index 00000000..86a70648 --- /dev/null +++ b/src/stories/KakaoBtn.stories.ts @@ -0,0 +1,17 @@ +import KakaoBtn from '@components/common/button/kakaoBtn/KakaoBtn'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/Button/KakaoBtn', + component: KakaoBtn, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/LargeMap.stories.ts b/src/stories/LargeMap.stories.ts new file mode 100644 index 00000000..6966b3b7 --- /dev/null +++ b/src/stories/LargeMap.stories.ts @@ -0,0 +1,31 @@ +import LargeMap from '@components/templeDetail/naverMap/largeMap/LargeMap'; +import type { Meta, StoryObj } from '@storybook/react'; +import { reactRouterParameters } from 'storybook-addon-react-router-v6'; + +const meta = { + title: 'Common/Map/LargeMap', + component: LargeMap, + parameters: { + layout: 'centered', + reactRouter: reactRouterParameters({ + location: { + pathParams: { + latitude: '37.55433', + longitude: '126.9686', + }, + }, + routing: { + path: '/map?latitude=37.55433&longitude=126.9686', + }, + }), + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: {}, +}; diff --git a/src/stories/LookCard.stories.ts b/src/stories/LookCard.stories.ts new file mode 100644 index 00000000..93afe2fd --- /dev/null +++ b/src/stories/LookCard.stories.ts @@ -0,0 +1,25 @@ +import LookCard from '@components/card/lookCard/LookCard'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Card/LookCard', + component: LookCard, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + name: { + control: { type: 'text' }, + }, + }, + args: { + name: 'μ ˆλ‘œκ°€', + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/MapCard.stories.ts b/src/stories/MapCard.stories.ts new file mode 100644 index 00000000..499a2223 --- /dev/null +++ b/src/stories/MapCard.stories.ts @@ -0,0 +1,17 @@ +import MapCard from '@components/card/mapCard/MapCard'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Card/MapCard', + component: MapCard, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/MyPage.stories.ts b/src/stories/MyPage.stories.ts new file mode 100644 index 00000000..9c78d961 --- /dev/null +++ b/src/stories/MyPage.stories.ts @@ -0,0 +1,17 @@ +import MyPage from '@pages/myPage/MyPage'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Page/Mypage', + component: MyPage, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/OnboardingButton.stories.ts b/src/stories/OnboardingButton.stories.ts new file mode 100644 index 00000000..da86ed00 --- /dev/null +++ b/src/stories/OnboardingButton.stories.ts @@ -0,0 +1,60 @@ +import Icon from '@assets/svgs'; +import OnboardingBtn from '@components/common/button/onboardingBtn/OnboardingBtn'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/Button/OnboardingButton', + component: OnboardingBtn, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + btnText: { + control: { type: 'text' }, + description: 'Text displayed on the button', + }, + isActive: { + control: { type: 'boolean' }, + description: 'Determines if the button is active', + }, + leftIcon: { + control: { type: 'select' }, + options: Object.keys(Icon), + description: 'Icon displayed on the left side of the button', + }, + }, + args: { + btnText: '이슬람ꡐ', + isActive: false, + leftIcon: undefined, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const Active: Story = { + args: { + btnText: '60λŒ€ 이상', + isActive: true, + }, +}; + +export const WithIcon: Story = { + args: { + btnText: '있음', + leftIcon: 'IcnO', + }, +}; + +export const WithIconActive: Story = { + args: { + btnText: 'μ—†μŒ', + leftIcon: 'IcnX', + isActive: true, + }, +}; diff --git a/src/stories/Page.stories.ts b/src/stories/Page.stories.ts deleted file mode 100644 index 41a4fa05..00000000 --- a/src/stories/Page.stories.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -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 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 b14370f3..00000000 --- a/src/stories/Page.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; - -import { Header } from './Header'; -import './page.css'; - -type User = { - name: string; -}; - -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 -
-
-
- ); -}; - -export default Page; diff --git a/src/stories/PageName.stories.ts b/src/stories/PageName.stories.ts new file mode 100644 index 00000000..86d16713 --- /dev/null +++ b/src/stories/PageName.stories.ts @@ -0,0 +1,44 @@ +import PageName from '@components/common/pageName/PageName'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/PageName', + component: PageName, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + title: { + control: { type: 'text' }, + }, + onLeftClick: { action: 'left button clicked' }, + onRightClick: { action: 'right button clicked' }, + isLikeBtn: { + control: { type: 'boolean' }, + }, + }, + args: { + title: 'GoToJeol', + isLikeBtn: true, + onLeftClick: () => alert('click !'), + onRightClick: () => alert('click !'), + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + title: 'GoToJeol', + }, +}; + +export const NoHeart: Story = { + args: { + title: 'Left Only', + isLikeBtn: false, + }, +}; diff --git a/src/stories/Pagination.stories.tsx b/src/stories/Pagination.stories.tsx new file mode 100644 index 00000000..e2e9bf5a --- /dev/null +++ b/src/stories/Pagination.stories.tsx @@ -0,0 +1,92 @@ +import Pagination from '@components/common/pagination/Pagination'; +import type { Meta, StoryObj } from '@storybook/react'; +import React, { useState } from 'react'; + +const meta: Meta = { + title: 'Components/Pagination', + component: Pagination, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + currentPage: { + control: { type: 'number' }, + description: 'Currently active page', + }, + totalPages: { + control: { type: 'number' }, + description: 'Total number of pages', + }, + onPageChange: { action: 'Page changed' }, + color: { + control: { type: 'inline-radio', options: ['gray', 'white'] }, + description: 'Background color of the pagination container', + }, + }, + args: { + currentPage: 1, + totalPages: 13, + color: 'gray', + }, +}; + +export default meta; +type Story = StoryObj; + +const PaginationExample = (props: { + currentPage: number; + totalPages: number; + onPageChange: (page: number) => void; + color: 'gray' | 'white'; +}) => { + const { onPageChange } = props; + const [currentPage, setCurrentPage] = useState(props.currentPage); + + const handlePageChange = (page: number) => { + setCurrentPage(page); + onPageChange?.(page); + }; + + return ( + + ); +}; + +export const Default: Story = { + args: { + currentPage: 1, + totalPages: 13, + color: 'white', + }, + render: (args) => { + return ; + }, +}; + +export const TotalOne: Story = { + args: { + currentPage: 13, + totalPages: 20, + color: 'white', + }, + render: (args) => { + return ; + }, +}; + +export const TotalThree: Story = { + args: { + currentPage: 1, + totalPages: 3, + color: 'white', + }, + render: (args) => { + return ; + }, +}; diff --git a/src/stories/PopularCard.stories.ts b/src/stories/PopularCard.stories.ts new file mode 100644 index 00000000..e4b1f7d6 --- /dev/null +++ b/src/stories/PopularCard.stories.ts @@ -0,0 +1,50 @@ +import PopularCard from '@components/card/popularCard/PopularCard'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Card/PopularCard', + component: PopularCard, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + ranking: { + control: { type: 'number' }, + }, + templeName: { + control: { type: 'text' }, + }, + templeLoc: { + control: { type: 'text' }, + }, + templeImg: { + control: { type: 'text' }, + }, + tag: { + control: { type: 'text' }, + }, + onClick: { + action: 'clicked', + }, + isLiked: { + control: { type: 'boolean' }, + }, + }, + args: { + ranking: 1, + templeName: '봉은사', + templeLoc: 'μ„œμšΈ', + templeImg: + 'https://img.danawa.com/images/descFiles/6/110/5109431_agiLaciMHn_1659098198501.jpeg', + tag: 'λ°©κΈ‹λ°©κΈ‹', + onClick: () => alert('click !'), + isLiked: false, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/PopularCarousel.stories.ts b/src/stories/PopularCarousel.stories.ts new file mode 100644 index 00000000..6a868b47 --- /dev/null +++ b/src/stories/PopularCarousel.stories.ts @@ -0,0 +1,17 @@ +import PopularCarousel from '@components/carousel/popularCarousel/PopularCarousel'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Carousel/PopularCarousel/PopularCarousel', + component: PopularCarousel, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/ProgressBar.stories.ts b/src/stories/ProgressBar.stories.ts new file mode 100644 index 00000000..cfc34cb6 --- /dev/null +++ b/src/stories/ProgressBar.stories.ts @@ -0,0 +1,29 @@ +import ProgressBar from '@components/common/progressBar/ProgressBar'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/ProgressBar', + component: ProgressBar, + parameters: { + layout: 'centered', + }, + args: { + currentStep: 1, + totalSteps: 4, + onBackClick: () => alert('clicked'), + }, + argTypes: { + currentStep: { + control: { type: 'number', min: 1 }, + }, + totalSteps: { + control: { type: 'number', min: 1 }, + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/ReviewCard.stories.ts b/src/stories/ReviewCard.stories.ts new file mode 100644 index 00000000..c7cce324 --- /dev/null +++ b/src/stories/ReviewCard.stories.ts @@ -0,0 +1,93 @@ +import ReviewCard from '@components/card/reviewCard/reviewCard/ReviewCard'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Card/ReviewCard', + component: ReviewCard, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + reviewTitle: { + control: 'text', + description: 'Title of the review.', + }, + reviewDate: { + control: 'text', + description: 'Date of the review.', + }, + reviewName: { + control: 'text', + description: 'Name of the reviewer.', + }, + reviewLink: { + control: 'text', + description: 'Link to the full review.', + }, + reviewDescription: { + control: 'text', + description: 'Content of the full review.', + }, + blogImage: { + control: 'text', + description: 'URL of the review thumbnail image.', + }, + size: { + control: 'radio', + options: ['small', 'large'], + description: 'Size of the review card.', + }, + }, + args: { + reviewTitle: '배영경배영경배영경', + reviewDate: '2025.01.15', + reviewName: 'Bae Young Kyoung', + reviewLink: 'https://san.chosun.com/news/articleView.html?idxno=15686', + blogImage: 'https://ifh.cc/g/YwlKhp.jpg', + size: 'small', + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const LargeCard: Story = { + args: { + size: 'large', + reviewTitle: 'κΉ€κ°€ν˜„κΉ€κ°€ν˜„κΉ€κ°€ν˜„', + reviewDate: '2025.01.15', + reviewName: 'Bae Young Kyoung', + reviewLink: 'https://san.chosun.com/news/articleView.html?idxno=15686', + reviewDescription: 'λ‚˜λ¬΄μ•„λ―Ένƒ€λΆˆλ‚˜λ¬΄μ•„λ―Ένƒ€λΆˆλ‚˜λ¬΄μ•„λ―Ένƒ€λΆˆ', + blogImage: 'https://ifh.cc/g/YwlKhp.jpg', + }, +}; + +export const SmallLongTitle: Story = { + args: { + reviewTitle: 'I am λ°°μ˜κ²½μ΄λ‹€I am λ°°μ˜κ²½μ΄λ‹€I am λ°°μ˜κ²½μ΄λ‹€I am λ°°μ˜κ²½μ΄λ‹€', + reviewDate: '2025.01.15', + reviewName: 'Bae Young Kyoung', + reviewLink: 'https://san.chosun.com/news/articleView.html?idxno=15686', + blogImage: 'https://ifh.cc/g/YwlKhp.jpg', + size: 'small', + }, +}; + +export const LargeLongContentAndTitle: Story = { + args: { + reviewTitle: + 'I am λ°°μ˜κ²½μ΄λ‹€λ°°μ˜κ²½μ΄λ‹€λ°°μ˜κ²½μ΄λ‹€λ°°μ˜κ²½μ΄λ‹€λ°°μ˜κ²½μ΄λ‹€λ°°μ˜κ²½μ΄λ‹€λ°°μ˜κ²½μ΄λ‹€λ°°μ˜κ²½μ΄λ‹€', + reviewDate: '2025.01.15', + reviewName: 'Bae Young Kyoung Kyoung', + reviewLink: 'https://san.chosun.com/news/articleView.html?idxno=15686', + reviewDescription: + 'λ‚˜λ¬΄μ•„λ―Ένƒ€λΆˆλ‚˜λ¬΄μ•„λ―Ένƒ€λΆˆλ‚˜λ¬΄μ•„λ―Ένƒ€λΆˆλ‚˜λ¬΄μ•„λ―Ένƒ€λΆˆλ‚˜λ¬΄μ•„λ―Ένƒ€λΆˆλ‚˜λ¬΄μ•„λ―Ένƒ€λΆˆλ‚˜λ¬΄μ•„λ―Ένƒ€λΆˆλ‚˜λ¬΄μ•„λ―Ένƒ€λΆˆλ‚˜λ¬΄μ•„λ―Ένƒ€λΆˆ', + blogImage: 'https://ifh.cc/g/YwlKhp.jpg', + size: 'large', + }, +}; diff --git a/src/stories/ScheduleCard.stories.ts b/src/stories/ScheduleCard.stories.ts new file mode 100644 index 00000000..db07ab3d --- /dev/null +++ b/src/stories/ScheduleCard.stories.ts @@ -0,0 +1,50 @@ +import ScheduleCard from '@components/schedule/ScheduleCard'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta: Meta = { + title: 'Schedule/ScheduleCard', + component: ScheduleCard, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + day: { + control: { type: 'text' }, + description: 'Day of the schedule', + }, + programs: { + control: 'object', + description: 'Programs with time and description', + }, + }, + args: { + day: '1일차 (마음 λΉ„μš°λŠ” λ‚ )', + programs: { + '14:00~14:20': '방사배정/수련볡 지급', + '14:30~15:20': 'μ˜€λ¦¬μ—”ν…Œμ΄μ…˜/사찰 예절', + '15:30~16:30': 'μŠ€λ‹˜κ³Όμ˜ μ°¨λ‹΄ λ˜λŠ” 사찰 μ•ˆλ‚΄', + '16:30~17:30': 'λ―Έλ₯΅λ³΄μ „ 108λ°°', + '18:00~22:00': '저녁곡양/달빛여행(μžμœ μ‹œκ°„)', + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const AnotherDay: Story = { + args: { + day: '2일차 (μƒˆλ‘œμš΄ μ‹œμž‘)', + programs: { + '05:00~05:30': 'μƒˆλ²½ 예뢈', + '06:00~07:00': 'λ°œμš°κ³΅μ–‘μ•Όμ•„μ•„μ•„μ•„μ•„γ…μ•™μ•Όμ•„μ•„μ•„μ•„', + '08:00~09:00': 'μ°Έμ„ ', + '10:00~11:00': 'μ°Έμ„ ν•˜μ„Έμš”', + '12:00~14:30': 'ν…œν”ŒμŠ€ν…Œμ΄ μ†Œκ° λ‚˜λˆ„κΈ°μ–΄μ©Œκ΅¬μ €κΊΌμ£Όλ¦¬λΆ€λ¦¬λΆ€λ¦¬λΆ€λ¦¬', + }, + }, +}; diff --git a/src/stories/SearchBar.stories.ts b/src/stories/SearchBar.stories.ts new file mode 100644 index 00000000..72982d4d --- /dev/null +++ b/src/stories/SearchBar.stories.ts @@ -0,0 +1,17 @@ +import SearchBar from '@components/search/searchBar/SearchBar'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/SearchBar', + component: SearchBar, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/SearchPage.stories.tsx b/src/stories/SearchPage.stories.tsx new file mode 100644 index 00000000..563fb3e7 --- /dev/null +++ b/src/stories/SearchPage.stories.tsx @@ -0,0 +1,17 @@ +import SearchPage from '@pages/searchPage/SearchPage'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Page/SearchPage', + component: SearchPage, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/SmallMap.stories.ts b/src/stories/SmallMap.stories.ts new file mode 100644 index 00000000..4f094084 --- /dev/null +++ b/src/stories/SmallMap.stories.ts @@ -0,0 +1,17 @@ +import SmallMap from '@components/templeDetail/naverMap/smallMap/SmallMap'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/Map/SmallMap', + component: SmallMap, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/Tag.stories.ts b/src/stories/Tag.stories.ts new file mode 100644 index 00000000..c3e46a61 --- /dev/null +++ b/src/stories/Tag.stories.ts @@ -0,0 +1,30 @@ +import Tag from '@components/common/tag/Tag'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/Tag', + component: Tag, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + color: { + control: { type: 'radio' }, + options: ['brown', 'blue', 'gray'], + }, + label: { + control: { type: 'text' }, + }, + }, + args: { + color: 'brown', + label: 'text', + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/TapBar.stories.ts b/src/stories/TapBar.stories.ts new file mode 100644 index 00000000..a539d604 --- /dev/null +++ b/src/stories/TapBar.stories.ts @@ -0,0 +1,34 @@ +import TapBar from '@components/common/tapBar/TapBar'; +import { TAPS } from '@constants/taps'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/TabBar', + component: TapBar, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + type: { + control: { type: 'radio' }, + options: Object.keys(TAPS), + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const FilterTab: Story = { + args: { + type: 'filter', + }, +}; + +export const DetailTab: Story = { + args: { + type: 'detail', + }, +}; diff --git a/src/stories/TempleInfo.stories.ts b/src/stories/TempleInfo.stories.ts new file mode 100644 index 00000000..eafecdaf --- /dev/null +++ b/src/stories/TempleInfo.stories.ts @@ -0,0 +1,17 @@ +import TempleInfo from '@components/templeDetail/templeInfo/templeInfo'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/Temple/TempleDetail/TempleInfo', + component: TempleInfo, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/TempleStayCard.stories.ts b/src/stories/TempleStayCard.stories.ts new file mode 100644 index 00000000..1ab8ad83 --- /dev/null +++ b/src/stories/TempleStayCard.stories.ts @@ -0,0 +1,72 @@ +import TempleStayCard from '@components/card/templeStayCard/TempleStayCard'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/card/TempleStayCard', + component: TempleStayCard, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + id: { + control: { type: 'number' }, + }, + templeName: { + control: { type: 'text' }, + }, + + templestayName: { + control: { type: 'text' }, + }, + tag: { + control: { type: 'text' }, + }, + region: { + control: { type: 'text' }, + }, + type: { + control: { type: 'text' }, + }, + imgUrl: { + control: { type: 'text' }, + }, + liked: { + control: { type: 'boolean' }, + }, + layout: { + control: { type: 'select' }, + options: ['vertical', 'horizontal'], + }, + }, + args: { + id: 1, + templeName: '봉인사', + templestayName: 'μ‚¬λΆˆμ‚°(ε››δ½›ε±±)... μ˜›κΈΈμ„ κ±·λ‹€', + tag: 'μ—°μ˜ˆμΈμ΄ λ‹€λ…€κ°„', + region: 'μ„œμšΈ', + type: 'νœ΄μ‹ν˜•', + imgUrl: + 'https://file.percenty.co.kr/public/65a89c361aa1f25215b17f4a/products/660db89df900ac2f15094bc4/47272ce2-f477-4472-955e-f2e8eddc521e.jpg', + liked: false, + layout: 'horizontal', + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const horizontalWishCard: Story = { + args: { + layout: 'horizontal', + }, +}; + +export const verticalWishCard: Story = { + args: { + layout: 'vertical', + }, +}; diff --git a/src/stories/UnderlinedBtn.stories.ts b/src/stories/UnderlinedBtn.stories.ts new file mode 100644 index 00000000..054ecaba --- /dev/null +++ b/src/stories/UnderlinedBtn.stories.ts @@ -0,0 +1,33 @@ +import UnderlinedBtn from '@components/common/button/underlinedBtn/UnderlinedBtn'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/Button/UnderlinedBtn', + component: UnderlinedBtn, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + label: { + control: { type: 'text' }, + }, + onClick: { + action: 'clicked', + }, + isActive: { + control: { type: 'boolean' }, + }, + }, + args: { + label: 'text', + onClick: () => alert('click !'), + isActive: false, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/WishListPage.stories.ts b/src/stories/WishListPage.stories.ts new file mode 100644 index 00000000..c3c3a81d --- /dev/null +++ b/src/stories/WishListPage.stories.ts @@ -0,0 +1,17 @@ +import WishListPage from '@pages/wishList/WishListPage'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Page/WishListPage', + component: WishListPage, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/stories/assets/accessibility.png b/src/stories/assets/accessibility.png deleted file mode 100644 index 6ffe6fea..00000000 Binary files a/src/stories/assets/accessibility.png and /dev/null differ diff --git a/src/stories/assets/accessibility.svg b/src/stories/assets/accessibility.svg deleted file mode 100644 index 107e93f8..00000000 --- a/src/stories/assets/accessibility.svg +++ /dev/null @@ -1 +0,0 @@ -Accessibility \ No newline at end of file diff --git a/src/stories/assets/addon-library.png b/src/stories/assets/addon-library.png deleted file mode 100644 index 95deb38a..00000000 Binary files a/src/stories/assets/addon-library.png and /dev/null differ diff --git a/src/stories/assets/assets.png b/src/stories/assets/assets.png deleted file mode 100644 index cfba6817..00000000 Binary files a/src/stories/assets/assets.png and /dev/null differ diff --git a/src/stories/assets/avif-test-image.avif b/src/stories/assets/avif-test-image.avif deleted file mode 100644 index 530709bc..00000000 Binary files a/src/stories/assets/avif-test-image.avif and /dev/null differ diff --git a/src/stories/assets/context.png b/src/stories/assets/context.png deleted file mode 100644 index e5cd249a..00000000 Binary files a/src/stories/assets/context.png and /dev/null differ diff --git a/src/stories/assets/discord.svg b/src/stories/assets/discord.svg deleted file mode 100644 index d638958b..00000000 --- a/src/stories/assets/discord.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/stories/assets/docs.png b/src/stories/assets/docs.png deleted file mode 100644 index a749629d..00000000 Binary files a/src/stories/assets/docs.png and /dev/null differ diff --git a/src/stories/assets/figma-plugin.png b/src/stories/assets/figma-plugin.png deleted file mode 100644 index 8f79b08c..00000000 Binary files a/src/stories/assets/figma-plugin.png and /dev/null differ diff --git a/src/stories/assets/github.svg b/src/stories/assets/github.svg deleted file mode 100644 index dc513528..00000000 --- a/src/stories/assets/github.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/stories/assets/share.png b/src/stories/assets/share.png deleted file mode 100644 index 8097a370..00000000 Binary files a/src/stories/assets/share.png and /dev/null differ diff --git a/src/stories/assets/styling.png b/src/stories/assets/styling.png deleted file mode 100644 index d341e826..00000000 Binary files a/src/stories/assets/styling.png and /dev/null differ diff --git a/src/stories/assets/testing.png b/src/stories/assets/testing.png deleted file mode 100644 index d4ac39a0..00000000 Binary files a/src/stories/assets/testing.png and /dev/null differ diff --git a/src/stories/assets/theming.png b/src/stories/assets/theming.png deleted file mode 100644 index 1535eb9b..00000000 Binary files a/src/stories/assets/theming.png and /dev/null differ diff --git a/src/stories/assets/tutorials.svg b/src/stories/assets/tutorials.svg deleted file mode 100644 index b492a9c6..00000000 --- a/src/stories/assets/tutorials.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/stories/assets/youtube.svg b/src/stories/assets/youtube.svg deleted file mode 100644 index a7515d7e..00000000 --- a/src/stories/assets/youtube.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/stories/button.css b/src/stories/button.css deleted file mode 100644 index 94d674b7..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: #1ea7fd; - 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 87f7ecb1..00000000 --- a/src/stories/page.css +++ /dev/null @@ -1,69 +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: #1ea7fd; - text-decoration: none; -} - -.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: #66bf3c; - 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; -} diff --git a/src/stories/pageBottomBtn.stories.ts b/src/stories/pageBottomBtn.stories.ts new file mode 100644 index 00000000..bc51b83f --- /dev/null +++ b/src/stories/pageBottomBtn.stories.ts @@ -0,0 +1,79 @@ +import PageBottomBtn from '@components/common/button/pageBottomBtn/PageBottomBtn'; +import type { Meta, StoryObj } from '@storybook/react'; + +interface PageBottomBtnProps { + btnText: string; + size: 'small' | 'large'; + isDisabled: boolean; + onClick: () => void; +} + +const meta: Meta = { + title: 'Common/Button/PageBottomBtn', + component: PageBottomBtn, + parameters: { + layout: 'centered', + }, + argTypes: { + btnText: { + control: { type: 'text' }, + description: 'The text displayed on the button', + }, + size: { + control: { type: 'select' }, + options: ['small', 'large'], + description: 'The size of the button', + }, + isDisabled: { + control: { type: 'boolean' }, + description: 'Indicates whether the button is disabled', + }, + onClick: { + action: 'clicked', + description: 'The function triggered when the button is clicked', + }, + }, + args: { + btnText: 'Click Me', + size: 'large', + isDisabled: false, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const LargeEnabled: Story = { + args: { + btnText: 'μ ˆλ‘œκ°€ μ‹œμž‘ν•˜κΈ°', + size: 'large', + isDisabled: false, + }, +}; + +export const SmallEnabled: Story = { + args: { + btnText: 'μ˜ˆμ•½ν•˜κΈ°', + size: 'small', + isDisabled: false, + }, +}; + +export const LargeDisabled: Story = { + args: { + btnText: 'λ‹€μŒ', + size: 'large', + isDisabled: true, + }, +}; + +export const SmallDisabled: Story = { + args: { + btnText: 'μ˜ˆμ•½ν•˜κΈ°', + size: 'small', + isDisabled: true, + }, +}; diff --git a/src/stories/pageBtn.stories.ts b/src/stories/pageBtn.stories.ts new file mode 100644 index 00000000..135224aa --- /dev/null +++ b/src/stories/pageBtn.stories.ts @@ -0,0 +1,56 @@ +import PageBtn from '@components/common/button/pageBtn/PageBtn'; +import type { Meta, StoryObj } from '@storybook/react'; + +interface PageBtnProps { + pageIndex: number; + currentPageNum: number; +} + +const meta: Meta = { + title: 'Common/Button/PageBtn', + component: PageBtn, + parameters: { + layout: 'centered', + }, + argTypes: { + pageIndex: { + control: { type: 'number' }, + description: 'The number displayed on the button', + }, + currentPageNum: { + control: { type: 'number' }, + description: 'The current active page number', + }, + }, + args: { + pageIndex: 1, + currentPageNum: 1, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const Page1Active: Story = { + args: { + pageIndex: 1, + currentPageNum: 1, + }, +}; + +export const Page2Active: Story = { + args: { + pageIndex: 2, + currentPageNum: 2, + }, +}; + +export const Page3Inactive: Story = { + args: { + pageIndex: 3, + currentPageNum: 1, + }, +}; diff --git a/src/stories/textBtn.stories.ts b/src/stories/textBtn.stories.ts new file mode 100644 index 00000000..4e5b26ba --- /dev/null +++ b/src/stories/textBtn.stories.ts @@ -0,0 +1,75 @@ +import Icon from '@assets/svgs'; +import TextBtn from '@components/common/button/textBtn/TextBtn'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Common/Button/TextBtn', + component: TextBtn, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + + argTypes: { + clicked: { + control: { type: 'boolean' }, + }, + size: { + control: { type: 'radio' }, + options: ['small', 'medium'], + }, + text: { + control: { type: 'text' }, + }, + leftIcon: { + control: { type: 'select' }, + options: Object.keys(Icon), + }, + rightIcon: { + control: { type: 'select' }, + options: Object.keys(Icon), + }, + onClick: { action: 'clicked' }, + }, + + args: { + clicked: false, + size: 'small', + text: 'TextButton', + leftIcon: undefined, + rightIcon: 'IcnPaste', + onClick: () => alert('click !'), + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +const createTextBtnStory = ( + text: string, + clicked?: boolean, + size?: 'small' | 'medium', + leftIcon?: keyof typeof Icon, + rightIcon?: keyof typeof Icon, +) => ({ + args: { clicked, size, text, leftIcon, rightIcon }, +}); + +export const GoToJeol: Story = createTextBtnStory( + 'GoToJeol', + undefined, + undefined, + undefined, + 'IcnPaste', +); + +export const Clicked: Story = createTextBtnStory( + 'Clicked', + true, + undefined, + 'IcnPaste', + 'IcnPaste', +); diff --git a/src/styles/fonts.css.ts b/src/styles/fonts.css.ts index 68b064ef..05bbaa9e 100644 --- a/src/styles/fonts.css.ts +++ b/src/styles/fonts.css.ts @@ -17,9 +17,3 @@ globalFontFace('Pretendard-SemiBold', { fontStyle: 'normal', src: "url('/src/assets/fonts/Pretendard-SemiBold.woff2') format('woff2')", }); - -globalFontFace('Pretendard-Bold', { - fontWeight: 700, - fontStyle: 'normal', - src: "url('/src/assets/fonts/Pretendard-Bold.woff2') format('woff2')", -}); diff --git a/src/styles/global.css.ts b/src/styles/global.css.ts index 718805f2..9e371607 100644 --- a/src/styles/global.css.ts +++ b/src/styles/global.css.ts @@ -11,10 +11,15 @@ globalStyle('html, body', { }); globalStyle('#root', { + display: 'flex', + flexDirection: 'column', + position: 'relative', + width: '100%', maxWidth: '37.5rem', minHeight: '100dvh', margin: '0 auto', + paddingTop: '1.2rem', }); globalStyle('body, button, input, select, table, textarea', { diff --git a/src/styles/reset.css.ts b/src/styles/reset.css.ts index a42ea7a1..700ecd49 100644 --- a/src/styles/reset.css.ts +++ b/src/styles/reset.css.ts @@ -59,3 +59,8 @@ globalStyle('input, textarea', { outline: 'none', border: 'none', }); + +globalStyle('a', { + textDecoration: 'none', + color: 'inherit', +}); diff --git a/src/styles/theme.css.ts b/src/styles/theme.css.ts index 0cdee91b..34594da4 100644 --- a/src/styles/theme.css.ts +++ b/src/styles/theme.css.ts @@ -16,19 +16,40 @@ const theme = createGlobalTheme(':root', { black: '#121212', white: '#ffffff', + black60: 'rgba(18, 18, 18, 0.6)', - primary200: '#D3ECD8', - primary400: '#6EBE7D', + primary200: '#9FECAD', + primary400: '#65C677', + primary600: '#3EBD55', + + green1: '#F7FFE9', + green2: '#D3ECD8', + green3: '#ABD6AE', + green4: '#6EBE7D', + green5: '#779971', + green6: '#EDF7F3', + + pink1: '#FF9999', + pink2: '#FF8B8B', + brown1: '#FFF2EC', + brown2: '#E39371', + blue1: '#EBEFFF', + blue2: '#6A7394', + kakao: '#FEE500', gradient: 'linear-gradient(180deg, #00000000 61.26%, #000000BF 100%)', + + filerDropshadow: '0px 4px 7px 0px #0000000D', + reserveBtnDropshadow: '0px -4px 7px 0px #0000000D', + boxArrowBtnDropshadow: '0px 3px 19px 0px rgba(0, 0, 0, 0.08)', }, FONTS: { //Heading - h0B24: { - fontSize: '2.4rem', - fontFamily: "'Pretendard-Bold', sans-serif", - lineHeight: '140%', + h0Sb22: { + fontSize: '2.2rem', + fontFamily: "'Pretendard-SemiBold', sans-serif", + lineHeight: '138%', letterSpacing: '1%', }, h1Sb24: { @@ -69,6 +90,13 @@ const theme = createGlobalTheme(':root', { }, //Body + b0R22: { + fontSize: '2.2rem', + fontFamily: "'Pretendard-Regular', sans-serif", + lineHeight: '138%', + letterSpacing: '1%', + }, + b1M20: { fontSize: '2rem', fontFamily: "'Pretendard-Medium', sans-serif", @@ -137,18 +165,64 @@ const theme = createGlobalTheme(':root', { lineHeight: '128%', letterSpacing: '2%', }, - c3R12: { - fontSize: '1.2rem', + c3Sb14: { + fontSize: '1.4rem', + fontFamily: "'Pretendard-SemiBold', sans-serif", + lineHeight: '128%', + letterSpacing: '2%', + }, + c4M14: { + fontSize: '1.4rem', + fontFamily: "'Pretendard-Medium', sans-serif", + lineHeight: '128%', + letterSpacing: '2%', + }, + c5M13: { + fontSize: '1.3rem', + fontFamily: "'Pretendard-Medium', sans-serif", + lineHeight: '128%', + letterSpacing: '2%', + }, + c6R13: { + fontSize: '1.3rem', fontFamily: "'Pretendard-Regular', sans-serif", lineHeight: '128%', letterSpacing: '2%', }, - c4M12: { + c7R12: { fontSize: '1.2rem', - fontFamily: "'Pretendard-Medium', sans-serif", + fontFamily: "'Pretendard-Regular', sans-serif", + lineHeight: '128%', + letterSpacing: '2%', + }, + + //footer + f1Sb11: { + fontSize: '1.1rem', + fontFamily: "'Pretendard-SemiBold', sans-serif", + lineHeight: '128%', + letterSpacing: '2%', + }, + f2Sb10: { + fontSize: '1rem', + fontFamily: "'Pretendard-SemiBold', sans-serif", + lineHeight: '128%', + letterSpacing: '2%', + }, + f3R10: { + fontSize: '1rem', + fontFamily: "'Pretendard-Regular', sans-serif", lineHeight: '128%', letterSpacing: '2%', }, + + //inform + i1R15: { + fontSize: '1.5rem', + fontFamily: "'Pretendard-Regular', sans-serif", + lineHeight: '170%', + letterSpacing: '1%', + }, }, }); diff --git a/src/svg.d.ts b/src/svg.d.ts new file mode 100644 index 00000000..927a3065 --- /dev/null +++ b/src/svg.d.ts @@ -0,0 +1,4 @@ +declare module '*.svg' { + const ReactComponent: React.FC>; + export default ReactComponent; +} diff --git a/src/utils/registDragEvent.ts b/src/utils/registDragEvent.ts new file mode 100644 index 00000000..bd352956 --- /dev/null +++ b/src/utils/registDragEvent.ts @@ -0,0 +1,51 @@ +const registDragEvent = ({ + onDragChange, + onDragEnd, + stopPropagation, +}: { + onDragChange?: (dx: number, dy: number) => void; + onDragEnd?: (dx: number, dy: number) => void; + stopPropagation?: boolean; +}) => ({ + onMouseDown: (clickEvent: React.MouseEvent) => { + if (stopPropagation) clickEvent.stopPropagation(); // λΆ€λͺ¨λ‘œ 클릭 이벀트 전달 μ•ˆ λ˜λ„λ‘ + + // 마우슀 이동거리 계산 + const mouseMoveHandler = (moveEvent: MouseEvent) => { + const dx = moveEvent.pageX - clickEvent.pageX; + const dy = moveEvent.pageY - clickEvent.pageY; + onDragChange?.(dx, dy); + }; + + const mouseUpHandler = (moveEvent: MouseEvent) => { + const dx = moveEvent.pageX - clickEvent.pageX; + const dy = moveEvent.pageY - clickEvent.pageY; + onDragEnd?.(dx, dy); + document.removeEventListener('mousemove', mouseMoveHandler); + }; + + document.addEventListener('mousemove', mouseMoveHandler); + document.addEventListener('mouseup', mouseUpHandler, { once: true }); + }, + onTouchStart: (touchEvent: React.TouchEvent) => { + if (stopPropagation) touchEvent.stopPropagation(); + + const touchMoveHandler = (moveEvent: TouchEvent) => { + const dx = moveEvent.touches[0].pageX - touchEvent.touches[0].pageX; + const dy = moveEvent.touches[0].pageY - touchEvent.touches[0].pageY; + onDragChange?.(dx, dy); + }; + + const touchEndHandler = (endEvent: TouchEvent) => { + const dx = endEvent.changedTouches[0].pageX - touchEvent.touches[0].pageX; + const dy = endEvent.changedTouches[0].pageY - touchEvent.touches[0].pageY; + onDragEnd?.(dx, dy); + document.removeEventListener('touchmove', touchMoveHandler); + }; + + document.addEventListener('touchmove', touchMoveHandler); + document.addEventListener('touchend', touchEndHandler, { once: true }); + }, +}); + +export default registDragEvent; diff --git a/tsconfig.app.json b/tsconfig.app.json index 7988ad5f..8be8e62b 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -34,5 +34,5 @@ "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src", "src/react-app-env.d.ts"] } diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo new file mode 100644 index 00000000..c7beef75 --- /dev/null +++ b/tsconfig.app.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/app.tsx","./src/main.tsx","./src/queryclient.ts","./src/react-app-env.d.ts","./src/svg.d.ts","./src/vite-env.d.ts","./src/assets/svgs/index.ts","./src/components/card/lookcard/lookcard.tsx","./src/components/card/lookcard/lookcard.css.ts","./src/components/card/mapcard/locbtn.tsx","./src/components/card/mapcard/map.tsx","./src/components/card/mapcard/mapcard.tsx","./src/components/card/mapcard/locbtn.css.ts","./src/components/card/mapcard/map.css.ts","./src/components/card/mapcard/mapcard.css.ts","./src/components/card/popularcard/popularcard.tsx","./src/components/card/popularcard/rankbtn.tsx","./src/components/card/popularcard/popularcard.css.ts","./src/components/card/popularcard/rankbtn.css.ts","./src/components/card/reviewcard/cardinfo/cardinfo.tsx","./src/components/card/reviewcard/cardinfo/cardinfo.css.ts","./src/components/card/reviewcard/reviewcard/reviewcard.tsx","./src/components/card/reviewcard/reviewcard/reviewcard.css.ts","./src/components/card/templestaycard/infosection.tsx","./src/components/card/templestaycard/templestaycard.tsx","./src/components/card/templestaycard/infosection.css.ts","./src/components/card/templestaycard/templestaycard.css.ts","./src/components/card/templestaycard/wishcardlist/wishcardlist.tsx","./src/components/card/templestaycard/wishcardlist/wishcardlist.css.ts","./src/components/carousel/curationcarousel/curationcarousel.tsx","./src/components/carousel/curationcarousel/curationcarousel.css.ts","./src/components/carousel/popularcarousel/carouselindex.tsx","./src/components/carousel/popularcarousel/popularcarousel.tsx","./src/components/carousel/popularcarousel/carouselindex.css.ts","./src/components/carousel/popularcarousel/popularcarousel.css.ts","./src/components/common/button/arrowbtn/arrowbtn.tsx","./src/components/common/button/arrowbtn/arrowbtn.css.ts","./src/components/common/button/basicbtn/basicbtn.tsx","./src/components/common/button/basicbtn/basicbtn.css.ts","./src/components/common/button/buttonbar/buttonbar.tsx","./src/components/common/button/buttonbar/buttonbar.css.ts","./src/components/common/button/flowerbtn/flowerbtn.tsx","./src/components/common/button/flowerbtn/flowerbtn.css.ts","./src/components/common/button/infobtn/infobtn.tsx","./src/components/common/button/infobtn/infobtn.css.ts","./src/components/common/button/kakaobtn/kakaobtn.tsx","./src/components/common/button/kakaobtn/kakaobtn.css.ts","./src/components/common/button/onboardingbtn/onboardingbtn.tsx","./src/components/common/button/onboardingbtn/onboardingbtn.css.ts","./src/components/common/button/pagebottombtn/pagebottombtn.tsx","./src/components/common/button/pagebottombtn/pagebottombtn.css.ts","./src/components/common/button/pagebtn/pagebtn.tsx","./src/components/common/button/pagebtn/pagebtn.css.ts","./src/components/common/button/textbtn/textbtn.tsx","./src/components/common/button/textbtn/textbtn.css.ts","./src/components/common/button/underlinedbtn/underlinedbtn.tsx","./src/components/common/button/underlinedbtn/underlinedbtn.css.ts","./src/components/common/detailinfo/detailinfo.tsx","./src/components/common/detailinfo/detailinfo.css.ts","./src/components/common/empty/wishempty/wishempty.tsx","./src/components/common/empty/wishempty/wishempty.css.ts","./src/components/common/icon/flowericon.tsx","./src/components/common/icon/flowericon/flowericon.tsx","./src/components/common/pagename/pagename.tsx","./src/components/common/pagename/pagename.css.ts","./src/components/common/pagination/pagination.tsx","./src/components/common/pagination/pagination.css.ts","./src/components/common/progressbar/progressbar.tsx","./src/components/common/progressbar/progressbar.css.ts","./src/components/common/tag/tag.tsx","./src/components/common/tag/tag.css.ts","./src/components/common/tapbar/tapbar.tsx","./src/components/common/tapbar/tapbar.css.ts","./src/components/curation/curationcard/curationcard.tsx","./src/components/curation/curationcard/curationcard.css.ts","./src/components/detailtitle/detailtitle.tsx","./src/components/detailtitle/detailtitle.css.ts","./src/components/footer/footer.tsx","./src/components/footer/footer.css.ts","./src/components/header/header.tsx","./src/components/header/header.css.ts","./src/components/schedule/schedulecard.tsx","./src/components/schedule/schedulecard.css.ts","./src/components/search/recentbtn/recentbtnbox.tsx","./src/components/search/recentbtn/recentbtnbox.css.ts","./src/components/search/searchbar/searchbar.tsx","./src/components/search/searchbar/searchbar.css.ts","./src/components/search/searchheader/searchheader.tsx","./src/components/search/searchheader/searchheader.css.ts","./src/components/templedetail/navermap/mapcontainer.tsx","./src/components/templedetail/navermap/largemap/largemap.tsx","./src/components/templedetail/navermap/largemap/largemap.css.ts","./src/components/templedetail/navermap/smallmap/smallmap.tsx","./src/components/templedetail/navermap/smallmap/smallmap.css.ts","./src/components/templedetail/templeinfo/templeinfo.css.ts","./src/components/templedetail/templeinfo/templeinfo.tsx","./src/components/templedetail/templeinfo/contentcollapse/contentcollapse.tsx","./src/components/templedetail/templeinfo/contentcollapse/contentcollapse.css.ts","./src/components/userinfo/userinfo.css.ts","./src/components/userinfo/userinfo.tsx","./src/components/userinfo/userinfocontent/accountaction/accountaction.tsx","./src/components/userinfo/userinfocontent/accountaction/accountaction.css.ts","./src/components/userinfo/userinfocontent/helpcontent/helpcontent.tsx","./src/components/userinfo/userinfocontent/helpcontent/helpcontent.css.ts","./src/components/userinfo/userinfocontent/namecontent/namecontent.tsx","./src/components/userinfo/userinfocontent/namecontent/namecontent.css.ts","./src/components/userinfo/userinfocontent/userdetailinfo.tsx/userdetailinfo.tsx","./src/components/userinfo/userinfocontent/userdetailinfo.tsx/userdetailinfo.css.ts","./src/components/userinfo/userinfocontent/userinfosection/userinfosection.css.ts","./src/components/userinfo/userinfocontent/userinfosection/userinfosection.tsx","./src/constants/constants.ts","./src/constants/curationinfo.ts","./src/constants/logininfos.ts","./src/constants/regioninfos.ts","./src/constants/taps.ts","./src/constants/userinfo.ts","./src/hooks/usecarousel.ts","./src/hooks/usenavigateto.tsx","./src/hooks/useexpandhook/useexpandhook.tsx","./src/pages/errorpage.tsx","./src/pages/homepage.tsx","./src/pages/wishpage.tsx","./src/pages/loginpage/loginpage.tsx","./src/pages/loginpage/loginpage.css.ts","./src/pages/mypage/mypage.tsx","./src/pages/searchpage/searchpage.tsx","./src/pages/searchpage/searchpage.css.ts","./src/pages/wishlist/wishlistpage.tsx","./src/pages/wishlist/wishlistpage.css.ts","./src/router/privateroute.tsx","./src/router/router.tsx","./src/stories/arrowbtn.stories.ts","./src/stories/basicbtn.stories.ts","./src/stories/buttonbar.stories.ts","./src/stories/carouselindex.stories.ts","./src/stories/curationcard.stories.ts","./src/stories/curationcarousel.stories.ts","./src/stories/detailinfo.stories.ts","./src/stories/detailtitle.stories.ts","./src/stories/flowerbtn.stories.ts","./src/stories/footer.stories.ts","./src/stories/header.stories.ts","./src/stories/infobtn.stories.ts","./src/stories/kakaobtn.stories.ts","./src/stories/largemap.stories.ts","./src/stories/lookcard.stories.ts","./src/stories/mapcard.stories.ts","./src/stories/mypage.stories.ts","./src/stories/onboardingbutton.stories.ts","./src/stories/pagename.stories.ts","./src/stories/pagination.stories.tsx","./src/stories/popularcard.stories.ts","./src/stories/popularcarousel.stories.ts","./src/stories/progressbar.stories.ts","./src/stories/reviewcard.stories.ts","./src/stories/schedulecard.stories.ts","./src/stories/searchbar.stories.ts","./src/stories/searchpage.stories.tsx","./src/stories/smallmap.stories.ts","./src/stories/tag.stories.ts","./src/stories/tapbar.stories.ts","./src/stories/templeinfo.stories.ts","./src/stories/templestaycard.stories.ts","./src/stories/underlinedbtn.stories.ts","./src/stories/wishlistpage.stories.ts","./src/stories/pagebottombtn.stories.ts","./src/stories/pagebtn.stories.ts","./src/stories/textbtn.stories.ts","./src/styles/fonts.css.ts","./src/styles/global.css.ts","./src/styles/reset.css.ts","./src/styles/theme.css.ts","./src/utils/registdragevent.ts"],"version":"5.6.3"} \ No newline at end of file diff --git a/vercel.json b/vercel.json new file mode 100644 index 00000000..3a48e56b --- /dev/null +++ b/vercel.json @@ -0,0 +1,3 @@ +{ + "rewrites": [{ "source": "/(.*)", "destination": "/" }] +} diff --git a/yarn.lock b/yarn.lock index 88239958..4c1adb86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -733,190 +733,190 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.1.tgz#9bd38df6a29afb7f0336d988bc8112af0c8816c0" integrity sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw== -"@rollup/rollup-android-arm-eabi@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.0.tgz#f2552f6984cfae52784b2fbf0e47633f38955d66" - integrity sha512-qFcFto9figFLz2g25DxJ1WWL9+c91fTxnGuwhToCl8BaqDsDYMl/kOnBXAyAqkkzAWimYMSWNPWEjt+ADAHuoQ== +"@rollup/rollup-android-arm-eabi@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz#14c737dc19603a096568044eadaa60395eefb809" + integrity sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q== "@rollup/rollup-android-arm64@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.29.1.tgz#bd1a98390e15b76eeef907175a37c5f0f9e4d214" integrity sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew== -"@rollup/rollup-android-arm64@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.0.tgz#7e5764268d3049b7341c60f1c650f1d71760a5b2" - integrity sha512-vqrQdusvVl7dthqNjWCL043qelBK+gv9v3ZiqdxgaJvmZyIAAXMjeGVSqZynKq69T7062T5VrVTuikKSAAVP6A== +"@rollup/rollup-android-arm64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz#9d81ea54fc5650eb4ebbc0a7d84cee331bfa30ad" + integrity sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w== "@rollup/rollup-darwin-arm64@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.29.1.tgz#bc6fa8a2cc77b5f367424e5e994e3537524e6879" integrity sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw== -"@rollup/rollup-darwin-arm64@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.0.tgz#c9245577f673802f0f6de0d46ee776691d77552e" - integrity sha512-617pd92LhdA9+wpixnzsyhVft3szYiN16aNUMzVkf2N+yAk8UXY226Bfp36LvxYTUt7MO/ycqGFjQgJ0wlMaWQ== +"@rollup/rollup-darwin-arm64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz#29448cb1370cf678b50743d2e392be18470abc23" + integrity sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q== "@rollup/rollup-darwin-x64@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.29.1.tgz#76059c91f06b17406347b127df10f065283b2e61" integrity sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng== -"@rollup/rollup-darwin-x64@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.0.tgz#e492705339542f8b54fa66f630c9d820bc708693" - integrity sha512-Y3b4oDoaEhCypg8ajPqigKDcpi5ZZovemQl9Edpem0uNv6UUjXv7iySBpGIUTSs2ovWOzYpfw9EbFJXF/fJHWw== +"@rollup/rollup-darwin-x64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz#0ca99741c3ed096700557a43bb03359450c7857d" + integrity sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA== "@rollup/rollup-freebsd-arm64@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.29.1.tgz#83178315c0be4b4c8c1fd835e1952d2dc1eb4e6e" integrity sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw== -"@rollup/rollup-freebsd-arm64@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.0.tgz#3e13b5d4d44ea87598d5d4db97181db1174fb3c8" - integrity sha512-3REQJ4f90sFIBfa0BUokiCdrV/E4uIjhkWe1bMgCkhFXbf4D8YN6C4zwJL881GM818qVYE9BO3dGwjKhpo2ABA== +"@rollup/rollup-freebsd-arm64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz#233f8e4c2f54ad9b719cd9645887dcbd12b38003" + integrity sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ== "@rollup/rollup-freebsd-x64@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.29.1.tgz#1ef24fa0576bf7899a0a0a649156606dbd7a0d46" integrity sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w== -"@rollup/rollup-freebsd-x64@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.0.tgz#138daa08d1b345d605f57b4dedd18a50420488e7" - integrity sha512-ZtY3Y8icbe3Cc+uQicsXG5L+CRGUfLZjW6j2gn5ikpltt3Whqjfo5mkyZ86UiuHF9Q3ZsaQeW7YswlHnN+lAcg== +"@rollup/rollup-freebsd-x64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz#dfba762a023063dc901610722995286df4a48360" + integrity sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw== "@rollup/rollup-linux-arm-gnueabihf@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.29.1.tgz#443a6f5681bf4611caae42988994a6d8ee676216" integrity sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A== -"@rollup/rollup-linux-arm-gnueabihf@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.0.tgz#bdaece34f93c3dfd521e9ab8f5c740121862468e" - integrity sha512-bsPGGzfiHXMhQGuFGpmo2PyTwcrh2otL6ycSZAFTESviUoBOuxF7iBbAL5IJXc/69peXl5rAtbewBFeASZ9O0g== +"@rollup/rollup-linux-arm-gnueabihf@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz#b9da54171726266c5ef4237f462a85b3c3cf6ac9" + integrity sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg== "@rollup/rollup-linux-arm-musleabihf@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.29.1.tgz#9738b27184102228637a683e5f35b22ea352394f" integrity sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ== -"@rollup/rollup-linux-arm-musleabihf@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.0.tgz#1804c6ec49be21521eac612513e0666cdde2188c" - integrity sha512-kvyIECEhs2DrrdfQf++maCWJIQ974EI4txlz1nNSBaCdtf7i5Xf1AQCEJWOC5rEBisdaMFFnOWNLYt7KpFqy5A== +"@rollup/rollup-linux-arm-musleabihf@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz#b9db69b3f85f5529eb992936d8f411ee6d04297b" + integrity sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug== "@rollup/rollup-linux-arm64-gnu@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.29.1.tgz#b5e9d5e30ff36a19bedd29c715ba18a1889ff269" integrity sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA== -"@rollup/rollup-linux-arm64-gnu@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.0.tgz#2c4bd90f77fcf769502743ec38f184c00a087e08" - integrity sha512-CFE7zDNrokaotXu+shwIrmWrFxllg79vciH4E/zeK7NitVuWEaXRzS0mFfFvyhZfn8WfVOG/1E9u8/DFEgK7WQ== +"@rollup/rollup-linux-arm64-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz#2550cf9bb4d47d917fd1ab4af756d7bbc3ee1528" + integrity sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw== "@rollup/rollup-linux-arm64-musl@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.29.1.tgz#1d8f68f0829b57f746ec03432ad046f1af014a98" integrity sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA== -"@rollup/rollup-linux-arm64-musl@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.0.tgz#63eadee20f220d28e85cbd10aba671ada8e89c84" - integrity sha512-MctNTBlvMcIBP0t8lV/NXiUwFg9oK5F79CxLU+a3xgrdJjfBLVIEHSAjQ9+ipofN2GKaMLnFFXLltg1HEEPaGQ== +"@rollup/rollup-linux-arm64-musl@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz#9d06b26d286c7dded6336961a2f83e48330e0c80" + integrity sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA== "@rollup/rollup-linux-loongarch64-gnu@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.29.1.tgz#07027feb883408e74a3002c8e50caaedd288ae38" integrity sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw== -"@rollup/rollup-linux-loongarch64-gnu@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.0.tgz#1c2c2bb30f61cbbc0fcf4e6c359777fcdb7108cc" - integrity sha512-fBpoYwLEPivL3q368+gwn4qnYnr7GVwM6NnMo8rJ4wb0p/Y5lg88vQRRP077gf+tc25akuqd+1Sxbn9meODhwA== +"@rollup/rollup-linux-loongarch64-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz#e957bb8fee0c8021329a34ca8dfa825826ee0e2e" + integrity sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ== "@rollup/rollup-linux-powerpc64le-gnu@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.29.1.tgz#544ce1b0847a9c1240425e86f33daceac7ec4e12" integrity sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w== -"@rollup/rollup-linux-powerpc64le-gnu@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.0.tgz#cea71e0359f086a01c57cf312bef9ec9cc3ba010" - integrity sha512-1hiHPV6dUaqIMXrIjN+vgJqtfkLpqHS1Xsg0oUfUVD98xGp1wX89PIXgDF2DWra1nxAd8dfE0Dk59MyeKaBVAw== +"@rollup/rollup-linux-powerpc64le-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz#e8585075ddfb389222c5aada39ea62d6d2511ccc" + integrity sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw== "@rollup/rollup-linux-riscv64-gnu@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.29.1.tgz#64be13d51852ec1e2dfbd25d997ed5f42f35ea6d" integrity sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ== -"@rollup/rollup-linux-riscv64-gnu@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.0.tgz#25ab4a6dbcbd27f4a68382f7963363f886a237aa" - integrity sha512-U0xcC80SMpEbvvLw92emHrNjlS3OXjAM0aVzlWfar6PR0ODWCTQtKeeB+tlAPGfZQXicv1SpWwRz9Hyzq3Jx3g== +"@rollup/rollup-linux-riscv64-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz#7d0d40cee7946ccaa5a4e19a35c6925444696a9e" + integrity sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw== "@rollup/rollup-linux-s390x-gnu@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.29.1.tgz#31f51e1e05c6264552d03875d9e2e673f0fd86e3" integrity sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g== -"@rollup/rollup-linux-s390x-gnu@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.0.tgz#7054b237152d9e36c51194532a6b70ca1a62a487" - integrity sha512-VU/P/IODrNPasgZDLIFJmMiLGez+BN11DQWfTVlViJVabyF3JaeaJkP6teI8760f18BMGCQOW9gOmuzFaI1pUw== +"@rollup/rollup-linux-s390x-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz#c2dcd8a4b08b2f2778eceb7a5a5dfde6240ebdea" + integrity sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA== "@rollup/rollup-linux-x64-gnu@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.29.1.tgz#f4c95b26f4ad69ebdb64b42f0ae4da2a0f617958" integrity sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ== -"@rollup/rollup-linux-x64-gnu@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.0.tgz#3656a8341a6048f2111f423301aaad8e84a5fe90" - integrity sha512-laQVRvdbKmjXuFA3ZiZj7+U24FcmoPlXEi2OyLfbpY2MW1oxLt9Au8q9eHd0x6Pw/Kw4oe9gwVXWwIf2PVqblg== +"@rollup/rollup-linux-x64-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz#183637d91456877cb83d0a0315eb4788573aa588" + integrity sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg== "@rollup/rollup-linux-x64-musl@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.29.1.tgz#ab7be89192f72beb9ea6e2386186fefde4f69d82" integrity sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA== -"@rollup/rollup-linux-x64-musl@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.0.tgz#cf8ae018ea6ff65eb36722a28beb93a20a6047f0" - integrity sha512-3wzKzduS7jzxqcOvy/ocU/gMR3/QrHEFLge5CD7Si9fyHuoXcidyYZ6jyx8OPYmCcGm3uKTUl+9jUSAY74Ln5A== +"@rollup/rollup-linux-x64-musl@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz#036a4c860662519f1f9453807547fd2a11d5bb01" + integrity sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow== "@rollup/rollup-win32-arm64-msvc@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.29.1.tgz#7f12efb8240b238346951559998802722944421e" integrity sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig== -"@rollup/rollup-win32-arm64-msvc@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.0.tgz#6b968f5b068469db16eac743811ee6c040671042" - integrity sha512-jROwnI1+wPyuv696rAFHp5+6RFhXGGwgmgSfzE8e4xfit6oLRg7GyMArVUoM3ChS045OwWr9aTnU+2c1UdBMyw== +"@rollup/rollup-win32-arm64-msvc@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz#51cad812456e616bfe4db5238fb9c7497e042a52" + integrity sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw== "@rollup/rollup-win32-ia32-msvc@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.29.1.tgz#353d14d6eee943004d129796e4feddd3aa260921" integrity sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng== -"@rollup/rollup-win32-ia32-msvc@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.0.tgz#0321de1a0540dd402e8e523d90cbd9d16f1b9e96" - integrity sha512-duzweyup5WELhcXx5H1jokpr13i3BV9b48FMiikYAwk/MT1LrMYYk2TzenBd0jj4ivQIt58JWSxc19y4SvLP4g== +"@rollup/rollup-win32-ia32-msvc@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz#661c8b3e4cd60f51deaa39d153aac4566e748e5e" + integrity sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw== "@rollup/rollup-win32-x64-msvc@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.1.tgz#c82f04a09ba481e13857d6f2516e072aaa51b7f4" integrity sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg== -"@rollup/rollup-win32-x64-msvc@4.30.0": - version "4.30.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.0.tgz#7384b359bb45c0c3c76ba2c7aaec1d047305efcb" - integrity sha512-DYvxS0M07PvgvavMIybCOBYheyrqlui6ZQBHJs6GqduVzHSZ06TPPvlfvnYstjODHQ8UUXFwt5YE+h0jFI8kwg== +"@rollup/rollup-win32-x64-msvc@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz#73bf1885ff052b82fbb0f82f8671f73c36e9137c" + integrity sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og== "@rtsao/scc@^1.1.0": version "1.1.0" @@ -1344,6 +1344,11 @@ dependencies: "@babel/types" "^7.20.7" +"@types/cookie@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" + integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== + "@types/doctrine@^0.0.9": version "0.0.9" resolved "https://registry.yarnpkg.com/@types/doctrine/-/doctrine-0.0.9.tgz#d86a5f452a15e3e3113b99e39616a9baa0f9863f" @@ -1354,6 +1359,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== +"@types/geojson@*": + version "7946.0.15" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.15.tgz#f9d55fd5a0aa2de9dc80b1b04e437538b7298868" + integrity sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA== + "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -1369,6 +1379,13 @@ resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.13.tgz#68f6877043d377092890ff5b298152b0a21671bd" integrity sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw== +"@types/navermaps@^3.7.8": + version "3.7.8" + resolved "https://registry.yarnpkg.com/@types/navermaps/-/navermaps-3.7.8.tgz#e4a3a4f351537dfc7dd66c29f48db88a489d2fe1" + integrity sha512-LzQffMWcUfhKzOuPpUONaXmMN6sAkNf92q1nycRplqorIl2oDjgdPftOw0LttTS0/k/YsotizawK+PtcRWbuog== + dependencies: + "@types/geojson" "*" + "@types/node@*": version "22.10.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.5.tgz#95af89a3fb74a2bb41ef9927f206e6472026e48b" @@ -1929,9 +1946,9 @@ browser-assert@^1.2.1: integrity sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ== browserslist@^4.24.0: - version "4.24.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.3.tgz#5fc2725ca8fb3c1432e13dac278c7cc103e026d2" - integrity sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA== + version "4.24.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" + integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== dependencies: caniuse-lite "^1.0.30001688" electron-to-chromium "^1.5.73" @@ -1980,9 +1997,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001688: - version "1.0.30001690" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz#f2d15e3aaf8e18f76b2b8c1481abde063b8104c8" - integrity sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w== + version "1.0.30001692" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz#4585729d95e6b95be5b439da6ab55250cd125bf9" + integrity sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A== chai@^5.1.1: version "5.1.2" @@ -2021,6 +2038,11 @@ chromatic@^11.15.0: resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-11.20.2.tgz#10b309179cdc0b9195a5b68970366f9ebe67dfd1" integrity sha512-c+M3HVl5Y60c7ipGTZTyeWzWubRW70YsJ7PPDpO1D735ib8+Lu3yGF90j61pvgkXGngpkTPHZyBw83lcu2JMxA== +chromatic@^11.22.1: + version "11.22.1" + resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-11.22.1.tgz#8d18540632447d1f0a4adec55eb9a82a3b42a412" + integrity sha512-GQP+xE00YDYuxVA4F7n5l1+hPWZC5xBgzFanKRfSbBgx4j2ZEbXa53C7fdkxMNplQR546nFIyhnoFRUB8ljIQg== + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -2040,6 +2062,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +compare-versions@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-6.1.1.tgz#7af3cc1099ba37d244b3145a9af5201b629148a9" + integrity sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2060,6 +2087,11 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" + integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== + cosmiconfig@^8.1.3: version "8.3.6" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" @@ -2245,9 +2277,9 @@ dunder-proto@^1.0.0, dunder-proto@^1.0.1: gopd "^1.2.0" electron-to-chromium@^1.5.73: - version "1.5.76" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz#db20295c5061b68f07c8ea4dfcbd701485d94a3d" - integrity sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ== + version "1.5.79" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.79.tgz#4424f23f319db7a653cf9ee76102e4ac283e6b3e" + integrity sha512-nYOxJNxQ9Om4EC88BE4pPoNI8xwSFf8pU/BAeOl4Hh/b/i6V4biTAzwV7pXi3ARKeoYO5JZKMIXTryXSVer5RA== emoji-regex@^9.2.2: version "9.2.2" @@ -3707,7 +3739,7 @@ pathval@^2.0.0: resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== -picocolors@^1.0.0, picocolors@^1.1.0, picocolors@^1.1.1: +picocolors@^1.0.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -3843,6 +3875,11 @@ react-docgen@^7.0.0: loose-envify "^1.1.0" scheduler "^0.23.2" +react-inspector@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-6.0.2.tgz#aa3028803550cb6dbd7344816d5c80bf39d07e9d" + integrity sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ== + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -3858,6 +3895,23 @@ react-refresh@^0.14.2: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== +react-router-dom@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.1.1.tgz#9e76fb63a762ba5da13032f5fd9e4a24946396b6" + integrity sha512-vSrQHWlJ5DCfyrhgo0k6zViOe9ToK8uT5XGSmnuC2R3/g261IdIMpZVqfjD6vWSXdnf5Czs4VA/V60oVR6/jnA== + dependencies: + react-router "7.1.1" + +react-router@7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.1.1.tgz#88f5657fa5b8f0b918c7222ec710de0274d00b2e" + integrity sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ== + dependencies: + "@types/cookie" "^0.6.0" + cookie "^1.0.1" + set-cookie-parser "^2.6.0" + turbo-stream "2.4.0" + "react@^16.8.0 || ^17.0.0 || ^18.0.0", react@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" @@ -3947,31 +4001,31 @@ reusify@^1.0.4: integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rollup@^4.20.0: - version "4.30.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.30.0.tgz#44ae4260029a8362113ef2a0cee7e02f3f740274" - integrity sha512-sDnr1pcjTgUT69qBksNF1N1anwfbyYG6TBQ22b03bII8EdiUQ7J0TlozVaTMjT/eEJAO49e1ndV7t+UZfL1+vA== + version "4.30.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.30.1.tgz#d5c3d066055259366cdc3eb6f1d051c5d6afaf74" + integrity sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w== dependencies: "@types/estree" "1.0.6" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.30.0" - "@rollup/rollup-android-arm64" "4.30.0" - "@rollup/rollup-darwin-arm64" "4.30.0" - "@rollup/rollup-darwin-x64" "4.30.0" - "@rollup/rollup-freebsd-arm64" "4.30.0" - "@rollup/rollup-freebsd-x64" "4.30.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.30.0" - "@rollup/rollup-linux-arm-musleabihf" "4.30.0" - "@rollup/rollup-linux-arm64-gnu" "4.30.0" - "@rollup/rollup-linux-arm64-musl" "4.30.0" - "@rollup/rollup-linux-loongarch64-gnu" "4.30.0" - "@rollup/rollup-linux-powerpc64le-gnu" "4.30.0" - "@rollup/rollup-linux-riscv64-gnu" "4.30.0" - "@rollup/rollup-linux-s390x-gnu" "4.30.0" - "@rollup/rollup-linux-x64-gnu" "4.30.0" - "@rollup/rollup-linux-x64-musl" "4.30.0" - "@rollup/rollup-win32-arm64-msvc" "4.30.0" - "@rollup/rollup-win32-ia32-msvc" "4.30.0" - "@rollup/rollup-win32-x64-msvc" "4.30.0" + "@rollup/rollup-android-arm-eabi" "4.30.1" + "@rollup/rollup-android-arm64" "4.30.1" + "@rollup/rollup-darwin-arm64" "4.30.1" + "@rollup/rollup-darwin-x64" "4.30.1" + "@rollup/rollup-freebsd-arm64" "4.30.1" + "@rollup/rollup-freebsd-x64" "4.30.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.30.1" + "@rollup/rollup-linux-arm-musleabihf" "4.30.1" + "@rollup/rollup-linux-arm64-gnu" "4.30.1" + "@rollup/rollup-linux-arm64-musl" "4.30.1" + "@rollup/rollup-linux-loongarch64-gnu" "4.30.1" + "@rollup/rollup-linux-powerpc64le-gnu" "4.30.1" + "@rollup/rollup-linux-riscv64-gnu" "4.30.1" + "@rollup/rollup-linux-s390x-gnu" "4.30.1" + "@rollup/rollup-linux-x64-gnu" "4.30.1" + "@rollup/rollup-linux-x64-musl" "4.30.1" + "@rollup/rollup-win32-arm64-msvc" "4.30.1" + "@rollup/rollup-win32-ia32-msvc" "4.30.1" + "@rollup/rollup-win32-x64-msvc" "4.30.1" fsevents "~2.3.2" rollup@^4.23.0: @@ -4054,6 +4108,11 @@ semver@^7.6.0, semver@^7.6.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +set-cookie-parser@^2.6.0: + version "2.7.1" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943" + integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ== + set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -4146,6 +4205,14 @@ source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +storybook-addon-react-router-v6@^2.0.15: + version "2.0.15" + resolved "https://registry.yarnpkg.com/storybook-addon-react-router-v6/-/storybook-addon-react-router-v6-2.0.15.tgz#01f890ce92f0af8de070fc3a25a9dfaf2bf13752" + integrity sha512-4vOYIQwehlQLyXaCT9K0Iu0po2Z+pANGsqkfm/BQuAEjE9MoN7vrsrQLHINhKbmQVtNMmuS/oBR3GGNbqQ1gew== + dependencies: + compare-versions "^6.0.0" + react-inspector "6.0.2" + storybook@^8.4.7: version "8.4.7" resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.4.7.tgz#a3068787a58074cec1b4197eed1c4427ec644b3f" @@ -4338,6 +4405,11 @@ tslib@^2.0.1, tslib@^2.0.3, tslib@^2.6.2: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +turbo-stream@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/turbo-stream/-/turbo-stream-2.4.0.tgz#1e4fca6725e90fa14ac4adb782f2d3759a5695f0" + integrity sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g== + tween-functions@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tween-functions/-/tween-functions-1.2.0.tgz#1ae3a50e7c60bb3def774eac707acbca73bbc3ff" @@ -4440,20 +4512,20 @@ universalify@^2.0.0: integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== unplugin@^1.3.1: - version "1.16.0" - resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.16.0.tgz#ca0f248bf8798cd752dd02e5b381223b737cef72" - integrity sha512-5liCNPuJW8dqh3+DM6uNM2EI3MLLpCKp/KY+9pB5M2S2SR2qvvDHhKgBOaTWEbZTAws3CXfB0rKTIolWKL05VQ== + version "1.16.1" + resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.16.1.tgz#a844d2e3c3b14a4ac2945c42be80409321b61199" + integrity sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w== dependencies: acorn "^8.14.0" webpack-virtual-modules "^0.6.2" update-browserslist-db@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" - integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== + version "1.1.2" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580" + integrity sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg== dependencies: escalade "^3.2.0" - picocolors "^1.1.0" + picocolors "^1.1.1" uri-js@^4.2.2: version "4.4.1"