Skip to content

Feat(client): 태그 선택 트리 컴포넌트 구현 - #287

Open
twossu wants to merge 8 commits into
developfrom
feat/tag-tree-select-component/#286
Open

Feat(client): 태그 선택 트리 컴포넌트 구현#287
twossu wants to merge 8 commits into
developfrom
feat/tag-tree-select-component/#286

Conversation

@twossu

@twossu twossu commented Aug 12, 2026

Copy link
Copy Markdown
Member

📌 Summary

태그를 트리 구조로 보여주고 다중 선택할 수 있는 TagTreeSelect 컴포넌트를 만들었습니다.

📚 Tasks

  • TagTreeSelectItem 컴포넌트 구현
  • TagTreeSelect 컴포넌트 구현
  • 하위 태그 펼침/접힘에 부드러운 열림 애니메이션 적용

🔍 Describe

TagTreeSelectItem

TagTreeSelectItem은 태그 노드 하나를 그리고 자식이 있으면 자기 자신을 재귀 호출해서 하위 태그까지 그려내는 컴포넌트입니다.

  • 펼침 버튼과 체크박스를 별도 버튼으로 분리: children.length > 0일 때만 hasChildrentrue가 되어 좌측에 펼침/접힘 버튼이 함께 노출됩니다. 이때 펼침 버튼과 체크박스(선택) 영역을 하나의 button에 몰아넣지 않고 서로 다른 <button>으로 분리했습니다. 만약 같은 버튼이었다면 "하위 태그가 있는 부모 태그를 선택"하려고 클릭했을 때 펼침 상태까지 같이 토글되는 의도치 않은 부작용이 생겼을 텐데 버튼을 분리함으로써 두 동작(선택 토글 / 펼침 토글)이 서로 완전히 독립적으로 동작합니다.
  • 재귀 렌더링: hasChildrentrue인 경우에만 tag.children을 순회하며 TagTreeSelectItem을 다시 렌더링합니다. 이 재귀 호출은 TreeLine.Branch로 감싸져 있어서 자식 목록에는 부모-자식 관계를 보여주는 커넥터 라인(TreeLine)이 자동으로 그려집니다.
  • 접근성: 체크박스 버튼에는 aria-pressed를 펼침 버튼에는 펼침/접힘 상태에 따라 다른 aria-label("하위 태그 펼치기"/"하위 태그 접기")을 넣어 스크린 리더에서도 현재 상태를 알 수 있게 했습니다.

TagTreeSelect

TagTreeSelectTagTreeSelectItemTreeLine으로 감싸서 트리 전체의 진입점 역할을 하는 컨테이너입니다.

  • props: tag: TreeNode<TagNode>(루트 태그 하나) / selectedIds / onToggle을 상위 컴포넌트에서 전달받습니다. 즉 선택 상태는 상위 컴포넌트가 소유하고 TagTreeSelect는 그 상태를 그대로 트리 아래로 흘려보내는 역할만 합니다.
  • 펼침/접힘 상태는 컴포넌트 내부에서 관리: collapsedIds(접힌 태그 id 배열)는 TagTreeSelect 내부 useState로 관리하며, handleToggleExpand가 이 상태를 토글합니다. 선택 상태와 달리 펼침 상태는 굳이 상위로 끌어올릴 필요가 없다고 판단해서 트리 컴포넌트 안에 캡슐화했습니다. 초기값이 빈 배열이라 별도 조작이 없으면 트리는 전부 펼쳐진 상태로 시작합니다.
  • 이 구조 덕분에 상위 컴포넌트는 "어떤 태그가 선택됐는지"만 신경 쓰면 되고, "트리가 지금 어디까지 펼쳐져 있는지" 같은 UI 디테일은 TagTreeSelect가 알아서 처리합니다.

부드럽게 열리는 드롭다운

display: gridgrid-template-rows를 이용한 트릭으로 하위 태그 목록이 접히고 펼쳐질 때 자연스러운 슬라이드 애니메이션이 나오도록 했습니다.

  • grid-template-rows: 0fr → 1fr 트릭: branchContainerdisplay: grid로 만들고 grid-template-rows를 접힘 상태에서는 0fr, 펼침 상태(isExpanded: true)에서는 1fr로 바꿔줍니다. fr 단위는 트랜지션이 가능한 값이라 실제 콘텐츠 높이가 얼마든 상관없이 0에서 콘텐츠의 실제 높이까지 부드럽게 전환됩니다.
  • branchInner가 필요한 이유: grid item(자식 요소)은 기본적으로 min-height: auto라서 grid row가 0fr이어도 콘텐츠가 넘쳐서 잘리지 않고 그대로 보여버리는 문제가 있었습니다. 그래서 branchContainer 안쪽에 overflow: hidden을 가진 branchInner를 한 겹 더 감싸서 row 크기가 줄어들 때 실제로 내용이 잘려 보이도록 만들었습니다.

👀 To Reviewer

tag-tree-select 네이밍 추천해주세요!
지금 네이밍이 더 좋은게 있을 것 같은데 추천해주사오요
저는 헤린맘애 사이드바pr이없었더라면 이세상에서도 없었을 것이오.
디자인분들과 디자인 쇼부중입니다 ---

📸 Screenshot

2026-08-19.3.29.35.mov

@twossu
twossu requested a review from a team as a code owner August 12, 2026 15:08
@twossu
twossu requested review from jm8468, jogpfls and jyeon03 and removed request for a team August 12, 2026 15:08
@twossu twossu linked an issue Aug 12, 2026 that may be closed by this pull request
@github-actions github-actions Bot added ✨ Feat 새로운 기능 추가 🦦 최윤하 웹 37기 최윤하 labels Aug 12, 2026
@twossu
twossu marked this pull request as draft August 12, 2026 15:08
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

🎨 Storybook 배포 완료

PR 작성자: @twossu

🔗 배포된 Storybook 보기

@twossu
twossu marked this pull request as ready for review August 14, 2026 12:35

@jm8468 jm8468 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

👍👍 태스크 맡아주셔서 완전감사합니다

onToggleExpand: (tagId: number) => void;
}

const TagTreeSelectItem = ({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p5) TagTreeNode?

트리에서 부모노드 자식노드 이런 말 쓰니까 요런 건 어떤가영

onToggle: (tagId: number) => void;
}

const TagTreeSelect = ({ tag, selectedIds, onToggle }: TagTreeSelectProps) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

같은 느낌으로 TagTree 어떤가용

@twossu twossu Aug 19, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

TagTree가 직관적이어서 좋기는 한데 사이드바의 SidebarTagSection도 태그 트리를 렌더링하는 역할이라 이름이 겹치는? 느낌이라 헷갈릴 것 같아서 난감하네요.......... 위 코멘트도 tagtreenode 너무 좋은데 이 TagTreeSelect상위 컴포넌트 네이밍이 정해지면 같이 바꾸면 좋을 것 같아요 이름정하기 정말 어렵네요...
tag-tree-node로 바꾸는 것에 대해서는 어차피 tag-tree-select컴포넌트 내에서만 사용되기에 변경되도 괜찮을 것 같다는 생각이 들기도하고하고하고..

Comment on lines +26 to +27
const isExpanded = !collapsedIds.includes(tag.tagId);
const isChecked = selectedIds.includes(tag.tagId);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

렌더마다 배열을 순회해서 시간적 비용이 높아질 것 같아요
Set이나 Record를 사용하면 읽기 비용이 O(1)이 돼서 추천드려용

@twossu
twossu marked this pull request as draft August 20, 2026 06:44
@twossu
twossu marked this pull request as ready for review August 22, 2026 02:17
@twossu
twossu force-pushed the feat/tag-tree-select-component/#286 branch from 1c61e5e to 9c71a78 Compare August 22, 2026 13:22
@twossu
twossu force-pushed the feat/tag-tree-select-component/#286 branch from 9c71a78 to a7b62e4 Compare August 22, 2026 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feat 새로운 기능 추가 🦦 최윤하 웹 37기 최윤하

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] tag-tree-select 컴포넌트 구현

2 participants