diff options
Diffstat (limited to 'components/ui/back-button.tsx')
| -rw-r--r-- | components/ui/back-button.tsx | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/components/ui/back-button.tsx b/components/ui/back-button.tsx new file mode 100644 index 00000000..364c0b96 --- /dev/null +++ b/components/ui/back-button.tsx @@ -0,0 +1,112 @@ +"use client" + +import * as React from "react" +import { useRouter, usePathname } from "next/navigation" +import { ArrowLeft } from "lucide-react" +import { Button } from "@/components/ui/button" +import { cn } from "@/lib/utils" + +/** + * BackButton 컴포넌트 - 세그먼트를 동적으로 제거하여 상위 경로로 이동 + * + * 사용 예시: + * + * // 기본 사용 (1개 세그먼트 제거) + * <BackButton>목록으로</BackButton> + * + * // 2개 세그먼트 제거 (/a/b/c/d -> /a/b) + * <BackButton segmentsToRemove={2}>상위 목록으로</BackButton> + * + * // 커스텀 경로로 이동 + * <BackButton customPath="/dashboard">대시보드로</BackButton> + * + * // 아이콘 없이 사용 + * <BackButton showIcon={false}>돌아가기</BackButton> + * + * // 커스텀 스타일링 + * <BackButton className="text-blue-600" variant="outline"> + * 이전 페이지 + * </BackButton> + */ + +interface BackButtonProps extends React.ComponentPropsWithoutRef<typeof Button> { + /** + * 제거할 세그먼트 개수 (기본값: 1) + * 예: segmentsToRemove=1이면 /a/b/c -> /a/b + * segmentsToRemove=2이면 /a/b/c -> /a + */ + segmentsToRemove?: number + + /** + * 버튼에 표시할 텍스트 (기본값: "목록으로") + */ + children?: React.ReactNode + + /** + * 아이콘을 표시할지 여부 (기본값: true) + */ + showIcon?: boolean + + /** + * 커스텀 경로를 지정할 경우 (segmentsToRemove 대신 사용) + */ + customPath?: string +} + +export const BackButton = React.forwardRef< + React.ElementRef<typeof Button>, + BackButtonProps +>(({ + segmentsToRemove = 1, + children = "Go Back", + showIcon = true, + customPath, + className, + onClick, + ...props +}, ref) => { + const router = useRouter() + const pathname = usePathname() + + const handleClick = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => { + // 커스텀 onClick이 있으면 먼저 실행 + if (onClick) { + onClick(event) + // preventDefault가 호출되었으면 기본 동작 중단 + if (event.defaultPrevented) { + return + } + } + + let targetPath: string + + if (customPath) { + targetPath = customPath + } else { + // 현재 경로에서 세그먼트 제거 + const segments = pathname.split('/').filter(Boolean) + const newSegments = segments.slice(0, -segmentsToRemove) + targetPath = newSegments.length > 0 ? `/${newSegments.join('/')}` : '/' + } + + router.push(targetPath) + }, [router, pathname, segmentsToRemove, customPath, onClick]) + + return ( + <Button + ref={ref} + variant="ghost" + className={cn( + "flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto", + className + )} + onClick={handleClick} + {...props} + > + {showIcon && <ArrowLeft className="mr-1 h-4 w-4" />} + <span>{children}</span> + </Button> + ) +}) + +BackButton.displayName = "BackButton"
\ No newline at end of file |
