Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REFACTOR] panel 컴포넌트 개선 #60

Merged
merged 3 commits into from
Mar 7, 2024
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
33 changes: 32 additions & 1 deletion apps/spectator/app/game/[id]/page.css.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { theme } from '@hcc/styles';
import { theme, rem } from '@hcc/styles';
import { style, styleVariants } from '@vanilla-extract/css';

const contentSection = style({ overflowY: 'auto', padding: '1.25rem' });
Expand Down Expand Up @@ -26,3 +26,34 @@ export const cheerTalk = styleVariants({
...theme.textVariants.lg,
},
});

const panelItemBase = style({
...theme.textVariants.default,
textAlign: 'center',
paddingBlock: rem(12),
color: theme.colors.gray[4],
borderBlock: `1px solid ${theme.colors.gray[2]}`,
});

export const panel = styleVariants({
wrapper: {
position: 'relative',
},
menu: {
display: 'grid',
gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
width: '100%',
},
});

export const item = styleVariants({
active: [
panelItemBase,
{
background: theme.colors.primary[1],
color: theme.colors.primary[3],
borderBottom: `1px solid ${theme.colors.primary[3]}`,
},
],
inactive: [panelItemBase],
});
78 changes: 41 additions & 37 deletions apps/spectator/app/game/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use client';

import { Tabs } from '@hcc/ui';

import Live from '@/app/_components/Live';
import AsyncBoundary from '@/components/AsyncBoundary';
import CheerTalkModal from '@/components/cheertalk/Modal/CheerTalkModal';
import Loader from '@/components/Loader';
import Panel from '@/components/Panel';

import Banner from './_components/Banner';
import BannerFallback from './_components/Banner/Error';
Expand All @@ -16,13 +17,22 @@ import Lineup from './_components/Lineup';
import Timeline from './_components/Timeline';
import * as styles from './page.css';

export default function Page({ params }: { params: { id: string } }) {
const options = [
{ label: '라인업' },
{ label: '타임라인' },
{ label: '경기영상' },
];
const tabs = [
{
label: '라인업',
renderer: (gameId: string) => <Lineup gameId={gameId} />,
},
{
label: '타임라인',
renderer: (gameId: string) => <Timeline gameId={gameId} />,
},
{
label: '경기영상',
renderer: (gameId: string) => <div>{gameId} 경기영상</div>,
},
];

export default function Page({ params }: { params: { id: string } }) {
return (
<section>
<AsyncBoundary
Expand Down Expand Up @@ -53,36 +63,30 @@ export default function Page({ params }: { params: { id: string } }) {
</AsyncBoundary>
</section>

<Panel options={options} defaultValue="라인업">
{({ selected }) => (
<>
{selected === '라인업' && (
<AsyncBoundary
errorFallback={() => <div>에러</div>}
loadingFallback={<Loader />}
>
<Lineup gameId={params.id} />
</AsyncBoundary>
)}
{selected === '타임라인' && (
<AsyncBoundary
errorFallback={() => <div>에러</div>}
loadingFallback={<Loader />}
>
<Timeline gameId={params.id} />
</AsyncBoundary>
)}
{selected === '경기영상' && (
<AsyncBoundary
errorFallback={() => <div>에러</div>}
loadingFallback={<Loader />}
>
<div></div>
</AsyncBoundary>
)}
</>
)}
</Panel>
<Tabs defaultValue="라인업" className={styles.panel.wrapper}>
<Tabs.List className={styles.panel.menu}>
{tabs.map(tab => (
<Tabs.Trigger
key={tab.label}
value={tab.label}
className={state => styles.item[state]}
>
{tab.label}
</Tabs.Trigger>
))}
</Tabs.List>
{tabs.map(tab => (
<Tabs.Content key={tab.label} value={tab.label}>
<AsyncBoundary
errorFallback={() => <div>에러</div>}
loadingFallback={<Loader />}
>
{tab.renderer(params.id)}
</AsyncBoundary>
</Tabs.Content>
))}
</Tabs>

<CheerTalkModal gameId={params.id} />
</section>
);
Expand Down
30 changes: 0 additions & 30 deletions apps/spectator/components/Panel/Panel.css.ts

This file was deleted.

45 changes: 0 additions & 45 deletions apps/spectator/components/Panel/index.tsx

This file was deleted.

35 changes: 35 additions & 0 deletions docs/storybook/stories/Tabs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Tabs } from '@hcc/ui';
import { Meta, StoryObj } from '@storybook/react';
import React from 'react';

const meta: Meta = {
title: '@hcc/Tabs',
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
};

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
render: () => {
return (
<Tabs defaultValue="A">
<Tabs.List>
<Tabs.Trigger value="A">A</Tabs.Trigger>
<Tabs.Trigger value="B">B</Tabs.Trigger>
</Tabs.List>

<Tabs.Content value="A">
<div>저는 A입니다.</div>
</Tabs.Content>
<Tabs.Content value="B">
<div>저는 B입니다.</div>
</Tabs.Content>
</Tabs>
);
},
};
1 change: 1 addition & 0 deletions packages/ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export { default as Icon } from './icon';
export { default as Uploader } from './image-uploader';
export { default as Input } from './input';
export { default as Modal } from './modal';
export { default as Tabs } from './tabs';
export { default as Toast } from './toast';
export { default as Tooltip } from './tooltip';
15 changes: 15 additions & 0 deletions packages/ui/src/tabs/Content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import useTabs from './hooks';

type TabsContentProps = {
value: string;
className?: string;
children: React.ReactNode;
};

const TabsContent = ({ value, className, children }: TabsContentProps) => {
const { value: valueState } = useTabs();

return value === valueState && <div className={className}>{children}</div>;
};

export default TabsContent;
14 changes: 14 additions & 0 deletions packages/ui/src/tabs/List.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { clsx } from 'clsx';

import * as styles from './Tabs.css';

type TabsListProps = {
className?: string;
children: React.ReactNode;
};

const TabsList = ({ className, children }: TabsListProps) => {
return <div className={clsx(styles.list, className)}>{children}</div>;
};

export default TabsList;
14 changes: 14 additions & 0 deletions packages/ui/src/tabs/Tabs.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { style } from '@vanilla-extract/css';

export const root = style({
display: 'flex',
flexDirection: 'column',

width: '100%',
});

export const list = style({
display: 'grid',
gridAutoFlow: 'column',
alignItems: 'center',
});
34 changes: 34 additions & 0 deletions packages/ui/src/tabs/Trigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import useTabs from './hooks';

type TabsTriggerProps = {
value: string;
className?: (state: DataState) => string;
children: React.ReactNode;
};

type DataStateParams = {
value: string;
stateValue: string;
};
type DataState = 'active' | 'inactive';

const getDataState = ({ value, stateValue }: DataStateParams) => {
return stateValue === value ? 'active' : 'inactive';
};

const TabsTrigger = ({ value, className, children }: TabsTriggerProps) => {
const { value: stateValue, setValue } = useTabs();
const activeState = getDataState({ value, stateValue });

return (
<button
onClick={() => setValue(value)}
data-state={activeState}
className={className && className(activeState)}
>
{children}
</button>
);
};

export default TabsTrigger;
15 changes: 15 additions & 0 deletions packages/ui/src/tabs/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useContext } from 'react';

import { TabsContext } from '.';

const useTabs = () => {
const context = useContext(TabsContext);

if (!context) {
throw new Error('useTabs must be used within a TabsProvider');
}

return context;
};

export default useTabs;
Loading
Loading