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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Dummy from '@/shared/assets/images/dummy/exampleCocktail.png';
import Link from 'next/link';
import Keep from '@/domains/shared/components/keep/Keep';

function ChatCocktailCard() {
function BotCocktailCard() {
return (
<div className="relative flex flex-col w-full min-w-[200px] rounded-2xl overflow-hidden bg-white shadow-[0_0_12px_rgba(255,255,255,0.4)]">
<Link href="/" className="block relative">
Expand All @@ -20,4 +20,4 @@ function ChatCocktailCard() {
</div>
);
}
export default ChatCocktailCard;
export default BotCocktailCard;
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
import Ssury from '@/shared/assets/ssury/ssury_shaker.webp';
import Image from 'next/image';
import { useState } from 'react';
import ChatRadio from './ChatRadio';
import ChatCocktailCard from './ChatCocktailCard';
import BotCocktailCard from './BotCocktailCard';
import BotOptions from './BotOptions';

interface Message {
id: string;
text?: string;
type?: 'radio' | 'text' | 'recommend';
}

function SsuryChat() {
function BotMessage() {
const [selected, setSelected] = useState('option1');

// radio 옵션
Expand Down Expand Up @@ -61,13 +61,13 @@ function SsuryChat() {
{msg.type === 'recommend' ? (
<ul className="inline-grid grid-cols-1 sm:grid-cols-3 gap-2 justify-start">
<li>
<ChatCocktailCard />
<BotCocktailCard />
</li>
<li>
<ChatCocktailCard />
<BotCocktailCard />
</li>
<li>
<ChatCocktailCard />
<BotCocktailCard />
</li>
</ul>
) : (
Expand All @@ -76,7 +76,7 @@ function SsuryChat() {

{/* radio */}
{msg.type === 'radio' && (
<ChatRadio options={options} value={selected} onChange={setSelected} />
<BotOptions options={options} value={selected} onChange={setSelected} />
)}
</div>
)}
Expand All @@ -86,4 +86,4 @@ function SsuryChat() {
</article>
);
}
export default SsuryChat;
export default BotMessage;
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface RadioGroupProps {
onChange: (value: string) => void;
}

function ChatRadio({ options, value, onChange }: RadioGroupProps) {
function BotOptions({ options, value, onChange }: RadioGroupProps) {
return (
<div role="radiogroup" className="flex flex-col gap-3 mt-5">
{options.map((opt) => (
Expand Down Expand Up @@ -39,4 +39,4 @@ function ChatRadio({ options, value, onChange }: RadioGroupProps) {
</div>
);
}
export default ChatRadio;
export default BotOptions;
14 changes: 8 additions & 6 deletions src/domains/recommend/components/ChatSection.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
'use client';

import { useState } from 'react';
import ChatForm from './ChatForm';
import MyChat from './MyChat';
import SsuryChat from './SsuryChat';
import BotMessage from './BotMessage';
import UserMessage from './UserMessage';
import MessageInput from './MessageInput';
import TypingIndicator from './TypingIndicator';

function ChatSection() {
const [messages, setMessages] = useState<string[]>([]);
Expand All @@ -16,12 +17,13 @@ function ChatSection() {
<section className="page-layout max-w-1024 py-12 ">
<h2 className="sr-only">대화 목록 및 입력 창</h2>
<div className="flex flex-col gap-10 pb-20">
<SsuryChat />
<BotMessage />
<TypingIndicator />
{messages.map((msg, i) => (
<MyChat key={i} message={msg} />
<UserMessage key={i} message={msg} />
))}
</div>
<ChatForm onSubmit={handleSubmit} />
<MessageInput onSubmit={handleSubmit} />
</section>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface Props {
onSubmit: (message: string) => void;
}

function ChatForm({ onSubmit }: Props) {
function MessageInput({ onSubmit }: Props) {
const [value, setValue] = useState('');
const textareaRef = useRef<HTMLTextAreaElement>(null);

Expand Down Expand Up @@ -51,4 +51,4 @@ function ChatForm({ onSubmit }: Props) {
</div>
);
}
export default ChatForm;
export default MessageInput;
14 changes: 14 additions & 0 deletions src/domains/recommend/components/TypingIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Image from 'next/image';
import shaker from '@/shared/assets/images/shaker.png';

function TypingIndicator() {
return (
<div className="relative flex items-center w-fit p-3 rounded-2xl rounded-tl-none bg-white text-black overflow-hidden">
<p className="inline-block animate-fade-in">준비 중…</p>
<div className="relative w-10 h-10 animate-shake">
<Image src={shaker} alt="Cocktail Shaker" fill className="object-contain" priority />
</div>
</div>
);
}
export default TypingIndicator;
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface Props {
// { id: '2', sender: 'user', text: '배고파요' },
// ];

function MyChat({ message }: Props) {
function UserMessage({ message }: Props) {
return (
<article aria-label="내 메시지" className="flex flex-col items-end">
<header className="w-fit">
Expand All @@ -28,4 +28,4 @@ function MyChat({ message }: Props) {
</article>
);
}
export default MyChat;
export default UserMessage;
Binary file added src/shared/assets/images/shaker.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/shared/assets/ssury/ssury_happy.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 32 additions & 12 deletions src/shared/styles/_utilities.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,44 @@
display: none;
}

@media (min-width: 768px) {
.custom-scrollbar {
scrollbar-width: thin; /* Firefox 전용 */
scrollbar-color: #7a7581; /* Firefox 전용: thumb track */
@keyframes shake {
0%,
100% {
transform: translateY(0) rotate(0deg) scale(1);
}

.custom-scrollbar::-webkit-scrollbar {
width: 4px;
border-radius: 8px;
10% {
transform: rotate(-25deg) scaleX(1.05) scaleY(0.95);
}
20% {
transform: rotate(25deg) scaleX(0.95) scaleY(1.05);
}
30% {
transform: rotate(-20deg) scaleX(1.05) scaleY(0.95);
}
40% {
transform: rotate(20deg) scaleX(0.95) scaleY(1.05);
}

.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: #ab98c2;
border-radius: 8px;
50% {
transform: translateY(-10px) rotate(360deg) scale(1);
}

.custom-scrollbar::-webkit-scrollbar-track {
background-color: aliceblue;
60% {
transform: translateY(-25px) rotate(20deg) scaleX(1.05) scaleY(0.95);
}
70% {
transform: translateY(-10px) rotate(-25deg) scaleX(0.95) scaleY(1.05);
}
80% {
transform: translateY(-5px) rotate(20deg) scaleX(1.02) scaleY(0.98);
}
90% {
transform: translateY(-2px) rotate(-15deg) scale(1);
}
}

.animate-shake {
animation: shake 3s ease-in-out infinite;
}
}