-
{highlightText(selectedPost?.body, searchQuery)}
- {renderComments(selectedPost?.id)}
+
{
+ if (!open) setSelectedPost(null)
+ }}
+ post={selectedPost}
+ searchQuery={searchQuery}
+ >
+ {selectedPost?.id && (
+
+
+
댓글
+
{
+ setNewCommentForm({ body: "", postId: selectedPost.id, userId: 1 })
+ setShowAddCommentDialog(true)
+ }}
+ />
+
+
+ {comments[selectedPost.id]?.map((comment) => (
+
+ {
+ likeCommentAction(id, comment.likes)
+ }}
+ />
+ {
+ setEditCommentForm({ id: c.id, body: c.body, postId: c.postId })
+ setShowEditCommentDialog(true)
+ }}
+ />
+ {
+ deleteCommentAction(id, pid)
+ }}
+ />
+ >
+ }
+ />
+ ))}
+
-
-
+ )}
+
{/* 사용자 모달 */}
-
+
)
}
diff --git a/src/shared/lib/index.ts b/src/shared/lib/index.ts
new file mode 100644
index 000000000..6417a56d0
--- /dev/null
+++ b/src/shared/lib/index.ts
@@ -0,0 +1 @@
+export * from "./text"
diff --git a/src/shared/lib/text/highlight-text.tsx b/src/shared/lib/text/highlight-text.tsx
new file mode 100644
index 000000000..e1d2d928a
--- /dev/null
+++ b/src/shared/lib/text/highlight-text.tsx
@@ -0,0 +1,34 @@
+import { ReactNode } from "react"
+
+/**
+ * 텍스트에서 검색어를 하이라이트합니다.
+ * @param text - 원본 텍스트
+ * @param query - 검색어 (대소문자 구분 없음)
+ * @returns 하이라이트된 JSX 또는 null
+ * @example
+ * highlightText("Hello World", "world")
+ * // =>
Hello World
+ */
+export const highlightText = (text: string, query: string): ReactNode => {
+ if (!text) return null
+ if (!query.trim()) {
+ return
{text}
+ }
+
+ const regex = new RegExp(`(${query})`, "gi")
+ const parts = text.split(regex)
+
+ return (
+
+ {parts.map((part, i) =>
+ regex.test(part) ? (
+
+ {part}
+
+ ) : (
+ {part}
+ ),
+ )}
+
+ )
+}
diff --git a/src/shared/lib/text/index.ts b/src/shared/lib/text/index.ts
new file mode 100644
index 000000000..b6124f294
--- /dev/null
+++ b/src/shared/lib/text/index.ts
@@ -0,0 +1 @@
+export { highlightText } from "./highlight-text"
diff --git a/src/shared/ui/button.tsx b/src/shared/ui/button.tsx
new file mode 100644
index 000000000..a8a85c376
--- /dev/null
+++ b/src/shared/ui/button.tsx
@@ -0,0 +1,36 @@
+import { cva, VariantProps } from "class-variance-authority"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
+ {
+ variants: {
+ variant: {
+ default: "bg-blue-500 text-white hover:bg-blue-600",
+ destructive: "bg-red-500 text-white hover:bg-red-600",
+ outline: "border border-gray-300 bg-transparent text-gray-700 hover:bg-gray-100",
+ secondary: "bg-gray-200 text-gray-800 hover:bg-gray-300",
+ ghost: "bg-transparent text-gray-700 hover:bg-gray-100",
+ link: "underline-offset-4 hover:underline text-blue-500",
+ },
+ size: {
+ default: "h-10 py-2 px-4",
+ sm: "h-8 px-3 rounded-md text-xs",
+ lg: "h-11 px-8 rounded-md",
+ icon: "h-9 w-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+)
+
+interface ButtonProps extends React.ButtonHTMLAttributes
, VariantProps {
+ className?: string
+ ref?: React.Ref
+}
+
+export const Button = ({ className, variant, size, ref, ...props }: ButtonProps) => {
+ return
+}
diff --git a/src/shared/ui/card.tsx b/src/shared/ui/card.tsx
new file mode 100644
index 000000000..f69dc356e
--- /dev/null
+++ b/src/shared/ui/card.tsx
@@ -0,0 +1,23 @@
+interface CardProps extends React.HTMLAttributes {
+ className?: string
+ ref?: React.Ref
+}
+
+// 카드 컴포넌트
+const Card = ({ className, ref, ...props }: CardProps) => (
+
+)
+
+const CardHeader = ({ className, ref, ...props }: CardProps) => (
+
+)
+
+const CardTitle = ({ className, ref, ...props }: CardProps) => (
+
+)
+
+const CardContent = ({ className, ref, ...props }: CardProps) => (
+
+)
+
+export { Card, CardHeader, CardTitle, CardContent }
diff --git a/src/shared/ui/dialog/dialog.tsx b/src/shared/ui/dialog/dialog.tsx
new file mode 100644
index 000000000..5774e5ad8
--- /dev/null
+++ b/src/shared/ui/dialog/dialog.tsx
@@ -0,0 +1,53 @@
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+
+// 대화상자 컴포넌트
+const Dialog = DialogPrimitive.Root
+const DialogTrigger = DialogPrimitive.Trigger
+const DialogPortal = DialogPrimitive.Portal
+const DialogOverlay = DialogPrimitive.Overlay
+
+interface DialogContentProps extends React.ComponentPropsWithoutRef {
+ className?: string
+ ref?: React.Ref
+}
+
+const DialogContent = ({ className, ref, children, ...props }: DialogContentProps) => (
+
+
+
+ {children}
+
+
+ 닫기
+
+
+
+)
+
+interface DialogHeaderProps extends React.HTMLAttributes {
+ className?: string
+}
+const DialogHeader = ({ className, ...props }: DialogHeaderProps) => (
+
+)
+
+interface DialogTitleProps extends React.ComponentPropsWithoutRef {
+ className?: string
+ ref?: React.Ref
+}
+
+const DialogTitle = ({ className, ref, ...props }: DialogTitleProps) => (
+
+)
+
+export { Dialog, DialogTrigger, DialogPortal, DialogOverlay, DialogContent, DialogHeader, DialogTitle }
+
diff --git a/src/shared/ui/dialog/index.tsx b/src/shared/ui/dialog/index.tsx
new file mode 100644
index 000000000..c826e186c
--- /dev/null
+++ b/src/shared/ui/dialog/index.tsx
@@ -0,0 +1,2 @@
+export { Dialog, DialogTrigger, DialogPortal, DialogOverlay, DialogContent, DialogHeader, DialogTitle } from "./dialog"
+
diff --git a/src/shared/ui/index.tsx b/src/shared/ui/index.tsx
new file mode 100644
index 000000000..695da9246
--- /dev/null
+++ b/src/shared/ui/index.tsx
@@ -0,0 +1,7 @@
+export { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table"
+export { Button } from "./button"
+export { Input } from "./input"
+export { Card, CardHeader, CardTitle, CardContent } from "./card"
+export { Textarea } from "./textarea"
+export { Dialog, DialogContent, DialogHeader, DialogTitle } from "./dialog"
+export { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select"
diff --git a/src/shared/ui/input.tsx b/src/shared/ui/input.tsx
new file mode 100644
index 000000000..f6a1125a0
--- /dev/null
+++ b/src/shared/ui/input.tsx
@@ -0,0 +1,16 @@
+// 입력 컴포넌트
+interface InputProps extends React.InputHTMLAttributes {
+ className?: string
+ ref?: React.Ref
+}
+
+export const Input = ({ className, type, ref, ...props }: InputProps) => {
+ return (
+
+ )
+}
diff --git a/src/shared/ui/select/index.tsx b/src/shared/ui/select/index.tsx
new file mode 100644
index 000000000..9373547f5
--- /dev/null
+++ b/src/shared/ui/select/index.tsx
@@ -0,0 +1,2 @@
+export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectItem } from "./select"
+
diff --git a/src/shared/ui/select/select.tsx b/src/shared/ui/select/select.tsx
new file mode 100644
index 000000000..01bf7c87f
--- /dev/null
+++ b/src/shared/ui/select/select.tsx
@@ -0,0 +1,62 @@
+import * as SelectPrimitive from "@radix-ui/react-select"
+import { Check, ChevronDown } from "lucide-react"
+
+export const Select = SelectPrimitive.Root
+export const SelectGroup = SelectPrimitive.Group
+export const SelectValue = SelectPrimitive.Value
+
+interface SelectTriggerProps extends React.ComponentPropsWithoutRef {
+ className?: string
+ ref?: React.Ref
+}
+
+export const SelectTrigger = ({ className, ref, children, ...props }: SelectTriggerProps) => (
+
+ {children}
+
+
+)
+
+interface SelectContentProps extends React.ComponentPropsWithoutRef {
+ className?: string
+ ref?: React.Ref
+ position?: "popper" | "item-aligned"
+}
+
+export const SelectContent = ({ className, ref, position = "popper", children, ...props }: SelectContentProps) => (
+
+
+ {children}
+
+
+)
+
+interface SelectItemProps extends React.ComponentPropsWithoutRef {
+ className?: string
+ ref?: React.Ref
+}
+
+export const SelectItem = ({ className, ref, children, ...props }: SelectItemProps) => (
+
+
+
+
+
+
+ {children}
+
+)
+
diff --git a/src/shared/ui/table/index.tsx b/src/shared/ui/table/index.tsx
new file mode 100644
index 000000000..aa37b1e36
--- /dev/null
+++ b/src/shared/ui/table/index.tsx
@@ -0,0 +1,2 @@
+export { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "./table"
+
diff --git a/src/shared/ui/table/table.tsx b/src/shared/ui/table/table.tsx
new file mode 100644
index 000000000..333a4efe3
--- /dev/null
+++ b/src/shared/ui/table/table.tsx
@@ -0,0 +1,66 @@
+interface TableProps extends React.ComponentPropsWithoutRef<"table"> {
+ className?: string
+ ref?: React.Ref
+}
+
+const Table = ({ className, ref, ...props }: TableProps) => (
+
+)
+
+interface TableHeaderProps extends React.ComponentPropsWithoutRef<"thead"> {
+ className?: string
+ ref?: React.Ref
+}
+
+const TableHeader = ({ className, ref, ...props }: TableHeaderProps) => (
+
+)
+
+interface TableBodyProps extends React.ComponentPropsWithoutRef<"tbody"> {
+ className?: string
+ ref?: React.Ref
+}
+
+const TableBody = ({ className, ref, ...props }: TableBodyProps) => (
+
+)
+
+interface TableRowProps extends React.ComponentPropsWithoutRef<"tr"> {
+ className?: string
+ ref?: React.Ref
+}
+
+const TableRow = ({ className, ref, ...props }: TableRowProps) => (
+
+)
+
+interface TableHeadProps extends React.ComponentPropsWithoutRef<"th"> {
+ className?: string
+ ref?: React.Ref
+}
+
+const TableHead = ({ className, ref, ...props }: TableHeadProps) => (
+ |
+)
+
+interface TableCellProps extends React.ComponentPropsWithoutRef<"td"> {
+ className?: string
+ ref?: React.Ref
+}
+
+const TableCell = ({ className, ref, ...props }: TableCellProps) => (
+ |
+)
+
+export { Table, TableHeader, TableBody, TableRow, TableHead, TableCell }
+
diff --git a/src/shared/ui/textarea.tsx b/src/shared/ui/textarea.tsx
new file mode 100644
index 000000000..53039dfcd
--- /dev/null
+++ b/src/shared/ui/textarea.tsx
@@ -0,0 +1,14 @@
+interface TextareaProps extends React.TextareaHTMLAttributes {
+ className?: string
+ ref?: React.Ref
+}
+
+export const Textarea = ({ className, ref, ...props }: TextareaProps) => {
+ return (
+
+ )
+}
diff --git a/src/components/Footer.tsx b/src/widgets/footer/ui/footer.tsx
similarity index 99%
rename from src/components/Footer.tsx
rename to src/widgets/footer/ui/footer.tsx
index 91af02f8c..4b46fb86f 100644
--- a/src/components/Footer.tsx
+++ b/src/widgets/footer/ui/footer.tsx
@@ -11,3 +11,4 @@ const Footer: React.FC = () => {
};
export default Footer;
+
diff --git a/src/widgets/footer/ui/index.tsx b/src/widgets/footer/ui/index.tsx
new file mode 100644
index 000000000..654d832dc
--- /dev/null
+++ b/src/widgets/footer/ui/index.tsx
@@ -0,0 +1,2 @@
+export { default as Footer } from "./footer"
+
diff --git a/src/components/Header.tsx b/src/widgets/header/ui/header.tsx
similarity index 100%
rename from src/components/Header.tsx
rename to src/widgets/header/ui/header.tsx
diff --git a/src/widgets/header/ui/index.tsx b/src/widgets/header/ui/index.tsx
new file mode 100644
index 000000000..c08a1a8eb
--- /dev/null
+++ b/src/widgets/header/ui/index.tsx
@@ -0,0 +1 @@
+export { default as Header } from "./header"
diff --git a/src/widgets/index.tsx b/src/widgets/index.tsx
new file mode 100644
index 000000000..ae46ec61d
--- /dev/null
+++ b/src/widgets/index.tsx
@@ -0,0 +1,2 @@
+export { Header } from "./header/ui"
+export { Footer } from "./footer/ui"
diff --git a/tsconfig.app.json b/tsconfig.app.json
index 5a2def4b7..72eec10f1 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -19,7 +19,13 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
+ "noUncheckedSideEffectImports": true,
+
+ /* Path mapping */
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
},
"include": ["src"]
}
diff --git a/vite.config.ts b/vite.config.ts
index be7b7a3d4..ff44c8671 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,9 +1,15 @@
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
+import path from "path"
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src"),
+ },
+ },
server: {
proxy: {
"/api": {