Skip to content
Open
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
16 changes: 16 additions & 0 deletions 04-Tabs/chan-byeong/tab-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Tabs } from "./tabs";

export function TabTest() {
return (
<Tabs defaultValue='1'>
<Tabs.List>
<Tabs.Trigger value='1' />
<Tabs.Trigger value='2' />
<Tabs.Trigger value='3' />
</Tabs.List>
<Tabs.Content value='1'>첫번째</Tabs.Content>
<Tabs.Content value='2'>두번째</Tabs.Content>
<Tabs.Content value='3'>세번째</Tabs.Content>
</Tabs>
);
}
84 changes: 84 additions & 0 deletions 04-Tabs/chan-byeong/tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
createContext,
useContext,
useState,
type ReactNode,
type Dispatch,
type SetStateAction,
} from "react";

type TabContextType = {
value: string;
setValue: Dispatch<SetStateAction<string>>;
};

const TabContext = createContext<TabContextType | null>(null);

interface TabsProps {
children: ReactNode;
defaultValue: string;
}

export function Tabs({ children, defaultValue }: TabsProps) {
const [activeTab, setActivetab] = useState(defaultValue);

const tabCtx: TabContextType = {
value: activeTab,
setValue: setActivetab,
};

return <TabContext value={tabCtx}>{children}</TabContext>;
}

function TabsList({ children, ...props }: { children: ReactNode }) {
return (
<ul {...props} style={{ display: "flex", gap: "2px", border: "green" }}>
{children}
</ul>
);
}

function TabTrigger({ value, ...props }: { value: string }) {
const tabCtx = useContext(TabContext);

if (!tabCtx) {
throw new Error("tab context must be called in TabsProvier");
}

const handleClick = () => {
tabCtx.setValue(value);
};

return (
<button
{...props}
onClick={handleClick}
style={{ backgroundColor: `${tabCtx.value === value ? "blue" : ""}` }}
>
{value}
</button>
);
}

function TabContent({
value,
children,
...props
}: {
value: string;
children: ReactNode;
}) {
const tabCtx = useContext(TabContext);

if (!tabCtx) {
throw new Error("tab context must be called in TabsProvier");
}

if (tabCtx.value !== value) return null;

return <div {...props}>{children}</div>;
}

Tabs.List = TabsList;
Tabs.Trigger = TabTrigger;
Tabs.Content = TabContent;
63 changes: 63 additions & 0 deletions 07-Stopwatch/chan-byeong/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useState, useRef, useEffect } from "react";

export default function Stopwatch() {
const [tick, setTick] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const rAfRef = useRef<number | null>(null);

useEffect(() => {
if (!isRunning) return;

let startTime = performance.now();
const update = (now: number) => {
const duration = now - startTime;
startTime = now;

setTick((prev) => prev + duration);
rAfRef.current = requestAnimationFrame(update);
};

rAfRef.current = requestAnimationFrame(update);

return () => {
if (rAfRef.current !== null) {
globalThis.cancelAnimationFrame(rAfRef.current);
}
};
}, [isRunning]);

const handleStartButton = () => {
setIsRunning((prev) => !prev);
};

const handleResetButton = () => {
setTick(0);
setIsRunning(false);
};

return (
<div>
<p>{formatTick(tick)}</p>
<div>
<button type='button' onClick={handleStartButton}>
{isRunning ? "stops" : "starts"}
</button>
<button type='button' onClick={handleResetButton}>
reset
</button>
</div>
</div>
);
}

function formatTick(tick: number) {
const totalMs = Math.floor(tick);
const ms = totalMs % 1000;
const totalSeconds = Math.floor(totalMs / 1000);
const s = totalSeconds % 60;
const totalMinutes = Math.floor(totalSeconds / 60);
const m = totalMinutes % 60;
const h = Math.floor(totalMinutes / 60);

return `${h > 0 ? h + "h : " : ""}${m > 0 ? m + "m : " : ""}${s}s : ${ms}ms`;
}