diff --git a/apps/web/public/Icons/backward.svg b/apps/web/public/Icons/backward.svg new file mode 100644 index 0000000..c5f1d86 --- /dev/null +++ b/apps/web/public/Icons/backward.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/web/public/Icons/blog.svg b/apps/web/public/Icons/blog.svg new file mode 100644 index 0000000..bd6ba3e --- /dev/null +++ b/apps/web/public/Icons/blog.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/apps/web/public/Icons/close-circle.svg b/apps/web/public/Icons/close-circle.svg new file mode 100644 index 0000000..b59d175 --- /dev/null +++ b/apps/web/public/Icons/close-circle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/web/public/Icons/close.svg b/apps/web/public/Icons/close.svg new file mode 100644 index 0000000..bc053ff --- /dev/null +++ b/apps/web/public/Icons/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/web/public/Icons/instagram.svg b/apps/web/public/Icons/instagram.svg new file mode 100644 index 0000000..ef79d4a --- /dev/null +++ b/apps/web/public/Icons/instagram.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/web/public/Icons/plus.svg b/apps/web/public/Icons/plus.svg new file mode 100644 index 0000000..8c150be --- /dev/null +++ b/apps/web/public/Icons/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/web/public/Icons/search.svg b/apps/web/public/Icons/search.svg new file mode 100644 index 0000000..4d4ce4e --- /dev/null +++ b/apps/web/public/Icons/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/web/public/Icons/symbol.svg b/apps/web/public/Icons/symbol.svg new file mode 100644 index 0000000..0706f09 --- /dev/null +++ b/apps/web/public/Icons/symbol.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/web/public/Icons/typo.svg b/apps/web/public/Icons/typo.svg new file mode 100644 index 0000000..04ff31f --- /dev/null +++ b/apps/web/public/Icons/typo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 730397d..b635309 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -1,6 +1,7 @@ 'use client' -import { Button, Text } from '@vook-client/design-system' +import { Button, Text, List, Icon, SearchBar } from '@vook-client/design-system' +import { useState } from 'react' import { TestComponent } from '@/components/TestComponent' @@ -8,10 +9,19 @@ const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://dev.vook-api.seungyeop-lee.com' const Home = () => { + const [wordState, setWordState] = useState('') + return (
Hello world! 프리텐다드 + Label + +

API_URL: {API_URL}

diff --git a/apps/workshop/.storybook/main.ts b/apps/workshop/.storybook/main.ts index 05d8823..882f921 100644 --- a/apps/workshop/.storybook/main.ts +++ b/apps/workshop/.storybook/main.ts @@ -76,6 +76,7 @@ const config: StorybookConfig = { docs: { autodocs: 'tag', }, + staticDirs: ['../public'], async viteFinal(config) { return mergeConfig(config, { plugins: [require('@vanilla-extract/vite-plugin').vanillaExtractPlugin()], diff --git a/apps/workshop/public/Icons/backward.svg b/apps/workshop/public/Icons/backward.svg new file mode 100644 index 0000000..c5f1d86 --- /dev/null +++ b/apps/workshop/public/Icons/backward.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/workshop/public/Icons/blog.svg b/apps/workshop/public/Icons/blog.svg new file mode 100644 index 0000000..bd6ba3e --- /dev/null +++ b/apps/workshop/public/Icons/blog.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/apps/workshop/public/Icons/close-circle.svg b/apps/workshop/public/Icons/close-circle.svg new file mode 100644 index 0000000..b59d175 --- /dev/null +++ b/apps/workshop/public/Icons/close-circle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/workshop/public/Icons/close.svg b/apps/workshop/public/Icons/close.svg new file mode 100644 index 0000000..bc053ff --- /dev/null +++ b/apps/workshop/public/Icons/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/workshop/public/Icons/instagram.svg b/apps/workshop/public/Icons/instagram.svg new file mode 100644 index 0000000..ef79d4a --- /dev/null +++ b/apps/workshop/public/Icons/instagram.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/workshop/public/Icons/plus.svg b/apps/workshop/public/Icons/plus.svg new file mode 100644 index 0000000..8c150be --- /dev/null +++ b/apps/workshop/public/Icons/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/workshop/public/Icons/search.svg b/apps/workshop/public/Icons/search.svg new file mode 100644 index 0000000..4d4ce4e --- /dev/null +++ b/apps/workshop/public/Icons/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/workshop/public/Icons/symbol.svg b/apps/workshop/public/Icons/symbol.svg new file mode 100644 index 0000000..0706f09 --- /dev/null +++ b/apps/workshop/public/Icons/symbol.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/workshop/public/Icons/typo.svg b/apps/workshop/public/Icons/typo.svg new file mode 100644 index 0000000..04ff31f --- /dev/null +++ b/apps/workshop/public/Icons/typo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/design-system/src/assets/Icon/Icon.css.ts b/packages/design-system/src/assets/Icon/Icon.css.ts new file mode 100644 index 0000000..4c9ae9b --- /dev/null +++ b/packages/design-system/src/assets/Icon/Icon.css.ts @@ -0,0 +1,28 @@ +import { RecipeVariants, recipe } from '@vanilla-extract/recipes' + +export const icon = recipe({ + base: { + display: 'flex', + width: 24, + height: 24, + alignItems: 'center', + justifyContent: 'center', + }, + variants: { + size: { + footer: { + width: 32, + height: 32, + }, + large: {}, + medium: {}, + small: { + width: 16, + height: 16, + }, + typo: { width: 60, height: 20 }, + }, + }, +}) + +export type IconVariants = RecipeVariants diff --git a/packages/design-system/src/assets/Icon/Icon.spec.tsx b/packages/design-system/src/assets/Icon/Icon.spec.tsx new file mode 100644 index 0000000..92e5068 --- /dev/null +++ b/packages/design-system/src/assets/Icon/Icon.spec.tsx @@ -0,0 +1,11 @@ +import { render, screen } from '@testing-library/react' + +import { Icon } from '.' + +describe('Icon', () => { + it('Icon은 정상적으로 렌더링 된다.', () => { + render() + + expect(screen.getByAltText('Icon')).toBeInTheDocument() + }) +}) diff --git a/packages/design-system/src/assets/Icon/Icon.stories.tsx b/packages/design-system/src/assets/Icon/Icon.stories.tsx new file mode 100644 index 0000000..01ae98c --- /dev/null +++ b/packages/design-system/src/assets/Icon/Icon.stories.tsx @@ -0,0 +1,76 @@ +import { Meta, StoryObj } from '@storybook/react' + +import { Icon, IconProps, IconType } from './Icon' + +const ICON_NAME: Array = [ + 'backward', + 'blog', + 'close', + 'close-circle', + 'instagram', + 'plus', + 'search', + 'symbol', + 'typo', +] + +const ICON_SIZE: Array = [ + 'footer', + 'large', + 'medium', + 'small', + 'typo', +] + +// List 컴포넌트의 Meta 정보 정의 +const meta = { + title: 'Icon', + component: Icon, + tags: ['autodocs'], + args: { + name: 'search', + size: 'large', + }, + argTypes: { + name: { + options: ICON_NAME, + control: { type: 'select' }, + description: '아이콘 이름', + }, + size: { + options: ICON_SIZE, + control: { type: 'select' }, + description: '아이콘 크기', + }, + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Playground: Story = {} + +export const Type: Story = { + argTypes: { + name: { + table: { + disable: true, + }, + }, + }, + render: (props) => { + return ( +
    + {ICON_NAME.map((iconName) => ( +
  • +

    {iconName} 아이콘

    + + {props.children} + +
  • + ))} +
+ ) + }, +} diff --git a/packages/design-system/src/assets/Icon/Icon.tsx b/packages/design-system/src/assets/Icon/Icon.tsx new file mode 100644 index 0000000..049dcbb --- /dev/null +++ b/packages/design-system/src/assets/Icon/Icon.tsx @@ -0,0 +1,31 @@ +import { HTMLAttributes } from 'react' + +import { IconVariants, icon } from './Icon.css' + +export interface IconType { + name?: + | 'backward' + | 'blog' + | 'close-circle' + | 'close' + | 'instagram' + | 'plus' + | 'search' + | 'symbol' + | 'typo' +} + +export type IconProps = HTMLAttributes & IconVariants & IconType + +export const Icon = ({ name = 'search', size = 'large' }: IconProps) => { + return ( +
+ {`This +
+ ) +} diff --git a/packages/design-system/src/assets/Icon/index.ts b/packages/design-system/src/assets/Icon/index.ts new file mode 100644 index 0000000..558c6a5 --- /dev/null +++ b/packages/design-system/src/assets/Icon/index.ts @@ -0,0 +1,2 @@ +export type { IconProps } from './Icon' +export { Icon } from './Icon' diff --git a/packages/design-system/src/components/List/List.css.ts b/packages/design-system/src/components/List/List.css.ts new file mode 100644 index 0000000..0469f92 --- /dev/null +++ b/packages/design-system/src/components/List/List.css.ts @@ -0,0 +1,54 @@ +import { RecipeVariants, recipe } from '@vanilla-extract/recipes' + +import { vars } from '../../styles/global.css' + +export const list = recipe({ + base: { + padding: '16px 12px', + boxSizing: 'border-box', + }, + variants: { + page: { + table: { + width: 220, + color: vars.colors['semantic-primary-normal'], + }, + synonym: { + width: 220, + color: vars.colors['semantic-label-alternative'], + }, + description: { + width: 480, + paddingRight: 100, + }, + title: { + width: 220, + backgroundColor: vars.colors['component-alternative'], + color: vars.colors['semantic-label-alternative'], + }, + }, + search: { + table: { + width: 120, + padding: '8px 12px', + color: vars.colors['semantic-primary-normal'], + }, + synonym: { + width: 120, + padding: '8px 12px', + }, + description: { + width: 340, + padding: '8px, 40px, 8px, 12px', + }, + title: { + width: 120, + padding: '4px 12px', + backgroundColor: vars.colors['palette-primary-50'], + color: vars.colors['semantic-label-normal'], + }, + }, + }, +}) + +export type ListVariants = RecipeVariants diff --git a/packages/design-system/src/components/List/List.spec.tsx b/packages/design-system/src/components/List/List.spec.tsx new file mode 100644 index 0000000..2eb483e --- /dev/null +++ b/packages/design-system/src/components/List/List.spec.tsx @@ -0,0 +1,11 @@ +import { render, screen } from '@testing-library/react' + +import { List } from '.' + +describe('List', () => { + it('List는 정상적으로 렌더링 된다.', () => { + render(List) + + expect(screen.getByText('List')).toBeInTheDocument() + }) +}) diff --git a/packages/design-system/src/components/List/List.stories.tsx b/packages/design-system/src/components/List/List.stories.tsx new file mode 100644 index 0000000..806d345 --- /dev/null +++ b/packages/design-system/src/components/List/List.stories.tsx @@ -0,0 +1,102 @@ +import { Meta, StoryObj } from '@storybook/react' + +import { List, ListType } from './List' + +const LIST_VARIANTS: Array = ['page', 'search'] +const LIST_KINDS: Array = [ + 'description', + 'synonym', + 'table', + 'title', +] + +// List 컴포넌트의 Meta 정보 정의 +const meta = { + title: 'List', + component: List, + tags: ['autodocs'], + args: { + children: 'List', + variant: 'page', + kind: 'description', + }, + argTypes: { + children: { + control: 'text', + description: '라벨 텍스트', + }, + variant: { + options: LIST_VARIANTS, + control: { type: 'select' }, + description: '라벨 타입', + }, + kind: { + options: LIST_KINDS, + control: { type: 'select' }, + description: '라벨 타입', + }, + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Playground: Story = {} + +export const Type: Story = { + argTypes: { + variant: { + table: { + disable: true, + }, + }, + kind: { + table: { + disable: true, + }, + }, + }, + render: (props) => { + return ( +
    +
  • +

    Label

    + + {props.children} + + + {props.children} + +
  • +
  • +

    Text

    + + {props.children} + + + {props.children} + +
  • +
  • +

    Descrption

    + + {props.children} + + + {props.children} + +
  • +
  • +

    Label

    + + {props.children} + + + {props.children} + +
  • +
+ ) + }, +} diff --git a/packages/design-system/src/components/List/List.tsx b/packages/design-system/src/components/List/List.tsx new file mode 100644 index 0000000..9204bb2 --- /dev/null +++ b/packages/design-system/src/components/List/List.tsx @@ -0,0 +1,35 @@ +import { HTMLAttributes, PropsWithChildren } from 'react' + +import { Text, TextProps } from '../Text' + +import { list } from './List.css' + +export interface ListType { + variant?: 'page' | 'search' + kind?: 'table' | 'synonym' | 'description' | 'title' +} + +export type ListProps = HTMLAttributes & + PropsWithChildren & + ListType + +export const List = ({ + variant = 'page', + kind = 'description', + children, + ...rest +}: ListProps) => { + const textType: TextProps['type'] = variant === 'page' ? 'body-2' : 'label' + const fontWeight: TextProps['fontWeight'] = + variant === 'page' && (kind === 'table' || kind === 'title') + ? 'medium' + : 'regular' + + return ( +
+ + {children} + +
+ ) +} diff --git a/packages/design-system/src/components/List/index.ts b/packages/design-system/src/components/List/index.ts new file mode 100644 index 0000000..1c09f84 --- /dev/null +++ b/packages/design-system/src/components/List/index.ts @@ -0,0 +1,2 @@ +export type { ListProps } from './List' +export { List } from './List' diff --git a/packages/design-system/src/components/SearchBar/SearchBar.css.ts b/packages/design-system/src/components/SearchBar/SearchBar.css.ts new file mode 100644 index 0000000..1930e38 --- /dev/null +++ b/packages/design-system/src/components/SearchBar/SearchBar.css.ts @@ -0,0 +1,93 @@ +import { RecipeVariants, recipe } from '@vanilla-extract/recipes' +import { style } from '@vanilla-extract/css' + +import { vars } from '../../styles/global.css' + +export const searchBarContainer = style({ + width: 580, + + backgroundColor: vars.colors['common-white'], + border: `1px solid ${vars.colors['semantic-line-normal']}`, + borderRadius: 6, + boxSizing: 'border-box', + ':hover': { + backgroundColor: vars.colors['component-normal'], + }, + ':focus-within': { + backgroundColor: vars.colors['common-white'], + }, +}) + +export const searchBar = recipe({ + base: { + display: 'flex', + height: 58, + justifyContent: 'center', + alignItems: 'center', + }, + variants: { + history: { + true: { + height: 44, + ':hover': { + cursor: 'pointer', + backgroundColor: vars.colors['component-alternative'], + }, + }, + }, + }, +}) + +export type SearchBarVariants = RecipeVariants + +export const iconContainer = recipe({ + base: { + flex: 1, + display: 'flex', + justifyContent: 'center', + }, + variants: { + click: { + true: { ':hover': { cursor: 'pointer' } }, + }, + visibile: { + true: { + visibility: 'visible', + }, + false: { + visibility: 'hidden', + }, + }, + }, +}) + +export const inputContainer = style({ + display: 'flex', + alignItems: 'center', + width: 440, + height: '100%', +}) + +export const inputBox = recipe({ + base: { + width: '100%', + outline: 'none', + border: 'none', + backgroundColor: 'inherit', + + fontSize: 18, + fontWeight: 500, + '::placeholder': { + color: vars.colors['semantic-label-assistive'], + }, + }, + variants: { + text: { + true: { + margin: 0, + fontSize: 16, + fontWeight: 400, + }, + }, + }, +}) diff --git a/packages/design-system/src/components/SearchBar/SearchBar.spec.tsx b/packages/design-system/src/components/SearchBar/SearchBar.spec.tsx new file mode 100644 index 0000000..37e0f84 --- /dev/null +++ b/packages/design-system/src/components/SearchBar/SearchBar.spec.tsx @@ -0,0 +1,11 @@ +import { render, screen } from '@testing-library/react' + +import { SearchBar } from '.' + +describe('List', () => { + it('List는 정상적으로 렌더링 된다.', () => { + render() + + expect(screen.getByText('SearchBar')).toBeInTheDocument() + }) +}) diff --git a/packages/design-system/src/components/SearchBar/SearchBar.stories.tsx b/packages/design-system/src/components/SearchBar/SearchBar.stories.tsx new file mode 100644 index 0000000..f183b22 --- /dev/null +++ b/packages/design-system/src/components/SearchBar/SearchBar.stories.tsx @@ -0,0 +1,42 @@ +import { Meta, StoryObj } from '@storybook/react' +import React, { useState } from 'react' + +import { SearchBar } from './SearchBar' + +// List 컴포넌트의 Meta 정보 정의 +const meta = { + title: 'SearchBar', + component: SearchBar, + tags: ['autodocs'], + args: { + // wordHistory: [], + // wordState: '', + // setWordState: '', + }, + argTypes: { + wordHistory: {}, + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Type: Story = { + argTypes: {}, + render: () => { + const [wordState, setWordState] = useState('') + return ( +
    +
  • +

    SearchBar

    + +
  • +
+ ) + }, +} diff --git a/packages/design-system/src/components/SearchBar/SearchBar.tsx b/packages/design-system/src/components/SearchBar/SearchBar.tsx new file mode 100644 index 0000000..490514c --- /dev/null +++ b/packages/design-system/src/components/SearchBar/SearchBar.tsx @@ -0,0 +1,115 @@ +import { HTMLAttributes, useState } from 'react' + +import { Icon } from '../../assets/Icon' +import { Text } from '../Text' + +import { + iconContainer, + inputBox, + inputContainer, + searchBar, + searchBarContainer, +} from './SearchBar.css' + +interface SearchBarType { + wordHistory: string[] + wordState: string + setWordState: React.Dispatch> +} +interface HistoryBarType { + word: string + // removeWordFromHistory: (word: string) => void +} + +export type SearchBarProps = HTMLAttributes & SearchBarType + +const HistoryBar = ({ word }: HistoryBarType) => { + const [isHovered, setIsHovered] = useState(false) + + return ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + onMouseDown={() => { + alert('검색한 단어:' + word) + }} + > +
+ +
+
+ {word} +
+
{ + e.stopPropagation() + e.preventDefault() + alert('삭제한 단어:' + word) + // removeWordFromHistory(word) + }} + > + +
+
+ ) +} + +export const SearchBar: React.FC = ({ + wordHistory, + wordState, + setWordState, +}) => { + const handleChange = (event: React.ChangeEvent) => { + setWordState(event.target.value) + } + // const removeWordFromHistory = (wordToRemove: string) => { + // setWordHistory((prev: string[]) => + // prev.filter((word) => word !== wordToRemove), + // ) + // } + + const [isFocused, setIsFocused] = useState(false) + + return ( +
setIsFocused(true)} + onBlur={() => { + setIsFocused(false) + }} + > +
+
+ +
+
+ +
+
{ + wordState !== '' && alert('입력한 단어:' + wordState) + setWordState('') + }} + > + +
+
+ + {isFocused && + wordHistory.map((word, index) => ( + + ))} +
+ ) +} diff --git a/packages/design-system/src/components/SearchBar/index.ts b/packages/design-system/src/components/SearchBar/index.ts new file mode 100644 index 0000000..f8e6724 --- /dev/null +++ b/packages/design-system/src/components/SearchBar/index.ts @@ -0,0 +1,2 @@ +export type { SearchBarProps } from './SearchBar' +export { SearchBar } from './SearchBar' diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts index af17e2a..9558445 100644 --- a/packages/design-system/src/index.ts +++ b/packages/design-system/src/index.ts @@ -1,7 +1,13 @@ 'use client' +export type { IconProps } from './assets/Icon' +export { Icon } from './assets/Icon' export type { ButtonProps } from './components/Button' export { Button } from './components/Button' +export type { ListProps } from './components/List' +export { List } from './components/List' +export type { SearchBarProps } from './components/SearchBar' +export { SearchBar } from './components/SearchBar' export type { TextProps } from './components/Text' export { Text } from './components/Text' export type { Sprinkles } from './styles/sprinkles.css' diff --git a/packages/design-system/src/tokens/colors.ts b/packages/design-system/src/tokens/colors.ts index 9588552..208046a 100644 --- a/packages/design-system/src/tokens/colors.ts +++ b/packages/design-system/src/tokens/colors.ts @@ -6,6 +6,7 @@ const semantic = { /* label */ 'semantic-label-title': '#161719', 'semantic-label-normal': '#161719', + 'semantic-label-assistive': 'rgba(22, 23, 25, 0.25)', 'semantic-label-alternative': 'rgba(22, 23, 25, 0.6)', 'semantic-label-disabled': 'rgba(22, 23, 25, 0.16)', /* line */ @@ -23,6 +24,7 @@ const common = { const palette = { /* Primary */ + 'palette-primary-50': '#EFEFFC', 'palette-primary-100': '#CDCCF7', 'palette-primary-200': '#B4B5F3', 'palette-primary-300': '#9292EE',