import * as react from 'react'; import { ComponentType, ReactNode, CSSProperties, ComponentProps } from 'react'; import { LucideIcon } from 'lucide-react'; import * as SwitchPrimitive from '@radix-ui/react-switch'; import * as PopoverPrimitive from '@radix-ui/react-popover'; import { DayPicker } from 'react-day-picker'; import { RowSelectionState, OnChangeFn } from '@tanstack/react-table'; export { RowSelectionState } from '@tanstack/react-table'; import * as TabsPrimitive from '@radix-ui/react-tabs'; import * as class_variance_authority_types from 'class-variance-authority/types'; import { VariantProps } from 'class-variance-authority'; import * as DialogPrimitive from '@radix-ui/react-dialog'; import * as AvatarPrimitive from '@radix-ui/react-avatar'; import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; import * as SeparatorPrimitive from '@radix-ui/react-separator'; import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; import * as TooltipPrimitive from '@radix-ui/react-tooltip'; import { ClassValue } from 'clsx'; /** * 공통 관리자 모듈에서 사용하는 도메인 비종속 타입 정의. * 데이터 구조는 모두 props로 주입받는 것을 전제로 한다. */ /** * 사이드 메뉴 항목 하나를 표현한다. * - items가 있으면 하위 메뉴를 가진 그룹 항목으로 동작한다. */ interface NavItem { /** 메뉴에 표시할 라벨 */ title: string; /** 이동 경로. 하위 메뉴만 있는 그룹 항목이면 생략 가능 */ href?: string; /** lucide-react 아이콘 컴포넌트 */ icon?: LucideIcon; /** 우측에 표시할 배지 텍스트(예: 알림 개수) */ badge?: string | number; /** 하위 메뉴 항목 */ items?: NavItem[]; /** true 이면 정확히 일치할 때만 활성화 (기본은 prefix 매칭) */ exact?: boolean; /** 비활성화 여부 */ disabled?: boolean; } /** * 라벨로 묶인 메뉴 섹션 그룹 (예: "운영", "설정"). * 그룹은 클릭되지 않는 정적 섹션 구분용 라벨이다. (아이콘 없음) * 클릭하면 펼쳐지는 대메뉴가 필요하면 NavGroup 대신 items 를 가진 NavItem 을 사용한다. */ interface NavGroup { /** 그룹 헤더 라벨. 생략하면 라벨 없는 그룹 */ title?: string; items: NavItem[]; } /** * 사이드바 색상 테마. 프로젝트별 컬러를 인스턴스 단위로 주입하기 위한 설정. * 각 값은 CSS 색상 문자열이면 무엇이든 가능하다. (예: "#1e293b", "oklch(0.2 0 0)", "rgb(30 41 59)") * 지정하지 않은 값은 전역 CSS 변수(--sidebar*)의 기본값을 그대로 사용한다. */ interface SidebarTheme { /** 사이드바 배경색 (--sidebar) */ background?: string; /** 사이드바 기본 글자색 (--sidebar-foreground) */ foreground?: string; /** 활성/호버 항목 배경색 (--sidebar-accent) */ accent?: string; /** 활성/호버 항목 글자색 (--sidebar-accent-foreground) */ accentForeground?: string; /** 구분선/테두리 색 (--sidebar-border) */ border?: string; /** 강조 색 (--sidebar-primary) */ primary?: string; /** 강조 색 위 글자색 (--sidebar-primary-foreground) */ primaryForeground?: string; /** 포커스 링 색 (--sidebar-ring) */ ring?: string; } /** * 헤더 색상 테마. 프로젝트별 컬러를 인스턴스 단위로 주입하기 위한 설정. * 각 값은 CSS 색상 문자열이면 무엇이든 가능하다. (예: "#1e293b", "oklch(0.2 0 0)", "rgb(30 41 59)") * 지정하지 않은 값은 기본 헤더 색상(bg-muted/60)을 그대로 사용한다. */ interface HeaderTheme { /** 헤더 배경색 */ background?: string; /** 헤더 기본 글자색 */ foreground?: string; /** 헤더 하단 테두리 색 */ border?: string; } /** 헤더/사용자 메뉴에 표시할 로그인 사용자 정보 */ interface AdminUser { name: string; email?: string; /** 프로필 이미지 URL */ avatarUrl?: string; /** 권한/직책 라벨 */ role?: string; } /** * 라우팅 링크 컴포넌트 주입용 타입. * 기본은 이며, Next.js 프로젝트에서는 next/link 의 Link 를 주입한다. */ type LinkComponent = ComponentType<{ href: string; className?: string; children?: ReactNode; } & Record>; interface AdminContainerProps { /** 최대 너비 (px 또는 CSS 값). 모바일 웹뷰 너비 제한 시 사용. */ maxWidth?: number | string; /** 컨테이너 클래스 */ containerClassName?: string; /** 사이드 메뉴 구성. 없으면 메뉴/헤더를 렌더하지 않는다. */ nav?: NavItem[] | NavGroup[]; /** 현재 활성 경로 (Next.js: usePathname()) */ pathname?: string; /** 라우팅 링크 컴포넌트 */ LinkComponent?: LinkComponent; /** 사이드바 로고/브랜드 영역 */ logo?: ReactNode | ((collapsed: boolean) => ReactNode); /** 사이드바 하단 고정 영역 */ sidebarFooter?: ReactNode; /** 사이드바 색상 테마 */ sidebarTheme?: SidebarTheme; /** 헤더 타이틀 */ title?: ReactNode; /** 로그인 사용자 정보 */ user?: AdminUser; /** 헤더 우측 추가 액션 */ headerActions?: ReactNode; /** 로그아웃 콜백 */ onLogout?: () => void; /** 사용자 드롭다운 커스텀 항목 */ userMenuItems?: ReactNode; /** 기본 "프로필" 메뉴 클릭 시 이동할 경로 */ profileHref?: string; /** 헤더 색상 테마 */ headerTheme?: HeaderTheme; /** 기본 헤더 대신 완전히 커스텀한 헤더 */ header?: ReactNode; /** 초기 사이드바 접힘 상태 */ defaultCollapsed?: boolean; /** 본문 영역 클래스 */ contentClassName?: string; children: ReactNode; } declare function AdminContainer({ maxWidth, containerClassName, nav, pathname, LinkComponent, logo, sidebarFooter, sidebarTheme, title, user, headerActions, onLogout, userMenuItems, profileHref, headerTheme, header, defaultCollapsed, contentClassName, children, }: AdminContainerProps): react.JSX.Element; interface AdminLayoutProps { /** * 레이아웃 모드. * - `"desktop"` : 사이드바 + 헤더 (PC 관리자 페이지) * - `"mobile"` : 헤더 + 햄버거 Drawer (모바일 웹뷰) * - 미지정 : 뷰포트 너비에 따라 자동 전환 (반응형) */ type?: "desktop" | "mobile"; /** 사이드 메뉴 구성 */ nav: NavItem[] | NavGroup[]; /** 현재 활성 경로 (Next.js: usePathname()) */ pathname?: string; /** 라우팅 링크 컴포넌트 (Next.js: next/link 의 Link, 기본: ) */ LinkComponent?: LinkComponent; /** * 사이드바 상단 로고/브랜드 영역. * 함수를 넘기면 접힘 상태(collapsed)를 받아 다르게 렌더링할 수 있다. */ logo?: ReactNode | ((collapsed: boolean) => ReactNode); /** 사이드바 하단 고정 영역 */ sidebarFooter?: ReactNode; /** 사이드바 색상 테마 */ sidebarTheme?: SidebarTheme; /** 헤더 타이틀 */ title?: ReactNode; /** 로그인 사용자 정보 */ user?: AdminUser; /** 헤더 우측 추가 액션 */ headerActions?: ReactNode; /** 로그아웃 콜백 */ onLogout?: () => void; /** 사용자 드롭다운 커스텀 항목 */ userMenuItems?: ReactNode; /** 기본 "프로필" 메뉴 클릭 시 이동할 경로 */ profileHref?: string; /** 헤더 색상 테마 */ headerTheme?: HeaderTheme; /** 기본 AdminHeader 대신 완전히 커스텀한 헤더를 쓸 때 */ header?: ReactNode; /** 초기 사이드바 접힘 상태 (데스크탑 전용) */ defaultCollapsed?: boolean; /** 본문 영역 컨테이너 클래스 */ contentClassName?: string; children: ReactNode; } declare function AdminLayout({ type, nav, pathname, LinkComponent, logo, sidebarFooter, sidebarTheme, title, user, headerActions, onLogout, userMenuItems, profileHref, headerTheme, header, defaultCollapsed, contentClassName, children, }: AdminLayoutProps): react.JSX.Element; interface MobileContainerProps { children: ReactNode; /** 최대 너비 (px 또는 CSS 값). 기본 430px */ maxWidth?: number | string; className?: string; } declare function MobileContainer({ children, maxWidth, className, }: MobileContainerProps): react.JSX.Element; interface AdminHeaderProps { /** 헤더 좌측 타이틀 (페이지명 등) */ title?: ReactNode; /** 로그인 사용자 정보. 있으면 우측에 사용자 메뉴를 표시한다 */ user?: AdminUser; /** 사용자 메뉴 우측에 추가로 배치할 액션 (알림, 테마 토글 등) */ actions?: ReactNode; /** 로그아웃 클릭 콜백 */ onLogout?: () => void; /** 사용자 드롭다운에 추가할 커스텀 메뉴 항목. 지정하지 않으면 profileHref 로 이동하는 기본 "프로필" 항목을 보여준다 */ userMenuItems?: ReactNode; /** 기본 "프로필" 메뉴 클릭 시 이동할 경로. userMenuItems 를 직접 넘기면 무시된다 */ profileHref?: string; /** 라우팅 링크 컴포넌트 (Next.js: next/link 의 Link, 기본: ) */ LinkComponent?: LinkComponent; /** 헤더 색상 테마. 지정하지 않으면 기본 색상(bg-muted/60)을 사용한다 */ theme?: HeaderTheme; className?: string; } declare function AdminHeader({ title, user, actions, onLogout, userMenuItems, profileHref, LinkComponent, theme, className, }: AdminHeaderProps): react.JSX.Element; /** * SidebarTheme 값을 사이드바 루트에 적용할 CSS 변수 스타일로 변환한다. * 지정한 값만 덮어쓰고, 나머지는 전역 --sidebar* 기본값을 사용한다. */ declare function sidebarThemeToStyle(theme?: SidebarTheme): CSSProperties; interface AdminSidebarProps { /** 메뉴 구성. 평면 항목 배열 또는 그룹 배열 */ nav: NavItem[] | NavGroup[]; /** 현재 활성 경로 (Next.js: usePathname()) */ pathname?: string; /** 라우팅 링크 컴포넌트 (기본: ) */ LinkComponent?: LinkComponent; /** * 상단 로고/브랜드 영역. * 함수를 넘기면 접힘 상태(collapsed)를 인자로 받아 다르게 렌더링할 수 있다. * (예: 펼침=풀네임, 접힘=아이콘/이니셜) */ logo?: ReactNode | ((collapsed: boolean) => ReactNode); /** 하단 고정 영역 (예: 버전, 사용자 요약) */ footer?: ReactNode; /** 아이콘 전용 축소 모드 */ collapsed?: boolean; /** 메뉴 클릭(이동) 시 콜백. 모바일에서 Drawer 닫기 등에 사용 */ onNavigate?: () => void; /** 사이드바 색상 테마 (프로젝트별 컬러 주입) */ theme?: SidebarTheme; className?: string; style?: CSSProperties; } declare function AdminSidebar({ nav, pathname, LinkComponent, logo, footer, collapsed, onNavigate, theme, className, style, }: AdminSidebarProps): react.JSX.Element; interface AdminLayoutContextValue { collapsed: boolean; setCollapsed: (v: boolean) => void; toggleCollapsed: () => void; mobileOpen: boolean; setMobileOpen: (v: boolean) => void; isMobile: boolean; } declare function useAdminLayout(): AdminLayoutContextValue; declare function AdminLayoutProvider({ value, children, }: { value: AdminLayoutContextValue; children: ReactNode; }): react.JSX.Element; interface AdminThemeProviderProps { /** 주조색. 버튼/체크박스/배지/툴팁 등 포인트 색 (CSS 색상 문자열) */ primary?: string; /** 주조색 위 글자색 (기본은 흰색 계열 유지) */ primaryForeground?: string; /** 포커스 링 색. 주조색과 맞추고 싶을 때 지정 */ ring?: string; /** 추가로 덮어쓸 임의의 CSS 변수 (예: { "--radius": "0.5rem" }) */ vars?: Record; children: ReactNode; } declare function AdminThemeProvider({ primary, primaryForeground, ring, vars, children, }: AdminThemeProviderProps): react.JSX.Element; interface AdminPageHeaderProps { /** 페이지명 */ title: ReactNode; /** 타이틀 아래 보조 설명 */ description?: ReactNode; /** 우측 정렬 액션 영역 (등록/수정 버튼 등) */ actions?: ReactNode; /** 타이틀 위 브레드크럼/뒤로가기 등 */ breadcrumb?: ReactNode; className?: string; } declare function AdminPageHeader({ title, description, actions, breadcrumb, className, }: AdminPageHeaderProps): react.JSX.Element; interface AdminPageProps extends AdminPageHeaderProps { children: ReactNode; /** 헤더와 본문을 감싸는 바깥 컨테이너 클래스 */ className?: string; /** 본문 영역 클래스 */ contentClassName?: string; } declare function AdminPage({ title, description, actions, breadcrumb, className, contentClassName, children, }: AdminPageProps): react.JSX.Element; /** * 공통 포맷 유틸리티 * * 표 컬럼의 type 으로도 쓰이지만, 독립 함수라 어디서든 import 해서 재사용할 수 있다. * * @example * import { comma, tel, bizNum } from "@linkup/admin-ui"; * comma(1234567) // "1,234,567" * tel("01012345678") // "010-1234-5678" * bizNum("1234567890") // "123-45-67890" */ type Numeric = number | string | null | undefined; /** 숫자에 천 단위 콤마를 넣는다. (소수점은 유지) */ declare function comma(value: Numeric): string; /** 전화번호에 하이픈을 넣는다. (휴대폰 · 서울(02) · 지역번호 · 대표번호 대응) */ declare function tel(value: Numeric): string; /** 사업자등록번호에 하이픈을 넣는다. (10자리 → XXX-XX-XXXXX) */ declare function bizNum(value: Numeric): string; /** 날짜만 "YYYY-MM-DD" 로 포맷한다. (시간은 버림) */ declare function formatDate(value: unknown): string; /** 날짜+시간을 "YYYY-MM-DD HH:mm:ss" 로 포맷한다. */ declare function formatDateTime(value: unknown): string; /** 표 컬럼 type 에서 사용할 수 있는 포맷 종류 */ type ColumnFormatType = "comma" | "tel" | "bizNum" | "date" | "datetime"; /** type 값에 맞는 포맷 함수를 적용한다. (AdminTable 내부에서 사용) */ declare function formatByType(type: ColumnFormatType, value: unknown): string; type SwitchSize = "sm" | "md" | "lg" | "xl" | "xxl"; interface SwitchProps extends ComponentProps { /** 크기 (기본 md) */ size?: SwitchSize; } declare function Switch({ className, size, ...props }: SwitchProps): react.JSX.Element; interface AdminSelectOption { value: string; label: string; /** 옵션 앞에 표시할 이미지 URL (상품 선택 등) */ image?: string; /** 라벨 아래 보조 설명 (가격/코드 등) */ description?: string; disabled?: boolean; } interface AdminSelectProps { options: AdminSelectOption[]; value?: string; onChange?: (value: string) => void; placeholder?: string; /** 검색(콤보박스) 사용 (기본 true) */ searchable?: boolean; /** 선택 취소(X) 버튼 (기본 true) */ clearable?: boolean; disabled?: boolean; /** 검색 결과 없을 때 문구 */ emptyText?: string; /** 콤보박스 열렸을 때 입력 placeholder */ searchPlaceholder?: string; id?: string; /** 트리거 클래스 (너비 등) */ className?: string; /** 드롭다운 패널 클래스 */ contentClassName?: string; /** 트리거에서 포커스가 빠질 때 (AdminFormField 의 touched 검증 트리거용) */ onBlur?: () => void; } /** ref 로 노출되는 명령형 핸들 — 외부에서 강제로 포커스(+ 메뉴 열기)를 트리거할 때 사용 */ interface AdminSelectHandle { focus: () => void; } declare const AdminSelect: react.ForwardRefExoticComponent>; interface AdminImageUploadProps { /** 이미지 URL 배열 */ value?: string[]; onChange?: (urls: string[]) => void; /** 최대 개수 (기본 1) */ maxCount?: number; /** accept 속성 (기본 "image/*") */ accept?: string; disabled?: boolean; /** 업로드 처리(예: 서버 업로드). 주면 반환 URL 을 value 에 넣는다. 없으면 data URL */ onUpload?: (file: File) => Promise; /** * 파일이 선택/드롭된 직후(업로드 전) 원본 File 목록을 받는다. * onUpload 없이 미리보기만 해두고, 저장 시점에 이 File 을 직접 업로드하는 흐름에 사용한다. */ onFilesAdded?: (files: File[]) => void; /** 썸네일 한 변 크기 px (기본 96) */ size?: number; /** 썸네일 클릭 시 확대(라이트박스)로 보기 (기본 true) */ preview?: boolean; className?: string; } /** ref 로 노출되는 명령형 핸들 — 외부에서 강제로 포커스를 트리거할 때 사용 */ interface AdminImageUploadHandle { focus: () => void; } declare const AdminImageUpload: react.ForwardRefExoticComponent>; type AdminFormFieldType = "text" | "email" | "password" | "number" | "textarea" | "select" | "switch" | "upload" | ColumnFormatType; /** 한 필드의 검증 규칙 (useAdminForm 등에서 사용) */ interface FieldRule { type?: AdminFormFieldType; required?: boolean; validate?: (value: string) => string | undefined; } /** * 규칙과 값으로 오류 메시지를 계산한다. (타이밍과 무관한 순수 검증 — 제출 검증용) * 순서: 필수 빈 값 → 커스텀 validate → 내장 type 검증 */ declare function getFieldError(rule: FieldRule, value: string | number | undefined): string | undefined; interface AdminFormFieldProps { /** 필드 라벨 */ label?: ReactNode; /** 라벨과 연결할 컨트롤 id */ htmlFor?: string; /** 필수 표시(*) */ required?: boolean; /** 입력 아래 도움말 */ description?: ReactNode; /** 에러 메시지 (있으면 빨간색으로 표시되고 description 을 대체) */ error?: ReactNode; /** 라벨 위치 (기본 vertical=위) */ orientation?: "vertical" | "horizontal"; /** horizontal 일 때 라벨 너비 클래스 (기본 sm:w-28) */ labelClassName?: string; className?: string; /** 자동 생성할 입력 종류 (children 미지정 시) */ type?: AdminFormFieldType; /** 자동 입력의 값 */ value?: string | number; /** 자동 입력의 변경 콜백 (comma 는 숫자만 추출한 문자열을 돌려준다) */ onChange?: (value: string) => void; /** 자동 입력의 placeholder */ placeholder?: string; /** * 커스텀 검증. 값을 받아 오류 메시지(문자열)를 반환하면 그 메시지가 표시되고, * 통과하면 undefined 를 반환한다. (예: 비밀번호 규칙처럼 서비스마다 다른 조건) * required 빈 값 검사 이후, 내장 type 검증보다 우선 적용된다. * @example validate={(v) => v.length < 8 ? "8자 이상 입력하세요." : undefined} */ validate?: (value: string) => string | undefined; /** * 제출(저장) 시점에 부모가 true 로 켜면 필수 누락 등 모든 오류를 즉시 표시한다. * 평소(false)에는 blur 이후 형식 오류만 표시하고, 필수 빈 값 오류는 띄우지 않는다. */ forceValidate?: boolean; /** 자동 입력에 추가로 전달할 input 속성 (type 이 "textarea" | "select" 가 아닐 때) */ inputProps?: Omit, "value" | "onChange" | "type" | "id" | "placeholder">; /** type="textarea" 일 때 표시할 줄 수 (기본 3) */ rows?: number; /** type="select" 일 때 사용할 옵션 목록 */ options?: AdminSelectOption[]; /** type="select" 일 때 AdminSelect 에 추가로 전달할 속성 (searchable, clearable 등) */ selectProps?: Omit; /** type="switch" 일 때 Switch 에 추가로 전달할 속성 (size 등) */ switchProps?: Omit; /** * type="upload" 일 때 AdminImageUpload 에 추가로 전달할 속성 (accept, onUpload, onFilesAdded 등). * value/onChange 는 이미지 1장(문자열 URL)만 다룬다 — 여러 장이 필요하면 AdminImageUpload 를 직접 사용한다. */ uploadProps?: Omit; /** * 에러가 뜰 때 이 필드로 포커스를 옮길지 여부 (기본 true). * 단독 사용 시엔 항상 자기 자신의 에러에 반응해 포커싱한다. * `useAdminForm.field()` 로 여러 필드를 한 폼에서 쓸 때는, 제출 시 여러 필드가 * 동시에 invalid 가 될 수 있어 폼이 "가장 먼저 있는 오류 필드"만 true 로 지정해준다 * (그 외 필드는 false 로 지정되어 포커스를 가져가지 않는다) — 별도 설정 없이 자동 적용됨. */ autoFocusOnError?: boolean; /** * (내부용) `useAdminForm` 이 validate() 를 호출할 때마다 증가시키는 값. * 같은 필드가 여전히 invalid 인 상태에서 제출을 다시 눌러도 재포커싱되도록 트리거한다. */ focusToken?: number; /** 커스텀 컨트롤 (주면 type 자동 생성 대신 이걸 쓴다) */ children?: ReactNode; } declare function AdminFormField({ label, htmlFor, required, description, error, orientation, labelClassName, className, type, value, onChange, placeholder, validate, forceValidate, inputProps, rows, options, selectProps, switchProps, uploadProps, autoFocusOnError, focusToken, children, }: AdminFormFieldProps): react.JSX.Element; declare function useAdminForm>(initial: { [K in keyof T]: string; }): { values: { [K in keyof T]: string; }; setValues: react.Dispatch>; submitted: boolean; setSubmitted: react.Dispatch>; field: (name: keyof T, rule?: FieldRule) => { value: { [K in keyof T]: string; }[keyof T]; onChange: (v: string) => void; type: AdminFormFieldType | undefined; required: boolean | undefined; validate: ((value: string) => string | undefined) | undefined; forceValidate: boolean; autoFocusOnError: boolean; focusToken: number; }; errorOf: (name: keyof T) => string | undefined; validate: () => boolean; reset: (source?: Partial> | null) => void; }; /** useAdminForm() 반환 타입. 단계별로 나뉜 폼 하위 컴포넌트에 form 을 props 로 넘길 때 사용한다. */ type UseAdminFormReturn> = ReturnType>; declare function Popover(props: ComponentProps): react.JSX.Element; declare function PopoverTrigger(props: ComponentProps): react.JSX.Element; declare function PopoverAnchor(props: ComponentProps): react.JSX.Element; declare function PopoverContent({ className, align, sideOffset, ...props }: ComponentProps): react.JSX.Element; type AdminInputSize = "sm" | "default" | "lg"; interface AdminInputProps extends Omit, "onChange" | "size"> { /** * 레이아웃 방식 * - "stacked"(기본): 라벨 위, input 아래 * - "inline": 라벨 왼쪽, input 오른쪽 */ layout?: "stacked" | "inline"; /** 입력 크기: "sm" | "default"(기본) | "lg" */ size?: AdminInputSize; /** 필드 라벨 */ label?: ReactNode; /** inline 일 때 라벨 너비 클래스 (기본 w-24) */ labelClassName?: string; /** 입력값 변경 콜백 */ onChange?: (value: string) => void; /** 값이 있을 때 X 버튼으로 초기화 */ clearable?: boolean; /** 입력 아래 도움말 / 에러 메시지 */ hint?: ReactNode; /** hint 를 에러(빨간색)로 표시 */ error?: boolean; className?: string; /** * useAdminForm().field() 의 반환값을 그대로 스프레드해서 쓸 수 있도록 받아두는 값. * AdminInput 은 검증 UI를 그리지 않으므로 실제로는 사용하지 않는다 (필요하면 AdminFormField 사용). */ validate?: (value: string) => string | undefined; forceValidate?: boolean; /** useAdminForm().field() 반환값 스프레드용 — AdminInput 은 자동 포커싱을 지원하지 않으므로 사용하지 않는다. */ autoFocusOnError?: boolean; /** useAdminForm().field() 반환값 스프레드용 — 위와 동일한 이유로 사용하지 않는다. */ focusToken?: number; } declare function AdminInput({ layout, size, label, labelClassName, onChange, clearable, hint, error, className, id: idProp, type, value, validate: _validate, forceValidate: _forceValidate, autoFocusOnError: _autoFocusOnError, focusToken: _focusToken, ...inputProps }: AdminInputProps): react.JSX.Element; interface AdminTextareaProps extends Omit, "onChange"> { /** * 레이아웃 방식 * - "stacked"(기본): 라벨 위, textarea 아래 * - "inline": 라벨 왼쪽, textarea 오른쪽 */ layout?: "stacked" | "inline"; /** 필드 라벨 */ label?: ReactNode; /** inline 일 때 라벨 너비 클래스 (기본 w-24) */ labelClassName?: string; /** 입력값 변경 콜백 */ onChange?: (value: string) => void; /** 입력 아래 도움말 / 에러 메시지 */ hint?: ReactNode; /** hint 를 에러(빨간색)로 표시 */ error?: boolean; className?: string; /** * useAdminForm().field() 의 반환값을 그대로 스프레드해서 쓸 수 있도록 받아두는 값. * AdminTextarea 는 검증 UI를 그리지 않으므로 실제로는 사용하지 않는다 (필요하면 AdminFormField 사용). */ validate?: (value: string) => string | undefined; forceValidate?: boolean; /** useAdminForm().field() 반환값 스프레드용 — AdminTextarea 는 자동 포커싱을 지원하지 않으므로 사용하지 않는다. */ autoFocusOnError?: boolean; /** useAdminForm().field() 반환값 스프레드용 — 위와 동일한 이유로 사용하지 않는다. */ focusToken?: number; } declare function AdminTextarea({ layout, label, labelClassName, onChange, hint, error, className, id: idProp, rows, value, validate: _validate, forceValidate: _forceValidate, autoFocusOnError: _autoFocusOnError, focusToken: _focusToken, ...textareaProps }: AdminTextareaProps): react.JSX.Element; interface AdminDateInputProps { /** "YYYY-MM-DD" 형식 날짜 문자열 */ value?: string; onChange?: (value: string) => void; /** 값이 없을 때 표시할 안내 문구 (기본 "날짜를 선택하세요") */ placeholder?: string; /** 선택 가능한 최솟값 ("YYYY-MM-DD") */ min?: string; /** 선택 가능한 최댓값 ("YYYY-MM-DD") */ max?: string; disabled?: boolean; /** 추가 CSS 클래스 */ className?: string; "aria-label"?: string; id?: string; } declare const AdminDateInput: react.ForwardRefExoticComponent>; interface AdminDatePickerProps { /** "YYYY-MM-DD" 날짜 문자열 */ value?: string; onChange?: (value: string) => void; /** 빈 값일 때 표시 문구 (기본 "날짜를 선택하세요") */ placeholder?: string; /** 선택 가능 최솟값 ("YYYY-MM-DD") */ min?: string; /** 선택 가능 최댓값 ("YYYY-MM-DD") */ max?: string; /** X 버튼으로 값 지우기 허용 (기본 false) */ clearable?: boolean; disabled?: boolean; className?: string; /** 입력창 너비 클래스 (기본 w-full) */ widthClassName?: string; id?: string; "aria-label"?: string; } declare function AdminDatePicker({ value, onChange, placeholder, min, max, clearable, disabled, className, widthClassName, id, "aria-label": ariaLabel, }: AdminDatePickerProps): react.JSX.Element; type CalendarProps = React.ComponentProps; declare function Calendar({ className, classNames, showOutsideDays, startMonth, endMonth, ...props }: CalendarProps): react.JSX.Element; interface AdminFilterProps { /** 필터 입력 필드들 (AdminFilterField 들을 넣는다) */ children: ReactNode; /** 검색(submit) 콜백 */ onSearch?: () => void; /** 초기화 콜백. 주어지면 초기화 버튼이 표시된다 */ onReset?: () => void; /** 필터 헤더 라벨 (기본 "검색 필터") */ title?: ReactNode; /** 접기/펴기 사용 여부 (기본 true) */ collapsible?: boolean; /** 초기 펼침 상태 (기본 true) */ defaultOpen?: boolean; /** 검색 버튼 라벨 (기본 "검색") */ searchLabel?: string; /** 초기화 버튼 라벨 (기본 "초기화") */ resetLabel?: string; /** 검색 중 비활성화/로딩 표시 */ loading?: boolean; /** 버튼 영역에 추가로 둘 액션 */ actions?: ReactNode; className?: string; } declare function AdminFilter({ children, onSearch, onReset, title, collapsible, defaultOpen, searchLabel, resetLabel, loading, actions, className, }: AdminFilterProps): react.JSX.Element; interface AdminFilterFieldProps { /** 필드 라벨 (왼쪽) */ label?: ReactNode; /** 라벨과 연결할 입력 요소 id */ htmlFor?: string; /** 컨트롤(오른쪽) */ children: ReactNode; /** 라벨 영역 너비 클래스 (기본 sm:w-32) */ labelClassName?: string; className?: string; } declare function AdminFilterField({ label, htmlFor, children, labelClassName, className, }: AdminFilterFieldProps): react.JSX.Element; interface KeywordTypeOption { value: string; label: string; } interface KeywordFilterProps { /** 필드 라벨 (기본 "검색어") */ label?: ReactNode; /** * 검색 유형 선택 옵션. 주면 키워드 입력 왼쪽에 select 가 표시된다. * (예: 이름/이메일/전화번호 중 무엇으로 검색할지) */ typeOptions?: KeywordTypeOption[]; /** 선택된 검색 유형 값 */ type?: string; onTypeChange?: (value: string) => void; /** 키워드 값 */ keyword: string; onKeywordChange: (value: string) => void; /** 입력창 placeholder (기본 "검색어 입력") */ placeholder?: string; /** 검색 유형 select 의 "전체" 옵션 라벨. null 이면 표시 안 함 (기본 "전체") */ allTypeLabel?: string | null; htmlFor?: string; className?: string; } declare function KeywordFilter({ label, typeOptions, type, onTypeChange, keyword, onKeywordChange, placeholder, allTypeLabel, htmlFor, className, }: KeywordFilterProps): react.JSX.Element; interface DateTypeOption { value: string; label: string; } interface DateRangePreset { label: string; /** 클릭 시 적용할 [시작일, 종료일] ("YYYY-MM-DD") 을 반환 */ getRange: () => [string, string]; } interface DateRangeFilterProps { /** 필드 라벨 (기본 "기간") */ label?: ReactNode; /** 날짜 유형 선택 옵션 (예: 가입일/수정일). 주면 좌측에 select 표시 */ typeOptions?: DateTypeOption[]; type?: string; onTypeChange?: (value: string) => void; /** 날짜 유형 select 의 "전체" 옵션 라벨. null 이면 표시 안 함 (기본 "전체") */ allTypeLabel?: string | null; startDate: string | null; endDate: string | null; onStartDateChange: (value: string) => void; onEndDateChange: (value: string) => void; /** * 빠른 기간 선택 버튼. * - true: 기본 프리셋(오늘/최근 7일/최근 30일/최근 3개월) * - 배열: 커스텀 프리셋 */ presets?: boolean | DateRangePreset[]; /** 시작일이 비었을 때 안내 문구 (기본 "날짜를 선택하세요") */ startPlaceholder?: string; /** 종료일이 비었을 때 안내 문구 (기본 "날짜를 선택하세요") */ endPlaceholder?: string; className?: string; } declare function DateRangeFilter({ label, typeOptions, type, onTypeChange, allTypeLabel, startDate, endDate, onStartDateChange, onEndDateChange, presets, startPlaceholder, endPlaceholder, className, }: DateRangeFilterProps): react.JSX.Element; interface AdminCheckboxFilterOption { value: string; label: ReactNode; disabled?: boolean; } interface AdminCheckboxFilterProps { /** 필드 라벨 */ label?: ReactNode; options: AdminCheckboxFilterOption[]; /** 선택된 값 배열 (기본 []) */ value?: string[]; onChange: (value: string[]) => void; /** 옵션 로딩 중 표시 (기본 false). 켜지면 옵션 대신 스켈레톤을 보여준다 */ loading?: boolean; /** 로딩 스켈레톤 개수 (기본 3) */ skeletonCount?: number; /** 옵션 id 접두사. 안 주면 인스턴스마다 useId()로 자동 생성되어 겹칠 일이 없다 */ htmlFor?: string; className?: string; } declare function AdminCheckboxFilter({ label, options, value, onChange, loading, skeletonCount, htmlFor, className, }: AdminCheckboxFilterProps): react.JSX.Element; interface AdminFilterFieldsCheckboxItem { type: "checkbox"; label?: ReactNode; options: AdminCheckboxFilterOption[]; /** 선택된 값 배열 (기본 []) */ value?: string[]; onChange: (value: string[]) => void; /** 옵션 로딩 중 표시 (기본 false) */ loading?: boolean; /** 로딩 스켈레톤 개수 (기본 3) */ skeletonCount?: number; htmlFor?: string; className?: string; } interface AdminFilterFieldsSelectItem { type: "select"; label?: ReactNode; options: AdminSelectOption[]; value?: string; onChange: (value: string) => void; placeholder?: string; /** 옵션 로딩 중 표시 (기본 false). 켜지면 select 대신 스켈레톤을 보여준다 */ loading?: boolean; htmlFor?: string; className?: string; /** AdminSelect 에 추가로 전달할 속성 (searchable, clearable 등) */ selectProps?: Omit; } type AdminFilterFieldsItem = AdminFilterFieldsCheckboxItem | AdminFilterFieldsSelectItem; interface AdminFilterFieldsProps { items: AdminFilterFieldsItem[]; className?: string; } declare function AdminFilterFields({ items, className }: AdminFilterFieldsProps): react.JSX.Element; interface PaginationType { page: number; pageSize: number; } interface SortType { sortKey?: string; sortType?: "ASC" | "DESC"; } interface BaseFilter { keyword: string; keywordType: string; options: Record; startDate: string | null; endDate: string | null; dateType: string; } interface UsePageSearchStateConfig { defaultPageSize?: number; _filters: BaseFilter; /** resetFilters() 호출 시 되돌아갈 정렬 상태. 기본은 정렬 없음({}) */ defaultSort?: SortType; params?: Record; } declare const usePageSearchState: (config: UsePageSearchStateConfig) => { pagination: PaginationType; sort: SortType; filters: BaseFilter; setPagination: react.Dispatch>; setFilters: react.Dispatch>; setSort: react.Dispatch>; setPaginationAndSync: (updater: PaginationType | ((prev: PaginationType) => PaginationType)) => void; setFiltersAndSync: (updater: BaseFilter | ((prev: BaseFilter) => BaseFilter)) => void; setSortAndSync: (updater: SortType | ((prev: SortType) => SortType)) => void; resetFilters: () => void; syncUrl: (nextPagination: PaginationType, nextFilters: BaseFilter, nextSort: SortType) => void; }; /** Ant Design 스타일 컬럼 정의 */ interface AdminColumn { /** 헤더 라벨 */ title: ReactNode; /** 표시할 데이터의 키 (없으면 render 로만 렌더링) */ dataIndex?: keyof TData; /** 컬럼 고유 키 (없으면 dataIndex 또는 인덱스로 자동 생성) */ key?: string; /** * 값 자동 포맷 종류. (render 가 없을 때 적용) * - "comma": 천 단위 콤마 (1234567 → 1,234,567) * - "tel": 전화번호 하이픈 (01012345678 → 010-1234-5678) * - "bizNum": 사업자등록번호 하이픈 (1234567890 → 123-45-67890) */ type?: ColumnFormatType; /** 컬럼 너비 (number=px) */ width?: number | string; /** 셀 정렬 (기본 left) */ align?: "left" | "center" | "right"; /** true 이면 헤더 클릭으로 정렬할 수 있다 (서버 정렬: onSortChange 로 전달) */ sortable?: boolean; /** 가로 스크롤 시 좌/우에 고정한다. (width 를 number 로 지정해야 정확히 동작) */ fixed?: "left" | "right"; /** true 이면 내용이 넘칠 때 "…" 로 말줄임한다 (width 지정 권장) */ ellipsis?: boolean; /** * 셀 커스텀 렌더링. value=해당 셀 값, record=행 데이터, index=행 번호 * value 타입은 any 다 — 컬럼마다 dataIndex 가 가리키는 필드 타입이 다른데, 이를 * `TData[keyof TData]` 유니온으로 만들면 TData 에 unknown 타입 필드가 하나만 있어도 * 유니온 전체가 unknown 으로 붕괴되어(예: address_json?: unknown) 모든 컬럼의 render 에서 * String(value) 같은 캐스팅을 강제하게 된다. antd 등 다른 테이블 라이브러리와 동일하게 any 로 둔다. */ render?: (value: any, record: TData, index: number) => ReactNode; /** * 값이 null/undefined/""일 때 대신 보여줄 내용. (render를 쓰면 적용되지 않는다) * 컬럼별로 다르게 하고 싶을 때만 지정 — 없으면 AdminTable의 emptyText(기본 "-")를 따른다. */ emptyText?: ReactNode; /** 셀(td) 추가 클래스 */ className?: string; /** 헤더(th) 추가 클래스 */ headerClassName?: string; } interface AdminTableProps { /** Ant Design 스타일 컬럼 정의 */ columns: AdminColumn[]; data: TData[]; /** 맨 앞에 체크박스(행 선택) 컬럼을 추가한다 */ enableRowSelection?: boolean; /** 행 선택 상태 (제어 컴포넌트). 생략하면 내부 상태로 동작 */ rowSelection?: RowSelectionState; onRowSelectionChange?: OnChangeFn; /** 행 고유 id 추출 (선택 상태 키로 사용). 권장. number를 반환해도 내부적으로 문자열로 변환된다 */ getRowId?: (row: TData, index: number) => string | number; /** 로딩 상태 표시 */ loading?: boolean; /** 데이터가 없을 때(행이 0개) 표시할 내용 */ emptyMessage?: ReactNode; /** * 셀 값이 null/undefined/""일 때 대신 보여줄 내용. (기본 "-") * render를 쓰는 컬럼에는 적용되지 않는다 — 그건 render가 전적으로 책임진다. */ emptyText?: ReactNode; /** * 정렬 상태 (제어형 서버 정렬). usePageSearchState의 sort와 동일한 타입이라 * 변환 없이 바로 넘길 수 있다. sortKey가 없으면 정렬 없음. */ sort?: SortType | null; /** * 정렬 변경 콜백. 오름차순 → 내림차순 → 해제 순으로 토글된다. * usePageSearchState의 setSortAndSync에 그대로 연결 가능하다 (예: onSortChange={setSortAndSync}). */ onSortChange?: (sort: SortType) => void; /** 행 클릭 콜백 */ onRowClick?: (row: TData) => void; /** * 표 본문의 최소 높이를 "행 개수" 기준으로 확보한다. * 행이 적거나 비어 있어도 높이가 유지되어 페이지 이동 시 레이아웃이 흔들리지 않는다. * (예: minRows={20} → 20행 분량의 최소 높이) */ minRows?: number; className?: string; /** * 표 맨 아래 합계/요약 행(tfoot, 위쪽 구분선만 있음). * 컬럼의 key(또는 dataIndex, 없으면 "col_인덱스")를 키로 사용해 원하는 컬럼 자리에만 * 내용을 넣을 수 있다. 지정하지 않은 컬럼은 빈 셀로 렌더링된다. */ footer?: Record; /** 툴바 좌측 요약 영역 (예: 총 N건, 선택 N건). 없으면 툴바 자체가 생략된다 */ summary?: ReactNode; /** 툴바 우측 액션 버튼 영역 (등록/삭제/엑셀 다운로드 등) */ toolbarActions?: ReactNode; toolbarClassName?: string; total?: number; page?: number; pageSize?: number; onPageChange?: (page: number) => void; /** 주어지면 "몇 개씩 보기" 선택이 표시된다 */ onPageSizeChange?: (size: number) => void; pageSizeOptions?: number[]; showJump?: boolean; paginationClassName?: string; } /** * rowSelection 객체에서 선택된 행 id 배열을 뽑는다. * (TanStack 의 RowSelectionState 는 선택된 행만 키로 가지므로 키 목록이 곧 선택 id 다.) * * @example * const ids = getSelectedRowIds(rowSelection); // ["3", "7"] * const rows = data.filter((d) => rowSelection[d.id]); // 선택된 행 객체 */ declare function getSelectedRowIds(rowSelection: RowSelectionState): string[]; declare function AdminTable({ columns, data, enableRowSelection, rowSelection: controlledSelection, onRowSelectionChange: controlledOnChange, getRowId, loading, emptyMessage, emptyText, sort, onSortChange, onRowClick, minRows, className, footer, summary, toolbarActions, toolbarClassName, total, page, pageSize, onPageChange, onPageSizeChange, pageSizeOptions, showJump, paginationClassName, }: AdminTableProps): react.JSX.Element; interface AdminTableToolbarProps { /** 왼쪽 요약/합계 영역 */ summary?: ReactNode; /** 오른쪽 버튼/액션 영역 */ actions?: ReactNode; className?: string; } declare function AdminTableToolbar({ summary, actions, className, }: AdminTableToolbarProps): react.JSX.Element; interface AdminPaginationProps { /** 현재 페이지 (1부터 시작) */ page: number; /** 페이지당 항목 수 */ pageSize: number; /** 전체 항목 수 */ total: number; /** 페이지 변경 콜백 */ onPageChange: (page: number) => void; /** page size 변경 콜백. 주어지면 "몇 개씩 보기" 선택이 표시된다 */ onPageSizeChange?: (size: number) => void; /** page size 후보 (기본 [10, 20, 50, 100]) */ pageSizeOptions?: number[]; /** "바로 이동" 입력 표시 (기본 true) */ showJump?: boolean; className?: string; } declare function AdminPagination({ page, pageSize, total, onPageChange, onPageSizeChange, pageSizeOptions, showJump, className, }: AdminPaginationProps): react.JSX.Element; type AdminModalSize = "sm" | "md" | "lg" | "xl" | "xxl" | "xxxl"; interface AdminModalProps { open: boolean; /** 모달이 닫힐 때 호출되는 콜백 (X/오버레이/ESC/취소 등 모든 닫힘에 공통) */ onClose: () => void; /** 모달 제목 */ title?: ReactNode; /** 제목 아래 보조 설명 */ description?: ReactNode; /** 본문 */ children?: ReactNode; /** 하단 버튼 영역 (취소/저장 등) */ footer?: ReactNode; /** 너비 (기본 md, xl=2xl, xxl=4xl, xxxl=6xl) */ size?: AdminModalSize; /** 오버레이 클릭/ESC 로 닫기 막기 */ disableClose?: boolean; /** 우상단 X 닫기 버튼 숨기기 */ hideCloseButton?: boolean; /** * 모달이 열릴 때 내부 첫 번째 포커스 가능한 요소(예: 폼의 첫 select)로 자동 포커스할지 여부. * 기본 false — 첫 필드가 검색형 select(AdminSelect)인 경우 포커스만으로 드롭다운이 * 자동으로 열려버리는 것을 막기 위한 기본값이다. 키보드 진입 편의를 위해 자동 포커스가 * 필요하면 true로 켠다. */ autoFocus?: boolean; className?: string; } declare function AdminModal({ open, onClose, title, description, children, footer, size, disableClose, hideCloseButton, autoFocus, className, }: AdminModalProps): react.JSX.Element; type AdminDrawerSide = "left" | "right" | "top" | "bottom"; type AdminDrawerSize = "sm" | "md" | "lg" | "xl"; interface AdminDrawerProps { open: boolean; onClose: () => void; /** 등장 방향 (기본 right) */ side?: AdminDrawerSide; /** 드로어 제목 */ title?: ReactNode; /** 제목 아래 보조 설명 */ description?: ReactNode; /** 본문 (길면 스크롤된다) */ children?: ReactNode; /** 하단 고정 버튼 영역 */ footer?: ReactNode; /** 너비 (좌/우 방향일 때 적용, 기본 md) */ size?: AdminDrawerSize; className?: string; } declare function AdminDrawer({ open, onClose, side, title, description, children, footer, size, className, }: AdminDrawerProps): react.JSX.Element; interface ConfirmDialogProps { open: boolean; onClose: () => void; /** 다이얼로그 제목 */ title?: ReactNode; /** 제목 아래 보조 설명 */ description?: ReactNode; /** 확인 버튼 라벨 (기본 "확인") */ confirmLabel?: string; /** 취소 버튼 라벨 (기본 "취소") */ cancelLabel?: string; /** 확인 버튼 클릭 시 실행 */ onConfirm: () => void; /** 확인 버튼 색상 (기본 "default" = 프라이머리 색) */ confirmVariant?: "default" | "destructive"; /** 확인 처리 중 여부 — 스피너 표시 + 처리 중엔 닫기(오버레이/ESC/취소) 막기 */ loading?: boolean; className?: string; } declare function ConfirmDialog({ open, onClose, title, description, confirmLabel, cancelLabel, onConfirm, confirmVariant, loading, className, }: ConfirmDialogProps): react.JSX.Element; /** * confirmToast — sonner 토스트 기반 확인/취소 액션 * * ConfirmDialog(모달)와 달리 open/onClose state 없이 함수 호출 한 번으로 띄운다 (toast.warning 과 동일한 감각). * sonner 의 가 이미 앱에 마운트돼 있어야 한다 (Toast 알림 기능과 동일한 전제). * * 반환값은 토스트 id — 부모 모달이 닫힐 때 같이 닫고 싶으면 toast.dismiss(id) 를 호출한다. * * @example * confirmToast({ * title: "제휴사를 삭제하시겠습니까?", * description: "삭제하면 되돌릴 수 없습니다.", * confirmLabel: "삭제", * onConfirm: () => deleteMutation.mutate(...), * }); * * @example // 부모 모달이 닫힐 때 같이 닫기 * const toastId = useRef(null); * toastId.current = confirmToast({ title: "...", onConfirm: handleDelete }); * // 부모 close 핸들러에서: * if (toastId.current != null) toast.dismiss(toastId.current); */ interface ConfirmToastOptions { /** 토스트 제목 */ title: ReactNode; /** 제목 아래 보조 설명 */ description?: ReactNode; /** 확인 버튼 라벨 (기본 "확인") */ confirmLabel?: string; /** 취소 버튼 라벨 (기본 "취소") */ cancelLabel?: string; /** 확인 버튼 클릭 시 실행 */ onConfirm: () => void; /** 취소 버튼 클릭 시 실행 (선택) */ onCancel?: () => void; /** 확인 버튼 색상 (기본 "default" = 프라이머리 색) */ confirmVariant?: "default" | "destructive"; /** 자동으로 닫히지 않는 시간(ms). 기본 Infinity(사용자가 버튼을 눌러야 닫힘) */ duration?: number; /** 화면 표시 위치 (기본 "top-center") */ position?: "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right"; } /** 반환값: 토스트 id (toast.dismiss(id) 로 직접 닫을 때 사용) */ declare function confirmToast(options: ConfirmToastOptions): string | number; /** * confirmModal — antd Modal.confirm 스타일의 확인 다이얼로그 * * ConfirmDialog(controlled 컴포넌트)와 달리 open/onClose state 관리 없이 함수 호출 한 번으로 띄운다. * 배경이 어두워지고(dim) 뒤 화면 클릭도 막히는 진짜 모달이다 — confirmToast(sonner 기반)와 달리 * sonner 에 의존하지 않는다. * * 내부적으로 document.body 에 임시 컨테이너를 만들어 필요할 때만 렌더링하고, 닫히면(애니메이션 끝난 뒤) * 스스로 언마운트해서 정리한다 — 앱 루트에 별도 Provider 를 마운트해둘 필요가 없다. * * @example * confirmModal({ * title: "제휴사를 삭제하시겠습니까?", * description: "삭제하면 되돌릴 수 없습니다.", * confirmLabel: "삭제", * onConfirm: () => deleteMutation.mutateAsync({ ... }), * }); */ interface ConfirmModalOptions { /** 다이얼로그 제목 */ title?: ReactNode; /** 제목 아래 보조 설명 */ description?: ReactNode; /** 확인 버튼 라벨 (기본 "확인") */ confirmLabel?: string; /** 취소 버튼 라벨 (기본 "취소") */ cancelLabel?: string; /** 확인 클릭 시 실행. Promise 를 반환하면 완료될 때까지 확인 버튼이 로딩 상태가 되고, 성공해야 닫힌다. */ onConfirm: () => void | Promise; /** 취소 클릭 시 실행 (선택) */ onCancel?: () => void; /** 확인 버튼 색상 (기본 "default" = 프라이머리 색) */ confirmVariant?: "default" | "destructive"; } declare function confirmModal(options: ConfirmModalOptions): void; type TabsVariant = "line" | "segment"; declare function Tabs({ className, variant, ...props }: ComponentProps & { variant?: TabsVariant; }): react.JSX.Element; declare function TabsList({ className, ...props }: ComponentProps): react.JSX.Element; declare function TabsTrigger({ className, ...props }: ComponentProps): react.JSX.Element; declare function TabsContent({ className, ...props }: ComponentProps): react.JSX.Element; interface AdminTabItem { value: string; label: ReactNode; content: ReactNode; disabled?: boolean; } interface AdminTabsProps { items: AdminTabItem[]; /** 탭 스타일 (기본 "line") */ type?: TabsVariant; /** 선택된 탭 값 (제어 컴포넌트로 쓸 때) */ value?: string; /** 초기 선택 탭 값 (비제어). 안 주면 첫 번째 탭 */ defaultValue?: string; onValueChange?: (value: string) => void; className?: string; listClassName?: string; contentClassName?: string; } declare function AdminTabs({ items, type, value, defaultValue, onValueChange, className, listClassName, contentClassName, }: AdminTabsProps): react.JSX.Element; type AdminStepStatus = "process" | "error"; type AdminStepsDirection = "horizontal" | "vertical"; type AdminStepsSize = "sm" | "md"; interface AdminStepItem { title: ReactNode; /** 제목 아래 보조 설명 */ description?: ReactNode; /** 완료된 단계라도 클릭 이동을 막는다 */ disabled?: boolean; } interface AdminStepsProps { items: AdminStepItem[]; /** 현재 단계 인덱스 (0부터 시작) */ current: number; /** 현재 단계의 상태. "error" 를 주면 현재 단계가 destructive 색으로 표시된다. (기본 "process") */ status?: AdminStepStatus; /** 완료된 단계를 클릭해 되돌아갈 수 있게 한다. 안 주면 클릭 이동 없이 표시만 한다. */ onChange?: (index: number) => void; /** 배치 방향 (기본 "horizontal") */ direction?: AdminStepsDirection; /** 크기 (기본 "md") */ size?: AdminStepsSize; className?: string; } declare function AdminSteps({ items, current, status, onChange, direction, size, className, }: AdminStepsProps): react.JSX.Element; type AdminWizardVariant = "steps" | "tabs"; interface AdminWizardItem { /** AdminTabs 로 렌더링될 때 사용하는 고유 key (tabs value 로도 쓰인다) */ key: string; title: ReactNode; /** variant="steps" 일 때만 표시되는 단계 설명 */ description?: ReactNode; /** 이 단계/탭에서 보여줄 콘텐츠 */ content: ReactNode; disabled?: boolean; } interface AdminWizardProps { items: AdminWizardItem[]; /** "steps"(기본): 이전/다음 버튼이 있는 순차 마법사. "tabs": 자유 이동 탭 */ variant?: AdminWizardVariant; /** 현재 단계/탭 인덱스 */ current: number; onChange: (index: number) => void; /** variant="steps" 일 때 이전 버튼 라벨 (기본 "이전") */ prevLabel?: ReactNode; /** variant="steps" 일 때 다음 버튼 라벨 (기본 "다음") */ nextLabel?: ReactNode; /** AdminSteps 크기 (variant="steps" 일 때만 적용, 기본 "md") */ size?: AdminStepsSize; className?: string; /** 콘텐츠 영역(각 단계/탭의 content)에 적용할 클래스 */ contentClassName?: string; /** * variant="steps" 일 때 기본 제공되는 이전/다음 버튼(하단 네비게이션)을 숨긴다. * 모달 footer 등 다른 위치에 직접 이전/다음 버튼을 두고 싶을 때 사용한다 (기본 false=보임). * 이 경우 이전/다음 버튼은 그냥 onChange(current - 1) / onChange(current + 1) 을 호출하면 된다. */ hideNav?: boolean; } declare function AdminWizard({ items, variant, current, onChange, prevLabel, nextLabel, size, className, contentClassName, hideNav, }: AdminWizardProps): react.JSX.Element; interface TransferItem { key: string; /** 기본(컬럼 미지정) 렌더용 제목 */ title?: string; /** 행 앞 이미지 URL (기본 렌더) */ image?: string; /** 제목 아래 보조 설명 (기본 렌더) */ description?: string; disabled?: boolean; /** 컬럼 모드에서 dataIndex 로 참조할 필드 */ [field: string]: unknown; } /** 표 형태 컬럼 정의 (columns 지정 시) */ interface TransferColumn { title: ReactNode; /** 표시할 필드명 (render 없으면 item[dataIndex]) */ dataIndex?: string; /** 너비 (number=px, 미지정 시 1fr) */ width?: number | string; /** 셀 커스텀 렌더 */ render?: (item: TransferItem) => ReactNode; } interface AdminTransferProps { dataSource: TransferItem[]; /** 우측(선택됨) 패널에 있는 항목 key 배열 */ targetKeys: string[]; onChange: (targetKeys: string[]) => void; /** [좌측, 우측] 패널 제목 (기본 ["목록", "선택됨"]) */ titles?: [ReactNode, ReactNode]; /** 검색 입력 표시 (기본 true) */ searchable?: boolean; searchPlaceholder?: string; /** 리스트 높이(px, 기본 280) */ listHeight?: number; /** 주면 패널별 클라이언트 페이지네이션 사용 */ pageSize?: number; /** 표 컬럼 정의. 주면 헤더에 컬럼명 + 행에 표 형태로 렌더링 */ columns?: TransferColumn[]; /** 항목 표시 커스터마이징 (columns 미지정 시, 기본: 이미지 + 제목/설명) */ render?: (item: TransferItem) => ReactNode; className?: string; } declare function AdminTransfer({ dataSource, targetKeys, onChange, titles, searchable, searchPlaceholder, listHeight, pageSize, columns, render, className, }: AdminTransferProps): react.JSX.Element; interface AdminImageProps extends Omit, "onClick" | "src"> { /** 이미지 URL (없으면 "이미지 없음" 표시) */ src?: string | null; alt?: string; /** 확대 시 보여줄 큰 이미지 URL (기본: src) */ previewSrc?: string; /** 클릭 확대 사용 (기본 true) */ preview?: boolean; /** * 마우스 오버 시 확대 아이콘 오버레이 표시 (기본 true). * 테이블 셀처럼 마우스가 자주 지나가는 좁은 영역에서는 false로 꺼서 * 스쳐 지나갈 때마다 오버레이가 반응하는 게 거슬리지 않게 할 수 있다. * (클릭 확대 자체는 preview 로 계속 켜져 있다 — 이 prop 은 hover 표시만 끈다) */ hoverOverlay?: boolean; /** 이미지가 없을 때 문구 (기본 "이미지 없음") */ noImageText?: string; /** 로드 실패 시 문구 (기본 "이미지를 불러올 수 없습니다") */ errorText?: string; } declare function AdminImage({ src, alt, previewSrc, preview, hoverOverlay, noImageText, errorText, className, ...props }: AdminImageProps): react.JSX.Element; interface AdminDescriptionItem { label: ReactNode; content: ReactNode; /** 차지할 열 수 (columns 까지). 기본 1 */ span?: number; /** * 라벨 옆에 빨간 * 표시 (입력 필드가 필수일 때). * 지정하지 않으면 content 엘리먼트의 required prop(true)을 자동으로 반영한다. */ required?: boolean; } interface AdminDescriptionsProps { /** 라벨/내용 목록. children을 대신 쓸 경우 생략 가능 */ items?: AdminDescriptionItem[]; /** 섹션 제목 (선택) */ title?: ReactNode; /** * items 대신 임의의 콘텐츠(AdminTable 등)를 넣고 싶을 때 사용. * 지정하면 items는 무시되고, title + 카드 테두리 안에 children을 그대로 보여준다. */ children?: ReactNode; /** * 배치 방식 (기본 "row") * - "row": 라벨(왼쪽) + 내용(오른쪽), 테두리 표 형태 * - "stacked": 작은 회색 라벨(위) + 내용(아래) 형태 */ layout?: "row" | "stacked"; /** 한 줄에 표시할 항목 수 (기본 1) */ columns?: number; /** 테두리형 (row 레이아웃에서만, 기본 true). false 면 구분선만 */ bordered?: boolean; /** 라벨 셀 최소 너비 (row 레이아웃, 기본 7rem) */ labelWidth?: string; className?: string; /** * true 면 내용을 흐리게(opacity) 하고 가운데에 스피너를 겹쳐 보여준다. * 레이아웃 높이가 유지되므로 상세 조회(fetch.isLoading) 중 화면이 흔들리지 않는다. */ loading?: boolean; } declare function AdminDescriptions({ items, title, layout, columns, bordered, labelWidth, className, loading, children, }: AdminDescriptionsProps): react.JSX.Element; type BadgeColor = "gray" | "primary" | "green" | "red" | "amber" | "yellow" | "blue"; type BadgeSize = "sm" | "md" | "lg"; interface BadgeProps extends ComponentProps<"span"> { /** 표시할 텍스트 (children 으로 대체 가능) */ text?: ReactNode; /** 프리셋 색상 또는 임의의 CSS 색상 문자열 */ color?: BadgeColor | (string & {}); /** 크기 (기본 md) */ size?: BadgeSize; } declare function Badge({ text, children, color, size, className, style, ...props }: BadgeProps): react.JSX.Element; declare const buttonVariants: (props?: ({ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined; size?: "default" | "sm" | "lg" | "icon" | null | undefined; } & class_variance_authority_types.ClassProp) | undefined) => string; declare function Button({ className, variant, size, asChild, loading, disabled, children, ...props }: ComponentProps<"button"> & VariantProps & { asChild?: boolean; /** true 이면 스피너를 표시하고 버튼을 자동 비활성화 */ loading?: boolean; }): react.JSX.Element; declare function Sheet(props: ComponentProps): react.JSX.Element; declare function SheetTrigger(props: ComponentProps): react.JSX.Element; declare function SheetClose(props: ComponentProps): react.JSX.Element; declare function SheetContent({ className, children, side, closeClassName, ...props }: ComponentProps & { side?: "top" | "right" | "bottom" | "left"; /** 닫기(X) 버튼에 추가할 클래스 (예: 색칠한 헤더 위에서 text-primary-foreground) */ closeClassName?: string; }): react.JSX.Element; declare function SheetHeader({ className, ...props }: ComponentProps<"div">): react.JSX.Element; declare function SheetFooter({ className, ...props }: ComponentProps<"div">): react.JSX.Element; declare function SheetTitle({ className, ...props }: ComponentProps): react.JSX.Element; declare function SheetDescription({ className, ...props }: ComponentProps): react.JSX.Element; declare function Avatar({ className, ...props }: ComponentProps): react.JSX.Element; declare function AvatarImage({ className, ...props }: ComponentProps): react.JSX.Element; declare function AvatarFallback({ className, ...props }: ComponentProps): react.JSX.Element; declare function Checkbox({ className, ...props }: ComponentProps): react.JSX.Element; declare function Dialog(props: ComponentProps): react.JSX.Element; declare function DialogTrigger(props: ComponentProps): react.JSX.Element; declare function DialogClose(props: ComponentProps): react.JSX.Element; declare function DialogContent({ className, children, showClose, ...props }: ComponentProps & { showClose?: boolean; }): react.JSX.Element; declare function DialogHeader({ className, ...props }: ComponentProps<"div">): react.JSX.Element; declare function DialogFooter({ className, ...props }: ComponentProps<"div">): react.JSX.Element; declare function DialogTitle({ className, ...props }: ComponentProps): react.JSX.Element; declare function DialogDescription({ className, ...props }: ComponentProps): react.JSX.Element; declare function Input({ className, type, ...props }: ComponentProps<"input">): react.JSX.Element; declare function Label({ className, ...props }: ComponentProps<"label">): react.JSX.Element; declare function Separator({ className, orientation, decorative, ...props }: ComponentProps): react.JSX.Element; declare function DropdownMenu(props: ComponentProps): react.JSX.Element; declare function DropdownMenuTrigger(props: ComponentProps): react.JSX.Element; declare function DropdownMenuContent({ className, sideOffset, ...props }: ComponentProps): react.JSX.Element; declare function DropdownMenuItem({ className, inset, variant, ...props }: ComponentProps & { inset?: boolean; variant?: "default" | "destructive"; }): react.JSX.Element; declare function DropdownMenuLabel({ className, inset, ...props }: ComponentProps & { inset?: boolean; }): react.JSX.Element; declare function DropdownMenuSeparator({ className, ...props }: ComponentProps): react.JSX.Element; declare function TooltipProvider({ delayDuration, ...props }: ComponentProps): react.JSX.Element; declare function Tooltip(props: ComponentProps): react.JSX.Element; declare function TooltipTrigger(props: ComponentProps): react.JSX.Element; declare function TooltipContent({ className, sideOffset, children, ...props }: ComponentProps): react.JSX.Element; declare function useIsMobile(breakpoint?: number): boolean; /** * useDirtyCheck — API에서 받은 원본 데이터와 현재 폼 값을 비교해 변경 여부를 감지 * * 값 비교는 타입에 관대하다 — useAdminForm 은 폼 값을 항상 string 으로 다루기 때문에 * (select 의 number 필드, JSON 객체 필드 등) 서버 원본과 타입이 달라도 값만 같으면 * 같은 것으로 취급한다. * - 원시값: number 3 ↔ string "3" 처럼 타입만 다르면 문자열로 변환해 비교한다. * - 객체/배열: 한쪽이 JSON.stringify 된 문자열이어도(예: address_json) 파싱해서 * 구조적으로(키 순서 무관) 비교한다. * * @example * // number 필드를 select 로 다뤄서 폼 값은 string 이어도 정상 비교된다 * // original.partner_key: number(3), form.values.partner_key: string("3") → 같은 값으로 판정 * const { isDirty } = useDirtyCheck(original, form.values); * * @example * // 기본 사용 — 수정 폼에서 변경 여부 감지 * // original 은 API 원본(더 많은 필드를 가진 타입)이어도 된다 — form.values 에 있는 키만 비교한다. * const { data: original } = useApiQuery({ queryKey: [...], url: "..." }); * const form = useAdminForm({ name: "", email: "" }); * * const { isDirty, dirtyFields, diff } = useDirtyCheck(original, form.values); * * // isDirty → true/false (하나라도 바뀌면 true) * // dirtyFields → { name: true, email: false } * // diff → { name: "새이름" } (바뀐 필드만 — PATCH 요청에 사용) * * @example * // PATCH — 변경된 필드만 전송 * const handleUpdate = () => { * if (!isDirty) return; * updateMutation.mutate({ url: `/users/${id}`, method: "patch", data: diff }); * }; * * @example * // 저장하지 않은 변경사항 경고 * * {isDirty &&

저장되지 않은 변경사항이 있습니다.

} * * @example * // normalizeEmpty: true — null / undefined / "" 을 동일하게 처리 * // API가 null을 반환하고 폼은 ""로 초기화할 때 유용 * const { isDirty } = useDirtyCheck(original, form.values, { normalizeEmpty: true }); */ type AnyObj = Record; interface DirtyCheckOptions { /** * null / undefined / ""를 모두 동일한 "빈 값"으로 처리한다. * API가 null을 반환하고 폼은 ""로 초기화하는 경우에 유용. * 기본값: false */ normalizeEmpty?: boolean; } interface DirtyCheckResult { /** 하나라도 변경된 필드가 있으면 true */ isDirty: boolean; /** 필드별 변경 여부 — UI에서 하이라이트 표시 등에 사용 */ dirtyFields: Partial>; /** 변경된 필드만 담은 객체 — PATCH 요청에 사용 */ diff: Partial; } /** * 순수 함수 버전 — React 없이 서버 또는 이벤트 핸들러에서 사용 가능 * * original 은 current 보다 필드가 많아도 된다 (예: API 원본 전체 타입 vs 폼에서 쓰는 일부 필드만 Pick 한 타입). * current 에 있는 키만 기준으로 비교하므로, 폼 모양에 맞춰 original 을 미리 추려낼 필요가 없다. */ declare function getDirtyCheck(original: Partial> | null | undefined, current: T, options?: DirtyCheckOptions): DirtyCheckResult; /** * useDirtyCheck — 원본과 현재 값을 비교해 변경 여부를 반환하는 훅 * original이 null/undefined이면 항상 isDirty=false (데이터 로딩 중 처리) */ declare function useDirtyCheck(original: Partial> | null | undefined, current: T, options?: DirtyCheckOptions): DirtyCheckResult; /** * cn — Tailwind 클래스 병합 유틸리티 * * clsx로 조건부 클래스를 합친 뒤 tailwind-merge로 중복/충돌 클래스를 정리한다. * (예: cn("px-2", isActive && "px-4") => "px-4") * * @example * cn("rounded border", isOpen && "bg-accent", className) */ declare function cn(...inputs: ClassValue[]): string; export { AdminCheckboxFilter, type AdminCheckboxFilterOption, type AdminCheckboxFilterProps, type AdminColumn, AdminContainer, type AdminContainerProps, AdminDateInput, type AdminDateInputProps, AdminDatePicker, type AdminDatePickerProps, type AdminDescriptionItem, AdminDescriptions, type AdminDescriptionsProps, AdminDrawer, type AdminDrawerProps, type AdminDrawerSide, type AdminDrawerSize, AdminFilter, AdminFilterField, type AdminFilterFieldProps, AdminFilterFields, type AdminFilterFieldsCheckboxItem, type AdminFilterFieldsItem, type AdminFilterFieldsProps, type AdminFilterFieldsSelectItem, type AdminFilterProps, AdminFormField, type AdminFormFieldProps, type AdminFormFieldType, AdminHeader, type AdminHeaderProps, AdminImage, type AdminImageProps, AdminImageUpload, type AdminImageUploadProps, AdminInput, type AdminInputProps, AdminLayout, type AdminLayoutProps, AdminLayoutProvider, AdminModal, type AdminModalProps, type AdminModalSize, AdminPage, AdminPageHeader, type AdminPageHeaderProps, type AdminPageProps, AdminPagination, type AdminPaginationProps, AdminSelect, type AdminSelectOption, type AdminSelectProps, AdminSidebar, type AdminSidebarProps, type AdminStepItem, type AdminStepStatus, AdminSteps, type AdminStepsDirection, type AdminStepsProps, type AdminStepsSize, type AdminTabItem, AdminTable, type AdminTableProps, AdminTableToolbar, type AdminTableToolbarProps, AdminTabs, type AdminTabsProps, AdminTextarea, type AdminTextareaProps, AdminThemeProvider, type AdminThemeProviderProps, AdminTransfer, type AdminTransferProps, type AdminUser, AdminWizard, type AdminWizardItem, type AdminWizardProps, type AdminWizardVariant, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeColor, type BadgeProps, type BadgeSize, Button, Calendar, type CalendarProps, Checkbox, type ColumnFormatType, ConfirmDialog, type ConfirmDialogProps, type ConfirmModalOptions, type ConfirmToastOptions, DateRangeFilter, type DateRangeFilterProps, type DateRangePreset, type DateTypeOption, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, type DirtyCheckOptions, type DirtyCheckResult, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, type FieldRule, type HeaderTheme, Input, KeywordFilter, type KeywordFilterProps, type KeywordTypeOption, Label, type LinkComponent, MobileContainer, type MobileContainerProps, type NavGroup, type NavItem, type BaseFilter as PageSearchBaseFilter, type PaginationType, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, type SidebarTheme, type SortType, Switch, type SwitchProps, type SwitchSize, Tabs, TabsContent, TabsList, TabsTrigger, type TabsVariant, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type TransferColumn, type TransferItem, type UseAdminFormReturn, type UsePageSearchStateConfig, bizNum, buttonVariants, cn, comma, confirmModal, confirmToast, formatByType, formatDate, formatDateTime, getDirtyCheck, getFieldError, getSelectedRowIds, sidebarThemeToStyle, tel, useAdminForm, useAdminLayout, useDirtyCheck, useIsMobile, usePageSearchState };