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
3 changes: 2 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"lint": "next lint --max-warnings 0",
"check-types": "tsc --noEmit",
"generate:api": "orval && biome check --fix --unsafe ./shared/api/**/*",
"postinstall": "panda codegen",
"panda:codegen": "panda codegen",
"postinstall": "pnpm run panda:codegen",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { useState } from "react";
import { css, cx } from "../../../../styled-system/css";
import type { FloatingActionButtonProps } from "./FloatingActionButton.type";
Expand Down
86 changes: 86 additions & 0 deletions apps/web/src/views/home/components/BottomNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"use client";

import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";

const NAV_ITEMS = [
{
href: "/",
label: "홈",
icon: "home",
},
{
href: "/schedule",
label: "일정",
icon: "schedule",
},
{
href: "/documents",
label: "문서",
icon: "documents",
},
{
href: "/profile",
label: "프로필",
icon: "profile",
},
];

export default function BottomNavigation() {
const pathname = usePathname();

return (
<nav
style={{
position: "fixed",
bottom: "0",
left: "0",
right: "0",
zIndex: 50,
display: "flex",
justifyContent: "center",
padding: "0 16px",
backgroundColor: "white",
paddingBottom: "env(safe-area-inset-bottom)",
}}
>
<div
style={{
display: "flex",
width: "100%",
padding: "4px 0",
maxWidth: "100%",
justifyContent: "space-between",
boxShadow: "0 -4px 9.2px -1px rgba(0, 0, 0, 0.03)",
}}
>
{NAV_ITEMS.map(item => {
const isActive = pathname === item.href;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

중첩 라우트에서 활성 상태 감지가 작동하지 않을 수 있습니다.

현재 pathname === item.href는 정확히 일치하는 경우만 활성 상태로 표시합니다. 예를 들어 /schedule/detail과 같은 중첩 라우트에서는 일정 탭이 활성 상태로 표시되지 않습니다.

다음과 같이 수정하여 하위 경로도 포함하도록 개선할 수 있습니다:

-          const isActive = pathname === item.href;
+          const isActive = item.href === "/" 
+            ? pathname === item.href 
+            : pathname.startsWith(item.href);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isActive = pathname === item.href;
const isActive = item.href === "/"
? pathname === item.href
: pathname.startsWith(item.href);
🤖 Prompt for AI Agents
In apps/web/src/views/home/components/BottomNavigation.tsx around line 66, the
active check uses strict equality (pathname === item.href) which fails for
nested routes; change it to treat a tab as active when the current pathname
equals the href OR the pathname is a subpath of the href (e.g.
pathname.startsWith(item.href + '/') ), and handle the root '/' href specially
so it only matches exactly; update the isActive assignment accordingly.


return (
<Link
key={item.href}
href={item.href}
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
width: "48px",
height: "48px",
}}
>
<Image
src={`/icons/${item.icon}${isActive ? "-active" : ""}.png`}
alt={item.label}
width={48}
height={48}
/>
Comment on lines +74 to +79
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

아이콘 파일의 존재 여부를 확인하세요.

각 네비게이션 아이템에 대해 일반 버전과 활성화 버전의 아이콘 파일이 필요합니다. 누락된 아이콘이 있을 경우 이미지 로딩 오류가 발생합니다.

다음 스크립트로 필요한 아이콘 파일들이 모두 존재하는지 확인하세요:


🏁 Script executed:

#!/bin/bash
# Description: 필요한 아이콘 파일들이 public/icons 디렉토리에 존재하는지 확인

icons=("home" "schedule" "documents" "profile")
missing=0

for icon in "${icons[@]}"; do
  if [ ! -f "public/icons/${icon}.png" ]; then
    echo "❌ Missing: public/icons/${icon}.png"
    missing=1
  fi
  if [ ! -f "public/icons/${icon}-active.png" ]; then
    echo "❌ Missing: public/icons/${icon}-active.png"
    missing=1
  fi
done

if [ $missing -eq 0 ]; then
  echo "✅ All required icon files exist"
else
  echo "⚠️  Some icon files are missing"
  exit 1
fi

Length of output: 347


추가 검증이 필요합니다. BottomNavigation 컴포넌트의 항목 정의와 실제 public 디렉토리의 구조를 확인하겠습니다.


🏁 Script executed:

#!/bin/bash
# Check the BottomNavigation component to see what icons are actually defined
cat apps/web/src/views/home/components/BottomNavigation.tsx

Length of output: 1823


🏁 Script executed:

#!/bin/bash
# Check what files exist in the public directory
find public -type f -name "*.png" 2>/dev/null | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check the structure of public/icons directory
ls -la public/icons/ 2>/dev/null || echo "Directory does not exist or is empty"

Length of output: 36


공개 디렉토리에 필수 아이콘 파일이 모두 누락되어 있습니다.

public/icons/ 디렉토리가 없으며, 다음 8개의 아이콘 파일이 필요합니다:

  • home.png, home-active.png
  • schedule.png, schedule-active.png
  • documents.png, documents-active.png
  • profile.png, profile-active.png

현재 상태로는 이미지 로딩이 실패하여 하단 네비게이션이 표시되지 않습니다.

🤖 Prompt for AI Agents
In apps/web/src/views/home/components/BottomNavigation.tsx around lines 74 to
79, the Image component expects icon files under public/icons but that directory
and the eight required PNGs are missing; create a public/icons directory and add
the following files at that path: home.png, home-active.png, schedule.png,
schedule-active.png, documents.png, documents-active.png, profile.png,
profile-active.png (ensure correct filenames and PNG format), commit them to the
repository so the Image src `/icons/{icon}{-active}.png` resolves correctly, and
verify image dimensions/quality match UI expectations.

</Link>
Comment on lines +62 to +80
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

접근성 속성이 누락되었습니다.

네비게이션 링크에 다음 접근성 개선사항이 필요합니다:

  • 활성 상태를 나타내는 aria-current="page" 속성 누락
  • 스크린 리더를 위한 텍스트 레이블 부재 (이미지만으로는 불충분)

다음과 같이 수정하여 접근성을 개선하세요:

            <Link
              key={item.href}
              href={item.href}
+             aria-label={item.label}
+             aria-current={isActive ? "page" : undefined}
              className={css({
                display: "flex",
                flexDirection: "column",
                alignItems: "center",
                justifyContent: "center",
                width: "48px",
                height: "48px",
              })}
            >
              <Image
                src={`/icons/${item.icon}${isActive ? "-active" : ""}.png`}
                alt={item.label}
                width={48}
                height={48}
+               aria-hidden="true"
              />
            </Link>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Link
key={item.href}
href={item.href}
className={css({
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
width: "48px",
height: "48px",
})}
>
<Image
src={`/icons/${item.icon}${isActive ? "-active" : ""}.png`}
alt={item.label}
width={48}
height={48}
/>
</Link>
<Link
key={item.href}
href={item.href}
aria-label={item.label}
aria-current={isActive ? "page" : undefined}
className={css({
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
width: "48px",
height: "48px",
})}
>
<Image
src={`/icons/${item.icon}${isActive ? "-active" : ""}.png`}
alt={item.label}
width={48}
height={48}
aria-hidden="true"
/>
</Link>
🤖 Prompt for AI Agents
In apps/web/src/views/home/components/BottomNavigation.tsx around lines 69 to
87, the Link items are missing accessibility attributes: add aria-current="page"
to the Link when the item is active (isActive true) and provide a
screen-reader-friendly label by adding aria-label={item.label} to the Link (or
include a visually-hidden span with the label inside the Link) so assistive tech
can identify the destination; keep the Image alt but ensure it doesn’t duplicate
verbose text.

);
})}
</div>
</nav>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"use client";

import { useState } from "react";
import type { FloatingActionButtonProps } from "./FloatingActionButton.type";

export function FloatingActionButton({
disabled = false,
className,
position = "fixed",
top,
left,
bottom = 16,
right = 16,
actions = [],
icon,
openIcon,
closeIcon,
onClick,
}: FloatingActionButtonProps) {
const [isOpen, setIsOpen] = useState(false);

const style: React.CSSProperties = { position };
if (top !== undefined) style.top = top;
if (left !== undefined) style.left = left;
if (bottom !== undefined) style.bottom = bottom;
if (right !== undefined) style.right = right;

return (
<div
className={className}
style={{
position: "relative",
display: "flex",
alignItems: "center",
justifyContent: "center",
...style,
}}
>
<button
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "50%",
width: "56px",
height: "56px",
cursor: "pointer",
border: "none",
backgroundColor: "#3A8DFF",
}}
disabled={disabled}
type="button"
onClick={() => {
setIsOpen(!isOpen);
onClick?.();
}}
>
{isOpen ? (closeIcon ?? icon) : (openIcon ?? icon)}
</button>
Comment on lines +39 to +59
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

접근성 속성을 추가하세요.

FloatingActionButton에 다음 접근성 속성이 누락되었습니다:

  • aria-expanded: 액션 패널의 열림/닫힘 상태 표시
  • aria-label: 버튼의 용도 설명

다음과 같이 수정하세요:

      <button
        className={css({...})}
        disabled={disabled}
        type="button"
+       aria-expanded={isOpen}
+       aria-label={isOpen ? "액션 메뉴 닫기" : "액션 메뉴 열기"}
        onClick={() => {
          setIsOpen(!isOpen);
          onClick?.();
        }}
      >
        {isOpen ? (closeIcon ?? icon) : (openIcon ?? icon)}
      </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
className={css({
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "50%",
width: "56px",
height: "56px",
cursor: "pointer",
border: "none",
backgroundColor: "#3A8DFF",
})}
disabled={disabled}
type="button"
onClick={() => {
setIsOpen(!isOpen);
onClick?.();
}}
>
{isOpen ? (closeIcon ?? icon) : (openIcon ?? icon)}
</button>
<button
className={css({
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "50%",
width: "56px",
height: "56px",
cursor: "pointer",
border: "none",
backgroundColor: "#3A8DFF",
})}
disabled={disabled}
type="button"
aria-expanded={isOpen}
aria-label={isOpen ? "액션 메뉴 닫기" : "액션 메뉴 열기"}
onClick={() => {
setIsOpen(!isOpen);
onClick?.();
}}
>
{isOpen ? (closeIcon ?? icon) : (openIcon ?? icon)}
</button>
🤖 Prompt for AI Agents
In
apps/web/src/views/home/components/FloatingActionButton/FloatingActionButton.tsx
around lines 42 to 62, the button is missing accessibility attributes; add
aria-expanded set to the current isOpen state (aria-expanded={isOpen}) so
assistive tech knows the panel state, and add an aria-label describing the
button’s purpose (use a provided prop if available or a sensible default like
"Open actions"/"Close actions" depending on state). Ensure aria-label is stable
and localized if your app supports i18n.

<div
style={{
position: "absolute",
display: "flex",
flexDirection: "column",
gap: "20px",
bottom: "76px",
right: "0",
transition: "all 0.3s ease-in-out",
opacity: isOpen ? 1 : 0,
visibility: isOpen ? "visible" : "hidden",
}}
>
{actions?.map((action, index) => (
<div
key={index.toString()}
style={{
transform: isOpen ? "translateY(0)" : `translateY(${40 - index * 20}px)`,
transition: "transform 0.3s ease-in-out",
transitionDelay: isOpen ? `${(index + 1) * 0.1}s` : "0s",
}}
Comment on lines +73 to +80
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

배열 인덱스를 key로 사용하지 마세요.

index를 key로 사용하면 actions 배열이 재정렬되거나 변경될 때 React의 reconciliation이 비효율적으로 작동하고 애니메이션 상태가 잘못될 수 있습니다.

각 action에 고유한 id 속성을 추가하거나, action의 불변 속성을 조합하여 key를 생성하세요:

Option 1: FloatingActionButtonAction 타입에 id 추가

FloatingActionButton.type.ts를 수정:

export type FloatingActionButtonAction = {
+  id: string;
  icon: React.ReactNode;
  onClick?: () => void;
  disabled?: boolean;
};

그리고 key 사용:

-        {actions?.map((action, index) => (
+        {actions?.map((action) => (
          <div
-           key={index.toString()}
+           key={action.id}

Option 2 (임시 방편): stable한 식별자가 없다면

-           key={index.toString()}
+           key={`action-${index}`}

단, Option 1이 권장되며 actions 배열이 동적으로 변경될 가능성이 있다면 반드시 고유 id를 사용해야 합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{actions?.map((action, index) => (
<div
key={index.toString()}
style={{
transform: isOpen ? "translateY(0)" : `translateY(${40 - index * 20}px)`,
transition: "transform 0.3s ease-in-out",
transitionDelay: isOpen ? `${(index + 1) * 0.1}s` : "0s",
}}
{actions?.map((action, index) => (
<div
key={`action-${index}`}
style={{
transform: isOpen ? "translateY(0)" : `translateY(${40 - index * 20}px)`,
transition: "transform 0.3s ease-in-out",
transitionDelay: isOpen ? `${(index + 1) * 0.1}s` : "0s",
}}

>
<button
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "50%",
width: "56px",
height: "56px",
cursor: "pointer",
border: "none",
backgroundColor: "#ffffff",
boxShadow: "0 0 5.6px 0 rgba(0, 0, 0, 0.04)",
}}
disabled={action.disabled}
type="button"
onClick={() => {
action.onClick?.();
setIsOpen(false);
}}
>
{action.icon}
</button>
Comment on lines +82 to +103
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

액션 버튼에 접근성 레이블을 추가하세요.

각 액션 버튼에 aria-label이 누락되어 스크린 리더 사용자가 버튼의 용도를 알 수 없습니다.

FloatingActionButtonAction 타입에 label 추가:

export type FloatingActionButtonAction = {
  id: string;
  icon: React.ReactNode;
+  label: string;  // 접근성을 위한 버튼 설명
  onClick?: () => void;
  disabled?: boolean;
};

버튼에 aria-label 적용:

            <button
              style={{...}}
              disabled={action.disabled}
              type="button"
+             aria-label={action.label}
              onClick={() => {
                action.onClick?.();
                setIsOpen(false);
              }}
            >

Committable suggestion skipped: line range outside the PR's diff.

</div>
))}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export type FloatingActionButtonAction = {
icon: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
};

export type FloatingActionButtonProps = {
className?: string;
disabled?: boolean;
position?: "fixed" | "absolute";
top?: React.CSSProperties["top"];
left?: React.CSSProperties["left"];
bottom?: React.CSSProperties["bottom"];
right?: React.CSSProperties["right"];
actions?: FloatingActionButtonAction[];
icon?: React.ReactNode;
openIcon?: React.ReactNode;
closeIcon?: React.ReactNode;
onClick?: () => void;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { FloatingActionButton } from "./FloatingActionButton";
export type { FloatingActionButtonProps } from "./FloatingActionButton.type";
87 changes: 87 additions & 0 deletions apps/web/src/views/home/components/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"use client";

import { TAB_ITEMS, type TabsState } from "@/views/home/types";
import Image from "next/image";

type TabsProps = {
activeTab: TabsState;
setActiveTab: (tab: TabsState) => void;
};
Comment on lines +6 to +9
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

TabsProps 타입 중복 정의를 제거하세요.

TabsProps 타입을 로컬에서 정의하지 말고 types.ts에서 export하여 재사용하는 것이 좋습니다. 이는 타입 일관성을 유지하고 중복을 방지합니다.

apps/web/src/views/home/types.ts에 다음을 추가하세요:

export type TabsProps = {
  activeTab: TabsState;
  setActiveTab: (tab: TabsState) => void;
};

그리고 이 파일에서 import하세요:

-type TabsProps = {
-  activeTab: TabsState;
-  setActiveTab: (tab: TabsState) => void;
-};
+import type { TabsProps } from "@/views/home/types";
🤖 Prompt for AI Agents
In apps/web/src/views/home/components/Tabs.tsx around lines 7 to 10, the
TabsProps type is defined locally and duplicates the type that should be shared;
remove the local TabsProps definition and instead import TabsProps from
apps/web/src/views/home/types.ts, and in apps/web/src/views/home/types.ts add
and export the TabsProps type with the shape { activeTab: TabsState;
setActiveTab: (tab: TabsState) => void; } so the component reuses the
centralized type.


export function Tabs({ activeTab, setActiveTab }: TabsProps) {
return (
<ul
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
width: "100%",
padding: "0 20px",
backgroundColor: "white",
}}
>
<li
onClick={() => setActiveTab(TAB_ITEMS.DAILY)}
style={{
cursor: "pointer",
flex: 1,
height: "35px",
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: "12px",
}}
>
<Image
src={`/icons/check${activeTab === TAB_ITEMS.DAILY ? "-active" : ""}.png`}
alt={TAB_ITEMS.DAILY}
width={18}
height={18}
/>
<p
style={{
fontSize: "14px",
lineHeight: "1.4",
letterSpacing: "-0.28px",
fontWeight: "400",
color: activeTab === TAB_ITEMS.DAILY ? "black" : "#B7C2D0",
}}
>
일일 업무
</p>
</li>
<li
onClick={() => setActiveTab(TAB_ITEMS.MEETING)}
style={{
cursor: "pointer",
flex: 1,
height: "35px",
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: "12px",
}}
>
<Image
src={`/icons/message${activeTab === TAB_ITEMS.MEETING ? "-active" : ""}.png`}
alt={TAB_ITEMS.MEETING}
width={20}
height={20}
/>
<p
style={{
fontSize: "14px",
lineHeight: "1.4",
letterSpacing: "-0.28px",
fontWeight: "400",
color: activeTab === TAB_ITEMS.MEETING ? "black" : "#B7C2D0",
}}
>
회의 일정
</p>
</li>
Comment on lines +23 to +84
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

탭 아이템 렌더링 로직을 추출하여 코드 중복을 제거하세요.

두 탭 간에 구조가 거의 동일하여 코드 중복이 발생하고 있습니다. 배열을 순회하는 방식으로 리팩토링하는 것이 유지보수성을 향상시킵니다.

다음과 같이 리팩토링할 수 있습니다:

const TAB_CONFIG = [
  {
    id: TAB_ITEMS.DAILY,
    icon: "check",
    label: "일일 업무",
    iconSize: { width: 18, height: 18 },
  },
  {
    id: TAB_ITEMS.MEETING,
    icon: "message",
    label: "회의 일정",
    iconSize: { width: 20, height: 20 },
  },
] as const;

export function Tabs({ activeTab, setActiveTab }: TabsProps) {
  return (
    <ul role="tablist" className={css({...})}>
      {TAB_CONFIG.map((tab) => (
        <li key={tab.id} role="presentation">
          <button
            role="tab"
            aria-selected={activeTab === tab.id}
            onClick={() => setActiveTab(tab.id)}
            className={css({...})}
          >
            <Image
              src={`/icons/${tab.icon}${activeTab === tab.id ? "-active" : ""}.png`}
              alt={tab.label}
              width={tab.iconSize.width}
              height={tab.iconSize.height}
            />
            <p className={css({
              fontSize: "14px",
              lineHeight: "1.4",
              letterSpacing: "-0.28px",
              fontWeight: "400",
              color: activeTab === tab.id ? "black" : "#B7C2D0",
            })}>
              {tab.label}
            </p>
          </button>
        </li>
      ))}
    </ul>
  );
}
🤖 Prompt for AI Agents
In apps/web/src/views/home/components/Tabs.tsx around lines 24-85, duplicate JSX
for the two tab items should be extracted into a config-driven render to remove
repetition; create a TAB_CONFIG array containing id, icon, label, and iconSize
for each tab, map over it to render each item, replace the <li> clickable
elements with accessible <button role="tab"> inside list items, wire onClick to
setActiveTab(tab.id), compute icon src and active styles from activeTab ===
tab.id, and keep existing css styles by reusing the style objects so behavior
and appearance remain identical.

</ul>
);
Comment on lines +13 to +86
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

접근성 및 시맨틱 HTML 개선이 필요합니다.

현재 구현의 문제점:

  1. <li> 요소에 onClick을 사용하는 것은 시맨틱하지 않습니다
  2. WAI-ARIA 탭 패턴 속성이 누락되었습니다 (role="tab", role="tablist", aria-selected)
  3. 키보드 네비게이션 지원이 없습니다 (화살표 키, Enter, Space)

다음과 같이 수정하여 접근성을 준수하세요:

    <ul
+     role="tablist"
      className={css({
        display: "flex",
        flexDirection: "row",
        alignItems: "center",
        width: "100%",
        px: "20px",
        bg: "white",
      })}
    >
-     <li
-       onClick={() => setActiveTab(TAB_ITEMS.DAILY)}
+     <li role="presentation">
+       <button
+         role="tab"
+         aria-selected={activeTab === TAB_ITEMS.DAILY}
+         onClick={() => setActiveTab(TAB_ITEMS.DAILY)}
+         onKeyDown={(e) => {
+           if (e.key === 'ArrowRight') setActiveTab(TAB_ITEMS.MEETING);
+         }}
          className={css({
            cursor: "pointer",
            flex: 1,
            height: "35px",
            display: "flex",
            flexDirection: "row",
            alignItems: "center",
            justifyContent: "center",
            gap: "12px",
+           border: "none",
+           background: "transparent",
          })}
        >
          {/* 기존 내용 */}
+       </button>
-     </li>
+     </li>

두 번째 탭에도 동일하게 적용하되 ArrowLeft로 첫 번째 탭으로 이동하도록 설정하세요.

Committable suggestion skipped: line range outside the PR's diff.

}
31 changes: 29 additions & 2 deletions apps/web/src/views/home/home.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { FloatingActionButton } from "@/shared/components/FloatingActionButton/FloatingActionButton";
import type { Meta, StoryObj } from "@storybook/nextjs-vite";
import React from "react";
import React, { useState } from "react";
import { HomePage } from ".";
import BottomNavigation from "./components/BottomNavigation";
import { FloatingActionButton } from "./components/FloatingActionButton";
import { Tabs } from "./components/Tabs";
import { TAB_ITEMS, type TabsState } from "./types";

const OpenIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none">
Expand Down Expand Up @@ -56,6 +59,17 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;

export const Navigation_Bar: Story = {
args: {},
render: () => {
return (
<div style={{ width: "360px", height: "360px" }}>
<BottomNavigation />
</div>
);
},
};

export const FAB_버튼: Story = {
args: {},
render: () => {
Expand All @@ -73,3 +87,16 @@ export const FAB_버튼: Story = {
);
},
};

export const 탭: Story = {
args: {},
render: () => {
const [activeTab, setActiveTab] = useState<TabsState>(TAB_ITEMS.DAILY);

return (
<div style={{ width: "360px", height: "360px" }}>
<Tabs activeTab={activeTab} setActiveTab={setActiveTab} />
</div>
);
},
};
6 changes: 3 additions & 3 deletions apps/web/src/widgets/home/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { TAB_ITEMS, type TabsState } from "@/views/home/types";
import Image from "next/image";
import { css } from "styled-system/css";
import { css } from "../../../styled-system/css";

type TabsProps = {
activeTab: TabsState;
Expand Down Expand Up @@ -49,7 +49,7 @@ export function Tabs({ activeTab, setActiveTab }: TabsProps) {
color: activeTab === TAB_ITEMS.DAILY ? "black" : "#B7C2D0",
})}
>
Check List
일일 업무
</p>
</li>
<li
Expand Down Expand Up @@ -80,7 +80,7 @@ export function Tabs({ activeTab, setActiveTab }: TabsProps) {
color: activeTab === TAB_ITEMS.MEETING ? "black" : "#B7C2D0",
})}
>
Meeting
회의 일정
</p>
</li>
</ul>
Expand Down
Loading