Skip to content

Commit

Permalink
feat(fe): add skeleton UI (#50)
Browse files Browse the repository at this point in the history
* feat: add skeleton UI components for chatting, question, and reply lists

* feat: add skeleton UI components for QuestionReplyPage, SessionPage, and route
  • Loading branch information
cjeongmin authored Jan 24, 2025
1 parent 1bb1628 commit 2f0522a
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 8 deletions.
8 changes: 5 additions & 3 deletions apps/client/src/pages/session/ui/QuestionReplyPage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React from 'react';
import React, { Suspense } from 'react';

import { ReplyListSkeletonUI } from '@/widgets/reply-list';

const LazyQuestionReplyPage = React.lazy(() =>
import('@/widgets/reply-list').then((module) => ({ default: module.ReplyList })),
);

function QuestionReplyPage() {
return (
<React.Suspense fallback={null}>
<Suspense fallback={<ReplyListSkeletonUI />}>
<LazyQuestionReplyPage />
</React.Suspense>
</Suspense>
);
}

Expand Down
11 changes: 8 additions & 3 deletions apps/client/src/pages/session/ui/SessionPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import React from 'react';
import React, { Suspense, useEffect } from 'react';

import { QuestionListSkeletonUI } from '@/widgets/question-list';

import { useSessionStore } from '@/entities/session';

Expand All @@ -16,7 +17,11 @@ function SessionPage() {
}
}, [sessionTitle]);

return <LazyQuestionList />;
return (
<Suspense fallback={<QuestionListSkeletonUI />}>
<LazyQuestionList />
</Suspense>
);
}

export default SessionPage;
8 changes: 6 additions & 2 deletions apps/client/src/routes/session/route.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { createFileRoute, Outlet, redirect, ScrollRestoration } from '@tanstack/react-router';
import React from 'react';
import React, { Suspense } from 'react';

import { ChattingListSkeletonUI } from '@/widgets/chatting-list';

import { SocketProvider } from '@/features/socket';

Expand All @@ -16,7 +18,9 @@ export const Route = createFileRoute('/session')({
<SocketProvider>
<ScrollRestoration />
<Outlet />
<LazyChattingList />
<Suspense fallback={<ChattingListSkeletonUI />}>
<LazyChattingList />
</Suspense>
</SocketProvider>
</div>
),
Expand Down
43 changes: 43 additions & 0 deletions apps/client/src/widgets/chatting-list/ChattingListSkeletonUI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useEffect, useState } from 'react';

function ChattingListSkeletonUI() {
const [shouldRender, setShouldRender] = useState(false);

useEffect(() => {
const timer = setTimeout(() => {
setShouldRender(true);
}, 500);

return () => clearTimeout(timer);
}, []);

return (
<div className='inline-flex h-full w-1/5 min-w-[240px] flex-col items-center justify-start rounded-lg bg-slate-50 shadow'>
{shouldRender && (
<>
<div className='inline-flex h-[54px] w-full items-center justify-between border-b border-gray-200 px-4 py-3'>
<div className='h-7 w-24 animate-pulse rounded bg-indigo-100'></div>
<div className='h-6 w-20 animate-pulse rounded bg-green-100'></div>
</div>

<div className='inline-flex h-full w-full flex-col items-start justify-start gap-3 overflow-y-auto p-2.5'>
<div className='flex w-full flex-col gap-3'>
{[...Array(6)].map((_, i) => (
<div key={i} className='flex w-3/4 flex-col gap-1'>
<div className='h-4 w-16 animate-pulse rounded bg-indigo-100'></div>
<div className='h-12 animate-pulse rounded-lg bg-indigo-50 p-2'></div>
</div>
))}
</div>
</div>

<div className='inline-flex h-[75px] w-full items-center justify-center gap-2.5 border-t border-gray-200 bg-gray-50 p-4'>
<div className='h-12 w-full animate-pulse rounded-md bg-white'></div>
</div>
</>
)}
</div>
);
}

export default ChattingListSkeletonUI;
1 change: 1 addition & 0 deletions apps/client/src/widgets/chatting-list/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as ChattingList } from './ChattingList';
export { default as ChattingListSkeletonUI } from './ChattingListSkeletonUI';
1 change: 1 addition & 0 deletions apps/client/src/widgets/question-list/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as QuestionList } from './ui/QuestionList';
export { default as QuestionListSkeletonUI } from './ui/QuestionListSkeletonUI';
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect, useState } from 'react';

function QuestionListSkeletonUI() {
const [shouldRender, setShouldRender] = useState(false);

useEffect(() => {
const timer = setTimeout(() => {
setShouldRender(true);
}, 500);

return () => clearTimeout(timer);
}, []);

return (
<div className='inline-flex h-full w-4/5 flex-grow animate-pulse flex-col items-center justify-start rounded-lg bg-slate-50 shadow'>
{shouldRender && (
<>
<div className='inline-flex h-[54px] w-full items-center justify-between border-b border-gray-200 px-8 py-2'>
<div className='h-6 w-32 animate-pulse rounded bg-indigo-100'></div>
<div className='flex gap-2'>
<div className='h-8 w-16 animate-pulse rounded bg-indigo-100'></div>
<div className='h-8 w-20 animate-pulse rounded bg-indigo-100'></div>
</div>
</div>
<div className='flex w-full flex-col gap-4 p-8'>
<div className='space-y-4'>
<hr className='h-1 w-full animate-pulse rounded bg-indigo-100' />
<div className='h-20 w-full animate-pulse rounded bg-indigo-50'></div>
<div className='h-20 w-full animate-pulse rounded bg-indigo-50'></div>
</div>
<div className='space-y-4'>
<hr className='h-1 w-full animate-pulse rounded bg-indigo-100' />
<div className='h-20 w-full animate-pulse rounded bg-indigo-50'></div>
<div className='h-20 w-full animate-pulse rounded bg-indigo-50'></div>
<div className='h-20 w-full animate-pulse rounded bg-indigo-50'></div>
</div>
</div>
</>
)}
</div>
);
}

export default QuestionListSkeletonUI;
1 change: 1 addition & 0 deletions apps/client/src/widgets/reply-list/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as ReplyList } from './ui/ReplyList';
export { default as ReplyListSkeletonUI } from './ui/ReplyListSkeletonUI';
41 changes: 41 additions & 0 deletions apps/client/src/widgets/reply-list/ui/ReplyListSkeletonUI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useEffect, useState } from 'react';

function ReplyListSkeletonUI() {
const [shouldRender, setShouldRender] = useState(false);

useEffect(() => {
const timer = setTimeout(() => {
setShouldRender(true);
}, 500);

return () => clearTimeout(timer);
}, []);

return (
<div className='inline-flex h-full w-4/5 flex-grow animate-pulse flex-col items-center justify-start rounded-lg bg-slate-50 shadow'>
{shouldRender && (
<>
<div className='inline-flex h-[54px] w-full items-center justify-between border-b border-gray-200 px-8 py-2'>
<div className='flex flex-row items-center gap-2'>
<div className='h-6 w-6 animate-pulse rounded bg-indigo-100'></div>
<div className='h-6 w-24 animate-pulse rounded bg-indigo-100'></div>
</div>
<div className='h-8 w-20 animate-pulse rounded bg-indigo-100'></div>
</div>
<div className='inline-flex h-full w-full flex-col items-start justify-start gap-4 overflow-y-auto pb-4'>
<div className='flex h-fit flex-col items-start justify-center gap-2.5 self-stretch border-b border-gray-200/50 px-12 py-4'>
<div className='h-24 w-full animate-pulse rounded bg-indigo-50'></div>
</div>
<div className='flex w-full flex-col gap-4 px-12'>
<div className='h-32 w-full animate-pulse rounded-lg bg-indigo-50 p-4'></div>
<div className='h-32 w-full animate-pulse rounded-lg bg-indigo-50 p-4'></div>
<div className='h-32 w-full animate-pulse rounded-lg bg-indigo-50 p-4'></div>
</div>
</div>
</>
)}
</div>
);
}

export default ReplyListSkeletonUI;

0 comments on commit 2f0522a

Please sign in to comment.