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 (
+