- Biome 설정 파일 마이그레이션 및 규칙 적용 │

│    - 전체 파일 대상 포맷팅 및 린트 오류 수정                                             │
 │    -  타입을 으로 변경하여 타입 안정성 강화                                │
 │    - 불필요한 import 제거 및 useEffect 의존성 배열 수정                                  │
 │    -  파일을 /로 마이그레이션하여 타입스크립트 일관성 확보               │
 │    - 에 개발 원칙 추가
This commit is contained in:
Lectom C Han
2025-08-02 18:51:22 +09:00
parent 211689e889
commit 8db8ce668c
86 changed files with 3434 additions and 1767 deletions

5
.gitignore vendored
View File

@@ -22,3 +22,8 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
.env
.env.local
.DS_Store

View File

@@ -57,3 +57,9 @@
- **인증 상태 관리**: 사용자의 로그인 상태를 전역으로 관리하고, 인증 상태에 따라 UI가 동적으로 변경되도록 설정 - **인증 상태 관리**: 사용자의 로그인 상태를 전역으로 관리하고, 인증 상태에 따라 UI가 동적으로 변경되도록 설정
- **라우트 보호**: 인증이 필요한 페이지에 접근 제어(Route Guard)를 적용하여 비인가 사용자의 접근 차단 - **라우트 보호**: 인증이 필요한 페이지에 접근 제어(Route Guard)를 적용하여 비인가 사용자의 접근 차단
- **컨테이너화**: Dockerfile을 작성하고 Docker Compose를 설정하여 개발 및 프로덕션 환경을 컨테이너 기반으로 구축 - **컨테이너화**: Dockerfile을 작성하고 Docker Compose를 설정하여 개발 및 프로덕션 환경을 컨테이너 기반으로 구축
## 6. 개발 원칙
- **코드 품질**: 모든 기능 구현 후, Biome 설정을 기반으로 코드를 정리하여 일관된 스타일과 높은 품질을 유지합니다.
- **버전 관리**: 각 기능 단위의 개발이 완료되<EBA38C><EB9098> 정상 동작이 확인되면, 사용자에게 git commit 여부를 물어봅니다.

View File

@@ -1,4 +1,4 @@
VITE_API_PROXY_TARGET=http://172.16.10.175:3030/_back VITE_API_PROXY_TARGET=https://feedback.hmac.kr/_back
# API 요청 시 필요한 인증 키 # API 요청 시 필요한 인증 키
VITE_API_KEY=F5FE0363E37C012204F5 VITE_API_KEY=F5FE0363E37C012204F5

View File

@@ -1,5 +1,5 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.1.2/schema.json", "$schema": "https://biomejs.dev/schemas/2.1.3/schema.json",
"vcs": { "vcs": {
"enabled": false, "enabled": false,
"clientKind": "git", "clientKind": "git",

View File

@@ -1,16 +1,16 @@
{ {
"$schema": "https://ui.shadcn.com/schema.json", "$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york", "style": "new-york",
"rsc": false, "rsc": false,
"tsx": true, "tsx": true,
"tailwind": { "tailwind": {
"config": "tailwind.config.ts", "config": "tailwind.config.ts",
"css": "src/index.css", "css": "src/index.css",
"baseColor": "neutral", "baseColor": "neutral",
"cssVariables": true "cssVariables": true
}, },
"aliases": { "aliases": {
"components": "@/components", "components": "@/components",
"utils": "@/lib/utils" "utils": "@/lib/utils"
} }
} }

View File

@@ -1,14 +0,0 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
// src/App.tsx
import { Routes, Route, Navigate } from "react-router-dom";
import { MainLayout } from "@/components/MainLayout";
import { FeedbackCreatePage } from "@/pages/FeedbackCreatePage";
import { FeedbackListPage } from "@/pages/FeedbackListPage";
import { FeedbackDetailPage } from "@/pages/FeedbackDetailPage";
import { IssueViewerPage } from "@/pages/IssueViewerPage";
import { ThemeProvider } from "./components/providers/ThemeProvider";
function App() {
return (_jsx(ThemeProvider, { children: _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(Navigate, { to: "/projects/1/channels/4/feedbacks" }) }), _jsxs(Route, { path: "/projects/:projectId/channels/:channelId/feedbacks", element: _jsx(MainLayout, {}), children: [_jsx(Route, { index: true, element: _jsx(FeedbackListPage, {}) }), _jsx(Route, { path: "new", element: _jsx(FeedbackCreatePage, {}) }), _jsx(Route, { path: ":feedbackId", element: _jsx(FeedbackDetailPage, {}) })] }), _jsx(Route, { path: "/issues/:issueId" // 이슈 ID만 받도록 단순화
, element: _jsx(IssueViewerPage, {}) }), _jsx(Route, { path: "*", element: _jsx(Navigate, { to: "/" }) })] }) }));
}
export default App;

View File

@@ -1,5 +1,5 @@
// src/App.tsx // src/App.tsx
import { Routes, Route, Navigate, Outlet } from "react-router-dom"; import { Routes, Route, Navigate } from "react-router-dom";
import { MainLayout } from "@/components/MainLayout"; import { MainLayout } from "@/components/MainLayout";
import { FeedbackCreatePage } from "@/pages/FeedbackCreatePage"; import { FeedbackCreatePage } from "@/pages/FeedbackCreatePage";
import { FeedbackListPage } from "@/pages/FeedbackListPage"; import { FeedbackListPage } from "@/pages/FeedbackListPage";

View File

@@ -1,10 +1,13 @@
import type { FeedbackField } from "@/services/feedback"; import type { FeedbackField } from "@/services/feedback";
interface DynamicFormProps { interface DynamicFormProps {
fields: FeedbackField[]; fields: FeedbackField[];
onSubmit: (formData: Record<string, any>) => Promise<void>; onSubmit: (formData: Record<string, unknown>) => Promise<void>;
initialData?: Record<string, any>; initialData?: Record<string, unknown>;
submitButtonText?: string; submitButtonText?: string;
} }
export declare function DynamicForm({ fields, onSubmit, initialData, // 기본값으로 상수 사용 export declare function DynamicForm({
submitButtonText, }: DynamicFormProps): import("react/jsx-runtime").JSX.Element; fields,
export {}; onSubmit,
initialData, // 기본값으로 상수 사용
submitButtonText,
}: DynamicFormProps): import("react/jsx-runtime").JSX.Element;

View File

@@ -3,51 +3,109 @@ import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectTrigger, SelectValue, } from "@/components/ui/select"; import {
Select,
SelectContent,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
// 컴포넌트 외부에 안정적인 참조를 가진 빈 객체 상수 선언 // 컴포넌트 외부에 안정적인 참조를 가진 빈 객체 상수 선언
const EMPTY_INITIAL_DATA = {}; const EMPTY_INITIAL_DATA = {};
export function DynamicForm({ fields, onSubmit, initialData = EMPTY_INITIAL_DATA, // 기본값으로 상수 사용 export function DynamicForm({
submitButtonText = "제출", }) { fields,
const [formData, setFormData] = useState(initialData); onSubmit,
const [isSubmitting, setIsSubmitting] = useState(false); initialData = EMPTY_INITIAL_DATA, // 기본값으로 상수 사용
useEffect(() => { submitButtonText = "제출",
// initialData prop이 변경될 때만 폼 데이터를 동기화 }) {
setFormData(initialData); const [formData, setFormData] = useState(initialData);
}, [initialData]); const [isSubmitting, setIsSubmitting] = useState(false);
const handleFormChange = (fieldId, value) => { useEffect(() => {
setFormData((prev) => ({ ...prev, [fieldId]: value })); // initialData prop이 변경될 때만 폼 데이터를 동기화
}; setFormData(initialData);
const handleSubmit = async (e) => { }, [initialData]);
e.preventDefault(); const handleFormChange = (fieldId, value) => {
setIsSubmitting(true); setFormData((prev) => ({ ...prev, [fieldId]: value }));
try { };
await onSubmit(formData); const handleSubmit = async (e) => {
} e.preventDefault();
catch (error) { setIsSubmitting(true);
console.error("Form submission error:", error); try {
} await onSubmit(formData);
finally { } catch (error) {
setIsSubmitting(false); console.error("Form submission error:", error);
} } finally {
}; setIsSubmitting(false);
const renderField = (field) => { }
const commonProps = { };
id: field.id, const renderField = (field) => {
value: formData[field.id] ?? "", const commonProps = {
disabled: field.readOnly, id: field.id,
}; value: formData[field.id] ?? "",
switch (field.type) { disabled: field.readOnly,
case "textarea": };
return (_jsx(Textarea, { ...commonProps, onChange: (e) => handleFormChange(field.id, e.target.value), placeholder: field.readOnly ? "" : `${field.name}...`, rows: 5 })); switch (field.type) {
case "text": case "textarea":
case "number": return _jsx(Textarea, {
return (_jsx(Input, { ...commonProps, type: field.type, onChange: (e) => handleFormChange(field.id, e.target.value), placeholder: field.readOnly ? "" : field.name })); ...commonProps,
case "select": onChange: (e) => handleFormChange(field.id, e.target.value),
return (_jsxs(Select, { value: commonProps.value, onValueChange: (value) => handleFormChange(field.id, value), disabled: field.readOnly, children: [_jsx(SelectTrigger, { id: field.id, children: _jsx(SelectValue, { placeholder: `-- ${field.name} 선택 --` }) }), _jsx(SelectContent, {})] })); placeholder: field.readOnly ? "" : `${field.name}...`,
default: rows: 5,
return _jsxs("p", { children: ["\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD544\uB4DC \uD0C0\uC785: ", field.type] }); });
} case "text":
}; case "number":
return (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [fields.map((field) => (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: field.id, children: field.name }), renderField(field)] }, field.id))), _jsx(Button, { type: "submit", disabled: isSubmitting, children: isSubmitting ? "전송 중..." : submitButtonText })] })); return _jsx(Input, {
...commonProps,
type: field.type,
onChange: (e) => handleFormChange(field.id, e.target.value),
placeholder: field.readOnly ? "" : field.name,
});
case "select":
return _jsxs(Select, {
value: commonProps.value,
onValueChange: (value) => handleFormChange(field.id, value),
disabled: field.readOnly,
children: [
_jsx(SelectTrigger, {
id: field.id,
children: _jsx(SelectValue, {
placeholder: `-- ${field.name} 선택 --`,
}),
}),
_jsx(SelectContent, {}),
],
});
default:
return _jsxs("p", {
children: [
"\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD544\uB4DC \uD0C0\uC785: ",
field.type,
],
});
}
};
return _jsxs("form", {
onSubmit: handleSubmit,
className: "space-y-4",
children: [
fields.map((field) =>
_jsxs(
"div",
{
className: "space-y-2",
children: [
_jsx(Label, { htmlFor: field.id, children: field.name }),
renderField(field),
],
},
field.id,
),
),
_jsx(Button, {
type: "submit",
disabled: isSubmitting,
children: isSubmitting ? "전송 중..." : submitButtonText,
}),
],
});
} }

View File

@@ -1,4 +1,3 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import type { FeedbackField } from "@/services/feedback"; import type { FeedbackField } from "@/services/feedback";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -17,8 +16,8 @@ const EMPTY_INITIAL_DATA = {};
interface DynamicFormProps { interface DynamicFormProps {
fields: FeedbackField[]; fields: FeedbackField[];
onSubmit: (formData: Record<string, any>) => Promise<void>; onSubmit: (formData: Record<string, unknown>) => Promise<void>;
initialData?: Record<string, any>; initialData?: Record<string, unknown>;
submitButtonText?: string; submitButtonText?: string;
} }
@@ -28,7 +27,7 @@ export function DynamicForm({
initialData = EMPTY_INITIAL_DATA, // 기본값으로 상수 사용 initialData = EMPTY_INITIAL_DATA, // 기본값으로 상수 사용
submitButtonText = "제출", submitButtonText = "제출",
}: DynamicFormProps) { }: DynamicFormProps) {
const [formData, setFormData] = useState<Record<string, any>>(initialData); const [formData, setFormData] = useState<Record<string, unknown>>(initialData);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => { useEffect(() => {
@@ -36,7 +35,7 @@ export function DynamicForm({
setFormData(initialData); setFormData(initialData);
}, [initialData]); }, [initialData]);
const handleFormChange = (fieldId: string, value: any) => { const handleFormChange = (fieldId: string, value: unknown) => {
setFormData((prev) => ({ ...prev, [fieldId]: value })); setFormData((prev) => ({ ...prev, [fieldId]: value }));
}; };

View File

@@ -1,9 +1,14 @@
import type { Feedback, FeedbackField } from "@/services/feedback"; import type { Feedback, FeedbackField } from "@/services/feedback";
interface DynamicTableProps { interface DynamicTableProps {
columns: FeedbackField[]; columns: FeedbackField[];
data: Feedback[]; data: Feedback[];
projectId: string; projectId: string;
channelId: string; channelId: string;
} }
export declare function DynamicTable({ columns: rawColumns, data, projectId, channelId, }: DynamicTableProps): import("react/jsx-runtime").JSX.Element; export declare function DynamicTable({
columns: rawColumns,
data,
projectId,
channelId,
}: DynamicTableProps): import("react/jsx-runtime").JSX.Element;
export default DynamicTable; export default DynamicTable;

View File

@@ -1,146 +1,534 @@
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import {
import { flexRender, getCoreRowModel, getExpandedRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table"; jsx as _jsx,
jsxs as _jsxs,
Fragment as _Fragment,
} from "react/jsx-runtime";
import {
flexRender,
getCoreRowModel,
getExpandedRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { addDays, format } from "date-fns"; import { addDays, format } from "date-fns";
import { ArrowUpDown, Calendar as CalendarIcon, ChevronDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, } from "lucide-react"; import {
ArrowUpDown,
Calendar as CalendarIcon,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
} from "lucide-react";
import { useMemo, useState, Fragment } from "react"; import { useMemo, useState, Fragment } from "react";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar"; import { Calendar } from "@/components/ui/calendar";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import {
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; Popover,
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const DEFAULT_COLUMN_ORDER = [ const DEFAULT_COLUMN_ORDER = [
"id", "id",
"title", "title",
"contents", "contents",
"issues", "issues",
"customer", "customer",
"updatedAt", "updatedAt",
]; ];
export function DynamicTable({ columns: rawColumns, data, projectId, channelId, }) { export function DynamicTable({
const navigate = useNavigate(); columns: rawColumns,
const [sorting, setSorting] = useState([]); data,
const [columnFilters, setColumnFilters] = useState([]); projectId,
const [columnVisibility, setColumnVisibility] = useState({}); channelId,
const [expanded, setExpanded] = useState({}); }) {
const [globalFilter, setGlobalFilter] = useState(""); const navigate = useNavigate();
const [date, setDate] = useState(); const [sorting, setSorting] = useState([]);
const columns = useMemo(() => { const [columnFilters, setColumnFilters] = useState([]);
const orderedRawColumns = [...rawColumns].sort((a, b) => { const [columnVisibility, setColumnVisibility] = useState({});
const indexA = DEFAULT_COLUMN_ORDER.indexOf(a.id); const [expanded, setExpanded] = useState({});
const indexB = DEFAULT_COLUMN_ORDER.indexOf(b.id); const [globalFilter, setGlobalFilter] = useState("");
if (indexA === -1 && indexB === -1) const [date, setDate] = useState();
return 0; const columns = useMemo(() => {
if (indexA === -1) const orderedRawColumns = [...rawColumns].sort((a, b) => {
return 1; const indexA = DEFAULT_COLUMN_ORDER.indexOf(a.id);
if (indexB === -1) const indexB = DEFAULT_COLUMN_ORDER.indexOf(b.id);
return -1; if (indexA === -1 && indexB === -1) return 0;
return indexA - indexB; if (indexA === -1) return 1;
}); if (indexB === -1) return -1;
const generatedColumns = orderedRawColumns.map((field) => ({ return indexA - indexB;
accessorKey: field.id, });
header: ({ column }) => { const generatedColumns = orderedRawColumns.map((field) => ({
if (field.id === "issues") { accessorKey: field.id,
return _jsx("div", { children: field.name }); header: ({ column }) => {
} if (field.id === "issues") {
return (_jsxs(Button, { variant: "ghost", onClick: () => column.toggleSorting(column.getIsSorted() === "asc"), children: [field.name, _jsx(ArrowUpDown, { className: "ml-2 h-4 w-4" })] })); return _jsx("div", { children: field.name });
}, }
cell: ({ row }) => { return _jsxs(Button, {
const value = row.original[field.id]; variant: "ghost",
switch (field.id) { onClick: () => column.toggleSorting(column.getIsSorted() === "asc"),
case "issues": { children: [
const issues = value; field.name,
if (!issues || issues.length === 0) _jsx(ArrowUpDown, { className: "ml-2 h-4 w-4" }),
return "N/A"; ],
return (_jsx("div", { className: "flex flex-col space-y-1", children: issues.map((issue) => (_jsx(Link, { to: `/issues/${issue.id}`, className: "text-blue-600 hover:underline", onClick: (e) => e.stopPropagation(), children: issue.name }, issue.id))) })); });
} },
case "title": cell: ({ row }) => {
return (_jsx("div", { className: "whitespace-normal break-words w-48", children: String(value ?? "N/A") })); const value = row.original[field.id];
case "contents": { switch (field.id) {
const content = String(value ?? "N/A"); case "issues": {
const truncated = content.length > 50 const issues = value;
? `${content.substring(0, 50)}...` if (!issues || issues.length === 0) return "N/A";
: content; return _jsx("div", {
return (_jsx("div", { className: "whitespace-normal break-words w-60", children: truncated })); className: "flex flex-col space-y-1",
} children: issues.map((issue) =>
case "createdAt": _jsx(
case "updatedAt": Link,
return String(value ?? "N/A").substring(0, 10); {
default: to: `/issues/${issue.id}`,
if (typeof value === "object" && value !== null) { className: "text-blue-600 hover:underline",
return JSON.stringify(value); onClick: (e) => e.stopPropagation(),
} children: issue.name,
return String(value ?? "N/A"); },
} issue.id,
}, ),
})); ),
return [ });
{ }
id: "expander", case "title":
header: () => null, return _jsx("div", {
cell: ({ row }) => { className: "whitespace-normal break-words w-48",
return (_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { children: String(value ?? "N/A"),
e.stopPropagation(); });
row.toggleExpanded(); case "contents": {
}, children: row.getIsExpanded() ? "▼" : "▶" })); const content = String(value ?? "N/A");
}, const truncated =
}, content.length > 50 ? `${content.substring(0, 50)}...` : content;
...generatedColumns, return _jsx("div", {
]; className: "whitespace-normal break-words w-60",
}, [rawColumns]); children: truncated,
const filteredData = useMemo(() => { });
if (!date?.from) { }
return data; case "createdAt":
} case "updatedAt":
const fromDate = date.from; return String(value ?? "N/A").substring(0, 10);
const toDate = date.to ? addDays(date.to, 1) : addDays(fromDate, 1); default:
return data.filter((item) => { if (typeof value === "object" && value !== null) {
const itemDate = new Date(item.updatedAt); return JSON.stringify(value);
return itemDate >= fromDate && itemDate < toDate; }
}); return String(value ?? "N/A");
}, [data, date]); }
const table = useReactTable({ },
data: filteredData, }));
columns, return [
onSortingChange: setSorting, {
onColumnFiltersChange: setColumnFilters, id: "expander",
onColumnVisibilityChange: setColumnVisibility, header: () => null,
onExpandedChange: setExpanded, cell: ({ row }) => {
getCoreRowModel: getCoreRowModel(), return _jsx(Button, {
getPaginationRowModel: getPaginationRowModel(), variant: "ghost",
getSortedRowModel: getSortedRowModel(), size: "sm",
getFilteredRowModel: getFilteredRowModel(), onClick: (e) => {
getExpandedRowModel: getExpandedRowModel(), e.stopPropagation();
initialState: { row.toggleExpanded();
pagination: { },
pageSize: 20, children: row.getIsExpanded() ? "▼" : "▶",
}, });
}, },
state: { },
sorting, ...generatedColumns,
columnFilters, ];
columnVisibility, }, [rawColumns]);
expanded, const filteredData = useMemo(() => {
globalFilter, if (!date?.from) {
}, return data;
onGlobalFilterChange: setGlobalFilter, }
}); const fromDate = date.from;
const handleRowClick = (feedbackId) => { const toDate = date.to ? addDays(date.to, 1) : addDays(fromDate, 1);
navigate(`/projects/${projectId}/channels/${channelId}/feedbacks/${feedbackId}`); return data.filter((item) => {
}; const itemDate = new Date(item.updatedAt);
return (_jsx(Card, { children: _jsxs(CardContent, { children: [_jsxs("div", { className: "flex items-center justify-between py-4", children: [_jsx(Input, { placeholder: "\uC804\uCCB4 \uB370\uC774\uD130\uC5D0\uC11C \uAC80\uC0C9...", value: globalFilter, onChange: (event) => setGlobalFilter(event.target.value), className: "max-w-sm" }), _jsxs("div", { className: "flex items-center space-x-2", children: [_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { id: "date", variant: "outline", className: cn("w-[300px] justify-start text-left font-normal", !date && "text-muted-foreground"), children: [_jsx(CalendarIcon, { className: "mr-2 h-4 w-4" }), date?.from ? (date.to ? (_jsxs(_Fragment, { children: [format(date.from, "LLL dd, y"), " -", " ", format(date.to, "LLL dd, y")] })) : (format(date.from, "LLL dd, y"))) : (_jsx("span", { children: "\uAE30\uAC04 \uC120\uD0DD" }))] }) }), _jsx(PopoverContent, { className: "w-auto p-0", align: "end", children: _jsx(Calendar, { initialFocus: true, mode: "range", defaultMonth: date?.from, selected: date, onSelect: setDate, numberOfMonths: 2 }) })] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", className: "ml-auto", children: ["\uCEEC\uB7FC \uD45C\uC2DC ", _jsx(ChevronDown, { className: "ml-2 h-4 w-4" })] }) }), _jsx(DropdownMenuContent, { align: "end", children: table return itemDate >= fromDate && itemDate < toDate;
.getAllColumns() });
.filter((column) => column.getCanHide()) }, [data, date]);
.map((column) => { const table = useReactTable({
return (_jsx(DropdownMenuCheckboxItem, { className: "capitalize", checked: column.getIsVisible(), onCheckedChange: (value) => column.toggleVisibility(!!value), children: column.id }, column.id)); data: filteredData,
}) })] })] })] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { children: headerGroup.headers.map((header) => (_jsx(TableHead, { style: { width: header.getSize() }, children: header.isPlaceholder columns,
? null onSortingChange: setSorting,
: flexRender(header.column.columnDef.header, header.getContext()) }, header.id))) }, headerGroup.id))) }), _jsx(TableBody, { children: table.getRowModel().rows?.length ? (table.getRowModel().rows.map((row) => (_jsxs(Fragment, { children: [_jsx(TableRow, { "data-state": row.getIsSelected() && "selected", onClick: () => handleRowClick(row.original.id.toString()), className: "cursor-pointer hover:bg-muted/50", children: row.getVisibleCells().map((cell) => (_jsx(TableCell, { children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }), row.getIsExpanded() && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length + 1, children: _jsxs("div", { className: "p-4 bg-muted rounded-md", children: [_jsx("h4", { className: "font-bold text-lg", children: row.original.title }), _jsx("p", { className: "mt-2 whitespace-pre-wrap", children: row.original.contents })] }) }) }, `${row.id}-expanded`))] }, row.id)))) : (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length + 1, className: "h-24 text-center", children: "\uD45C\uC2DC\uD560 \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) })) })] }) }), _jsxs("div", { className: "flex items-center justify-between py-4", children: [_jsxs("div", { className: "flex-1 text-sm text-muted-foreground", children: ["\uCD1D ", table.getFilteredRowModel().rows.length, "\uAC1C"] }), _jsxs("div", { className: "flex items-center space-x-6", children: [_jsxs("div", { className: "flex items-center space-x-2", children: [_jsx("p", { className: "text-sm font-medium", children: "\uD398\uC774\uC9C0 \uB2F9 \uD589 \uC218" }), _jsxs(Select, { value: `${table.getState().pagination.pageSize}`, onValueChange: (value) => { onColumnFiltersChange: setColumnFilters,
table.setPageSize(Number(value)); onColumnVisibilityChange: setColumnVisibility,
}, children: [_jsx(SelectTrigger, { className: "h-8 w-[70px]", children: _jsx(SelectValue, { placeholder: table.getState().pagination.pageSize }) }), _jsx(SelectContent, { side: "top", children: [20, 30, 50].map((pageSize) => (_jsx(SelectItem, { value: `${pageSize}`, children: pageSize }, pageSize))) })] })] }), _jsxs("div", { className: "flex w-[100px] items-center justify-center text-sm font-medium", children: [table.getPageCount(), " \uD398\uC774\uC9C0 \uC911", " ", table.getState().pagination.pageIndex + 1] }), _jsxs("div", { className: "flex items-center space-x-2", children: [_jsxs(Button, { variant: "outline", className: "hidden h-8 w-8 p-0 lg:flex", onClick: () => table.setPageIndex(0), disabled: !table.getCanPreviousPage(), children: [_jsx("span", { className: "sr-only", children: "\uCCAB \uD398\uC774\uC9C0\uB85C" }), _jsx(ChevronsLeft, { className: "h-4 w-4" })] }), _jsxs(Button, { variant: "outline", className: "h-8 w-8 p-0", onClick: () => table.previousPage(), disabled: !table.getCanPreviousPage(), children: [_jsx("span", { className: "sr-only", children: "\uC774\uC804 \uD398\uC774\uC9C0\uB85C" }), _jsx(ChevronLeft, { className: "h-4 w-4" })] }), _jsxs(Button, { variant: "outline", className: "h-8 w-8 p-0", onClick: () => table.nextPage(), disabled: !table.getCanNextPage(), children: [_jsx("span", { className: "sr-only", children: "\uB2E4\uC74C \uD398\uC774\uC9C0\uB85C" }), _jsx(ChevronRight, { className: "h-4 w-4" })] }), _jsxs(Button, { variant: "outline", className: "hidden h-8 w-8 p-0 lg:flex", onClick: () => table.setPageIndex(table.getPageCount() - 1), disabled: !table.getCanNextPage(), children: [_jsx("span", { className: "sr-only", children: "\uB9C8\uC9C0\uB9C9 \uD398\uC774\uC9C0\uB85C" }), _jsx(ChevronsRight, { className: "h-4 w-4" })] })] })] })] })] }) })); onExpandedChange: setExpanded,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getExpandedRowModel: getExpandedRowModel(),
initialState: {
pagination: {
pageSize: 20,
},
},
state: {
sorting,
columnFilters,
columnVisibility,
expanded,
globalFilter,
},
onGlobalFilterChange: setGlobalFilter,
});
const handleRowClick = (feedbackId) => {
navigate(
`/projects/${projectId}/channels/${channelId}/feedbacks/${feedbackId}`,
);
};
return _jsx(Card, {
children: _jsxs(CardContent, {
children: [
_jsxs("div", {
className: "flex items-center justify-between py-4",
children: [
_jsx(Input, {
placeholder:
"\uC804\uCCB4 \uB370\uC774\uD130\uC5D0\uC11C \uAC80\uC0C9...",
value: globalFilter,
onChange: (event) => setGlobalFilter(event.target.value),
className: "max-w-sm",
}),
_jsxs("div", {
className: "flex items-center space-x-2",
children: [
_jsxs(Popover, {
children: [
_jsx(PopoverTrigger, {
asChild: true,
children: _jsxs(Button, {
id: "date",
variant: "outline",
className: cn(
"w-[300px] justify-start text-left font-normal",
!date && "text-muted-foreground",
),
children: [
_jsx(CalendarIcon, { className: "mr-2 h-4 w-4" }),
date?.from
? date.to
? _jsxs(_Fragment, {
children: [
format(date.from, "LLL dd, y"),
" -",
" ",
format(date.to, "LLL dd, y"),
],
})
: format(date.from, "LLL dd, y")
: _jsx("span", {
children: "\uAE30\uAC04 \uC120\uD0DD",
}),
],
}),
}),
_jsx(PopoverContent, {
className: "w-auto p-0",
align: "end",
children: _jsx(Calendar, {
initialFocus: true,
mode: "range",
defaultMonth: date?.from,
selected: date,
onSelect: setDate,
numberOfMonths: 2,
}),
}),
],
}),
_jsxs(DropdownMenu, {
children: [
_jsx(DropdownMenuTrigger, {
asChild: true,
children: _jsxs(Button, {
variant: "outline",
className: "ml-auto",
children: [
"\uCEEC\uB7FC \uD45C\uC2DC ",
_jsx(ChevronDown, { className: "ml-2 h-4 w-4" }),
],
}),
}),
_jsx(DropdownMenuContent, {
align: "end",
children: table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return _jsx(
DropdownMenuCheckboxItem,
{
className: "capitalize",
checked: column.getIsVisible(),
onCheckedChange: (value) =>
column.toggleVisibility(!!value),
children: column.id,
},
column.id,
);
}),
}),
],
}),
],
}),
],
}),
_jsx("div", {
className: "rounded-md border",
children: _jsxs(Table, {
children: [
_jsx(TableHeader, {
children: table
.getHeaderGroups()
.map((headerGroup) =>
_jsx(
TableRow,
{
children: headerGroup.headers.map((header) =>
_jsx(
TableHead,
{
style: { width: header.getSize() },
children: header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
),
},
header.id,
),
),
},
headerGroup.id,
),
),
}),
_jsx(TableBody, {
children: table.getRowModel().rows?.length
? table
.getRowModel()
.rows.map((row) =>
_jsxs(
Fragment,
{
children: [
_jsx(TableRow, {
"data-state": row.getIsSelected() && "selected",
onClick: () =>
handleRowClick(row.original.id.toString()),
className: "cursor-pointer hover:bg-muted/50",
children: row
.getVisibleCells()
.map((cell) =>
_jsx(
TableCell,
{
children: flexRender(
cell.column.columnDef.cell,
cell.getContext(),
),
},
cell.id,
),
),
}),
row.getIsExpanded() &&
_jsx(
TableRow,
{
children: _jsx(TableCell, {
colSpan: columns.length + 1,
children: _jsxs("div", {
className: "p-4 bg-muted rounded-md",
children: [
_jsx("h4", {
className: "font-bold text-lg",
children: row.original.title,
}),
_jsx("p", {
className:
"mt-2 whitespace-pre-wrap",
children: row.original.contents,
}),
],
}),
}),
},
`${row.id}-expanded`,
),
],
},
row.id,
),
)
: _jsx(TableRow, {
children: _jsx(TableCell, {
colSpan: columns.length + 1,
className: "h-24 text-center",
children:
"\uD45C\uC2DC\uD560 \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
}),
}),
}),
],
}),
}),
_jsxs("div", {
className: "flex items-center justify-between py-4",
children: [
_jsxs("div", {
className: "flex-1 text-sm text-muted-foreground",
children: [
"\uCD1D ",
table.getFilteredRowModel().rows.length,
"\uAC1C",
],
}),
_jsxs("div", {
className: "flex items-center space-x-6",
children: [
_jsxs("div", {
className: "flex items-center space-x-2",
children: [
_jsx("p", {
className: "text-sm font-medium",
children: "\uD398\uC774\uC9C0 \uB2F9 \uD589 \uC218",
}),
_jsxs(Select, {
value: `${table.getState().pagination.pageSize}`,
onValueChange: (value) => {
table.setPageSize(Number(value));
},
children: [
_jsx(SelectTrigger, {
className: "h-8 w-[70px]",
children: _jsx(SelectValue, {
placeholder: table.getState().pagination.pageSize,
}),
}),
_jsx(SelectContent, {
side: "top",
children: [20, 30, 50].map((pageSize) =>
_jsx(
SelectItem,
{ value: `${pageSize}`, children: pageSize },
pageSize,
),
),
}),
],
}),
],
}),
_jsxs("div", {
className:
"flex w-[100px] items-center justify-center text-sm font-medium",
children: [
table.getPageCount(),
" \uD398\uC774\uC9C0 \uC911",
" ",
table.getState().pagination.pageIndex + 1,
],
}),
_jsxs("div", {
className: "flex items-center space-x-2",
children: [
_jsxs(Button, {
variant: "outline",
className: "hidden h-8 w-8 p-0 lg:flex",
onClick: () => table.setPageIndex(0),
disabled: !table.getCanPreviousPage(),
children: [
_jsx("span", {
className: "sr-only",
children: "\uCCAB \uD398\uC774\uC9C0\uB85C",
}),
_jsx(ChevronsLeft, { className: "h-4 w-4" }),
],
}),
_jsxs(Button, {
variant: "outline",
className: "h-8 w-8 p-0",
onClick: () => table.previousPage(),
disabled: !table.getCanPreviousPage(),
children: [
_jsx("span", {
className: "sr-only",
children: "\uC774\uC804 \uD398\uC774\uC9C0\uB85C",
}),
_jsx(ChevronLeft, { className: "h-4 w-4" }),
],
}),
_jsxs(Button, {
variant: "outline",
className: "h-8 w-8 p-0",
onClick: () => table.nextPage(),
disabled: !table.getCanNextPage(),
children: [
_jsx("span", {
className: "sr-only",
children: "\uB2E4\uC74C \uD398\uC774\uC9C0\uB85C",
}),
_jsx(ChevronRight, { className: "h-4 w-4" }),
],
}),
_jsxs(Button, {
variant: "outline",
className: "hidden h-8 w-8 p-0 lg:flex",
onClick: () =>
table.setPageIndex(table.getPageCount() - 1),
disabled: !table.getCanNextPage(),
children: [
_jsx("span", {
className: "sr-only",
children:
"\uB9C8\uC9C0\uB9C9 \uD398\uC774\uC9C0\uB85C",
}),
_jsx(ChevronsRight, { className: "h-4 w-4" }),
],
}),
],
}),
],
}),
],
}),
],
}),
});
} }
export default DynamicTable; export default DynamicTable;

View File

@@ -1,4 +1,3 @@
import { import {
flexRender, flexRender,
getCoreRowModel, getCoreRowModel,
@@ -321,7 +320,10 @@ export function DynamicTable({
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}> <TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => ( {headerGroup.headers.map((header) => (
<TableHead key={header.id} style={{ width: header.getSize() }}> <TableHead
key={header.id}
style={{ width: header.getSize() }}
>
{header.isPlaceholder {header.isPlaceholder
? null ? null
: flexRender( : flexRender(

View File

@@ -1,5 +1,6 @@
interface ErrorDisplayProps { interface ErrorDisplayProps {
message: string; message: string;
} }
export declare function ErrorDisplay({ message }: ErrorDisplayProps): import("react/jsx-runtime").JSX.Element; export declare function ErrorDisplay({
export {}; message,
}: ErrorDisplayProps): import("react/jsx-runtime").JSX.Element;

View File

@@ -1,4 +1,14 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
export function ErrorDisplay({ message }) { export function ErrorDisplay({ message }) {
return (_jsxs("div", { className: "bg-destructive/15 text-destructive p-4 rounded-md text-center", role: "alert", children: [_jsx("p", { className: "font-semibold", children: "\uC624\uB958 \uBC1C\uC0DD" }), _jsx("p", { className: "text-sm", children: message })] })); return _jsxs("div", {
className: "bg-destructive/15 text-destructive p-4 rounded-md text-center",
role: "alert",
children: [
_jsx("p", {
className: "font-semibold",
children: "\uC624\uB958 \uBC1C\uC0DD",
}),
_jsx("p", { className: "text-sm", children: message }),
],
});
} }

View File

@@ -1,30 +1,57 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import { ProjectSelectBox } from "./ProjectSelectBox"; import { ProjectSelectBox } from "./ProjectSelectBox";
import { Separator } from "./ui/separator";
import { ThemeSelectBox } from "./ThemeSelectBox"; import { ThemeSelectBox } from "./ThemeSelectBox";
import { LanguageSelectBox } from "./LanguageSelectBox"; import { LanguageSelectBox } from "./LanguageSelectBox";
import { UserProfileBox } from "./UserProfileBox"; import { UserProfileBox } from "./UserProfileBox";
import { useSettingsStore } from "@/store/useSettingsStore"; import { useSettingsStore } from "@/store/useSettingsStore";
const menuItems = [ const menuItems = [
{ name: "Home", path: "/" }, { name: "Home", path: "/" },
{ name: "Feedback", path: "/feedbacks" }, { name: "Feedback", path: "/feedbacks" },
{ name: "Issue", path: "/issues" }, { name: "Issue", path: "/issues" },
]; ];
export function Header() { export function Header() {
const projectId = useSettingsStore((state) => state.projectId); const projectId = useSettingsStore((state) => state.projectId);
const channelId = useSettingsStore((state) => state.channelId); const channelId = useSettingsStore((state) => state.channelId);
const getFullPath = (path) => { const getFullPath = (path) => {
if (path === "/") if (path === "/") return `/`; // Landing 페이지 경로 예시
return `/`; // Landing 페이지 경로 예시 if (path.startsWith("/feedbacks")) {
if (path.startsWith("/feedbacks")) { return `/projects/${projectId}/channels/${channelId}${path}`;
return `/projects/${projectId}/channels/${channelId}${path}`; }
} if (path.startsWith("/issues")) {
if (path.startsWith("/issues")){ return `/projects/${projectId}${path}`;
return `/projects/${projectId}${path}`; }
} return path;
return path; };
}; return _jsxs("header", {
return (_jsxs("header", { className: "flex h-16 items-center border-b px-4", children: [_jsx(ProjectSelectBox, { className: "mr-4"}), _jsx("nav", { className: "flex items-center space-x-4 lg:space-x-6 flex-1 ml-8", children: menuItems.map((item) => (_jsx(NavLink, { to: getFullPath(item.path), className: ({ isActive }) => `text-sm font-medium transition-colors hover:text-primary ${!isActive ? "text-muted-foreground" : ""}`, children: item.name }, item.name))) }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(ThemeSelectBox, {}), _jsx(LanguageSelectBox, {}), _jsx(UserProfileBox, {})] })] })); className: "flex h-16 items-center border-b px-4",
children: [
_jsx(ProjectSelectBox, { className: "mr-4" }),
_jsx("nav", {
className: "flex items-center space-x-4 lg:space-x-6 flex-1 ml-8",
children: menuItems.map((item) =>
_jsx(
NavLink,
{
to: getFullPath(item.path),
className: ({ isActive }) =>
`text-sm font-medium transition-colors hover:text-primary ${!isActive ? "text-muted-foreground" : ""}`,
children: item.name,
},
item.name,
),
),
}),
_jsxs("div", {
className: "flex items-center gap-3",
children: [
_jsx(ThemeSelectBox, {}),
_jsx(LanguageSelectBox, {}),
_jsx(UserProfileBox, {}),
],
}),
],
});
} }

View File

@@ -62,4 +62,4 @@ export function Header() {
</div> </div>
</header> </header>
); );
} }

View File

@@ -2,5 +2,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { Languages } from "lucide-react"; import { Languages } from "lucide-react";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
export function LanguageSelectBox() { export function LanguageSelectBox() {
return (_jsxs(Button, { variant: "ghost", size: "icon", children: [_jsx(Languages, {}), _jsx("span", { className: "sr-only", children: "\uC5B8\uC5B4 \uBCC0\uACBD" })] })); return _jsxs(Button, {
variant: "ghost",
size: "icon",
children: [
_jsx(Languages, {}),
_jsx("span", {
className: "sr-only",
children: "\uC5B8\uC5B4 \uBCC0\uACBD",
}),
],
});
} }

View File

@@ -3,5 +3,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import { Header } from "./Header"; import { Header } from "./Header";
export function MainLayout() { export function MainLayout() {
return (_jsxs("div", { className: "flex flex-col min-h-screen", children: [_jsx(Header, {}), _jsx("main", { className: "flex-1 p-6", children: _jsx(Outlet, {}) })] })); return _jsxs("div", {
className: "flex flex-col min-h-screen",
children: [
_jsx(Header, {}),
_jsx("main", { className: "flex-1 p-6", children: _jsx(Outlet, {}) }),
],
});
} }

View File

@@ -1,25 +1,51 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { getProjects } from "@/services/project"; import { getProjects } from "@/services/project";
import { useSettingsStore } from "@/store/useSettingsStore"; import { useSettingsStore } from "@/store/useSettingsStore";
export function ProjectSelectBox() { export function ProjectSelectBox() {
const [projects, setProjects] = useState([]); const [projects, setProjects] = useState([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const { projectId, setProjectId } = useSettingsStore(); const { projectId, setProjectId } = useSettingsStore();
useEffect(() => { useEffect(() => {
getProjects().then((loadedProjects) => { getProjects().then((loadedProjects) => {
setProjects(loadedProjects); setProjects(loadedProjects);
// 로드된 프로젝트 목록에 현재 ID가 없으면, 첫 번째 프로젝트로 ID를 설정 // 로드된 프로젝트 목록에 현재 ID가 없으면, 첫 번째 프로젝트로 ID를 설정
if (loadedProjects.length > 0 && if (
!loadedProjects.find((p) => p.id === projectId)) { loadedProjects.length > 0 &&
setProjectId(loadedProjects[0].id); !loadedProjects.find((p) => p.id === projectId)
} ) {
setIsLoading(false); setProjectId(loadedProjects[0].id);
}); }
}, []); // 마운트 시 한 번만 실행 setIsLoading(false);
if (isLoading) { });
return (_jsx("div", { className: "w-[180px] h-10 bg-muted rounded-md animate-pulse" })); }, [projectId, setProjectId]); // 마운트 시 한 번만 실행
} if (isLoading) {
return (_jsxs(Select, { value: projectId ?? "", onValueChange: setProjectId, children: [_jsx(SelectTrigger, { className: "w-[180px]", children: _jsx(SelectValue, { placeholder: "\uD504\uB85C\uC81D\uD2B8 \uC120\uD0DD" }) }), _jsx(SelectContent, { children: projects.map((p) => (_jsx(SelectItem, { value: p.id, children: p.name }, p.id))) })] })); return _jsx("div", {
className: "w-[180px] h-10 bg-muted rounded-md animate-pulse",
});
}
return _jsxs(Select, {
value: projectId ?? "",
onValueChange: setProjectId,
children: [
_jsx(SelectTrigger, {
className: "w-[180px]",
children: _jsx(SelectValue, {
placeholder: "\uD504\uB85C\uC81D\uD2B8 \uC120\uD0DD",
}),
}),
_jsx(SelectContent, {
children: projects.map((p) =>
_jsx(SelectItem, { value: p.id, children: p.name }, p.id),
),
}),
],
});
} }

View File

@@ -26,12 +26,10 @@ export function ProjectSelectBox() {
} }
setIsLoading(false); setIsLoading(false);
}); });
}, []); // 마운트 시 한 번만 실행 }, [projectId, setProjectId]); // 마운트 시 한 번만 실행
if (isLoading) { if (isLoading) {
return ( return <div className="w-[180px] h-10 bg-muted rounded-md animate-pulse" />;
<div className="w-[180px] h-10 bg-muted rounded-md animate-pulse" />
);
} }
return ( return (
@@ -48,4 +46,4 @@ export function ProjectSelectBox() {
</SelectContent> </SelectContent>
</Select> </Select>
); );
} }

View File

@@ -1,9 +1,55 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { Moon, Sun, Laptop } from "lucide-react"; import { Moon, Sun, Laptop } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useSettingsStore } from "@/store/useSettingsStore"; import { useSettingsStore } from "@/store/useSettingsStore";
export function ThemeSelectBox() { export function ThemeSelectBox() {
const { setTheme } = useSettingsStore(); const { setTheme } = useSettingsStore();
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "icon", children: [_jsx(Sun, { className: "h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" }), _jsx(Moon, { className: "absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" }), _jsx("span", { className: "sr-only", children: "\uD14C\uB9C8 \uBCC0\uACBD" })] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: () => setTheme("light"), children: [_jsx(Sun, { className: "mr-2 h-4 w-4" }), "Light"] }), _jsxs(DropdownMenuItem, { onClick: () => setTheme("dark"), children: [_jsx(Moon, { className: "mr-2 h-4 w-4" }), "Dark"] }), _jsxs(DropdownMenuItem, { onClick: () => setTheme("system"), children: [_jsx(Laptop, { className: "mr-2 h-4 w-4" }), "System"] })] })] })); return _jsxs(DropdownMenu, {
children: [
_jsx(DropdownMenuTrigger, {
asChild: true,
children: _jsxs(Button, {
variant: "ghost",
size: "icon",
children: [
_jsx(Sun, {
className:
"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0",
}),
_jsx(Moon, {
className:
"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100",
}),
_jsx("span", {
className: "sr-only",
children: "\uD14C\uB9C8 \uBCC0\uACBD",
}),
],
}),
}),
_jsxs(DropdownMenuContent, {
align: "end",
children: [
_jsxs(DropdownMenuItem, {
onClick: () => setTheme("light"),
children: [_jsx(Sun, { className: "mr-2 h-4 w-4" }), "Light"],
}),
_jsxs(DropdownMenuItem, {
onClick: () => setTheme("dark"),
children: [_jsx(Moon, { className: "mr-2 h-4 w-4" }), "Dark"],
}),
_jsxs(DropdownMenuItem, {
onClick: () => setTheme("system"),
children: [_jsx(Laptop, { className: "mr-2 h-4 w-4" }), "System"],
}),
],
}),
],
});
} }

View File

@@ -2,5 +2,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { CircleUser } from "lucide-react"; import { CircleUser } from "lucide-react";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
export function UserProfileBox() { export function UserProfileBox() {
return (_jsxs(Button, { variant: "ghost", size: "icon", children: [_jsx(CircleUser, {}), _jsx("span", { className: "sr-only", children: "\uC0AC\uC6A9\uC790 \uD504\uB85C\uD544" })] })); return _jsxs(Button, {
variant: "ghost",
size: "icon",
children: [
_jsx(CircleUser, {}),
_jsx("span", {
className: "sr-only",
children: "\uC0AC\uC6A9\uC790 \uD504\uB85C\uD544",
}),
],
});
} }

View File

@@ -1,5 +1,6 @@
interface ThemeProviderProps { interface ThemeProviderProps {
children: React.ReactNode; children: React.ReactNode;
} }
export declare function ThemeProvider({ children }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element; export declare function ThemeProvider({
export {}; children,
}: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;

View File

@@ -2,19 +2,19 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
import { useEffect } from "react"; import { useEffect } from "react";
import { useSettingsStore } from "@/store/useSettingsStore"; import { useSettingsStore } from "@/store/useSettingsStore";
export function ThemeProvider({ children }) { export function ThemeProvider({ children }) {
const theme = useSettingsStore((state) => state.theme); const theme = useSettingsStore((state) => state.theme);
useEffect(() => { useEffect(() => {
const root = window.document.documentElement; const root = window.document.documentElement;
root.classList.remove("light", "dark"); root.classList.remove("light", "dark");
if (theme === "system") { if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches .matches
? "dark" ? "dark"
: "light"; : "light";
root.classList.add(systemTheme); root.classList.add(systemTheme);
return; return;
} }
root.classList.add(theme); root.classList.add(theme);
}, [theme]); }, [theme]);
return _jsx(_Fragment, { children: children }); return _jsx(_Fragment, { children: children });
} }

View File

@@ -1,10 +1,29 @@
import type * as React from "react"; import type * as React from "react";
import { type VariantProps } from "class-variance-authority"; import { type VariantProps } from "class-variance-authority";
declare const buttonVariants: (props?: ({ declare const buttonVariants: (
variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined; props?:
size?: "default" | "sm" | "lg" | "icon" | null | undefined; | ({
} & import("class-variance-authority/types").ClassProp) | undefined) => string; variant?:
declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { | "link"
asChild?: boolean; | "default"
}): import("react/jsx-runtime").JSX.Element; | "destructive"
| "outline"
| "secondary"
| "ghost"
| null
| undefined;
size?: "default" | "sm" | "lg" | "icon" | null | undefined;
} & import("class-variance-authority/types").ClassProp)
| undefined,
) => string;
declare function Button({
className,
variant,
size,
asChild,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}): import("react/jsx-runtime").JSX.Element;
export { Button, buttonVariants }; export { Button, buttonVariants };

View File

@@ -2,30 +2,42 @@ import { jsx as _jsx } from "react/jsx-runtime";
import { Slot } from "@radix-ui/react-slot"; import { Slot } from "@radix-ui/react-slot";
import { cva } from "class-variance-authority"; import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { const buttonVariants = cva(
variants: { "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
variant: { {
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", variants: {
destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", variant: {
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", default:
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", destructive:
link: "text-primary underline-offset-4 hover:underline", "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
}, outline:
size: { "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
default: "h-9 px-4 py-2 has-[>svg]:px-3", secondary:
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4", ghost:
icon: "size-9", "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
}, link: "text-primary underline-offset-4 hover:underline",
}, },
defaultVariants: { size: {
variant: "default", default: "h-9 px-4 py-2 has-[>svg]:px-3",
size: "default", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
}, lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
}); icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
function Button({ className, variant, size, asChild = false, ...props }) { function Button({ className, variant, size, asChild = false, ...props }) {
const Comp = asChild ? Slot : "button"; const Comp = asChild ? Slot : "button";
return (_jsx(Comp, { "data-slot": "button", className: cn(buttonVariants({ variant, size, className })), ...props })); return _jsx(Comp, {
"data-slot": "button",
className: cn(buttonVariants({ variant, size, className })),
...props,
});
} }
export { Button, buttonVariants }; export { Button, buttonVariants };

View File

@@ -1,8 +1,24 @@
import * as React from "react"; import * as React from "react";
import { DayButton, DayPicker } from "react-day-picker"; import { DayButton, DayPicker } from "react-day-picker";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
declare function Calendar({ className, classNames, showOutsideDays, captionLayout, buttonVariant, formatters, components, ...props }: React.ComponentProps<typeof DayPicker> & { declare function Calendar({
buttonVariant?: React.ComponentProps<typeof Button>["variant"]; className,
classNames,
showOutsideDays,
captionLayout,
buttonVariant,
formatters,
components,
...props
}: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"];
}): import("react/jsx-runtime").JSX.Element; }): import("react/jsx-runtime").JSX.Element;
declare function CalendarDayButton({ className, day, modifiers, ...props }: React.ComponentProps<typeof DayButton>): import("react/jsx-runtime").JSX.Element; declare function CalendarDayButton({
className,
day,
modifiers,
...props
}: React.ComponentProps<
typeof DayButton
>): import("react/jsx-runtime").JSX.Element;
export { Calendar, CalendarDayButton }; export { Calendar, CalendarDayButton };

View File

@@ -1,73 +1,187 @@
import { jsx as _jsx } from "react/jsx-runtime"; import { jsx as _jsx } from "react/jsx-runtime";
import * as React from "react"; import * as React from "react";
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, } from "lucide-react"; import {
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"; ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "lucide-react";
import { DayPicker, getDefaultClassNames } from "react-day-picker";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Button, buttonVariants } from "@/components/ui/button"; import { Button, buttonVariants } from "@/components/ui/button";
function Calendar({ className, classNames, showOutsideDays = true, captionLayout = "label", buttonVariant = "ghost", formatters, components, ...props }) { function Calendar({
const defaultClassNames = getDefaultClassNames(); className,
return (_jsx(DayPicker, { showOutsideDays: showOutsideDays, className: cn("bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent", String.raw `rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw `rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className), captionLayout: captionLayout, formatters: { classNames,
formatMonthDropdown: (date) => date.toLocaleString("default", { month: "short" }), showOutsideDays = true,
...formatters, captionLayout = "label",
}, classNames: { buttonVariant = "ghost",
root: cn("w-fit", defaultClassNames.root), formatters,
months: cn("relative flex flex-col gap-4 md:flex-row", defaultClassNames.months), components,
month: cn("flex w-full flex-col gap-4", defaultClassNames.month), ...props
nav: cn("absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", defaultClassNames.nav), }) {
button_previous: cn(buttonVariants({ variant: buttonVariant }), "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50", defaultClassNames.button_previous), const defaultClassNames = getDefaultClassNames();
button_next: cn(buttonVariants({ variant: buttonVariant }), "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50", defaultClassNames.button_next), return _jsx(DayPicker, {
month_caption: cn("flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]", defaultClassNames.month_caption), showOutsideDays: showOutsideDays,
dropdowns: cn("flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium", defaultClassNames.dropdowns), className: cn(
dropdown_root: cn("has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border", defaultClassNames.dropdown_root), "bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
dropdown: cn("bg-popover absolute inset-0 opacity-0", defaultClassNames.dropdown), String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
caption_label: cn("select-none font-medium", captionLayout === "label" String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
? "text-sm" className,
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5", defaultClassNames.caption_label), ),
table: "w-full border-collapse", captionLayout: captionLayout,
weekdays: cn("flex", defaultClassNames.weekdays), formatters: {
weekday: cn("text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal", defaultClassNames.weekday), formatMonthDropdown: (date) =>
week: cn("mt-2 flex w-full", defaultClassNames.week), date.toLocaleString("default", { month: "short" }),
week_number_header: cn("w-[--cell-size] select-none", defaultClassNames.week_number_header), ...formatters,
week_number: cn("text-muted-foreground select-none text-[0.8rem]", defaultClassNames.week_number), },
day: cn("group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md", defaultClassNames.day), classNames: {
range_start: cn("bg-accent rounded-l-md", defaultClassNames.range_start), root: cn("w-fit", defaultClassNames.root),
range_middle: cn("rounded-none", defaultClassNames.range_middle), months: cn(
range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end), "relative flex flex-col gap-4 md:flex-row",
today: cn("bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", defaultClassNames.today), defaultClassNames.months,
outside: cn("text-muted-foreground aria-selected:text-muted-foreground", defaultClassNames.outside), ),
disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled), month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
hidden: cn("invisible", defaultClassNames.hidden), nav: cn(
...classNames, "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
}, components: { defaultClassNames.nav,
Root: ({ className, rootRef, ...props }) => { ),
return (_jsx("div", { "data-slot": "calendar", ref: rootRef, className: cn(className), ...props })); button_previous: cn(
}, buttonVariants({ variant: buttonVariant }),
Chevron: ({ className, orientation, ...props }) => { "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
if (orientation === "left") { defaultClassNames.button_previous,
return (_jsx(ChevronLeftIcon, { className: cn("size-4", className), ...props })); ),
} button_next: cn(
if (orientation === "right") { buttonVariants({ variant: buttonVariant }),
return (_jsx(ChevronRightIcon, { className: cn("size-4", className), ...props })); "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
} defaultClassNames.button_next,
return (_jsx(ChevronDownIcon, { className: cn("size-4", className), ...props })); ),
}, month_caption: cn(
DayButton: CalendarDayButton, "flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
WeekNumber: ({ children, ...props }) => { defaultClassNames.month_caption,
return (_jsx("td", { ...props, children: _jsx("div", { className: "flex size-[--cell-size] items-center justify-center text-center", children: children }) })); ),
}, dropdowns: cn(
...components, "flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
}, ...props })); defaultClassNames.dropdowns,
),
dropdown_root: cn(
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
defaultClassNames.dropdown_root,
),
dropdown: cn(
"bg-popover absolute inset-0 opacity-0",
defaultClassNames.dropdown,
),
caption_label: cn(
"select-none font-medium",
captionLayout === "label"
? "text-sm"
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
defaultClassNames.caption_label,
),
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
defaultClassNames.weekday,
),
week: cn("mt-2 flex w-full", defaultClassNames.week),
week_number_header: cn(
"w-[--cell-size] select-none",
defaultClassNames.week_number_header,
),
week_number: cn(
"text-muted-foreground select-none text-[0.8rem]",
defaultClassNames.week_number,
),
day: cn(
"group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
defaultClassNames.day,
),
range_start: cn("bg-accent rounded-l-md", defaultClassNames.range_start),
range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
today: cn(
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
defaultClassNames.today,
),
outside: cn(
"text-muted-foreground aria-selected:text-muted-foreground",
defaultClassNames.outside,
),
disabled: cn(
"text-muted-foreground opacity-50",
defaultClassNames.disabled,
),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
},
components: {
Root: ({ className, rootRef, ...props }) => {
return _jsx("div", {
"data-slot": "calendar",
ref: rootRef,
className: cn(className),
...props,
});
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return _jsx(ChevronLeftIcon, {
className: cn("size-4", className),
...props,
});
}
if (orientation === "right") {
return _jsx(ChevronRightIcon, {
className: cn("size-4", className),
...props,
});
}
return _jsx(ChevronDownIcon, {
className: cn("size-4", className),
...props,
});
},
DayButton: CalendarDayButton,
WeekNumber: ({ children, ...props }) => {
return _jsx("td", {
...props,
children: _jsx("div", {
className:
"flex size-[--cell-size] items-center justify-center text-center",
children: children,
}),
});
},
...components,
},
...props,
});
} }
function CalendarDayButton({ className, day, modifiers, ...props }) { function CalendarDayButton({ className, day, modifiers, ...props }) {
const defaultClassNames = getDefaultClassNames(); const defaultClassNames = getDefaultClassNames();
const ref = React.useRef(null); const ref = React.useRef(null);
React.useEffect(() => { React.useEffect(() => {
if (modifiers.focused) if (modifiers.focused) ref.current?.focus();
ref.current?.focus(); }, [modifiers.focused]);
}, [modifiers.focused]); return _jsx(Button, {
return (_jsx(Button, { ref: ref, variant: "ghost", size: "icon", "data-day": day.date.toLocaleDateString(), "data-selected-single": modifiers.selected && ref: ref,
!modifiers.range_start && variant: "ghost",
!modifiers.range_end && size: "icon",
!modifiers.range_middle, "data-range-start": modifiers.range_start, "data-range-end": modifiers.range_end, "data-range-middle": modifiers.range_middle, className: cn("data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70", defaultClassNames.day, className), ...props })); "data-day": day.date.toLocaleDateString(),
"data-selected-single":
modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle,
"data-range-start": modifiers.range_start,
"data-range-end": modifiers.range_end,
"data-range-middle": modifiers.range_middle,
className: cn(
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
defaultClassNames.day,
className,
),
...props,
});
} }
export { Calendar, CalendarDayButton }; export { Calendar, CalendarDayButton };

View File

@@ -1,211 +1,211 @@
import * as React from "react" import * as React from "react";
import { import {
ChevronDownIcon, ChevronDownIcon,
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
} from "lucide-react" } from "lucide-react";
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker" import { type DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { Button, buttonVariants } from "@/components/ui/button" import { Button, buttonVariants } from "@/components/ui/button";
function Calendar({ function Calendar({
className, className,
classNames, classNames,
showOutsideDays = true, showOutsideDays = true,
captionLayout = "label", captionLayout = "label",
buttonVariant = "ghost", buttonVariant = "ghost",
formatters, formatters,
components, components,
...props ...props
}: React.ComponentProps<typeof DayPicker> & { }: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"] buttonVariant?: React.ComponentProps<typeof Button>["variant"];
}) { }) {
const defaultClassNames = getDefaultClassNames() const defaultClassNames = getDefaultClassNames();
return ( return (
<DayPicker <DayPicker
showOutsideDays={showOutsideDays} showOutsideDays={showOutsideDays}
className={cn( className={cn(
"bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent", "bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className className,
)} )}
captionLayout={captionLayout} captionLayout={captionLayout}
formatters={{ formatters={{
formatMonthDropdown: (date) => formatMonthDropdown: (date) =>
date.toLocaleString("default", { month: "short" }), date.toLocaleString("default", { month: "short" }),
...formatters, ...formatters,
}} }}
classNames={{ classNames={{
root: cn("w-fit", defaultClassNames.root), root: cn("w-fit", defaultClassNames.root),
months: cn( months: cn(
"relative flex flex-col gap-4 md:flex-row", "relative flex flex-col gap-4 md:flex-row",
defaultClassNames.months defaultClassNames.months,
), ),
month: cn("flex w-full flex-col gap-4", defaultClassNames.month), month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
nav: cn( nav: cn(
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
defaultClassNames.nav defaultClassNames.nav,
), ),
button_previous: cn( button_previous: cn(
buttonVariants({ variant: buttonVariant }), buttonVariants({ variant: buttonVariant }),
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50", "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
defaultClassNames.button_previous defaultClassNames.button_previous,
), ),
button_next: cn( button_next: cn(
buttonVariants({ variant: buttonVariant }), buttonVariants({ variant: buttonVariant }),
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50", "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
defaultClassNames.button_next defaultClassNames.button_next,
), ),
month_caption: cn( month_caption: cn(
"flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]", "flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
defaultClassNames.month_caption defaultClassNames.month_caption,
), ),
dropdowns: cn( dropdowns: cn(
"flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium", "flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
defaultClassNames.dropdowns defaultClassNames.dropdowns,
), ),
dropdown_root: cn( dropdown_root: cn(
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border", "has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
defaultClassNames.dropdown_root defaultClassNames.dropdown_root,
), ),
dropdown: cn( dropdown: cn(
"bg-popover absolute inset-0 opacity-0", "bg-popover absolute inset-0 opacity-0",
defaultClassNames.dropdown defaultClassNames.dropdown,
), ),
caption_label: cn( caption_label: cn(
"select-none font-medium", "select-none font-medium",
captionLayout === "label" captionLayout === "label"
? "text-sm" ? "text-sm"
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5", : "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
defaultClassNames.caption_label defaultClassNames.caption_label,
), ),
table: "w-full border-collapse", table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays), weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn( weekday: cn(
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal", "text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
defaultClassNames.weekday defaultClassNames.weekday,
), ),
week: cn("mt-2 flex w-full", defaultClassNames.week), week: cn("mt-2 flex w-full", defaultClassNames.week),
week_number_header: cn( week_number_header: cn(
"w-[--cell-size] select-none", "w-[--cell-size] select-none",
defaultClassNames.week_number_header defaultClassNames.week_number_header,
), ),
week_number: cn( week_number: cn(
"text-muted-foreground select-none text-[0.8rem]", "text-muted-foreground select-none text-[0.8rem]",
defaultClassNames.week_number defaultClassNames.week_number,
), ),
day: cn( day: cn(
"group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md", "group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
defaultClassNames.day defaultClassNames.day,
), ),
range_start: cn( range_start: cn(
"bg-accent rounded-l-md", "bg-accent rounded-l-md",
defaultClassNames.range_start defaultClassNames.range_start,
), ),
range_middle: cn("rounded-none", defaultClassNames.range_middle), range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end), range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
today: cn( today: cn(
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
defaultClassNames.today defaultClassNames.today,
), ),
outside: cn( outside: cn(
"text-muted-foreground aria-selected:text-muted-foreground", "text-muted-foreground aria-selected:text-muted-foreground",
defaultClassNames.outside defaultClassNames.outside,
), ),
disabled: cn( disabled: cn(
"text-muted-foreground opacity-50", "text-muted-foreground opacity-50",
defaultClassNames.disabled defaultClassNames.disabled,
), ),
hidden: cn("invisible", defaultClassNames.hidden), hidden: cn("invisible", defaultClassNames.hidden),
...classNames, ...classNames,
}} }}
components={{ components={{
Root: ({ className, rootRef, ...props }) => { Root: ({ className, rootRef, ...props }) => {
return ( return (
<div <div
data-slot="calendar" data-slot="calendar"
ref={rootRef} ref={rootRef}
className={cn(className)} className={cn(className)}
{...props} {...props}
/> />
) );
}, },
Chevron: ({ className, orientation, ...props }) => { Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") { if (orientation === "left") {
return ( return (
<ChevronLeftIcon className={cn("size-4", className)} {...props} /> <ChevronLeftIcon className={cn("size-4", className)} {...props} />
) );
} }
if (orientation === "right") { if (orientation === "right") {
return ( return (
<ChevronRightIcon <ChevronRightIcon
className={cn("size-4", className)} className={cn("size-4", className)}
{...props} {...props}
/> />
) );
} }
return ( return (
<ChevronDownIcon className={cn("size-4", className)} {...props} /> <ChevronDownIcon className={cn("size-4", className)} {...props} />
) );
}, },
DayButton: CalendarDayButton, DayButton: CalendarDayButton,
WeekNumber: ({ children, ...props }) => { WeekNumber: ({ children, ...props }) => {
return ( return (
<td {...props}> <td {...props}>
<div className="flex size-[--cell-size] items-center justify-center text-center"> <div className="flex size-[--cell-size] items-center justify-center text-center">
{children} {children}
</div> </div>
</td> </td>
) );
}, },
...components, ...components,
}} }}
{...props} {...props}
/> />
) );
} }
function CalendarDayButton({ function CalendarDayButton({
className, className,
day, day,
modifiers, modifiers,
...props ...props
}: React.ComponentProps<typeof DayButton>) { }: React.ComponentProps<typeof DayButton>) {
const defaultClassNames = getDefaultClassNames() const defaultClassNames = getDefaultClassNames();
const ref = React.useRef<HTMLButtonElement>(null) const ref = React.useRef<HTMLButtonElement>(null);
React.useEffect(() => { React.useEffect(() => {
if (modifiers.focused) ref.current?.focus() if (modifiers.focused) ref.current?.focus();
}, [modifiers.focused]) }, [modifiers.focused]);
return ( return (
<Button <Button
ref={ref} ref={ref}
variant="ghost" variant="ghost"
size="icon" size="icon"
data-day={day.date.toLocaleDateString()} data-day={day.date.toLocaleDateString()}
data-selected-single={ data-selected-single={
modifiers.selected && modifiers.selected &&
!modifiers.range_start && !modifiers.range_start &&
!modifiers.range_end && !modifiers.range_end &&
!modifiers.range_middle !modifiers.range_middle
} }
data-range-start={modifiers.range_start} data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end} data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle} data-range-middle={modifiers.range_middle}
className={cn( className={cn(
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70", "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
defaultClassNames.day, defaultClassNames.day,
className className,
)} )}
{...props} {...props}
/> />
) );
} }
export { Calendar, CalendarDayButton } export { Calendar, CalendarDayButton };

View File

@@ -1,9 +1,38 @@
import type * as React from "react"; import type * as React from "react";
declare function Card({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element; declare function Card({
declare function CardHeader({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element; className,
declare function CardTitle({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element; ...props
declare function CardDescription({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element; }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
declare function CardAction({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element; declare function CardHeader({
declare function CardContent({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element; className,
declare function CardFooter({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element; ...props
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, }; }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
declare function CardTitle({
className,
...props
}: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
declare function CardDescription({
className,
...props
}: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
declare function CardAction({
className,
...props
}: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
declare function CardContent({
className,
...props
}: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
declare function CardFooter({
className,
...props
}: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
};

View File

@@ -1,24 +1,69 @@
import { jsx as _jsx } from "react/jsx-runtime"; import { jsx as _jsx } from "react/jsx-runtime";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Card({ className, ...props }) { function Card({ className, ...props }) {
return (_jsx("div", { "data-slot": "card", className: cn("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", className), ...props })); return _jsx("div", {
"data-slot": "card",
className: cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className,
),
...props,
});
} }
function CardHeader({ className, ...props }) { function CardHeader({ className, ...props }) {
return (_jsx("div", { "data-slot": "card-header", className: cn("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", className), ...props })); return _jsx("div", {
"data-slot": "card-header",
className: cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className,
),
...props,
});
} }
function CardTitle({ className, ...props }) { function CardTitle({ className, ...props }) {
return (_jsx("div", { "data-slot": "card-title", className: cn("leading-none font-semibold", className), ...props })); return _jsx("div", {
"data-slot": "card-title",
className: cn("leading-none font-semibold", className),
...props,
});
} }
function CardDescription({ className, ...props }) { function CardDescription({ className, ...props }) {
return (_jsx("div", { "data-slot": "card-description", className: cn("text-muted-foreground text-sm", className), ...props })); return _jsx("div", {
"data-slot": "card-description",
className: cn("text-muted-foreground text-sm", className),
...props,
});
} }
function CardAction({ className, ...props }) { function CardAction({ className, ...props }) {
return (_jsx("div", { "data-slot": "card-action", className: cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className), ...props })); return _jsx("div", {
"data-slot": "card-action",
className: cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className,
),
...props,
});
} }
function CardContent({ className, ...props }) { function CardContent({ className, ...props }) {
return (_jsx("div", { "data-slot": "card-content", className: cn("px-6", className), ...props })); return _jsx("div", {
"data-slot": "card-content",
className: cn("px-6", className),
...props,
});
} }
function CardFooter({ className, ...props }) { function CardFooter({ className, ...props }) {
return (_jsx("div", { "data-slot": "card-footer", className: cn("flex items-center px-6 [.border-t]:pt-6", className), ...props })); return _jsx("div", {
"data-slot": "card-footer",
className: cn("flex items-center px-6 [.border-t]:pt-6", className),
...props,
});
} }
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, }; export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
};

View File

@@ -1,92 +1,92 @@
import type * as React from "react" import type * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function Card({ className, ...props }: React.ComponentProps<"div">) { function Card({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card" data-slot="card"
className={cn( className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function CardHeader({ className, ...props }: React.ComponentProps<"div">) { function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-header" data-slot="card-header"
className={cn( className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function CardTitle({ className, ...props }: React.ComponentProps<"div">) { function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-title" data-slot="card-title"
className={cn("leading-none font-semibold", className)} className={cn("leading-none font-semibold", className)}
{...props} {...props}
/> />
) );
} }
function CardDescription({ className, ...props }: React.ComponentProps<"div">) { function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-description" data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)} className={cn("text-muted-foreground text-sm", className)}
{...props} {...props}
/> />
) );
} }
function CardAction({ className, ...props }: React.ComponentProps<"div">) { function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-action" data-slot="card-action"
className={cn( className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end", "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function CardContent({ className, ...props }: React.ComponentProps<"div">) { function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-content" data-slot="card-content"
className={cn("px-6", className)} className={cn("px-6", className)}
{...props} {...props}
/> />
) );
} }
function CardFooter({ className, ...props }: React.ComponentProps<"div">) { function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-footer" data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)} className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props} {...props}
/> />
) );
} }
export { export {
Card, Card,
CardHeader, CardHeader,
CardFooter, CardFooter,
CardTitle, CardTitle,
CardAction, CardAction,
CardDescription, CardDescription,
CardContent, CardContent,
} };

View File

@@ -1,27 +1,108 @@
import * as React from "react"; import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
declare const DropdownMenu: React.FC<DropdownMenuPrimitive.DropdownMenuProps>; declare const DropdownMenu: React.FC<DropdownMenuPrimitive.DropdownMenuProps>;
declare const DropdownMenuTrigger: React.ForwardRefExoticComponent<DropdownMenuPrimitive.DropdownMenuTriggerProps & React.RefAttributes<HTMLButtonElement>>; declare const DropdownMenuTrigger: React.ForwardRefExoticComponent<
declare const DropdownMenuGroup: React.ForwardRefExoticComponent<DropdownMenuPrimitive.DropdownMenuGroupProps & React.RefAttributes<HTMLDivElement>>; DropdownMenuPrimitive.DropdownMenuTriggerProps &
React.RefAttributes<HTMLButtonElement>
>;
declare const DropdownMenuGroup: React.ForwardRefExoticComponent<
DropdownMenuPrimitive.DropdownMenuGroupProps &
React.RefAttributes<HTMLDivElement>
>;
declare const DropdownMenuPortal: React.FC<DropdownMenuPrimitive.DropdownMenuPortalProps>; declare const DropdownMenuPortal: React.FC<DropdownMenuPrimitive.DropdownMenuPortalProps>;
declare const DropdownMenuSub: React.FC<DropdownMenuPrimitive.DropdownMenuSubProps>; declare const DropdownMenuSub: React.FC<DropdownMenuPrimitive.DropdownMenuSubProps>;
declare const DropdownMenuRadioGroup: React.ForwardRefExoticComponent<DropdownMenuPrimitive.DropdownMenuRadioGroupProps & React.RefAttributes<HTMLDivElement>>; declare const DropdownMenuRadioGroup: React.ForwardRefExoticComponent<
declare const DropdownMenuSubTrigger: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuSubTriggerProps & React.RefAttributes<HTMLDivElement>, "ref"> & { DropdownMenuPrimitive.DropdownMenuRadioGroupProps &
inset?: boolean; React.RefAttributes<HTMLDivElement>
} & React.RefAttributes<HTMLDivElement>>; >;
declare const DropdownMenuSubContent: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuSubContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>; declare const DropdownMenuSubTrigger: React.ForwardRefExoticComponent<
declare const DropdownMenuContent: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>; Omit<
declare const DropdownMenuItem: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & { DropdownMenuPrimitive.DropdownMenuSubTriggerProps &
inset?: boolean; React.RefAttributes<HTMLDivElement>,
} & React.RefAttributes<HTMLDivElement>>; "ref"
declare const DropdownMenuCheckboxItem: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuCheckboxItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>; > & {
declare const DropdownMenuRadioItem: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuRadioItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>; inset?: boolean;
declare const DropdownMenuLabel: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuLabelProps & React.RefAttributes<HTMLDivElement>, "ref"> & { } & React.RefAttributes<HTMLDivElement>
inset?: boolean; >;
} & React.RefAttributes<HTMLDivElement>>; declare const DropdownMenuSubContent: React.ForwardRefExoticComponent<
declare const DropdownMenuSeparator: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuSeparatorProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>; Omit<
DropdownMenuPrimitive.DropdownMenuSubContentProps &
React.RefAttributes<HTMLDivElement>,
"ref"
> &
React.RefAttributes<HTMLDivElement>
>;
declare const DropdownMenuContent: React.ForwardRefExoticComponent<
Omit<
DropdownMenuPrimitive.DropdownMenuContentProps &
React.RefAttributes<HTMLDivElement>,
"ref"
> &
React.RefAttributes<HTMLDivElement>
>;
declare const DropdownMenuItem: React.ForwardRefExoticComponent<
Omit<
DropdownMenuPrimitive.DropdownMenuItemProps &
React.RefAttributes<HTMLDivElement>,
"ref"
> & {
inset?: boolean;
} & React.RefAttributes<HTMLDivElement>
>;
declare const DropdownMenuCheckboxItem: React.ForwardRefExoticComponent<
Omit<
DropdownMenuPrimitive.DropdownMenuCheckboxItemProps &
React.RefAttributes<HTMLDivElement>,
"ref"
> &
React.RefAttributes<HTMLDivElement>
>;
declare const DropdownMenuRadioItem: React.ForwardRefExoticComponent<
Omit<
DropdownMenuPrimitive.DropdownMenuRadioItemProps &
React.RefAttributes<HTMLDivElement>,
"ref"
> &
React.RefAttributes<HTMLDivElement>
>;
declare const DropdownMenuLabel: React.ForwardRefExoticComponent<
Omit<
DropdownMenuPrimitive.DropdownMenuLabelProps &
React.RefAttributes<HTMLDivElement>,
"ref"
> & {
inset?: boolean;
} & React.RefAttributes<HTMLDivElement>
>;
declare const DropdownMenuSeparator: React.ForwardRefExoticComponent<
Omit<
DropdownMenuPrimitive.DropdownMenuSeparatorProps &
React.RefAttributes<HTMLDivElement>,
"ref"
> &
React.RefAttributes<HTMLDivElement>
>;
declare const DropdownMenuShortcut: { declare const DropdownMenuShortcut: {
({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>): import("react/jsx-runtime").JSX.Element; ({
displayName: string; className,
...props
}: React.HTMLAttributes<HTMLSpanElement>): import("react/jsx-runtime").JSX.Element;
displayName: string;
};
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}; };
export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, };

View File

@@ -2,34 +2,164 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import * as React from "react"; import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons"; import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
} from "@radix-ui/react-icons";
const DropdownMenu = DropdownMenuPrimitive.Root; const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group; const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal; const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub; const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.SubTrigger, { ref: ref, className: cn("flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", inset && "pl-8", className), ...props, children: [children, _jsx(ChevronRightIcon, { className: "ml-auto" })] }))); const DropdownMenuSubTrigger = React.forwardRef(
({ className, inset, children, ...props }, ref) =>
_jsxs(DropdownMenuPrimitive.SubTrigger, {
ref: ref,
className: cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className,
),
...props,
children: [children, _jsx(ChevronRightIcon, { className: "ml-auto" })],
}),
);
DropdownMenuSubTrigger.displayName = DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName; DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef(({ className, ...props }, ref) => (_jsx(DropdownMenuPrimitive.SubContent, { ref: ref, className: cn("z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]", className), ...props }))); const DropdownMenuSubContent = React.forwardRef(
({ className, ...props }, ref) =>
_jsx(DropdownMenuPrimitive.SubContent, {
ref: ref,
className: cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className,
),
...props,
}),
);
DropdownMenuSubContent.displayName = DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName; DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, { ref: ref, sideOffset: sideOffset, className: cn("z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]", className), ...props }) }))); const DropdownMenuContent = React.forwardRef(
({ className, sideOffset = 4, ...props }, ref) =>
_jsx(DropdownMenuPrimitive.Portal, {
children: _jsx(DropdownMenuPrimitive.Content, {
ref: ref,
sideOffset: sideOffset,
className: cn(
"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className,
),
...props,
}),
}),
);
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Item, { ref: ref, className: cn("relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0", inset && "pl-8", className), ...props }))); const DropdownMenuItem = React.forwardRef(
({ className, inset, ...props }, ref) =>
_jsx(DropdownMenuPrimitive.Item, {
ref: ref,
className: cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8",
className,
),
...props,
}),
);
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef(({ className, children, checked, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.CheckboxItem, { ref: ref, className: cn("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), checked: checked, ...props, children: [_jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(CheckIcon, { className: "h-4 w-4" }) }) }), children] }))); const DropdownMenuCheckboxItem = React.forwardRef(
({ className, children, checked, ...props }, ref) =>
_jsxs(DropdownMenuPrimitive.CheckboxItem, {
ref: ref,
className: cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
),
checked: checked,
...props,
children: [
_jsx("span", {
className:
"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",
children: _jsx(DropdownMenuPrimitive.ItemIndicator, {
children: _jsx(CheckIcon, { className: "h-4 w-4" }),
}),
}),
children,
],
}),
);
DropdownMenuCheckboxItem.displayName = DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName; DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.RadioItem, { ref: ref, className: cn("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), ...props, children: [_jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(DotFilledIcon, { className: "h-2 w-2 fill-current" }) }) }), children] }))); const DropdownMenuRadioItem = React.forwardRef(
({ className, children, ...props }, ref) =>
_jsxs(DropdownMenuPrimitive.RadioItem, {
ref: ref,
className: cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
),
...props,
children: [
_jsx("span", {
className:
"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",
children: _jsx(DropdownMenuPrimitive.ItemIndicator, {
children: _jsx(DotFilledIcon, {
className: "h-2 w-2 fill-current",
}),
}),
}),
children,
],
}),
);
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Label, { ref: ref, className: cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className), ...props }))); const DropdownMenuLabel = React.forwardRef(
({ className, inset, ...props }, ref) =>
_jsx(DropdownMenuPrimitive.Label, {
ref: ref,
className: cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className,
),
...props,
}),
);
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Separator, { ref: ref, className: cn("-mx-1 my-1 h-px bg-muted", className), ...props }))); const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) =>
_jsx(DropdownMenuPrimitive.Separator, {
ref: ref,
className: cn("-mx-1 my-1 h-px bg-muted", className),
...props,
}),
);
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ className, ...props }) => { const DropdownMenuShortcut = ({ className, ...props }) => {
return (_jsx("span", { className: cn("ml-auto text-xs tracking-widest opacity-60", className), ...props })); return _jsx("span", {
className: cn("ml-auto text-xs tracking-widest opacity-60", className),
...props,
});
}; };
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, }; export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
};

View File

@@ -1,198 +1,202 @@
import * as React from "react" import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons" import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
} from "@radix-ui/react-icons";
const DropdownMenu = DropdownMenuPrimitive.Root const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef< const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, children, ...props }, ref) => ( >(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRightIcon className="ml-auto" /> <ChevronRightIcon className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>
)) ));
DropdownMenuSubTrigger.displayName = DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef< const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuSubContent.displayName = DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef< const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>, React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => ( >(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal> <DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content <DropdownMenuPrimitive.Content
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md", "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className className,
)} )}
{...props} {...props}
/> />
</DropdownMenuPrimitive.Portal> </DropdownMenuPrimitive.Portal>
)) ));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef< const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>, React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0", "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef< const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => ( >(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
checked={checked} checked={checked}
{...props} {...props}
> >
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" /> <CheckIcon className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</DropdownMenuPrimitive.CheckboxItem> </DropdownMenuPrimitive.CheckboxItem>
)) ));
DropdownMenuCheckboxItem.displayName = DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef< const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
{...props} {...props}
> >
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<DotFilledIcon className="h-2 w-2 fill-current" /> <DotFilledIcon className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</DropdownMenuPrimitive.RadioItem> </DropdownMenuPrimitive.RadioItem>
)) ));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef< const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>, React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
ref={ref} ref={ref}
className={cn( className={cn(
"px-2 py-1.5 text-sm font-semibold", "px-2 py-1.5 text-sm font-semibold",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef< const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>, React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
ref={ref} ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
)) ));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ const DropdownMenuShortcut = ({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)} className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props} {...props}
/> />
) );
} };
DropdownMenuShortcut.displayName = "DropdownMenuShortcut" DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export { export {
DropdownMenu, DropdownMenu,
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuRadioItem, DropdownMenuRadioItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuGroup, DropdownMenuGroup,
DropdownMenuPortal, DropdownMenuPortal,
DropdownMenuSub, DropdownMenuSub,
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup, DropdownMenuRadioGroup,
} };

View File

@@ -1,3 +1,7 @@
import type * as React from "react"; import type * as React from "react";
declare function Input({ className, type, ...props }: React.ComponentProps<"input">): import("react/jsx-runtime").JSX.Element; declare function Input({
className,
type,
...props
}: React.ComponentProps<"input">): import("react/jsx-runtime").JSX.Element;
export { Input }; export { Input };

View File

@@ -1,6 +1,16 @@
import { jsx as _jsx } from "react/jsx-runtime"; import { jsx as _jsx } from "react/jsx-runtime";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Input({ className, type, ...props }) { function Input({ className, type, ...props }) {
return (_jsx("input", { type: type, "data-slot": "input", className: cn("file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className), ...props })); return _jsx("input", {
type: type,
"data-slot": "input",
className: cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className,
),
...props,
});
} }
export { Input }; export { Input };

View File

@@ -1,21 +1,21 @@
import type * as React from "react" import type * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function Input({ className, type, ...props }: React.ComponentProps<"input">) { function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return ( return (
<input <input
type={type} type={type}
data-slot="input" data-slot="input"
className={cn( className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
export { Input } export { Input };

View File

@@ -1,5 +1,16 @@
import * as React from "react"; import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label"; import * as LabelPrimitive from "@radix-ui/react-label";
import { type VariantProps } from "class-variance-authority"; import { type VariantProps } from "class-variance-authority";
declare const Label: React.ForwardRefExoticComponent<Omit<LabelPrimitive.LabelProps & React.RefAttributes<HTMLLabelElement>, "ref"> & VariantProps<(props?: import("class-variance-authority/types").ClassProp | undefined) => string> & React.RefAttributes<HTMLLabelElement>>; declare const Label: React.ForwardRefExoticComponent<
Omit<
LabelPrimitive.LabelProps & React.RefAttributes<HTMLLabelElement>,
"ref"
> &
VariantProps<
(
props?: import("class-variance-authority/types").ClassProp | undefined,
) => string
> &
React.RefAttributes<HTMLLabelElement>
>;
export { Label }; export { Label };

View File

@@ -3,7 +3,15 @@ import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label"; import * as LabelPrimitive from "@radix-ui/react-label";
import { cva } from "class-variance-authority"; import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"); const labelVariants = cva(
const Label = React.forwardRef(({ className, ...props }, ref) => (_jsx(LabelPrimitive.Root, { ref: ref, className: cn(labelVariants(), className), ...props }))); "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
);
const Label = React.forwardRef(({ className, ...props }, ref) =>
_jsx(LabelPrimitive.Root, {
ref: ref,
className: cn(labelVariants(), className),
...props,
}),
);
Label.displayName = LabelPrimitive.Root.displayName; Label.displayName = LabelPrimitive.Root.displayName;
export { Label }; export { Label };

View File

@@ -1,24 +1,24 @@
import * as React from "react" import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label" import * as LabelPrimitive from "@radix-ui/react-label";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const labelVariants = cva( const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
) );
const Label = React.forwardRef< const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>, React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants> VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<LabelPrimitive.Root <LabelPrimitive.Root
ref={ref} ref={ref}
className={cn(labelVariants(), className)} className={cn(labelVariants(), className)}
{...props} {...props}
/> />
)) ));
Label.displayName = LabelPrimitive.Root.displayName Label.displayName = LabelPrimitive.Root.displayName;
export { Label } export { Label };

View File

@@ -1,7 +1,17 @@
import * as React from "react"; import * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover"; import * as PopoverPrimitive from "@radix-ui/react-popover";
declare const Popover: React.FC<PopoverPrimitive.PopoverProps>; declare const Popover: React.FC<PopoverPrimitive.PopoverProps>;
declare const PopoverTrigger: React.ForwardRefExoticComponent<PopoverPrimitive.PopoverTriggerProps & React.RefAttributes<HTMLButtonElement>>; declare const PopoverTrigger: React.ForwardRefExoticComponent<
declare const PopoverAnchor: React.ForwardRefExoticComponent<PopoverPrimitive.PopoverAnchorProps & React.RefAttributes<HTMLDivElement>>; PopoverPrimitive.PopoverTriggerProps & React.RefAttributes<HTMLButtonElement>
declare const PopoverContent: React.ForwardRefExoticComponent<Omit<PopoverPrimitive.PopoverContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>; >;
declare const PopoverAnchor: React.ForwardRefExoticComponent<
PopoverPrimitive.PopoverAnchorProps & React.RefAttributes<HTMLDivElement>
>;
declare const PopoverContent: React.ForwardRefExoticComponent<
Omit<
PopoverPrimitive.PopoverContentProps & React.RefAttributes<HTMLDivElement>,
"ref"
> &
React.RefAttributes<HTMLDivElement>
>;
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };

View File

@@ -6,6 +6,20 @@ import { cn } from "@/lib/utils";
const Popover = PopoverPrimitive.Root; const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger; const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverAnchor = PopoverPrimitive.Anchor; const PopoverAnchor = PopoverPrimitive.Anchor;
const PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => (_jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { ref: ref, align: align, sideOffset: sideOffset, className: cn("z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]", className), ...props }) }))); const PopoverContent = React.forwardRef(
({ className, align = "center", sideOffset = 4, ...props }, ref) =>
_jsx(PopoverPrimitive.Portal, {
children: _jsx(PopoverPrimitive.Content, {
ref: ref,
align: align,
sideOffset: sideOffset,
className: cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]",
className,
),
...props,
}),
}),
);
PopoverContent.displayName = PopoverPrimitive.Content.displayName; PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };

View File

@@ -1,33 +1,33 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover" import * as PopoverPrimitive from "@radix-ui/react-popover";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Popover = PopoverPrimitive.Root const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverAnchor = PopoverPrimitive.Anchor const PopoverAnchor = PopoverPrimitive.Anchor;
const PopoverContent = React.forwardRef< const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>, React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal> <PopoverPrimitive.Portal>
<PopoverPrimitive.Content <PopoverPrimitive.Content
ref={ref} ref={ref}
align={align} align={align}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]", "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]",
className className,
)} )}
{...props} {...props}
/> />
</PopoverPrimitive.Portal> </PopoverPrimitive.Portal>
)) ));
PopoverContent.displayName = PopoverPrimitive.Content.displayName PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };

View File

@@ -1,13 +1,72 @@
import * as React from "react"; import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select"; import * as SelectPrimitive from "@radix-ui/react-select";
declare const Select: React.FC<SelectPrimitive.SelectProps>; declare const Select: React.FC<SelectPrimitive.SelectProps>;
declare const SelectGroup: React.ForwardRefExoticComponent<SelectPrimitive.SelectGroupProps & React.RefAttributes<HTMLDivElement>>; declare const SelectGroup: React.ForwardRefExoticComponent<
declare const SelectValue: React.ForwardRefExoticComponent<SelectPrimitive.SelectValueProps & React.RefAttributes<HTMLSpanElement>>; SelectPrimitive.SelectGroupProps & React.RefAttributes<HTMLDivElement>
declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>; >;
declare const SelectScrollUpButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollUpButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>; declare const SelectValue: React.ForwardRefExoticComponent<
declare const SelectScrollDownButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollDownButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>; SelectPrimitive.SelectValueProps & React.RefAttributes<HTMLSpanElement>
declare const SelectContent: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>; >;
declare const SelectLabel: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectLabelProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>; declare const SelectTrigger: React.ForwardRefExoticComponent<
declare const SelectItem: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>; Omit<
declare const SelectSeparator: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectSeparatorProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>; SelectPrimitive.SelectTriggerProps & React.RefAttributes<HTMLButtonElement>,
export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton, }; "ref"
> &
React.RefAttributes<HTMLButtonElement>
>;
declare const SelectScrollUpButton: React.ForwardRefExoticComponent<
Omit<
SelectPrimitive.SelectScrollUpButtonProps &
React.RefAttributes<HTMLDivElement>,
"ref"
> &
React.RefAttributes<HTMLDivElement>
>;
declare const SelectScrollDownButton: React.ForwardRefExoticComponent<
Omit<
SelectPrimitive.SelectScrollDownButtonProps &
React.RefAttributes<HTMLDivElement>,
"ref"
> &
React.RefAttributes<HTMLDivElement>
>;
declare const SelectContent: React.ForwardRefExoticComponent<
Omit<
SelectPrimitive.SelectContentProps & React.RefAttributes<HTMLDivElement>,
"ref"
> &
React.RefAttributes<HTMLDivElement>
>;
declare const SelectLabel: React.ForwardRefExoticComponent<
Omit<
SelectPrimitive.SelectLabelProps & React.RefAttributes<HTMLDivElement>,
"ref"
> &
React.RefAttributes<HTMLDivElement>
>;
declare const SelectItem: React.ForwardRefExoticComponent<
Omit<
SelectPrimitive.SelectItemProps & React.RefAttributes<HTMLDivElement>,
"ref"
> &
React.RefAttributes<HTMLDivElement>
>;
declare const SelectSeparator: React.ForwardRefExoticComponent<
Omit<
SelectPrimitive.SelectSeparatorProps & React.RefAttributes<HTMLDivElement>,
"ref"
> &
React.RefAttributes<HTMLDivElement>
>;
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
};

View File

@@ -2,25 +2,134 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import * as React from "react"; import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select"; import * as SelectPrimitive from "@radix-ui/react-select";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "@radix-ui/react-icons"; import {
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from "@radix-ui/react-icons";
const Select = SelectPrimitive.Root; const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group; const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value; const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(SelectPrimitive.Trigger, { ref: ref, className: cn("flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className), ...props, children: [children, _jsx(SelectPrimitive.Icon, { asChild: true, children: _jsx(ChevronDownIcon, { className: "h-4 w-4 opacity-50" }) })] }))); const SelectTrigger = React.forwardRef(
({ className, children, ...props }, ref) =>
_jsxs(SelectPrimitive.Trigger, {
ref: ref,
className: cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className,
),
...props,
children: [
children,
_jsx(SelectPrimitive.Icon, {
asChild: true,
children: _jsx(ChevronDownIcon, { className: "h-4 w-4 opacity-50" }),
}),
],
}),
);
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => (_jsx(SelectPrimitive.ScrollUpButton, { ref: ref, className: cn("flex cursor-default items-center justify-center py-1", className), ...props, children: _jsx(ChevronUpIcon, { className: "h-4 w-4" }) }))); const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) =>
_jsx(SelectPrimitive.ScrollUpButton, {
ref: ref,
className: cn(
"flex cursor-default items-center justify-center py-1",
className,
),
...props,
children: _jsx(ChevronUpIcon, { className: "h-4 w-4" }),
}),
);
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) => (_jsx(SelectPrimitive.ScrollDownButton, { ref: ref, className: cn("flex cursor-default items-center justify-center py-1", className), ...props, children: _jsx(ChevronDownIcon, { className: "h-4 w-4" }) }))); const SelectScrollDownButton = React.forwardRef(
({ className, ...props }, ref) =>
_jsx(SelectPrimitive.ScrollDownButton, {
ref: ref,
className: cn(
"flex cursor-default items-center justify-center py-1",
className,
),
...props,
children: _jsx(ChevronDownIcon, { className: "h-4 w-4" }),
}),
);
SelectScrollDownButton.displayName = SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName; SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { ref: ref, className: cn("relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]", position === "popper" && const SelectContent = React.forwardRef(
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className), position: position, ...props, children: [_jsx(SelectScrollUpButton, {}), _jsx(SelectPrimitive.Viewport, { className: cn("p-1", position === "popper" && ({ className, children, position = "popper", ...props }, ref) =>
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"), children: children }), _jsx(SelectScrollDownButton, {})] }) }))); _jsx(SelectPrimitive.Portal, {
children: _jsxs(SelectPrimitive.Content, {
ref: ref,
className: cn(
"relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
),
position: position,
...props,
children: [
_jsx(SelectScrollUpButton, {}),
_jsx(SelectPrimitive.Viewport, {
className: cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
),
children: children,
}),
_jsx(SelectScrollDownButton, {}),
],
}),
}),
);
SelectContent.displayName = SelectPrimitive.Content.displayName; SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef(({ className, ...props }, ref) => (_jsx(SelectPrimitive.Label, { ref: ref, className: cn("px-2 py-1.5 text-sm font-semibold", className), ...props }))); const SelectLabel = React.forwardRef(({ className, ...props }, ref) =>
_jsx(SelectPrimitive.Label, {
ref: ref,
className: cn("px-2 py-1.5 text-sm font-semibold", className),
...props,
}),
);
SelectLabel.displayName = SelectPrimitive.Label.displayName; SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(SelectPrimitive.Item, { ref: ref, className: cn("relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), ...props, children: [_jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(SelectPrimitive.ItemIndicator, { children: _jsx(CheckIcon, { className: "h-4 w-4" }) }) }), _jsx(SelectPrimitive.ItemText, { children: children })] }))); const SelectItem = React.forwardRef(({ className, children, ...props }, ref) =>
_jsxs(SelectPrimitive.Item, {
ref: ref,
className: cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
),
...props,
children: [
_jsx("span", {
className:
"absolute right-2 flex h-3.5 w-3.5 items-center justify-center",
children: _jsx(SelectPrimitive.ItemIndicator, {
children: _jsx(CheckIcon, { className: "h-4 w-4" }),
}),
}),
_jsx(SelectPrimitive.ItemText, { children: children }),
],
}),
);
SelectItem.displayName = SelectPrimitive.Item.displayName; SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => (_jsx(SelectPrimitive.Separator, { ref: ref, className: cn("-mx-1 my-1 h-px bg-muted", className), ...props }))); const SelectSeparator = React.forwardRef(({ className, ...props }, ref) =>
_jsx(SelectPrimitive.Separator, {
ref: ref,
className: cn("-mx-1 my-1 h-px bg-muted", className),
...props,
}),
);
SelectSeparator.displayName = SelectPrimitive.Separator.displayName; SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton, }; export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
};

View File

@@ -1,156 +1,160 @@
import * as React from "react" import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select" import * as SelectPrimitive from "@radix-ui/react-select";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "@radix-ui/react-icons" import {
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from "@radix-ui/react-icons";
const Select = SelectPrimitive.Root const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef< const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>, React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger <SelectPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<SelectPrimitive.Icon asChild> <SelectPrimitive.Icon asChild>
<ChevronDownIcon className="h-4 w-4 opacity-50" /> <ChevronDownIcon className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon> </SelectPrimitive.Icon>
</SelectPrimitive.Trigger> </SelectPrimitive.Trigger>
)) ));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef< const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton> React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton <SelectPrimitive.ScrollUpButton
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default items-center justify-center py-1", "flex cursor-default items-center justify-center py-1",
className className,
)} )}
{...props} {...props}
> >
<ChevronUpIcon className="h-4 w-4" /> <ChevronUpIcon className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton> </SelectPrimitive.ScrollUpButton>
)) ));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef< const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>, React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton> React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton <SelectPrimitive.ScrollDownButton
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default items-center justify-center py-1", "flex cursor-default items-center justify-center py-1",
className className,
)} )}
{...props} {...props}
> >
<ChevronDownIcon className="h-4 w-4" /> <ChevronDownIcon className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton> </SelectPrimitive.ScrollDownButton>
)) ));
SelectScrollDownButton.displayName = SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef< const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>, React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => ( >(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal> <SelectPrimitive.Portal>
<SelectPrimitive.Content <SelectPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]", "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
position === "popper" && position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className className,
)} )}
position={position} position={position}
{...props} {...props}
> >
<SelectScrollUpButton /> <SelectScrollUpButton />
<SelectPrimitive.Viewport <SelectPrimitive.Viewport
className={cn( className={cn(
"p-1", "p-1",
position === "popper" && position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
)} )}
> >
{children} {children}
</SelectPrimitive.Viewport> </SelectPrimitive.Viewport>
<SelectScrollDownButton /> <SelectScrollDownButton />
</SelectPrimitive.Content> </SelectPrimitive.Content>
</SelectPrimitive.Portal> </SelectPrimitive.Portal>
)) ));
SelectContent.displayName = SelectPrimitive.Content.displayName SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef< const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>, React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.Label <SelectPrimitive.Label
ref={ref} ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", className)} className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props} {...props}
/> />
)) ));
SelectLabel.displayName = SelectPrimitive.Label.displayName SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef< const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>, React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item <SelectPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
{...props} {...props}
> >
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator> <SelectPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" /> <CheckIcon className="h-4 w-4" />
</SelectPrimitive.ItemIndicator> </SelectPrimitive.ItemIndicator>
</span> </span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item> </SelectPrimitive.Item>
)) ));
SelectItem.displayName = SelectPrimitive.Item.displayName SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef< const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>, React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.Separator <SelectPrimitive.Separator
ref={ref} ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
)) ));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export { export {
Select, Select,
SelectGroup, SelectGroup,
SelectValue, SelectValue,
SelectTrigger, SelectTrigger,
SelectContent, SelectContent,
SelectLabel, SelectLabel,
SelectItem, SelectItem,
SelectSeparator, SelectSeparator,
SelectScrollUpButton, SelectScrollUpButton,
SelectScrollDownButton, SelectScrollDownButton,
} };

View File

@@ -1,4 +1,11 @@
import type * as React from "react"; import type * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator"; import * as SeparatorPrimitive from "@radix-ui/react-separator";
declare function Separator({ className, orientation, decorative, ...props }: React.ComponentProps<typeof SeparatorPrimitive.Root>): import("react/jsx-runtime").JSX.Element; declare function Separator({
className,
orientation,
decorative,
...props
}: React.ComponentProps<
typeof SeparatorPrimitive.Root
>): import("react/jsx-runtime").JSX.Element;
export { Separator }; export { Separator };

View File

@@ -1,7 +1,21 @@
import { jsx as _jsx } from "react/jsx-runtime"; import { jsx as _jsx } from "react/jsx-runtime";
import * as SeparatorPrimitive from "@radix-ui/react-separator"; import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Separator({ className, orientation = "horizontal", decorative = true, ...props }) { function Separator({
return (_jsx(SeparatorPrimitive.Root, { "data-slot": "separator", decorative: decorative, orientation: orientation, className: cn("bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", className), ...props })); className,
orientation = "horizontal",
decorative = true,
...props
}) {
return _jsx(SeparatorPrimitive.Root, {
"data-slot": "separator",
decorative: decorative,
orientation: orientation,
className: cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className,
),
...props,
});
} }
export { Separator }; export { Separator };

View File

@@ -1,26 +1,26 @@
import type * as React from "react" import type * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator" import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function Separator({ function Separator({
className, className,
orientation = "horizontal", orientation = "horizontal",
decorative = true, decorative = true,
...props ...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) { }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return ( return (
<SeparatorPrimitive.Root <SeparatorPrimitive.Root
data-slot="separator" data-slot="separator"
decorative={decorative} decorative={decorative}
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
export { Separator } export { Separator };

View File

@@ -1,10 +1,42 @@
import * as React from "react"; import * as React from "react";
declare const Table: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableElement> & React.RefAttributes<HTMLTableElement>>; declare const Table: React.ForwardRefExoticComponent<
declare const TableHeader: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>; React.HTMLAttributes<HTMLTableElement> & React.RefAttributes<HTMLTableElement>
declare const TableBody: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>; >;
declare const TableFooter: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>; declare const TableHeader: React.ForwardRefExoticComponent<
declare const TableRow: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableRowElement> & React.RefAttributes<HTMLTableRowElement>>; React.HTMLAttributes<HTMLTableSectionElement> &
declare const TableHead: React.ForwardRefExoticComponent<React.ThHTMLAttributes<HTMLTableCellElement> & React.RefAttributes<HTMLTableCellElement>>; React.RefAttributes<HTMLTableSectionElement>
declare const TableCell: React.ForwardRefExoticComponent<React.TdHTMLAttributes<HTMLTableCellElement> & React.RefAttributes<HTMLTableCellElement>>; >;
declare const TableCaption: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableCaptionElement> & React.RefAttributes<HTMLTableCaptionElement>>; declare const TableBody: React.ForwardRefExoticComponent<
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, }; React.HTMLAttributes<HTMLTableSectionElement> &
React.RefAttributes<HTMLTableSectionElement>
>;
declare const TableFooter: React.ForwardRefExoticComponent<
React.HTMLAttributes<HTMLTableSectionElement> &
React.RefAttributes<HTMLTableSectionElement>
>;
declare const TableRow: React.ForwardRefExoticComponent<
React.HTMLAttributes<HTMLTableRowElement> &
React.RefAttributes<HTMLTableRowElement>
>;
declare const TableHead: React.ForwardRefExoticComponent<
React.ThHTMLAttributes<HTMLTableCellElement> &
React.RefAttributes<HTMLTableCellElement>
>;
declare const TableCell: React.ForwardRefExoticComponent<
React.TdHTMLAttributes<HTMLTableCellElement> &
React.RefAttributes<HTMLTableCellElement>
>;
declare const TableCaption: React.ForwardRefExoticComponent<
React.HTMLAttributes<HTMLTableCaptionElement> &
React.RefAttributes<HTMLTableCaptionElement>
>;
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
};

View File

@@ -1,20 +1,92 @@
import { jsx as _jsx } from "react/jsx-runtime"; import { jsx as _jsx } from "react/jsx-runtime";
import * as React from "react"; import * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const Table = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { className: "relative w-full overflow-auto", children: _jsx("table", { ref: ref, className: cn("w-full caption-bottom text-sm", className), ...props }) }))); const Table = React.forwardRef(({ className, ...props }, ref) =>
_jsx("div", {
className: "relative w-full overflow-auto",
children: _jsx("table", {
ref: ref,
className: cn("w-full caption-bottom text-sm", className),
...props,
}),
}),
);
Table.displayName = "Table"; Table.displayName = "Table";
const TableHeader = React.forwardRef(({ className, ...props }, ref) => (_jsx("thead", { ref: ref, className: cn("[&_tr]:border-b", className), ...props }))); const TableHeader = React.forwardRef(({ className, ...props }, ref) =>
_jsx("thead", {
ref: ref,
className: cn("[&_tr]:border-b", className),
...props,
}),
);
TableHeader.displayName = "TableHeader"; TableHeader.displayName = "TableHeader";
const TableBody = React.forwardRef(({ className, ...props }, ref) => (_jsx("tbody", { ref: ref, className: cn("[&_tr:last-child]:border-0", className), ...props }))); const TableBody = React.forwardRef(({ className, ...props }, ref) =>
_jsx("tbody", {
ref: ref,
className: cn("[&_tr:last-child]:border-0", className),
...props,
}),
);
TableBody.displayName = "TableBody"; TableBody.displayName = "TableBody";
const TableFooter = React.forwardRef(({ className, ...props }, ref) => (_jsx("tfoot", { ref: ref, className: cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className), ...props }))); const TableFooter = React.forwardRef(({ className, ...props }, ref) =>
_jsx("tfoot", {
ref: ref,
className: cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className,
),
...props,
}),
);
TableFooter.displayName = "TableFooter"; TableFooter.displayName = "TableFooter";
const TableRow = React.forwardRef(({ className, ...props }, ref) => (_jsx("tr", { ref: ref, className: cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className), ...props }))); const TableRow = React.forwardRef(({ className, ...props }, ref) =>
_jsx("tr", {
ref: ref,
className: cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className,
),
...props,
}),
);
TableRow.displayName = "TableRow"; TableRow.displayName = "TableRow";
const TableHead = React.forwardRef(({ className, ...props }, ref) => (_jsx("th", { ref: ref, className: cn("h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className), ...props }))); const TableHead = React.forwardRef(({ className, ...props }, ref) =>
_jsx("th", {
ref: ref,
className: cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
),
...props,
}),
);
TableHead.displayName = "TableHead"; TableHead.displayName = "TableHead";
const TableCell = React.forwardRef(({ className, ...props }, ref) => (_jsx("td", { ref: ref, className: cn("p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className), ...props }))); const TableCell = React.forwardRef(({ className, ...props }, ref) =>
_jsx("td", {
ref: ref,
className: cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
),
...props,
}),
);
TableCell.displayName = "TableCell"; TableCell.displayName = "TableCell";
const TableCaption = React.forwardRef(({ className, ...props }, ref) => (_jsx("caption", { ref: ref, className: cn("mt-4 text-sm text-muted-foreground", className), ...props }))); const TableCaption = React.forwardRef(({ className, ...props }, ref) =>
_jsx("caption", {
ref: ref,
className: cn("mt-4 text-sm text-muted-foreground", className),
...props,
}),
);
TableCaption.displayName = "TableCaption"; TableCaption.displayName = "TableCaption";
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, }; export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
};

View File

@@ -1,120 +1,120 @@
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Table = React.forwardRef< const Table = React.forwardRef<
HTMLTableElement, HTMLTableElement,
React.HTMLAttributes<HTMLTableElement> React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto"> <div className="relative w-full overflow-auto">
<table <table
ref={ref} ref={ref}
className={cn("w-full caption-bottom text-sm", className)} className={cn("w-full caption-bottom text-sm", className)}
{...props} {...props}
/> />
</div> </div>
)) ));
Table.displayName = "Table" Table.displayName = "Table";
const TableHeader = React.forwardRef< const TableHeader = React.forwardRef<
HTMLTableSectionElement, HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement> React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} /> <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
)) ));
TableHeader.displayName = "TableHeader" TableHeader.displayName = "TableHeader";
const TableBody = React.forwardRef< const TableBody = React.forwardRef<
HTMLTableSectionElement, HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement> React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<tbody <tbody
ref={ref} ref={ref}
className={cn("[&_tr:last-child]:border-0", className)} className={cn("[&_tr:last-child]:border-0", className)}
{...props} {...props}
/> />
)) ));
TableBody.displayName = "TableBody" TableBody.displayName = "TableBody";
const TableFooter = React.forwardRef< const TableFooter = React.forwardRef<
HTMLTableSectionElement, HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement> React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<tfoot <tfoot
ref={ref} ref={ref}
className={cn( className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TableFooter.displayName = "TableFooter" TableFooter.displayName = "TableFooter";
const TableRow = React.forwardRef< const TableRow = React.forwardRef<
HTMLTableRowElement, HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement> React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<tr <tr
ref={ref} ref={ref}
className={cn( className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TableRow.displayName = "TableRow" TableRow.displayName = "TableRow";
const TableHead = React.forwardRef< const TableHead = React.forwardRef<
HTMLTableCellElement, HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement> React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<th <th
ref={ref} ref={ref}
className={cn( className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TableHead.displayName = "TableHead" TableHead.displayName = "TableHead";
const TableCell = React.forwardRef< const TableCell = React.forwardRef<
HTMLTableCellElement, HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement> React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<td <td
ref={ref} ref={ref}
className={cn( className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TableCell.displayName = "TableCell" TableCell.displayName = "TableCell";
const TableCaption = React.forwardRef< const TableCaption = React.forwardRef<
HTMLTableCaptionElement, HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement> React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<caption <caption
ref={ref} ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)} className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
TableCaption.displayName = "TableCaption" TableCaption.displayName = "TableCaption";
export { export {
Table, Table,
TableHeader, TableHeader,
TableBody, TableBody,
TableFooter, TableFooter,
TableHead, TableHead,
TableRow, TableRow,
TableCell, TableCell,
TableCaption, TableCaption,
} };

View File

@@ -1,3 +1,6 @@
import type * as React from "react"; import type * as React from "react";
declare function Textarea({ className, ...props }: React.ComponentProps<"textarea">): import("react/jsx-runtime").JSX.Element; declare function Textarea({
className,
...props
}: React.ComponentProps<"textarea">): import("react/jsx-runtime").JSX.Element;
export { Textarea }; export { Textarea };

View File

@@ -1,6 +1,13 @@
import { jsx as _jsx } from "react/jsx-runtime"; import { jsx as _jsx } from "react/jsx-runtime";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Textarea({ className, ...props }) { function Textarea({ className, ...props }) {
return (_jsx("textarea", { "data-slot": "textarea", className: cn("border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className), ...props })); return _jsx("textarea", {
"data-slot": "textarea",
className: cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className,
),
...props,
});
} }
export { Textarea }; export { Textarea };

View File

@@ -1,18 +1,18 @@
import type * as React from "react" import type * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return ( return (
<textarea <textarea
data-slot="textarea" data-slot="textarea"
className={cn( className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
export { Textarea } export { Textarea };

View File

@@ -18,5 +18,5 @@ export function useSyncChannelId() {
if (channelId) { if (channelId) {
setChannelId(channelId); setChannelId(channelId);
} }
}, [channelId]); }, [channelId, setChannelId]);
} }

View File

@@ -108,7 +108,7 @@ html {
} }
.example-title, .example-title,
.welcome-title { .welcome-title {
font-family: JetBrains Mono; font-family: "JetBrains Mono", monospace;
font-style: normal; font-style: normal;
letter-spacing: 0.6px; letter-spacing: 0.6px;
margin-bottom: 0; margin-bottom: 0;
@@ -144,7 +144,7 @@ h1 {
} }
p { p {
display: flex; display: flex;
font-family: Barlow; font-family: "Barlow", sans-serif;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
letter-spacing: 0.6px; letter-spacing: 0.6px;
@@ -179,4 +179,4 @@ p {
.example { .example {
min-width: 350px; min-width: 350px;
} }
} }

View File

@@ -1,5 +1,5 @@
import { clsx } from "clsx"; import { clsx } from "clsx";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
export function cn(...inputs) { export function cn(...inputs) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }

View File

@@ -1,7 +0,0 @@
import { jsx as _jsx } from "react/jsx-runtime";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App.tsx";
import "./index.css";
createRoot(document.getElementById("root")).render(_jsx(StrictMode, { children: _jsx(BrowserRouter, { children: _jsx(App, {}) }) }));

View File

@@ -5,7 +5,13 @@ import App from "./App.tsx";
import { ThemeProvider } from "./components/providers/ThemeProvider.tsx"; import { ThemeProvider } from "./components/providers/ThemeProvider.tsx";
import "./index.css"; import "./index.css";
createRoot(document.getElementById("root")!).render( const rootElement = document.getElementById("root");
if (!rootElement) {
throw new Error("Failed to find the root element");
}
createRoot(rootElement).render(
<StrictMode> <StrictMode>
<BrowserRouter> <BrowserRouter>
<ThemeProvider> <ThemeProvider>

View File

@@ -6,73 +6,103 @@ import { getFeedbackFields, createFeedback } from "@/services/feedback";
import { ErrorDisplay } from "@/components/ErrorDisplay"; import { ErrorDisplay } from "@/components/ErrorDisplay";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
export function FeedbackCreatePage() { export function FeedbackCreatePage() {
const navigate = useNavigate(); const navigate = useNavigate();
const [fields, setFields] = useState([]); const [fields, setFields] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [submitMessage, setSubmitMessage] = useState(null); const [submitMessage, setSubmitMessage] = useState(null);
// TODO: projectId와 channelId는 URL 파라미터나 컨텍스트에서 가져와야 합니다. // TODO: projectId와 channelId는 URL 파라미터나 컨텍스트에서 가져와야 합니다.
const projectId = "1"; const projectId = "1";
const channelId = "4"; const channelId = "4";
useEffect(() => { useEffect(() => {
const fetchFields = async () => { const fetchFields = async () => {
try { try {
setLoading(true); setLoading(true);
const fieldsData = await getFeedbackFields(projectId, channelId); const fieldsData = await getFeedbackFields(projectId, channelId);
// 사용자에게 보여주지 않을 필드 목록 // 사용자에게 보여주지 않을 필드 목록
const hiddenFields = ["id", "createdAt", "updatedAt", "issues"]; const hiddenFields = ["id", "createdAt", "updatedAt", "issues"];
const processedFields = fieldsData const processedFields = fieldsData
.filter((field) => !hiddenFields.includes(field.id)) .filter((field) => !hiddenFields.includes(field.id))
.map((field) => { .map((field) => {
// 'contents' 필드를 항상 textarea로 처리 // 'contents' 필드를 항상 textarea로 처리
if (field.id === "contents") { if (field.id === "contents") {
return { ...field, type: "textarea" }; return { ...field, type: "textarea" };
} }
return field; return field;
}); });
setFields(processedFields); setFields(processedFields);
} } catch (err) {
catch (err) { if (err instanceof Error) {
if (err instanceof Error) { setError(err.message);
setError(err.message); } else {
} setError("알 수 없는 오류가 발생했습니다.");
else { }
setError("알 수 없는 오류가 발생했습니다."); } finally {
} setLoading(false);
} }
finally { };
setLoading(false); fetchFields();
} }, []);
}; const handleSubmit = async (formData) => {
fetchFields(); try {
}, [projectId, channelId]); setError(null);
const handleSubmit = async (formData) => { setSubmitMessage(null);
try { const requestData = {
setError(null); ...formData,
setSubmitMessage(null); issueNames: [],
const requestData = { };
...formData, await createFeedback(projectId, channelId, requestData);
issueNames: [], setSubmitMessage(
}; "피드백이 성공적으로 등록되었습니다! 곧 목록으로 돌아갑니다.",
await createFeedback(projectId, channelId, requestData); );
setSubmitMessage("피드백이 성공적으로 등록되었습니다! 곧 목록으로 돌아갑니다."); // 2초 후 목록 페이지로 이동
// 2초 후 목록 페이지로 이동 setTimeout(() => {
setTimeout(() => { navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`);
navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`); }, 2000);
}, 2000); } catch (err) {
} if (err instanceof Error) {
catch (err) { setError(err.message);
if (err instanceof Error) { } else {
setError(err.message); setError("피드백 등록 중 알 수 없는 오류가 발생했습니다.");
} }
else { throw err;
setError("피드백 등록 중 알 수 없는 오류가 발생했습니다."); }
} };
throw err; if (loading) {
} return _jsx("div", {
}; children: "\uD3FC\uC744 \uBD88\uB7EC\uC624\uB294 \uC911...",
if (loading) { });
return _jsx("div", { children: "\uD3FC\uC744 \uBD88\uB7EC\uC624\uB294 \uC911..." }); }
} return _jsxs("div", {
return (_jsxs("div", { className: "container mx-auto p-4", children: [_jsxs("div", { className: "space-y-2 mb-6", children: [_jsx("h1", { className: "text-2xl font-bold", children: "\uD53C\uB4DC\uBC31 \uC791\uC131" }), _jsx("p", { className: "text-muted-foreground", children: "\uC544\uB798 \uD3FC\uC744 \uC791\uC131\uD558\uC5EC \uD53C\uB4DC\uBC31\uC744 \uC81C\uCD9C\uD574\uC8FC\uC138\uC694." })] }), _jsx(Separator, {}), _jsxs("div", { className: "mt-6", children: [_jsx(DynamicForm, { fields: fields, onSubmit: handleSubmit }), error && _jsx(ErrorDisplay, { message: error }), submitMessage && (_jsx("div", { className: "mt-4 p-3 bg-green-100 text-green-800 rounded-md", children: submitMessage }))] })] })); className: "container mx-auto p-4",
children: [
_jsxs("div", {
className: "space-y-2 mb-6",
children: [
_jsx("h1", {
className: "text-2xl font-bold",
children: "\uD53C\uB4DC\uBC31 \uC791\uC131",
}),
_jsx("p", {
className: "text-muted-foreground",
children:
"\uC544\uB798 \uD3FC\uC744 \uC791\uC131\uD558\uC5EC \uD53C\uB4DC\uBC31\uC744 \uC81C\uCD9C\uD574\uC8FC\uC138\uC694.",
}),
],
}),
_jsx(Separator, {}),
_jsxs("div", {
className: "mt-6",
children: [
_jsx(DynamicForm, { fields: fields, onSubmit: handleSubmit }),
error && _jsx(ErrorDisplay, { message: error }),
submitMessage &&
_jsx("div", {
className: "mt-4 p-3 bg-green-100 text-green-800 rounded-md",
children: submitMessage,
}),
],
}),
],
});
} }

View File

@@ -31,7 +31,9 @@ export function FeedbackCreatePage() {
const schemaData = await getFeedbackSchema(projectId, channelId); const schemaData = await getFeedbackSchema(projectId, channelId);
setSchema(schemaData); setSchema(schemaData);
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : "폼을 불러오는 데 실패했습니다."); setError(
err instanceof Error ? err.message : "폼을 불러오는 데 실패했습니다.",
);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -40,7 +42,7 @@ export function FeedbackCreatePage() {
fetchSchema(); fetchSchema();
}, [projectId, channelId]); }, [projectId, channelId]);
const handleSubmit = async (formData: Record<string, any>) => { const handleSubmit = async (formData: Record<string, unknown>) => {
if (!projectId || !channelId) return; if (!projectId || !channelId) return;
try { try {
@@ -53,13 +55,19 @@ export function FeedbackCreatePage() {
}; };
await createFeedback(projectId, channelId, requestData); await createFeedback(projectId, channelId, requestData);
setSubmitMessage("피드백이 성공적으로 등록되었습니다! 곧 목록으로 돌아갑니다."); setSubmitMessage(
"피드백이 성공적으로 등록되었습니다! 곧 목록으로 돌아갑니다.",
);
setTimeout(() => { setTimeout(() => {
navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`); navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`);
}, 2000); }, 2000);
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : "피드백 등록 중 오류가 발생했습니다."); setError(
err instanceof Error
? err.message
: "피드백 등록 중 오류가 발생했습니다.",
);
throw err; // DynamicForm이 오류 상태를 인지하도록 re-throw throw err; // DynamicForm이 오류 상태를 인지하도록 re-throw
} }
}; };
@@ -95,4 +103,4 @@ export function FeedbackCreatePage() {
)} )}
</div> </div>
); );
} }

View File

@@ -2,95 +2,154 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect, useMemo } from "react"; import { useState, useEffect, useMemo } from "react";
import { useParams, useNavigate } from "react-router-dom"; import { useParams, useNavigate } from "react-router-dom";
import { DynamicForm } from "@/components/DynamicForm"; import { DynamicForm } from "@/components/DynamicForm";
import { getFeedbackFields, getFeedbackById, updateFeedback, } from "@/services/feedback"; import {
getFeedbackFields,
getFeedbackById,
updateFeedback,
} from "@/services/feedback";
import { ErrorDisplay } from "@/components/ErrorDisplay"; import { ErrorDisplay } from "@/components/ErrorDisplay";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
export function FeedbackDetailPage() { export function FeedbackDetailPage() {
const { projectId, channelId, feedbackId } = useParams(); const { projectId, channelId, feedbackId } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const [fields, setFields] = useState([]); const [fields, setFields] = useState([]);
const [feedback, setFeedback] = useState(null); const [feedback, setFeedback] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [successMessage, setSuccessMessage] = useState(null); const [successMessage, setSuccessMessage] = useState(null);
const initialData = useMemo(() => feedback ?? {}, [feedback]); const initialData = useMemo(() => feedback ?? {}, [feedback]);
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
if (!projectId || !channelId || !feedbackId) if (!projectId || !channelId || !feedbackId) return;
return; try {
try { setLoading(true);
setLoading(true); const [fieldsData, feedbackData] = await Promise.all([
const [fieldsData, feedbackData] = await Promise.all([ getFeedbackFields(projectId, channelId),
getFeedbackFields(projectId, channelId), getFeedbackById(projectId, channelId, feedbackId),
getFeedbackById(projectId, channelId, feedbackId), ]);
]); // 폼에서 숨길 필드 목록
// 폼에서 숨길 필드 목록 const hiddenFields = [
const hiddenFields = ["id", "createdAt", "updatedAt", "issues", "screenshot"]; "id",
const processedFields = fieldsData "createdAt",
.filter((field) => !hiddenFields.includes(field.id)) "updatedAt",
.map((field) => { "issues",
// 'contents' 필드는 항상 textarea로 "screenshot",
if (field.id === "contents") { ];
return { ...field, type: "textarea" }; const processedFields = fieldsData
} .filter((field) => !hiddenFields.includes(field.id))
// 'customer' 필드는 읽기 전용으로 .map((field) => {
if (field.id === "customer") { // 'contents' 필드는 항상 textarea로
return { ...field, readOnly: true }; if (field.id === "contents") {
} return { ...field, type: "textarea" };
return field; }
}); // 'customer' 필드는 읽기 전용으로
setFields(processedFields); if (field.id === "customer") {
setFeedback(feedbackData); return { ...field, readOnly: true };
} }
catch (err) { return field;
if (err instanceof Error) { });
setError(err.message); setFields(processedFields);
} setFeedback(feedbackData);
else { } catch (err) {
setError("데이터를 불러오는 중 알 수 없는 오류가 발생했습니다."); if (err instanceof Error) {
} setError(err.message);
} } else {
finally { setError("데이터를 불러오는 중 알 수 없는 오류가 발생했습니다.");
setLoading(false); }
} } finally {
}; setLoading(false);
fetchData(); }
}, [projectId, channelId, feedbackId]); };
const handleSubmit = async (formData) => { fetchData();
if (!projectId || !channelId || !feedbackId) }, [projectId, channelId, feedbackId]);
return; const handleSubmit = async (formData) => {
try { if (!projectId || !channelId || !feedbackId) return;
setError(null); try {
setSuccessMessage(null); setError(null);
// API에 전송할 데이터 정제 (수정 가능한 필드만 포함) setSuccessMessage(null);
const dataToUpdate = {}; // API에 전송할 데이터 정제 (수정 가능한 필드만 포함)
fields.forEach(field => { const dataToUpdate = {};
if (!field.readOnly && formData[field.id] !== undefined) { fields.forEach((field) => {
dataToUpdate[field.id] = formData[field.id]; if (!field.readOnly && formData[field.id] !== undefined) {
} dataToUpdate[field.id] = formData[field.id];
}); }
console.log("Updating with data:", dataToUpdate); // [Debug] });
await updateFeedback(projectId, channelId, feedbackId, dataToUpdate); console.log("Updating with data:", dataToUpdate); // [Debug]
setSuccessMessage("피드백이 성공적으로 수정되었습니다! 곧 목록으로 돌아갑니다."); await updateFeedback(projectId, channelId, feedbackId, dataToUpdate);
setTimeout(() => { setSuccessMessage(
navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`); "피드백이 성공적으로 수정되었습니다! 곧 목록으로 돌아갑니다.",
}, 2000); );
} setTimeout(() => {
catch (err) { navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`);
if (err instanceof Error) { }, 2000);
setError(err.message); } catch (err) {
} if (err instanceof Error) {
else { setError(err.message);
setError("피드백 수정 중 알 수 없는 오류가 발생했습니다."); } else {
} setError("피드백 수정 중 알 수 없는 오류가 발생했습니다.");
throw err; }
} throw err;
}; }
if (loading) { };
return _jsx("div", { children: "\uB85C\uB529 \uC911..." }); if (loading) {
} return _jsx("div", { children: "\uB85C\uB529 \uC911..." });
if (error) { }
return _jsx(ErrorDisplay, { message: error }); if (error) {
} return _jsx(ErrorDisplay, { message: error });
return (_jsxs("div", { className: "container mx-auto p-4", children: [_jsxs("div", { className: "space-y-2 mb-6", children: [_jsx("h1", { className: "text-2xl font-bold", children: "\uD53C\uB4DC\uBC31 \uC0C1\uC138 \uBC0F \uC218\uC815" }), _jsx("p", { className: "text-muted-foreground", children: "\uD53C\uB4DC\uBC31 \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uACE0 \uC218\uC815\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4." })] }), _jsx(Separator, {}), _jsxs("div", { className: "mt-6", children: [_jsxs("div", { className: "flex justify-between items-center mb-4 p-3 bg-slate-50 rounded-md", children: [_jsxs("span", { className: "text-sm font-medium text-slate-600", children: ["ID: ", feedback?.id] }), _jsxs("span", { className: "text-sm text-slate-500", children: ["\uC0DD\uC131\uC77C: ", feedback?.createdAt ? new Date(feedback.createdAt).toLocaleString("ko-KR") : 'N/A'] })] }), _jsx(DynamicForm, { fields: fields, initialData: initialData, onSubmit: handleSubmit, submitButtonText: "\uC218\uC815\uD558\uAE30" }), successMessage && (_jsx("div", { className: "mt-4 p-3 bg-green-100 text-green-800 rounded-md", children: successMessage }))] })] })); }
return _jsxs("div", {
className: "container mx-auto p-4",
children: [
_jsxs("div", {
className: "space-y-2 mb-6",
children: [
_jsx("h1", {
className: "text-2xl font-bold",
children: "\uD53C\uB4DC\uBC31 \uC0C1\uC138 \uBC0F \uC218\uC815",
}),
_jsx("p", {
className: "text-muted-foreground",
children:
"\uD53C\uB4DC\uBC31 \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uACE0 \uC218\uC815\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
}),
],
}),
_jsx(Separator, {}),
_jsxs("div", {
className: "mt-6",
children: [
_jsxs("div", {
className:
"flex justify-between items-center mb-4 p-3 bg-slate-50 rounded-md",
children: [
_jsxs("span", {
className: "text-sm font-medium text-slate-600",
children: ["ID: ", feedback?.id],
}),
_jsxs("span", {
className: "text-sm text-slate-500",
children: [
"\uC0DD\uC131\uC77C: ",
feedback?.createdAt
? new Date(feedback.createdAt).toLocaleString("ko-KR")
: "N/A",
],
}),
],
}),
_jsx(DynamicForm, {
fields: fields,
initialData: initialData,
onSubmit: handleSubmit,
submitButtonText: "\uC218\uC815\uD558\uAE30",
}),
successMessage &&
_jsx("div", {
className: "mt-4 p-3 bg-green-100 text-green-800 rounded-md",
children: successMessage,
}),
],
}),
],
});
} }

View File

@@ -3,11 +3,8 @@ import { useParams, useNavigate } from "react-router-dom";
import { DynamicForm } from "@/components/DynamicForm"; import { DynamicForm } from "@/components/DynamicForm";
import { useSyncChannelId } from "@/hooks/useSyncChannelId"; import { useSyncChannelId } from "@/hooks/useSyncChannelId";
import { import {
getFeedbackSchema,
getFeedbackById, getFeedbackById,
updateFeedback, updateFeedback,
type Feedback,
type FeedbackSchema,
} from "@/services/feedback"; } from "@/services/feedback";
import { ErrorDisplay } from "@/components/ErrorDisplay"; import { ErrorDisplay } from "@/components/ErrorDisplay";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
@@ -41,8 +38,14 @@ export function FeedbackDetailPage() {
]); ]);
// 폼에서 숨길 필드 목록 // 폼에서 숨길 필드 목록
const hiddenFields = ["id", "createdAt", "updatedAt", "issues", "screenshot"]; const hiddenFields = [
"id",
"createdAt",
"updatedAt",
"issues",
"screenshot",
];
const processedFields = fieldsData const processedFields = fieldsData
.filter((field) => !hiddenFields.includes(field.id)) .filter((field) => !hiddenFields.includes(field.id))
.map((field) => { .map((field) => {
@@ -73,7 +76,7 @@ export function FeedbackDetailPage() {
fetchData(); fetchData();
}, [projectId, channelId, feedbackId]); }, [projectId, channelId, feedbackId]);
const handleSubmit = async (formData: Record<string, any>) => { const handleSubmit = async (formData: Record<string, unknown>) => {
if (!projectId || !channelId || !feedbackId) return; if (!projectId || !channelId || !feedbackId) return;
try { try {
@@ -81,22 +84,23 @@ export function FeedbackDetailPage() {
setSuccessMessage(null); setSuccessMessage(null);
// API에 전송할 데이터 정제 (수정 가능한 필드만 포함) // API에 전송할 데이터 정제 (수정 가능한 필드만 포함)
const dataToUpdate: Record<string, any> = {}; const dataToUpdate: Record<string, unknown> = {};
fields.forEach(field => { fields.forEach((field) => {
if (!field.readOnly && formData[field.id] !== undefined) { if (!field.readOnly && formData[field.id] !== undefined) {
dataToUpdate[field.id] = formData[field.id]; dataToUpdate[field.id] = formData[field.id];
} }
}); });
console.log("Updating with data:", dataToUpdate); // [Debug] console.log("Updating with data:", dataToUpdate); // [Debug]
await updateFeedback(projectId, channelId, feedbackId, dataToUpdate); await updateFeedback(projectId, channelId, feedbackId, dataToUpdate);
setSuccessMessage("피드백이 성공적으로 수정되었습니다! 곧 목록으로 돌아갑니다."); setSuccessMessage(
"피드백이 성공적으로 수정되었습니다! 곧 목록으로 돌아갑니다.",
);
setTimeout(() => { setTimeout(() => {
navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`); navigate(`/projects/${projectId}/channels/${channelId}/feedbacks`);
}, 2000); }, 2000);
} catch (err) { } catch (err) {
if (err instanceof Error) { if (err instanceof Error) {
setError(err.message); setError(err.message);
@@ -130,7 +134,10 @@ export function FeedbackDetailPage() {
ID: {feedback?.id} ID: {feedback?.id}
</span> </span>
<span className="text-sm text-slate-500"> <span className="text-sm text-slate-500">
: {feedback?.createdAt ? new Date(feedback.createdAt).toLocaleString("ko-KR") : 'N/A'} :{" "}
{feedback?.createdAt
? new Date(feedback.createdAt).toLocaleString("ko-KR")
: "N/A"}
</span> </span>
</div> </div>
@@ -148,4 +155,4 @@ export function FeedbackDetailPage() {
</div> </div>
</div> </div>
); );
} }

View File

@@ -6,44 +6,67 @@ import { getFeedbacks, getFeedbackFields } from "@/services/feedback";
import { ErrorDisplay } from "@/components/ErrorDisplay"; import { ErrorDisplay } from "@/components/ErrorDisplay";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
export function FeedbackListPage() { export function FeedbackListPage() {
const [fields, setFields] = useState([]); const [fields, setFields] = useState([]);
const [feedbacks, setFeedbacks] = useState([]); const [feedbacks, setFeedbacks] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null); const [error, setError] = useState(null);
// TODO: projectId와 channelId는 URL 파라미터나 컨텍스트에서 가져와야 합니다. // TODO: projectId와 channelId는 URL 파라미터나 컨텍스트에서 가져와야 합니다.
const projectId = "1"; const projectId = "1";
const channelId = "4"; const channelId = "4";
useEffect(() => { useEffect(() => {
const fetchFieldsAndFeedbacks = async () => { const fetchFieldsAndFeedbacks = async () => {
try { try {
setLoading(true); setLoading(true);
const fieldsData = await getFeedbackFields(projectId, channelId); const fieldsData = await getFeedbackFields(projectId, channelId);
setFields(fieldsData); setFields(fieldsData);
try { try {
const feedbacksData = await getFeedbacks(projectId, channelId); const feedbacksData = await getFeedbacks(projectId, channelId);
setFeedbacks(feedbacksData); setFeedbacks(feedbacksData);
} } catch (feedbackError) {
catch (feedbackError) { console.error("Failed to fetch feedbacks:", feedbackError);
console.error("Failed to fetch feedbacks:", feedbackError); setError("피드백 목록을 불러오는 데 실패했습니다.");
setError("피드백 목록을 불러오는 데 실패했습니다."); }
} } catch (fieldsError) {
} if (fieldsError instanceof Error) {
catch (fieldsError) { setError(fieldsError.message);
if (fieldsError instanceof Error) { } else {
setError(fieldsError.message); setError("테이블 구조를 불러오는 데 실패했습니다.");
} }
else { } finally {
setError("테이블 구조를 불러오는 데 실패했습니다."); setLoading(false);
} }
} };
finally { fetchFieldsAndFeedbacks();
setLoading(false); }, []);
} if (loading) {
}; return _jsx("div", { children: "\uB85C\uB529 \uC911..." });
fetchFieldsAndFeedbacks(); }
}, [projectId, channelId]); return _jsxs("div", {
if (loading) { className: "container mx-auto p-4",
return _jsx("div", { children: "\uB85C\uB529 \uC911..." }); children: [
} _jsxs("div", {
return (_jsxs("div", { className: "container mx-auto p-4", children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsx("h1", { className: "text-2xl font-bold", children: "\uD53C\uB4DC\uBC31 \uBAA9\uB85D" }), _jsx(Button, { asChild: true, children: _jsx(Link, { to: "new", children: "\uC0C8 \uD53C\uB4DC\uBC31 \uC791\uC131" }) })] }), error && _jsx(ErrorDisplay, { message: error }), _jsx(DynamicTable, { columns: fields, data: feedbacks, projectId: projectId, channelId: channelId })] })); className: "flex justify-between items-center mb-4",
children: [
_jsx("h1", {
className: "text-2xl font-bold",
children: "\uD53C\uB4DC\uBC31 \uBAA9\uB85D",
}),
_jsx(Button, {
asChild: true,
children: _jsx(Link, {
to: "new",
children: "\uC0C8 \uD53C\uB4DC\uBC31 \uC791\uC131",
}),
}),
],
}),
error && _jsx(ErrorDisplay, { message: error }),
_jsx(DynamicTable, {
columns: fields,
data: feedbacks,
projectId: projectId,
channelId: channelId,
}),
],
});
} }

View File

@@ -36,7 +36,9 @@ export function FeedbackListPage() {
const feedbacksData = await getFeedbacks(projectId, channelId); const feedbacksData = await getFeedbacks(projectId, channelId);
setFeedbacks(feedbacksData); setFeedbacks(feedbacksData);
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : "데이터 로딩에 실패했습니다."); setError(
err instanceof Error ? err.message : "데이터 로딩에 실패했습니다.",
);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -46,7 +48,9 @@ export function FeedbackListPage() {
}, [projectId, channelId]); }, [projectId, channelId]);
const handleRowClick = (row: Feedback) => { const handleRowClick = (row: Feedback) => {
navigate(`/projects/${projectId}/channels/${channelId}/feedbacks/${row.id}`); navigate(
`/projects/${projectId}/channels/${channelId}/feedbacks/${row.id}`,
);
}; };
if (loading) { if (loading) {

View File

@@ -3,33 +3,125 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { getIssues } from "@/services/issue"; import { getIssues } from "@/services/issue";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { ErrorDisplay } from "@/components/ErrorDisplay"; import { ErrorDisplay } from "@/components/ErrorDisplay";
// 테이블 헤더 정의 // 테이블 헤더 정의
const issueTableHeaders = [ const issueTableHeaders = [
{ key: "title", label: "Title" }, { key: "title", label: "Title" },
{ key: "feedbackCount", label: "Feedback Count" }, { key: "feedbackCount", label: "Feedback Count" },
{ key: "description", label: "Description" }, { key: "description", label: "Description" },
{ key: "status", label: "Status" }, { key: "status", label: "Status" },
{ key: "createdAt", label: "Created" }, { key: "createdAt", label: "Created" },
{ key: "updatedAt", label: "Updated" }, { key: "updatedAt", label: "Updated" },
{ key: "category", label: "Category" }, { key: "category", label: "Category" },
]; ];
export function IssueViewerPage() { export function IssueViewerPage() {
const { projectId } = useParams(); const { projectId } = useParams();
const [issues, setIssues] = useState([]); const [issues, setIssues] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
useEffect(() => { useEffect(() => {
if (!projectId) if (!projectId) return;
return; setLoading(true);
setLoading(true); setError(null);
setError(null); getIssues(projectId)
getIssues(projectId) .then(setIssues)
.then(setIssues) .catch((err) => setError(err.message))
.catch((err) => setError(err.message)) .finally(() => setLoading(false));
.finally(() => setLoading(false)); }, [projectId]);
}, [projectId]); return _jsxs("div", {
return (_jsxs("div", { className: "container mx-auto p-4 md:p-8", children: [_jsxs("header", { className: "mb-8", children: [_jsx("h1", { className: "text-3xl font-bold tracking-tight", children: "\uC774\uC288 \uBDF0\uC5B4" }), _jsxs("p", { className: "text-muted-foreground mt-1", children: ["\uD504\uB85C\uC81D\uD2B8: ", projectId] })] }), error && _jsx(ErrorDisplay, { message: error }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "\uC774\uC288 \uBAA9\uB85D" }) }), _jsxs(CardContent, { children: [loading && _jsx("p", { className: "text-center", children: "\uB85C\uB529 \uC911..." }), !loading && (_jsx("div", { className: "border rounded-md", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsx(TableRow, { children: issueTableHeaders.map((header) => (_jsx(TableHead, { children: header.label }, header.key))) }) }), _jsx(TableBody, { children: issues.length > 0 ? (issues.map((issue) => (_jsx(TableRow, { children: issueTableHeaders.map((header) => (_jsx(TableCell, { children: String(issue[header.key] ?? "") }, `${issue.id}-${header.key}`))) }, issue.id)))) : (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: issueTableHeaders.length, className: "h-24 text-center", children: "\uD45C\uC2DC\uD560 \uC774\uC288\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) })) })] }) }))] })] })] })); className: "container mx-auto p-4 md:p-8",
children: [
_jsxs("header", {
className: "mb-8",
children: [
_jsx("h1", {
className: "text-3xl font-bold tracking-tight",
children: "\uC774\uC288 \uBDF0\uC5B4",
}),
_jsxs("p", {
className: "text-muted-foreground mt-1",
children: ["\uD504\uB85C\uC81D\uD2B8: ", projectId],
}),
],
}),
error && _jsx(ErrorDisplay, { message: error }),
_jsxs(Card, {
children: [
_jsx(CardHeader, {
children: _jsx(CardTitle, {
children: "\uC774\uC288 \uBAA9\uB85D",
}),
}),
_jsxs(CardContent, {
children: [
loading &&
_jsx("p", {
className: "text-center",
children: "\uB85C\uB529 \uC911...",
}),
!loading &&
_jsx("div", {
className: "border rounded-md",
children: _jsxs(Table, {
children: [
_jsx(TableHeader, {
children: _jsx(TableRow, {
children: issueTableHeaders.map((header) =>
_jsx(
TableHead,
{ children: header.label },
header.key,
),
),
}),
}),
_jsx(TableBody, {
children:
issues.length > 0
? issues.map((issue) =>
_jsx(
TableRow,
{
children: issueTableHeaders.map((header) =>
_jsx(
TableCell,
{
children: String(
issue[header.key] ?? "",
),
},
`${issue.id}-${header.key}`,
),
),
},
issue.id,
),
)
: _jsx(TableRow, {
children: _jsx(TableCell, {
colSpan: issueTableHeaders.length,
className: "h-24 text-center",
children:
"\uD45C\uC2DC\uD560 \uC774\uC288\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
}),
}),
}),
],
}),
}),
],
}),
],
}),
],
});
} }

View File

@@ -90,4 +90,4 @@ export function IssueViewerPage() {
</CardContent> </CardContent>
</Card> </Card>
); );
} }

View File

@@ -3,4 +3,7 @@
* @param message 프로덕션 환경에서 보여줄 기본 에러 메시지 * @param message 프로덕션 환경에서 보여줄 기본 에러 메시지
* @param response fetch API의 응답 객체 * @param response fetch API의 응답 객체
*/ */
export declare const handleApiError: (message: string, response: Response) => Promise<never>; export declare const handleApiError: (
message: string,
response: Response,
) => Promise<never>;

View File

@@ -5,9 +5,11 @@
* @param response fetch API의 응답 객체 * @param response fetch API의 응답 객체
*/ */
export const handleApiError = async (message, response) => { export const handleApiError = async (message, response) => {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
const errorBody = await response.text(); const errorBody = await response.text();
throw new Error(`[Dev] ${message} | URL: ${response.url} | Status: ${response.status} ${response.statusText} | Body: ${errorBody || "Empty"}`); throw new Error(
} `[Dev] ${message} | URL: ${response.url} | Status: ${response.status} ${response.statusText} | Body: ${errorBody || "Empty"}`,
throw new Error(message); );
}
throw new Error(message);
}; };

View File

@@ -1,43 +1,65 @@
export interface Feedback { export interface Feedback {
id: string; id: string;
content: string; content: string;
[key: string]: any; [key: string]: unknown;
} }
export interface Issue { export interface Issue {
id: string; id: string;
name: string; name: string;
} }
export interface FeedbackField { export interface FeedbackField {
id: string; id: string;
name: string; name: string;
type: "text" | "textarea" | "number" | "select"; type: "text" | "textarea" | "number" | "select";
readOnly?: boolean; readOnly?: boolean;
} }
export interface CreateFeedbackRequest { export interface CreateFeedbackRequest {
issueNames: string[]; issueNames: string[];
[key: string]: any; [key: string]: unknown;
} }
/** /**
* 특정 채널의 피드백 목록을 조회합니다. * 특정 채널의 피드백 목록을 조회합니다.
*/ */
export declare const getFeedbacks: (projectId: string, channelId: string) => Promise<Feedback[]>; export declare const getFeedbacks: (
projectId: string,
channelId: string,
) => Promise<Feedback[]>;
/** /**
* 특정 채널의 동적 폼 필드 스키마를 조회합니다. * 특정 채널의 동적 폼 필드 스키마를 조회합니다.
*/ */
export declare const getFeedbackFields: (projectId: string, channelId: string) => Promise<FeedbackField[]>; export declare const getFeedbackFields: (
projectId: string,
channelId: string,
) => Promise<FeedbackField[]>;
/** /**
* 특정 채널에 새로운 피드백을 생성합니다. * 특정 채널에 새로운 피드백을 생성합니다.
*/ */
export declare const createFeedback: (projectId: string, channelId: string, feedbackData: CreateFeedbackRequest) => Promise<Feedback>; export declare const createFeedback: (
projectId: string,
channelId: string,
feedbackData: CreateFeedbackRequest,
) => Promise<Feedback>;
/** /**
* 프로젝트의 이슈를 검색합니다. * 프로젝트의 이슈를 검색합니다.
*/ */
export declare const searchIssues: (projectId: string, query: string) => Promise<Issue[]>; export declare const searchIssues: (
projectId: string,
query: string,
) => Promise<Issue[]>;
/** /**
* 특정 ID의 피드백 상세 정보를 조회합니다. * 특정 ID의 피드백 상세 정보를 조회합니다.
*/ */
export declare const getFeedbackById: (projectId: string, channelId: string, feedbackId: string) => Promise<Feedback>; export declare const getFeedbackById: (
projectId: string,
channelId: string,
feedbackId: string,
) => Promise<Feedback>;
/** /**
* 특정 피드백을 수정합니다. * 특정 피드백을 수정합니다.
*/ */
export declare const updateFeedback: (projectId: string, channelId: string, feedbackId: string, feedbackData: Partial<CreateFeedbackRequest>) => Promise<Feedback>; export declare const updateFeedback: (
projectId: string,
channelId: string,
feedbackId: string,
feedbackData: Partial<CreateFeedbackRequest>,
) => Promise<Feedback>;

View File

@@ -1,114 +1,128 @@
// src/services/feedback.ts // src/services/feedback.ts
import { handleApiError } from "./error"; import { handleApiError } from "./error";
// --- API 함수 --- // --- API 함수 ---
const getFeedbacksSearchApiUrl = (projectId, channelId) => `/api/v2/projects/${projectId}/channels/${channelId}/feedbacks/search`; const getFeedbacksSearchApiUrl = (projectId, channelId) =>
const getFeedbackFieldsApiUrl = (projectId, channelId) => `/api/projects/${projectId}/channels/${channelId}/fields`; `/api/v2/projects/${projectId}/channels/${channelId}/feedbacks/search`;
const getIssuesApiUrl = (projectId) => `/api/projects/${projectId}/issues/search`; const getFeedbackFieldsApiUrl = (projectId, channelId) =>
`/api/projects/${projectId}/channels/${channelId}/fields`;
const getIssuesApiUrl = (projectId) =>
`/api/projects/${projectId}/issues/search`;
/** /**
* 특정 채널의 피드백 목록을 조회합니다. * 특정 채널의 피드백 목록을 조회합니다.
*/ */
export const getFeedbacks = async (projectId, channelId) => { export const getFeedbacks = async (projectId, channelId) => {
const url = getFeedbacksSearchApiUrl(projectId, channelId); const url = getFeedbacksSearchApiUrl(projectId, channelId);
const response = await fetch(url, { const response = await fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
}); });
if (!response.ok) { if (!response.ok) {
await handleApiError("피드백 목록을 불러오는 데 실패했습니다.", response); await handleApiError("피드백 목록을 불러오는 데 실패했습니다.", response);
} }
const result = await response.json(); const result = await response.json();
return result.items || []; return result.items || [];
}; };
/** /**
* 특정 채널의 동적 폼 필드 스키마를 조회합니다. * 특정 채널의 동적 폼 필드 스키마를 조회합니다.
*/ */
export const getFeedbackFields = async (projectId, channelId) => { export const getFeedbackFields = async (projectId, channelId) => {
const url = getFeedbackFieldsApiUrl(projectId, channelId); const url = getFeedbackFieldsApiUrl(projectId, channelId);
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
await handleApiError("피드백 필드 정보를 불러오는 데 실패했습니다.", response); await handleApiError(
} "피드백 필드 정보를 불러오는 데 실패했습니다.",
const apiFields = await response.json(); response,
if (!Array.isArray(apiFields)) { );
console.error("Error: Fields API response is not an array.", apiFields); }
return []; const apiFields = await response.json();
} if (!Array.isArray(apiFields)) {
return apiFields console.error("Error: Fields API response is not an array.", apiFields);
.filter((field) => field.status === "ACTIVE") return [];
.map((field) => ({ }
id: field.key, return apiFields
name: field.name, .filter((field) => field.status === "ACTIVE")
type: field.format, .map((field) => ({
})); id: field.key,
name: field.name,
type: field.format,
}));
}; };
/** /**
* 특정 채널에 새로운 피드백을 생성합니다. * 특정 채널에 새로운 피드백을 생성합니다.
*/ */
export const createFeedback = async (projectId, channelId, feedbackData) => { export const createFeedback = async (projectId, channelId, feedbackData) => {
const url = `/api/projects/${projectId}/channels/${channelId}/feedbacks`; const url = `/api/projects/${projectId}/channels/${channelId}/feedbacks`;
const response = await fetch(url, { const response = await fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(feedbackData), body: JSON.stringify(feedbackData),
}); });
if (!response.ok) { if (!response.ok) {
await handleApiError("피드백 생성에 실패했습니다.", response); await handleApiError("피드백 생성에 실패했습니다.", response);
} }
return response.json(); return response.json();
}; };
/** /**
* 프로젝트의 이슈를 검색합니다. * 프로젝트의 이슈를 검색합니다.
*/ */
export const searchIssues = async (projectId, query) => { export const searchIssues = async (projectId, query) => {
const url = getIssuesApiUrl(projectId); const url = getIssuesApiUrl(projectId);
const response = await fetch(url, { const response = await fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
query: { name: query }, query: { name: query },
limit: 10, limit: 10,
page: 1, page: 1,
sort: { createdAt: "ASC" }, sort: { createdAt: "ASC" },
}), }),
}); });
if (!response.ok) { if (!response.ok) {
await handleApiError("이슈 검색에 실패했습니다.", response); await handleApiError("이슈 검색에 실패했습니다.", response);
} }
const result = await response.json(); const result = await response.json();
return result.items || []; return result.items || [];
}; };
/** /**
* 특정 ID의 피드백 상세 정보를 조회합니다. * 특정 ID의 피드백 상세 정보를 조회합니다.
*/ */
export const getFeedbackById = async (projectId, channelId, feedbackId) => { export const getFeedbackById = async (projectId, channelId, feedbackId) => {
const url = `/api/projects/${projectId}/channels/${channelId}/feedbacks/${feedbackId}`; const url = `/api/projects/${projectId}/channels/${channelId}/feedbacks/${feedbackId}`;
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
await handleApiError("피드백 상세 정보를 불러오는 데 실패했습니다.", response); await handleApiError(
} "피드백 상세 정보를 불러오는 데 실패했습니다.",
return response.json(); response,
);
}
return response.json();
}; };
/** /**
* 특정 피드백을 수정합니다. * 특정 피드백을 수정합니다.
*/ */
export const updateFeedback = async (projectId, channelId, feedbackId, feedbackData) => { export const updateFeedback = async (
const url = `/api/projects/${projectId}/channels/${channelId}/feedbacks/${feedbackId}`; projectId,
const response = await fetch(url, { channelId,
method: "PUT", feedbackId,
headers: { feedbackData,
"Content-Type": "application/json", ) => {
}, const url = `/api/projects/${projectId}/channels/${channelId}/feedbacks/${feedbackId}`;
body: JSON.stringify(feedbackData), const response = await fetch(url, {
}); method: "PUT",
if (!response.ok) { headers: {
await handleApiError("피드백 수정에 실패했습니다.", response); "Content-Type": "application/json",
} },
return response.json(); body: JSON.stringify(feedbackData),
});
if (!response.ok) {
await handleApiError("피드백 수정에 실패했습니다.", response);
}
return response.json();
}; };

View File

@@ -3,10 +3,17 @@ import { handleApiError } from "./error";
// --- 타입 정의 --- // --- 타입 정의 ---
interface ApiField {
key: string;
name: string;
format: "text" | "textarea" | "number" | "select";
status: string;
}
export interface Feedback { export interface Feedback {
id: string; id: string;
content: string; content: string;
[key: string]: any; // 동적 필드를 위해 인덱스 시그니처 사용 [key: string]: unknown; // 동적 필드를 위해 인덱스 시그니처 사용
} }
export interface Issue { export interface Issue {
@@ -25,7 +32,7 @@ export interface FeedbackField {
// 피드백 생성 요청 타입 (동적 데이터 포함) // 피드백 생성 요청 타입 (동적 데이터 포함)
export interface CreateFeedbackRequest { export interface CreateFeedbackRequest {
issueNames: string[]; issueNames: string[];
[key: string]: any; // 폼 데이터 필드 (예: { message: "...", rating: 5 }) [key: string]: unknown; // 폼 데이터 필드 (예: { message: "...", rating: 5 })
} }
// --- API 함수 --- // --- API 함수 ---
@@ -72,7 +79,10 @@ export const getFeedbackFields = async (
const url = getFeedbackFieldsApiUrl(projectId, channelId); const url = getFeedbackFieldsApiUrl(projectId, channelId);
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
await handleApiError("피드백 필드 정보를 불러오는 데 실패했습니다.", response); await handleApiError(
"피드백 필드 정보를 불러오는 데 실패했습니다.",
response,
);
} }
const apiFields = await response.json(); const apiFields = await response.json();
@@ -82,8 +92,8 @@ export const getFeedbackFields = async (
} }
return apiFields return apiFields
.filter((field: any) => field.status === "ACTIVE") .filter((field: ApiField) => field.status === "ACTIVE")
.map((field: any) => ({ .map((field: ApiField) => ({
id: field.key, id: field.key,
name: field.name, name: field.name,
type: field.format, type: field.format,
@@ -150,7 +160,10 @@ export const getFeedbackById = async (
const url = `/api/projects/${projectId}/channels/${channelId}/feedbacks/${feedbackId}`; const url = `/api/projects/${projectId}/channels/${channelId}/feedbacks/${feedbackId}`;
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
await handleApiError("피드백 상세 정보를 불러오는 데 실패했습니다.", response); await handleApiError(
"피드백 상세 정보를 불러오는 데 실패했습니다.",
response,
);
} }
return response.json(); return response.json();
}; };

View File

@@ -1,13 +1,13 @@
export interface Issue { export interface Issue {
id: string; id: string;
title: string; title: string;
feedbackCount: number; feedbackCount: number;
description: string; description: string;
status: string; status: string;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
category: string; category: string;
[key: string]: any; [key: string]: unknown;
} }
/** /**
* 특정 프로젝트의 모든 이슈를 검색합니다. * 특정 프로젝트의 모든 이슈를 검색합니다.

View File

@@ -6,19 +6,19 @@ import { handleApiError } from "./error";
* @returns 이슈 목록 Promise * @returns 이슈 목록 Promise
*/ */
export const getIssues = async (projectId) => { export const getIssues = async (projectId) => {
const url = `/api/projects/${projectId}/issues/search`; const url = `/api/projects/${projectId}/issues/search`;
// body를 비워서 보내면 모든 이슈를 가져오는 것으로 가정합니다. // body를 비워서 보내면 모든 이슈를 가져오는 것으로 가정합니다.
// 실제 API 명세에 따라 수정이 필요할 수 있습니다. // 실제 API 명세에 따라 수정이 필요할 수 있습니다.
const response = await fetch(url, { const response = await fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
}); });
if (!response.ok) { if (!response.ok) {
await handleApiError("이슈 목록을 불러오는 데 실패했습니다.", response); await handleApiError("이슈 목록을 불러오는 데 실패했습니다.", response);
} }
const result = await response.json(); const result = await response.json();
return result.items || []; return result.items || [];
}; };

View File

@@ -12,7 +12,7 @@ export interface Issue {
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
category: string; category: string;
[key: string]: any; // 그 외 다른 필드들 [key: string]: unknown; // 그 외 다른 필드들
} }
/** /**
@@ -54,8 +54,11 @@ export const getIssue = async (
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
await handleApiError("이슈 상세 정보를 불러오는 데 실패했습니다.", response); await handleApiError(
"이슈 상세 정보를 불러오는 데 실패했습니다.",
response,
);
} }
return response.json(); return response.json();
}; };

View File

@@ -1,14 +1,14 @@
export interface Project { export interface Project {
id: string; id: string;
name: string; name: string;
description: string; description: string;
timezone: { timezone: {
countryCode: string; countryCode: string;
name: string; name: string;
offset: string; offset: string;
}; };
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} }
/** /**
* 모든 접근 가능한 프로젝트 목록을 가져옵니다. * 모든 접근 가능한 프로젝트 목록을 가져옵니다.
@@ -19,4 +19,6 @@ export declare const getProjects: () => Promise<Project[]>;
* 특정 ID를 가진 프로젝트의 상세 정보를 가져옵니다. * 특정 ID를 가진 프로젝트의 상세 정보를 가져옵니다.
* @param id - 조회할 프로젝트의 ID * @param id - 조회할 프로젝트의 ID
*/ */
export declare const getProjectById: (id: string) => Promise<Project | undefined>; export declare const getProjectById: (
id: string,
) => Promise<Project | undefined>;

View File

@@ -4,35 +4,33 @@ const API_BASE_URL = "/api";
* 현재는 ID가 1인 프로젝트 하나만 가져오도록 구현되어 있습니다. * 현재는 ID가 1인 프로젝트 하나만 가져오도록 구현되어 있습니다.
*/ */
export const getProjects = async () => { export const getProjects = async () => {
try { try {
const project = await getProjectById("1"); const project = await getProjectById("1");
return project ? [project] : []; return project ? [project] : [];
} } catch (error) {
catch (error) { console.error("Failed to fetch projects:", error);
console.error("Failed to fetch projects:", error); return [];
return []; }
}
}; };
/** /**
* 특정 ID를 가진 프로젝트의 상세 정보를 가져옵니다. * 특정 ID를 가진 프로젝트의 상세 정보를 가져옵니다.
* @param id - 조회할 프로젝트의 ID * @param id - 조회할 프로젝트의 ID
*/ */
export const getProjectById = async (id) => { export const getProjectById = async (id) => {
try { try {
// 'project' -> 'projects'로 수정 // 'project' -> 'projects'로 수정
const response = await fetch(`${API_BASE_URL}/projects/${id}`); const response = await fetch(`${API_BASE_URL}/projects/${id}`);
if (!response.ok) { if (!response.ok) {
throw new Error(`API call failed with status: ${response.status}`); throw new Error(`API call failed with status: ${response.status}`);
} }
const data = await response.json(); const data = await response.json();
// API 응답(id: number)을 내부 모델(id: string)로 변환 // API 응답(id: number)을 내부 모델(id: string)로 변환
return { return {
...data, ...data,
id: data.id.toString(), id: data.id.toString(),
}; };
} } catch (error) {
catch (error) { console.error(`Failed to fetch project with id ${id}:`, error);
console.error(`Failed to fetch project with id ${id}:`, error); return undefined;
return undefined; }
}
}; };

View File

@@ -1,19 +1,32 @@
type Theme = "light" | "dark" | "system"; type Theme = "light" | "dark" | "system";
interface SettingsState { interface SettingsState {
projectId: string | null; projectId: string | null;
theme: Theme; theme: Theme;
setProjectId: (projectId: string) => void; setProjectId: (projectId: string) => void;
setTheme: (theme: Theme) => void; setTheme: (theme: Theme) => void;
} }
export declare const useSettingsStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<SettingsState>, "persist"> & { export declare const useSettingsStore: import("zustand").UseBoundStore<
persist: { Omit<import("zustand").StoreApi<SettingsState>, "persist"> & {
setOptions: (options: Partial<import("zustand/middleware").PersistOptions<SettingsState, SettingsState>>) => void; persist: {
clearStorage: () => void; setOptions: (
rehydrate: () => Promise<void> | void; options: Partial<
hasHydrated: () => boolean; import("zustand/middleware").PersistOptions<
onHydrate: (fn: (state: SettingsState) => void) => () => void; SettingsState,
onFinishHydration: (fn: (state: SettingsState) => void) => () => void; SettingsState
getOptions: () => Partial<import("zustand/middleware").PersistOptions<SettingsState, SettingsState>>; >
}; >,
}>; ) => void;
export {}; clearStorage: () => void;
rehydrate: () => Promise<void> | void;
hasHydrated: () => boolean;
onHydrate: (fn: (state: SettingsState) => void) => () => void;
onFinishHydration: (fn: (state: SettingsState) => void) => () => void;
getOptions: () => Partial<
import("zustand/middleware").PersistOptions<
SettingsState,
SettingsState
>
>;
};
}
>;

View File

@@ -1,10 +0,0 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
export const useSettingsStore = create()(persist((set) => ({
projectId: "1", // 기본 프로젝트 ID를 1로 설정
theme: "system",
setProjectId: (projectId) => set({ projectId }),
setTheme: (theme) => set({ theme }),
}), {
name: "settings-storage", // localStorage에 저장될 이름
}));

View File

@@ -1,25 +1,11 @@
import { create } from "zustand"; import { create } from "zustand";
import { persist } from "zustand/middleware"; import { persist } from "zustand/middleware";
export const useSettingsStore = create()(
type Theme = "light" | "dark" | "system";
interface SettingsState {
projectId: string | null;
channelId: string | null;
theme: Theme;
setProjectId: (projectId: string) => void;
setChannelId: (channelId: string) => void;
setTheme: (theme: Theme) => void;
}
export const useSettingsStore = create<SettingsState>()(
persist( persist(
(set) => ({ (set) => ({
projectId: import.meta.env.VITE_DEFAULT_PROJECT_ID, projectId: "1", // 기본 프로젝트 ID를 1로 설정
channelId: import.meta.env.VITE_DEFAULT_CHANNEL_ID, theme: "system",
theme: "light",
setProjectId: (projectId) => set({ projectId }), setProjectId: (projectId) => set({ projectId }),
setChannelId: (channelId) => set({ channelId }),
setTheme: (theme) => set({ theme }), setTheme: (theme) => set({ theme }),
}), }),
{ {

View File

@@ -1,31 +1,29 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "target": "ES2022",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"], "lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true, "skipLibCheck": true,
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
/* Linting */ /* Linting */
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": [ "@/*": ["./src/*"]
"./src/*" }
] },
} "include": ["src"]
}, }
"include": ["src"]
}

View File

@@ -1,36 +1,36 @@
import path from "path"; import path from "node:path";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import { defineConfig, loadEnv } from "vite"; import { defineConfig, loadEnv } from "vite";
import tailwindcss from "tailwindcss"; import tailwindcss from "tailwindcss";
import autoprefixer from "autoprefixer"; import autoprefixer from "autoprefixer";
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), ""); const env = loadEnv(mode, process.cwd(), "");
return { return {
plugins: [react()], plugins: [react()],
css: { css: {
postcss: { postcss: {
plugins: [tailwindcss, autoprefixer], plugins: [tailwindcss, autoprefixer],
}, },
}, },
resolve: { resolve: {
alias: { alias: {
"@": path.resolve(__dirname, "./src"), "@": path.resolve(__dirname, "./src"),
}, },
}, },
server: { server: {
proxy: { proxy: {
// 나머지 /api 경로 처리 // 나머지 /api 경로 처리
"/api": { "/api": {
target: env.VITE_API_PROXY_TARGET, target: env.VITE_API_PROXY_TARGET,
changeOrigin: true, changeOrigin: true,
configure: (proxy, _options) => { configure: (proxy, _options) => {
proxy.on("proxyReq", (proxyReq, _req, _res) => { proxy.on("proxyReq", (proxyReq, _req, _res) => {
proxyReq.setHeader("X-Api-Key", env.VITE_API_KEY); proxyReq.setHeader("X-Api-Key", env.VITE_API_KEY);
proxyReq.removeHeader("cookie"); proxyReq.removeHeader("cookie");
}); });
} },
} },
} },
} },
}; };
}); });

View File

@@ -1,4 +1,4 @@
import path from "path"; import path from "node:path";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import { defineConfig, loadEnv } from "vite"; import { defineConfig, loadEnv } from "vite";
import tailwindcss from "tailwindcss"; import tailwindcss from "tailwindcss";
@@ -35,4 +35,4 @@ export default defineConfig(({ mode }) => {
}, },
}, },
}; };
}); });