diff --git a/apps/web/next.config.js b/apps/web/next.config.js index a03f14e..090e508 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -1,5 +1,21 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + /** + * + * @param {import('webpack').Configuration} config + * @param {import('next/dist/server/config-shared').WebpackConfigContext} context + * @returns {import('webpack').Configuration} + */ + webpack: (config) => { + if (process.env.NEXT_OUTPUT_MODE !== 'export' || !config.module) { + return config + } + config.module.rules?.push({ + test: /src\/app\/test/, + loader: 'ignore-loader', + }) + return config + }, experimental: { externalDir: true, }, diff --git a/apps/web/package.json b/apps/web/package.json index 7c82a6b..f1d9396 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -53,6 +53,7 @@ "@types/react-dom": "18.2.6", "chromatic": "^6.22.0", "eslint-config-custom": "*", + "ignore-loader": "^0.1.2", "storybook": "^7.1.0", "storybook-dark-mode": "^3.0.1", "storybook-tailwind-dark-mode": "^1.0.22", diff --git a/apps/web/src/app/test/input/page.tsx b/apps/web/src/app/test/input/page.tsx new file mode 100644 index 0000000..41eb5ce --- /dev/null +++ b/apps/web/src/app/test/input/page.tsx @@ -0,0 +1,27 @@ +'use client' +import { InputComment, InputSearch } from '@80000coding/ui' +import { useState } from 'react' + +export default function Page() { + const [value, setValue] = useState('') + const [commentValue, setComment] = useState('') + + return ( +
+ {/* + + + */} + + + +
+ ) +} diff --git a/package-lock.json b/package-lock.json index e2bcb3b..fc7202f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,6 +93,7 @@ "@types/react-dom": "18.2.6", "chromatic": "^6.22.0", "eslint-config-custom": "*", + "ignore-loader": "^0.1.2", "storybook": "^7.1.0", "storybook-dark-mode": "^3.0.1", "storybook-tailwind-dark-mode": "^1.0.22", @@ -20121,6 +20122,12 @@ "node": ">= 4" } }, + "node_modules/ignore-loader": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ignore-loader/-/ignore-loader-0.1.2.tgz", + "integrity": "sha512-yOJQEKrNwoYqrWLS4DcnzM7SEQhRKis5mB+LdKKh4cPmGYlLPR0ozRzHV5jmEk2IxptqJNQA5Cc0gw8Fj12bXA==", + "dev": true + }, "node_modules/image-size": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz", diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 2ba02c7..c39f18b 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -5,5 +5,8 @@ export * from './button' export * from './chip' export * from './dropdown' export * from './icon' +export * from './input' +export * from './input/input-comment' +export * from './input/input-search' export * from './notification' export * from './toggle' diff --git a/packages/ui/src/input/index.tsx b/packages/ui/src/input/index.tsx new file mode 100644 index 0000000..df3f00a --- /dev/null +++ b/packages/ui/src/input/index.tsx @@ -0,0 +1,89 @@ +import { StaticConfirmIcon } from '@80000coding/web-icons' +import { Input as Input$1 } from '@nextui-org/react' +import { Dispatch, SetStateAction, useState } from 'react' + +export type InputProps = { + value: string + setValue: Dispatch> + displayLength?: boolean + isDescription?: boolean + isCorrect?: boolean +} & Omit, 'classNames'> + +export function Input({ + value, + setValue, + isInvalid = false, + placeholder = 'placeholder', + errorMessage: errorMessage$1 = 'Please enter a valid value', + displayLength = true, + description: description$1 = 'Please enter a value', + isDescription = true, + isCorrect = false, + label, + ...rest +}: InputProps) { + const [isFocus, setIsFocus] = useState(false) + const description = displayLength ? ( + <> + {description$1} + {value.length + '/20'} + + ) : ( + description$1 + ) + + const errorMessage = displayLength ? ( + <> + {errorMessage$1} + {value.length + '/20'} + + ) : ( + errorMessage$1 + ) + + return ( + { + setIsFocus(e) + }} + /* styles */ + type='text' + radius='full' + labelPlacement='outside' + endContent={isCorrect && !isFocus && } + classNames={{ + label: ['mx-[12px]', 'body-2'], + description: ['w-full', 'text-gray-400', 'caption-2', 'left-[12px]'], + errorMessage: ['w-full', 'text-red-warning', 'caption-2', 'left-[12px]'], + input: ['!bg-white', 'text-black', 'placeholder:text-gray-300', 'body-3', 'h-100'], + innerWrapper: [], + inputWrapper: [ + 'items-start', + '!bg-white', + 'border', + isInvalid ? 'border-red-warning' : isCorrect ? 'border-green' : 'border-gray-300', + isInvalid ? 'hover:border-red-warning' : 'hover:border-green', + isInvalid ? 'focus-within:border-red-warning' : 'focus-within:border-green', + '!cursor-text', + 'rounded-[20px]', + 'px-[20px]', + 'py-[13px]', + ], + mainWrapper: 'pb-[28px]', // 28px = padding 12px + lineheight 16px + }} + {...rest} + /> + ) +} diff --git a/packages/ui/src/input/input-comment/index.tsx b/packages/ui/src/input/input-comment/index.tsx new file mode 100644 index 0000000..e662afc --- /dev/null +++ b/packages/ui/src/input/input-comment/index.tsx @@ -0,0 +1,63 @@ +import { Textarea as TextArea$1 } from '@nextui-org/react' +import { Dispatch, SetStateAction, useState } from 'react' + +export type InputCommentProps = { + value: string + setValue: Dispatch> + displayLength?: boolean + isDescription?: boolean + isCorrect?: boolean +} & Omit, 'classNames'> + +export function InputComment({ + value, + setValue, + isInvalid = false, + placeholder = '댓글을 입력해주세요', + isCorrect = false, + label, + ...rest +}: InputCommentProps) { + const [isFocus, setIsFocus] = useState(false) + + return ( + { + setIsFocus(e) + }} + /* styles */ + maxRows={3} + minRows={1} + type='text' + radius='full' + labelPlacement='outside' + classNames={{ + label: ['mx-[12px]', 'body-2'], + input: ['!bg-white', 'text-black', 'placeholder:text-gray-300', 'body-3', 'px-[0px]', 'py-[0]'], + innerWrapper: [], + inputWrapper: [ + 'items-start', + '!bg-white', + 'border', + isInvalid ? 'border-red-warning' : isCorrect ? 'border-green' : 'border-gray-300', + isInvalid ? 'hover:border-red-warning' : 'hover:border-green', + isInvalid ? 'focus-within:border-red-warning' : 'focus-within:border-green', + '!cursor-text', + 'rounded-[20px]', + 'px-[20px]', + 'py-[10px]', + ], + }} + {...rest} + /> + ) +} diff --git a/packages/ui/src/input/input-search/index.tsx b/packages/ui/src/input/input-search/index.tsx new file mode 100644 index 0000000..14645f1 --- /dev/null +++ b/packages/ui/src/input/input-search/index.tsx @@ -0,0 +1,66 @@ +import { DynamicBackIcon, DynamicDeleteIcon, DynamicSearchIcon } from '@80000coding/web-icons' +import { Input as Input$1 } from '@nextui-org/react' +import { Dispatch, SetStateAction, useState } from 'react' + +export type InputSearchProps = { + value: string + setValue: Dispatch> + isBackBtn?: boolean +} & Omit, 'classNames'> + +export function InputSearch({ value, setValue, isInvalid = false, isBackBtn = false, ...rest }: InputSearchProps) { + // const [value, setValue] = useState('') + + const onClear = () => { + setValue('') + } + + const onSearch = () => { + console.log('search') + } + + const onClickGoBack = () => { + console.log('Go Back!') + } + + const [isFocus, setIsFocus] = useState(false) + + return ( + } + endContent={ +
+ {value !== '' && } + +
+ } + onFocusChange={(e) => { + setIsFocus(e) + }} + onValueChange={setValue} + isInvalid={isInvalid} + type='text' + placeholder='검색어를 입력하세요' + radius='full' + classNames={{ + label: ['mx-[12px]', 'body-2'], + description: ['mx-[12px]', 'caption-2'], + input: ['!bg-white', 'text-black', 'placeholder:text-gray-300', 'body-3', 'h-100'], + innerWrapper: [], + inputWrapper: [ + 'items-start', + '!bg-white', + 'border', + 'hover:border-green', + 'focus-within:border-green', + '!cursor-text', + 'rounded-[20px]', + 'px-[20px]', + 'py-[13px]', + ], + }} + /> + ) +} diff --git a/packages/web-icons/icons/dynamic/dynamic-delete.svg b/packages/web-icons/icons/dynamic/dynamic-delete.svg new file mode 100644 index 0000000..c7ee465 --- /dev/null +++ b/packages/web-icons/icons/dynamic/dynamic-delete.svg @@ -0,0 +1,10 @@ + + + + + + + + + +