diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 4783daef..d42df8e7 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -3,20 +3,6 @@ import '@/styles/globals.css'; import type { Preview } from '@storybook/nextjs'; import { Providers } from '../src/app/providers'; -import { pretendard } from '../src/lib/fonts'; - -(() => { - console.log('body className added'); - const addFontClass = () => { - document.body.classList.add(pretendard.className); - }; - - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', addFontClass, { once: true }); - } else { - addFontClass(); - } -})(); const preview: Preview = { parameters: { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 481e4ddf..bba802fa 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -7,7 +7,6 @@ import { Suspense } from 'react'; import { LayoutWrapper } from '@/components/layout'; import { initMocks } from '@/mock'; -import { pretendard } from '../lib/fonts'; import { Providers } from './providers'; export const metadata: Metadata = { @@ -29,7 +28,7 @@ export default async function RootLayout({ return ( - +
diff --git a/src/components/icon/index.tsx b/src/components/icon/index.tsx index f26d295b..61760f69 100644 --- a/src/components/icon/index.tsx +++ b/src/components/icon/index.tsx @@ -1,53 +1,52 @@ -// This file is auto-generated. Do not edit manually. import { type ComponentProps } from 'react'; -import type { IconMetadata } from 'flexisvg'; +import { iconComponentMap } from './registry'; -export type DynamicIconId = - | 'arrow-down' - | 'arrow-up' - | 'bell-read' - | 'calendar-1' - | 'calendar-2' - | 'check' - | 'chevron-left-1' - | 'chevron-left-2' - | 'chevron-right-1' - | 'clock' - | 'edit-bar' - | 'edit' - | 'heart' - | 'home' - | 'kebab' - | 'map-pin-1' - | 'map-pin-2' - | 'message' - | 'plus' - | 'search' - | 'send' - | 'small-x-1' - | 'small-x-2' - | 'symbol' - | 'tag' - | 'user-1' - | 'user-2' - | 'users-1' - | 'users-2' - | 'x-1' - | 'x-2'; -export type ResizableIconId = - | 'bell-unread' - | 'congratulate' - | 'empty' - | 'google-login' - | 'kick' - | 'not-found' - | 'plus-circle' - | 'visibility-false' - | 'visibility-true' - | 'wego-logo'; +export const iconMetadataMap = [ + { id: 'arrow-down', variant: 'dynamic' }, + { id: 'arrow-up', variant: 'dynamic' }, + { id: 'bell-read', variant: 'dynamic' }, + { id: 'calendar-1', variant: 'dynamic' }, + { id: 'calendar-2', variant: 'dynamic' }, + { id: 'check', variant: 'dynamic' }, + { id: 'chevron-left-1', variant: 'dynamic' }, + { id: 'chevron-left-2', variant: 'dynamic' }, + { id: 'chevron-right-1', variant: 'dynamic' }, + { id: 'clock', variant: 'dynamic' }, + { id: 'edit-bar', variant: 'dynamic' }, + { id: 'edit', variant: 'dynamic' }, + { id: 'heart', variant: 'dynamic' }, + { id: 'home', variant: 'dynamic' }, + { id: 'kebab', variant: 'dynamic' }, + { id: 'map-pin-1', variant: 'dynamic' }, + { id: 'map-pin-2', variant: 'dynamic' }, + { id: 'message', variant: 'dynamic' }, + { id: 'plus', variant: 'dynamic' }, + { id: 'search', variant: 'dynamic' }, + { id: 'send', variant: 'dynamic' }, + { id: 'small-x-1', variant: 'dynamic' }, + { id: 'small-x-2', variant: 'dynamic' }, + { id: 'symbol', variant: 'dynamic' }, + { id: 'tag', variant: 'dynamic' }, + { id: 'user-1', variant: 'dynamic' }, + { id: 'user-2', variant: 'dynamic' }, + { id: 'users-1', variant: 'dynamic' }, + { id: 'users-2', variant: 'dynamic' }, + { id: 'x-1', variant: 'dynamic' }, + { id: 'x-2', variant: 'dynamic' }, + { id: 'bell-unread', variant: 'resizable' }, + { id: 'congratulate', variant: 'resizable' }, + { id: 'empty', variant: 'resizable' }, + { id: 'google-login', variant: 'resizable' }, + { id: 'kick', variant: 'resizable' }, + { id: 'not-found', variant: 'resizable' }, + { id: 'plus-circle', variant: 'resizable' }, + { id: 'visibility-false', variant: 'resizable' }, + { id: 'visibility-true', variant: 'resizable' }, + { id: 'wego-logo', variant: 'resizable' }, +] as const; -export type IconId = DynamicIconId | ResizableIconId; +export type IconId = (typeof iconMetadataMap)[number]['id']; type IconProps = ComponentProps<'svg'> & { id: IconId; @@ -55,176 +54,6 @@ type IconProps = ComponentProps<'svg'> & { }; export const Icon = ({ id, size = 24, ...props }: IconProps) => { - return ( - - - - ); + const IconComponent = iconComponentMap[id]; + return ; }; - -export const iconMetadataMap: IconMetadata[] = [ - { - id: 'arrow-down', - variant: 'dynamic', - }, - { - id: 'arrow-up', - variant: 'dynamic', - }, - { - id: 'bell-read', - variant: 'dynamic', - }, - { - id: 'calendar-1', - variant: 'dynamic', - }, - { - id: 'calendar-2', - variant: 'dynamic', - }, - { - id: 'check', - variant: 'dynamic', - }, - { - id: 'chevron-left-1', - variant: 'dynamic', - }, - { - id: 'chevron-left-2', - variant: 'dynamic', - }, - { - id: 'chevron-right-1', - variant: 'dynamic', - }, - { - id: 'clock', - variant: 'dynamic', - }, - { - id: 'edit-bar', - variant: 'dynamic', - }, - { - id: 'edit', - variant: 'dynamic', - }, - { - id: 'heart', - variant: 'dynamic', - }, - { - id: 'home', - variant: 'dynamic', - }, - { - id: 'kebab', - variant: 'dynamic', - }, - { - id: 'map-pin-1', - variant: 'dynamic', - }, - { - id: 'map-pin-2', - variant: 'dynamic', - }, - { - id: 'message', - variant: 'dynamic', - }, - { - id: 'plus', - variant: 'dynamic', - }, - { - id: 'search', - variant: 'dynamic', - }, - { - id: 'send', - variant: 'dynamic', - }, - { - id: 'small-x-1', - variant: 'dynamic', - }, - { - id: 'small-x-2', - variant: 'dynamic', - }, - { - id: 'symbol', - variant: 'dynamic', - }, - { - id: 'tag', - variant: 'dynamic', - }, - { - id: 'user-1', - variant: 'dynamic', - }, - { - id: 'user-2', - variant: 'dynamic', - }, - { - id: 'users-1', - variant: 'dynamic', - }, - { - id: 'users-2', - variant: 'dynamic', - }, - { - id: 'x-1', - variant: 'dynamic', - }, - { - id: 'x-2', - variant: 'dynamic', - }, - { - id: 'bell-unread', - variant: 'resizable', - }, - { - id: 'congratulate', - variant: 'resizable', - }, - { - id: 'empty', - variant: 'resizable', - }, - { - id: 'google-login', - variant: 'resizable', - }, - { - id: 'kick', - variant: 'resizable', - }, - { - id: 'not-found', - variant: 'resizable', - }, - { - id: 'plus-circle', - variant: 'resizable', - }, - { - id: 'visibility-false', - variant: 'resizable', - }, - { - id: 'visibility-true', - variant: 'resizable', - }, - { - id: 'wego-logo', - variant: 'resizable', - }, -]; diff --git a/src/components/icon/registry/index.ts b/src/components/icon/registry/index.ts new file mode 100644 index 00000000..b8e9592f --- /dev/null +++ b/src/components/icon/registry/index.ts @@ -0,0 +1,89 @@ +import { type FC, type SVGProps } from 'react'; + +import IconArrowDown from '../../../../public/icons/dynamic/icon-arrow-down.svg'; +import IconArrowUp from '../../../../public/icons/dynamic/icon-arrow-up.svg'; +import IconBellRead from '../../../../public/icons/dynamic/icon-bell-read.svg'; +import IconCalendar1 from '../../../../public/icons/dynamic/icon-calendar-1.svg'; +import IconCalendar2 from '../../../../public/icons/dynamic/icon-calendar-2.svg'; +import IconCheck from '../../../../public/icons/dynamic/icon-check.svg'; +import IconChevronLeft1 from '../../../../public/icons/dynamic/icon-chevron-left-1.svg'; +import IconChevronLeft2 from '../../../../public/icons/dynamic/icon-chevron-left-2.svg'; +import IconChevronRight1 from '../../../../public/icons/dynamic/icon-chevron-right-1.svg'; +import IconClock from '../../../../public/icons/dynamic/icon-clock.svg'; +import IconEdit from '../../../../public/icons/dynamic/icon-edit.svg'; +import IconEditBar from '../../../../public/icons/dynamic/icon-edit-bar.svg'; +import IconHeart from '../../../../public/icons/dynamic/icon-heart.svg'; +import IconHome from '../../../../public/icons/dynamic/icon-home.svg'; +import IconKebab from '../../../../public/icons/dynamic/icon-kebab.svg'; +import IconMapPin1 from '../../../../public/icons/dynamic/icon-map-pin-1.svg'; +import IconMapPin2 from '../../../../public/icons/dynamic/icon-map-pin-2.svg'; +import IconMessage from '../../../../public/icons/dynamic/icon-message.svg'; +import IconPlus from '../../../../public/icons/dynamic/icon-plus.svg'; +import IconSearch from '../../../../public/icons/dynamic/icon-search.svg'; +import IconSend from '../../../../public/icons/dynamic/icon-send.svg'; +import IconSmallX1 from '../../../../public/icons/dynamic/icon-small-x-1.svg'; +import IconSmallX2 from '../../../../public/icons/dynamic/icon-small-x-2.svg'; +import IconSymbol from '../../../../public/icons/dynamic/icon-symbol.svg'; +import IconTag from '../../../../public/icons/dynamic/icon-tag.svg'; +import IconUser1 from '../../../../public/icons/dynamic/icon-user-1.svg'; +import IconUser2 from '../../../../public/icons/dynamic/icon-user-2.svg'; +import IconUsers1 from '../../../../public/icons/dynamic/icon-users-1.svg'; +import IconUsers2 from '../../../../public/icons/dynamic/icon-users-2.svg'; +import IconX1 from '../../../../public/icons/dynamic/icon-x-1.svg'; +import IconX2 from '../../../../public/icons/dynamic/icon-x-2.svg'; +import IconBellUnread from '../../../../public/icons/resizable/icon-bell-unread.svg'; +import IconCongratulate from '../../../../public/icons/resizable/icon-congratulate.svg'; +import IconEmpty from '../../../../public/icons/resizable/icon-empty.svg'; +import IconGoogleLogin from '../../../../public/icons/resizable/icon-google-login.svg'; +import IconKick from '../../../../public/icons/resizable/icon-kick.svg'; +import IconNotFound from '../../../../public/icons/resizable/icon-not-found.svg'; +import IconPlusCircle from '../../../../public/icons/resizable/icon-plus-circle.svg'; +import IconVisibilityFalse from '../../../../public/icons/resizable/icon-visibility-false.svg'; +import IconVisibilityTrue from '../../../../public/icons/resizable/icon-visibility-true.svg'; +import IconWegoLogo from '../../../../public/icons/resizable/icon-wego-logo.svg'; + +type SvgComponent = FC>; + +export const iconComponentMap: Record = { + 'arrow-down': IconArrowDown, + 'arrow-up': IconArrowUp, + 'bell-read': IconBellRead, + 'calendar-1': IconCalendar1, + 'calendar-2': IconCalendar2, + check: IconCheck, + 'chevron-left-1': IconChevronLeft1, + 'chevron-left-2': IconChevronLeft2, + 'chevron-right-1': IconChevronRight1, + clock: IconClock, + 'edit-bar': IconEditBar, + edit: IconEdit, + heart: IconHeart, + home: IconHome, + kebab: IconKebab, + 'map-pin-1': IconMapPin1, + 'map-pin-2': IconMapPin2, + message: IconMessage, + plus: IconPlus, + search: IconSearch, + send: IconSend, + 'small-x-1': IconSmallX1, + 'small-x-2': IconSmallX2, + symbol: IconSymbol, + tag: IconTag, + 'user-1': IconUser1, + 'user-2': IconUser2, + 'users-1': IconUsers1, + 'users-2': IconUsers2, + 'x-1': IconX1, + 'x-2': IconX2, + 'bell-unread': IconBellUnread, + congratulate: IconCongratulate, + empty: IconEmpty, + 'google-login': IconGoogleLogin, + kick: IconKick, + 'not-found': IconNotFound, + 'plus-circle': IconPlusCircle, + 'visibility-false': IconVisibilityFalse, + 'visibility-true': IconVisibilityTrue, + 'wego-logo': IconWegoLogo, +}; diff --git a/src/components/shared/card/card-tags/index.test.tsx b/src/components/shared/card/card-tags/index.test.tsx index fd9a8dba..9d3fbede 100644 --- a/src/components/shared/card/card-tags/index.test.tsx +++ b/src/components/shared/card/card-tags/index.test.tsx @@ -2,13 +2,6 @@ import { render, screen } from '@testing-library/react'; import { type CardTag, CardTags, getLastVisibleIndex } from '.'; -// ResizeObserver 목 -global.ResizeObserver = class ResizeObserver { - observe = jest.fn(); - disconnect = jest.fn(); - unobserve = jest.fn(); -} as unknown as typeof global.ResizeObserver; - describe('getLastVisibleIndex', () => { it('태그들이 카드 너비를 넘어가지 않으면 모든 태그를 표시한다', () => { const maxWidth = 300; diff --git a/src/components/shared/card/card-tags/index.tsx b/src/components/shared/card/card-tags/index.tsx index e98e0080..a2155a5d 100644 --- a/src/components/shared/card/card-tags/index.tsx +++ b/src/components/shared/card/card-tags/index.tsx @@ -1,8 +1,3 @@ -'use client'; - -/* eslint-disable react-hooks/set-state-in-effect */ -import { useCallback, useLayoutEffect, useRef, useState } from 'react'; - export type CardTag = { id: string | number; label: string; @@ -11,14 +6,6 @@ export type CardTag = { type CardTagsProps = { tags: CardTag[]; }; - -const TAG_GAP = 4; - -const BASE_TAG_CLASSES = - 'bg-mint-100 text-text-2xs-medium text-mint-700 inline-flex shrink-0 items-center rounded-full px-2 py-0.5'; - -const HIDDEN_TAG_CLASSES = `${BASE_TAG_CLASSES} invisible absolute`; - export const getLastVisibleIndex = ( maxWidth: number, tagWidths: number[], @@ -44,71 +31,16 @@ export const getLastVisibleIndex = ( }; export const CardTags = ({ tags }: CardTagsProps) => { - const containerRef = useRef(null); - - const tagRefs = useRef<(HTMLSpanElement | null)[]>([]); - - const [lastVisibleIndex, setLastVisibleIndex] = useState(null); - - const updateVisibleTags = useCallback(() => { - const container = containerRef.current; - - if (!container || tags.length === 0) { - setLastVisibleIndex(null); - return; - } - - const maxWidth = container.offsetWidth; - const tagWidths: number[] = []; - - for (let index = 0; index < tags.length; index++) { - const tagElement = tagRefs.current[index]; - if (!tagElement) { - tagWidths.push(0); - continue; - } - - tagWidths.push(tagElement.offsetWidth); - } - - const nextLastVisibleIndex = getLastVisibleIndex(maxWidth, tagWidths, TAG_GAP); - setLastVisibleIndex(nextLastVisibleIndex); - }, [tags]); - - useLayoutEffect(() => { - updateVisibleTags(); - - const container = containerRef.current; - if (!container) return; - - const resizeObserver = new ResizeObserver(() => { - updateVisibleTags(); - }); - - resizeObserver.observe(container); - - return () => { - resizeObserver.disconnect(); - }; - }, [updateVisibleTags]); - return ( -
- {tags?.map((tag, index) => { - const isVisible = lastVisibleIndex !== null && index <= lastVisibleIndex; - - return ( - { - tagRefs.current[index] = element; - }} - className={isVisible ? BASE_TAG_CLASSES : HIDDEN_TAG_CLASSES} - > - {tag.label} - - ); - })} +
+ {tags?.map((tag) => ( + + {tag.label} + + ))}
); }; diff --git a/src/lib/fonts.ts b/src/lib/fonts.ts deleted file mode 100644 index 1d1a4c75..00000000 --- a/src/lib/fonts.ts +++ /dev/null @@ -1,7 +0,0 @@ -import localFont from 'next/font/local'; - -export const pretendard = localFont({ - src: [{ path: '../assets/fonts/PretendardVariable.woff2', weight: '45 920' }], - variable: '--font-pretendard', - display: 'swap', -}); diff --git a/src/styles/globals.css b/src/styles/globals.css index 8ce49d76..cdc9969f 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -1,3 +1,4 @@ +@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css'); @import 'tailwindcss'; @import '../styles/base.css'; @import '../styles/colors.css'; @@ -24,6 +25,13 @@ } body { + font-family: + 'Pretendard Variable', + Pretendard, + -apple-system, + BlinkMacSystemFont, + system-ui, + sans-serif; background: var(--background); color: var(--foreground); }