Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions src/app/preview/button/page.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
'use client';

import { Button } from '@/components/ui/Button';
import { FloatingButton } from '@/components/ui/FloatingButton';
import {
ArrowUp,
Download,
Heart,
LogOut,
MessageCircle,
Plus,
Settings,
Share2,
Star,
} from 'lucide-react';
import React from 'react';
import React, { useCallback, useState } from 'react';

export default function ButtonExamples() {
const handleClick = () => {
alert('hiii');
};

return (
<div className="flex flex-col gap-8 bg-gray-50 p-8">
<div className="flex flex-col gap-8 bg-gray-50 p-8 pb-32">
{/* Default buttons with different variants */}
<div className="space-y-4">
<h2 className="mb-4 text-xl font-semibold">๊ธฐ๋ณธ ๋ฒ„ํŠผ ์Šคํƒ€์ผ</h2>
Expand All @@ -36,7 +40,6 @@ export default function ButtonExamples() {
</Button>
</div>
</div>

{/* Small buttons row */}
<div className="space-y-4">
<h2 className="mb-4 text-xl font-semibold">์ž‘์€ ํฌ๊ธฐ ๋ฒ„ํŠผ</h2>
Expand All @@ -52,7 +55,6 @@ export default function ButtonExamples() {
</Button>
</div>
</div>

{/* Disabled states */}
<div className="space-y-4">
<h2 className="mb-4 text-xl font-semibold">๋น„ํ™œ์„ฑํ™” ์ƒํƒœ</h2>
Expand All @@ -71,6 +73,40 @@ export default function ButtonExamples() {
</Button>
</div>
</div>
{/* ์ฐœ ๋ฒ„ํŠผ */}
<div className="space-y-4">
<h2 className="mb-4 text-xl font-semibold">์ฐœ ๋ฒ„ํŠผ</h2>
<LikeButton />
<LikeButton />
<LikeButton />
</div>
{/* ํ”Œ๋กœํŒ… ๋ฒ„ํŠผ ์„น์…˜ */}
<h2 className="mb-4 text-xl font-semibold">ํ”Œ๋กœํŒ… ๋ฒ„ํŠผ</h2>
<FloatingButton icon={<ArrowUp />} className="bottom-24" />
<FloatingButton variant="text" icon={<Plus />}>
๋ชจ์ž„ ๋งŒ๋“ค๊ธฐ
</FloatingButton>
<FloatingButton
icon={<Plus />}
className="right-6 top-1/2 -translate-y-1/2" // ์ค‘๊ฐ„์œ„์น˜
/>
</div>
);
}

function LikeButton() {
const [isLiked, setIsLiked] = useState(false);

const handleLikeClick = useCallback(() => {
setIsLiked((prev) => !prev);
}, []);

return (
<Button
variant="text"
size="sm"
onClick={handleLikeClick}
icon={<Heart className={isLiked ? 'fill-red-500 text-red-500' : ''} />}
></Button>
);
}
8 changes: 2 additions & 6 deletions src/components/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,31 @@ const buttonVariants = cva(
variant: {
solid: [
'bg-main',
'typo-button1',
'text-white',
'hover:opacity-90',
'disabled:bg-disable disabled:text-disable_text',
],
default: [
'bg-default',
'text-main',
'typo-button1',
'hover:opacity-90',
'disabled:bg-disable disabled:text-disable_text',
],
outline: [
'border border-main',
'text-main',
'typo-button1',
'hover:bg-default',
'disabled:border-disable disabled:text-disable_text',
],
text: [
'text-main',
'typo-button1',
'hover:text-opacity-80',
'disabled:text-disable_text',
],
},
size: {
default: 'h-[46px] w-[332px]',
sm: 'h-10 w-[120px]',
default: ['typo-button1 h-[46px] w-[332px]'],
sm: ['typo-button2 h-10 w-[120px]'],
},
},
defaultVariants: {
Expand Down
63 changes: 63 additions & 0 deletions src/components/ui/FloatingButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { cn } from '@/util/cn';
import { Slot } from '@radix-ui/react-slot';
import { type VariantProps, cva } from 'class-variance-authority';
import * as React from 'react';

const floatingButtonVariants = cva(
'fixed bottom-6 right-6 inline-flex items-center justify-center gap-2 rounded-full bg-main text-white transition-colors hover:opacity-90 focus-visible:outline-none disabled:pointer-events-none disabled:bg-disable disabled:text-disable_text [&_svg]:pointer-events-none [&_svg]:shrink-0',
{
variants: {
variant: {
icon: ['h-14 w-14', '[&_svg]:size-6'],
text: ['typo-head3', 'p-4', '[&_svg]:size-6'],
},
},
defaultVariants: {
variant: 'icon',
},
},
);

export interface FloatingButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof floatingButtonVariants> {
asChild?: boolean;
icon?: React.ReactNode;
}

/**
* Floating Button ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.(๊ธฐ๋ณธ fixed ์šฐ์ธกํ•˜๋‹จ-6)
* @example
* // ์•„์ด์ฝ˜๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ
* <FloatingButton icon={<Plus />} />
*
* // ํ…์ŠคํŠธ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ
* <FloatingButton icon={<Plus />} variant="text">๋ชจ์ž„ ๋งŒ๋“ค๊ธฐ</FloatingButton>
*
* @param {object} props
* @param {string} [props.className] - Tailwind CSS ํด๋ž˜์Šค๋ฅผ ํ†ตํ•œ ์ปค์Šคํ…€ ์Šคํƒ€์ผ
* @param {'icon' | 'text'} [props.variant='icon'] - ๋ฒ„ํŠผ์˜ ์Šคํƒ€์ผ variant (์•„์ด์ฝ˜๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ / ํ…์ŠคํŠธ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ)
* @param {React.ReactNode} [props.icon] - ๋ฒ„ํŠผ์— ํ‘œ์‹œ๋  ์•„์ด์ฝ˜
* @param {boolean} [props.disabled] - ๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ
* @param {() => void} [props.onClick] - ํด๋ฆญ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
* @param {boolean} [props.asChild=false] - true์ผ ๊ฒฝ์šฐ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ž˜ํ•‘ ๊ฐ€๋Šฅ
*/
const FloatingButton = React.forwardRef<HTMLButtonElement, FloatingButtonProps>(
({ className, variant, icon, children, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(floatingButtonVariants({ variant, className }))}
ref={ref}
{...props}
>
{icon}
{children}
</Comp>
);
},
);

FloatingButton.displayName = 'FloatingButton';

export { FloatingButton, floatingButtonVariants };