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] DropDownOption 에 대해서 context 로 분리 #93

Merged
merged 29 commits into from
Jul 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8f751e6
feat : 서치바 구현
SeieunYoo Jul 16, 2024
4cf6973
feat : 스토리북 추가
SeieunYoo Jul 17, 2024
00c26bf
feat : 서치바 테스트 코드 작성 및 aria 속성 수정
SeieunYoo Jul 17, 2024
5c57c1a
refactor: useFormCOntrol 로 분리
SeieunYoo Jul 19, 2024
69c641d
chore: 체인지셋 작성
SeieunYoo Jul 19, 2024
42500ff
refactor : 드롭다운 컨텍스트로 분리
SeieunYoo Jul 21, 2024
829f096
Merge branch 'main' of https://github.com/GDSC-Hongik/wow-design-syst…
SeieunYoo Jul 21, 2024
ea56104
refactor : 절대경로로 수정
SeieunYoo Jul 21, 2024
dc8c589
fix : ts 로 변경
SeieunYoo Jul 21, 2024
6b3db85
feat : DropDownOption 에 use client 추가, useSafeContext 추가
SeieunYoo Jul 21, 2024
6574310
refactor: useSafeContext 의 타입 추론 강화, aria 속성 추가
SeieunYoo Jul 21, 2024
4343ad0
fix : 접근성 테스트 해결, test ci 해결
SeieunYoo Jul 21, 2024
88f7fce
fix: 텍필 원복
SeieunYoo Jul 21, 2024
c5edc59
style : trigger 가 있을 때는 100% 유지
SeieunYoo Jul 21, 2024
3c38d6b
refactor: useDropDownstate 수정
SeieunYoo Jul 25, 2024
b001317
refactor : DropDownTrigger 로 리팩토링
SeieunYoo Jul 26, 2024
504878c
refactor : collection context 로 분리
SeieunYoo Jul 26, 2024
f8527b2
refactor : 안쓰는 import 삭제, 제외하는 컴포넌트 build 스크립트 추가
SeieunYoo Jul 26, 2024
eb86299
refactor : useCallback 추가해서 렌더링 최소화
SeieunYoo Jul 26, 2024
fdcb756
fix : 필요없는 코드 삭제
SeieunYoo Jul 26, 2024
8be18f5
refactor: values함수 내부로 이동
SeieunYoo Jul 26, 2024
2b66ec1
fix : flat 해주는 훅 삭제
SeieunYoo Jul 26, 2024
ad51ddb
Merge branch 'main' of https://github.com/GDSC-Hongik/wow-design-syst…
SeieunYoo Jul 26, 2024
50d1ee5
chore : changeset 추가
SeieunYoo Jul 26, 2024
d772386
fix : defaultValue 관련 수정, 드롭다운 width 스타일 수정
SeieunYoo Jul 27, 2024
c2ad801
feat : 스토리북 보완
SeieunYoo Jul 27, 2024
b9072e3
fix: 스토리북 수정
SeieunYoo Jul 27, 2024
6ca0434
refacotor: OptionList 에서 focus 로직 처리하도록 분리
SeieunYoo Jul 28, 2024
6440016
refactor : itemMap 객체에서 변경
SeieunYoo Jul 28, 2024
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
5 changes: 5 additions & 0 deletions .changeset/four-peaches-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wowds-ui": patch
---

DropDownOption 컴포넌트를 context 를 이용하여 리팩토링 합니다.
4 changes: 2 additions & 2 deletions apps/wow-docs/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ const Home = () => {
<RadioButton label="2학년" value="2학년" />
</RadioGroup>
<DropDown placeholder="목록을 입력하세요">
<DropDownOption text="option 1" value="option 1" />
<DropDownOption text="option 1" value="option 2" />
<DropDownOption text=" 1" value="option 1" />
<DropDownOption text="옵 2" value="option 2" />
</DropDown>
<MultiGroup variant="checkbox">
<Checkbox label="checkbox1" value="checkbox1" />
Expand Down
11 changes: 10 additions & 1 deletion packages/scripts/generateBuildConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,23 @@ type EntryFileKey = string;
type EntryFileValue = string;
type EntryFileObject = { [key: EntryFileKey]: EntryFileValue };

// 제외할 컴포넌트 목록
const excludedComponents = [
"DropDownTrigger",
"DropDownWrapper",
"CollectionContext",
"DropDownOptionList",
];

const getFilteredComponentFiles = async (directoryPath: string) => {
const files = await fs.readdir(directoryPath, { recursive: true });

return files.filter(
(file) =>
file.endsWith(".tsx") &&
!file.includes("test") &&
!file.includes("stories")
!file.includes("stories") &&
!excludedComponents.some((excluded) => file.includes(excluded))
);
};

Expand Down
36 changes: 32 additions & 4 deletions packages/wow-ui/src/components/DropDown/DropDown.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ const meta = {
component: DropDown,
tags: ["autodocs"],
parameters: {
componentSubtitle: "드롭다운 컴포넌트",
componentSubtitle:
"사용자가 외부 트리거 컴포넌트나 내부 요소를 통해서 선택 옵션 리스트 중에 아이템을 선택할 수 있는 드롭다운 컴포넌트 입니다.",
a11y: {
config: {
rules: [{ id: "color-contrast", enabled: false }],
},
},
docs: {
description: {
component:
"children 에 DropDownOption 컴포넌트를 넣어서 사용합니다. 외부 트리거를 사용할 경우 trigger 를 사용합니다.",
},
},
},
argTypes: {
children: {
Expand Down Expand Up @@ -48,6 +55,13 @@ const meta = {
},
control: { type: "text" },
},
id: {
description: "드롭다운의 id 를 나타냅니다.",
table: {
type: { summary: "string" },
},
control: { type: "text" },
},
value: {
description: "현재 선택된 값을 나타냅니다.",
table: {
Expand All @@ -72,6 +86,20 @@ const meta = {
},
action: "changed",
},
style: {
description: "드롭다운의 커스텀 스타일을 설정할 수 있습니다.",
table: {
type: { summary: "CSSProperties" },
},
control: "object",
},
className: {
description: "드롭다운 전달하는 커스텀 클래스를 설정합니다.",
table: {
type: { summary: "string" },
},
control: "text",
},
},
} satisfies Meta<typeof DropDown>;

Expand Down Expand Up @@ -122,13 +150,13 @@ export const WithDefaultValue: Story = {
args: {
children: (
<>
<DropDownOption text="option 1" value="option 1" />
<DropDownOption text="option 2" value="option 2" />
<DropDownOption text="옵션 1" value="option 1" />
<DropDownOption text="옵션 2" value="option 2" />
</>
),
label: "Select an Option",
placeholder: "Please select",
defaultValue: "Option 2",
defaultValue: "option 2",
},
parameters: {
docs: {
Expand Down
52 changes: 39 additions & 13 deletions packages/wow-ui/src/components/DropDown/DropDownOption.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,65 @@
"use client";

import { cva } from "@styled-system/css";
import { styled } from "@styled-system/jsx";
import type { ReactNode } from "react";
import { forwardRef } from "react";
import { forwardRef, useCallback, useEffect } from "react";

import { useDropDownContext } from "@/components/DropDown/context/DropDownContext";

import { useCollection } from "./context/CollectionContext";

/**
* @description 드롭다운 옵션의 props입니다.
* @description DropDown의 옵션을 나타내는 DropDownOption 컴포넌트입니다.
*
* @param {boolean} [focused] 옵션이 포커스된 상태인지 여부를 나타냅니다.
* @param {boolean} [selected] 옵션이 선택된 상태인지 여부를 나타냅니다.
* @param {string} value 옵션의 값입니다.
* @param {() => void} [onClick] 옵션이 클릭되었을 때 호출되는 함수입니다.
* @param {React.ReactNode} [text] 드롭다운 옵션에 들어갈 텍스트.
*/
export interface DropDownOptionProps {
focused?: boolean;
selected?: boolean;
value: string;
onClick?: () => void;
text: ReactNode;
}

const DropDownOption = forwardRef<HTMLDivElement, DropDownOptionProps>(
function Option({ value, onClick, focused, text, selected }, ref) {
const DropDownOption = forwardRef<HTMLLIElement, DropDownOptionProps>(
function Option({ value, onClick, text }, ref) {
const { focusedValue, selectedValue, handleSelect } = useDropDownContext();
SeieunYoo marked this conversation as resolved.
Show resolved Hide resolved
const isSelected = selectedValue === value;
const isFocused = focusedValue !== null && focusedValue === value;

const handleOptionClick = useCallback(
(value: string, onClick?: () => void) => {
if (onClick) onClick();
handleSelect(value, text);
},
[handleSelect, text]
);

const itemMap = useCollection();

useEffect(() => {
const currentItem = itemMap.get(value);
if (!currentItem || currentItem !== text) {
itemMap.set(value, text);
}
}, [itemMap, value, text]);

return (
<styled.div
<styled.li
id={`dropdown-option-${value}`}
ref={ref}
tabIndex={-1}
role="option"
tabIndex={isSelected ? 0 : -1}
className={optionStyle({
type: selected ? "selected" : focused ? "focused" : "default",
type: isSelected ? "selected" : isFocused ? "focused" : "default",
})}
onClick={onClick}
onClick={() => {
handleOptionClick(value, onClick);
}}
>
{text}
</styled.div>
</styled.li>
);
}
);
Expand Down
127 changes: 127 additions & 0 deletions packages/wow-ui/src/components/DropDown/DropDownOptionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { cva } from "@styled-system/css";
import { styled } from "@styled-system/jsx";
import {
type KeyboardEvent,
type PropsWithChildren,
useCallback,
useEffect,
useRef,
} from "react";

import { useCollection } from "./context/CollectionContext";
import { useDropDownContext } from "./context/DropDownContext";

interface DropDownWrapperProps extends PropsWithChildren {
hasCustomTrigger?: boolean;
}
export const DropDownOptionList = ({
children,
hasCustomTrigger,
}: DropDownWrapperProps) => {
const { open, setFocusedValue, focusedValue, handleSelect } =
useDropDownContext();
const itemMap = useCollection();
const listRef = useRef<HTMLUListElement>(null);

useEffect(() => {
if (open && listRef.current) {
listRef.current.focus();
}
}, [open]);

const updateFocusedValue = useCallback(
(direction: number) => {
const values = Array.from(itemMap.keys());
setFocusedValue((prevValue) => {
const currentIndex = values.indexOf(prevValue ?? "");
const nextIndex =
(currentIndex + direction + values.length) % values.length;
return values[nextIndex] ?? "";
});
},
[itemMap, setFocusedValue]
);

const handleKeyDown = useCallback(
(event: KeyboardEvent<HTMLUListElement>) => {
if (!open) return;

const { key } = event;

if (key === "ArrowDown") {
updateFocusedValue(1);
event.preventDefault();
} else if (key === "ArrowUp") {
updateFocusedValue(-1);
event.preventDefault();
} else if (key === "Enter" && focusedValue !== null) {
handleSelect(focusedValue, itemMap.get(focusedValue));
event.preventDefault();
}
},
[open, focusedValue, updateFocusedValue, handleSelect, itemMap]
);

return (
<styled.ul
display="flex"
flexDirection="column"
ref={listRef}
role="listbox"
style={{ visibility: open ? "visible" : "hidden" }}
tabIndex={0}
visibility={open ? "visible" : "hidden"}
className={dropdownContentStyle({
type: hasCustomTrigger ? "custom" : "default",
})}
onKeyDown={handleKeyDown}
>
{children}
</styled.ul>
);
};

const dropdownContentStyle = cva({
base: {
position: "absolute",
outline: "none",
top: "calc(100% + 0.5rem)",
left: 0,
zIndex: "dropdown",
maxHeight: "18.75rem",
width: "100%",
lg: {
maxWidth: "22.375rem",
},
smDown: {
width: "100%",
},
backgroundColor: "backgroundNormal",
border: "1px solid",
borderRadius: "sm",
borderColor: "outline",
overflow: "auto",
_scrollbar: {
width: "2px",
},
_scrollbarThumb: {
width: "2px",
height: "65px",
borderRadius: "sm",
backgroundColor: "outline",
},
_scrollbarTrack: {
marginTop: "2px",
marginBottom: "2px",
},
},
variants: {
type: {
custom: {
lg: {},
},
default: {},
},
},
defaultVariants: { type: "default" },
});
Loading
Loading