From a722cab92d4015a046ae3cf6a5ec39a0ba4f356c Mon Sep 17 00:00:00 2001 From: chunjaemin Date: Mon, 23 Feb 2026 14:06:07 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[DEL/#18]=20shared/ui/intex.ts=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/index.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/shared/ui/index.ts diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts deleted file mode 100644 index e1856bb..0000000 --- a/src/shared/ui/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./select"; From 8f0b16340cfd1baab3064f20016f1008dafeab72 Mon Sep 17 00:00:00 2001 From: chunjaemin Date: Mon, 23 Feb 2026 14:32:09 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[REFACTOR/#18]=20css=20style=20nativewind?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/select/Select.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/shared/ui/select/Select.tsx b/src/shared/ui/select/Select.tsx index 1cc0ae7..22ec3b1 100644 --- a/src/shared/ui/select/Select.tsx +++ b/src/shared/ui/select/Select.tsx @@ -43,7 +43,7 @@ export function Select({ return ( {!!label && ( - + {label} )} @@ -119,13 +119,10 @@ export function Select({ return ( {item.label} From e16d53acc337c0322454ba17b4ac43b66fb6f43a Mon Sep 17 00:00:00 2001 From: chunjaemin Date: Mon, 23 Feb 2026 14:34:57 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[ADD/#18]=20select=20README.md=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/select/README.md | 160 +++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 src/shared/ui/select/README.md diff --git a/src/shared/ui/select/README.md b/src/shared/ui/select/README.md new file mode 100644 index 0000000..c8c7109 --- /dev/null +++ b/src/shared/ui/select/README.md @@ -0,0 +1,160 @@ +# Select(드롭다운) 사용 가이드 + +이 문서는 `A:SSU` 프로젝트의 공용 `Select`(드롭다운) 컴포넌트 사용법을 정리합니다. + +## 구현 세부사항 + +- **내부 구현**: `react-native-element-dropdown`라이브러리 기반 래퍼(Wrapper) +- **스타일링**: + - NativeWind `className` + RN `style` 혼합 + - 색상은 `global.styles.css` 디자인 토큰과 **동일 값**을 갖는 TS 토큰(`src/shared/styles/tokens.ts`)을 통해 사용 + +## 설치 의존성 + +이 컴포넌트는 내부적으로 `react-native-element-dropdown`를 사용합니다. + +```bash +yarn add react-native-element-dropdown +``` + +## import 방법 +- `import { Select } from "@/shared/ui";` + + + +## 빠른 사용 예시 (가장 기본) + +`Select`는 **controlled 컴포넌트**입니다. 즉, `items/value/onChange`를 항상 함께 사용합니다. + +```tsx +import { useMemo, useState } from "react"; +import { View, Text } from "react-native"; +import { Select } from "@/shared/ui"; + +export default function Example() { + const items = useMemo( + () => [ + { label: "총학생회", value: "university" }, + { label: "단과대학 학생회", value: "college" }, + { label: "학과/부 학생회", value: "department" }, + ], + [] + ); + + const [value, setValue] = useState(null); + + return ( + + ; +``` + +### 2) Zustand(전역 상태) + +상태를 store에서 꺼내 props로 그대로 연결합니다. + +```tsx +const value = useLoginStore((s) => s.councilType); +const setValue = useLoginStore((s) => s.setCouncilType); + + +``` + +### 2) “특정 옵션만” disabled (`items[].disabled`) + +이건 **목록은 보여주되, 일부 항목만 선택 불가**로 만드는 옵션입니다. + +- **언제 쓰나** + - “준비중/마감/권한 없음” 같은 상태를 옵션으로 노출해야 할 때 + - 리스트에서 존재는 알려야 하지만, 선택은 막아야 할 때 + +```tsx +const items = [ + { label: "총학생회", value: "university" }, + { label: "단과대학 학생회(준비중)", value: "college", disabled: true }, + { label: "학과/부 학생회", value: "department" }, +]; +``` + From e4158d89ceb828e2b5f81326d44b79d39094992d Mon Sep 17 00:00:00 2001 From: chunjaemin Date: Mon, 23 Feb 2026 14:59:45 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[FIX/#18]=20disabled=20=EC=98=B5=EC=85=98?= =?UTF-8?q?=20=EC=98=AC=EB=B0=94=EB=A5=B4=EA=B2=8C=20=EB=8F=99=EC=9E=91?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/select/Select.tsx | 42 +++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/shared/ui/select/Select.tsx b/src/shared/ui/select/Select.tsx index 22ec3b1..8833919 100644 --- a/src/shared/ui/select/Select.tsx +++ b/src/shared/ui/select/Select.tsx @@ -1,5 +1,5 @@ import { Ionicons } from "@expo/vector-icons"; -import { useMemo, useState } from "react"; +import { useState } from "react"; import { Text, View } from "react-native"; import { Dropdown } from "react-native-element-dropdown"; import { shadows } from "@/shared/styles/shadows"; @@ -33,12 +33,7 @@ export function Select({ }: SelectProps) { const sizeToken = SIZES[size]; const [isOpen, setIsOpen] = useState(false); - - const dropdownData = useMemo(() => { - // react-native-element-dropdown은 disabled key가 없어서 - // item 렌더링/선택 로직에서 직접 처리한다. - return items; - }, [items]); + const [disabledTapNonce, setDisabledTapNonce] = useState(0); return ( @@ -49,7 +44,11 @@ export function Select({ )} setIsOpen(false)} onChange={(item: SelectItem) => { // disabled 항목은 선택 무시 - if (item?.disabled) return; + if (item?.disabled) { + setDisabledTapNonce((n) => n + 1); + return; + } onChange(item?.value ?? null); }} // NOTE: Dropdown의 `style`은 내부에서 width를 측정하는 컨테이너(View)에 적용됩니다. @@ -115,25 +117,39 @@ export function Select({ }} renderItem={(item: SelectItem) => { const isSelected = item.value === value; + const isDisabled = Boolean(item.disabled); return ( - + {item.label} - {isSelected && ( + {isDisabled ? ( + + ) : isSelected ? ( - )} + ) : null} ); }} From 736be068855dfb4c48d9d45b2d8f0441635caab0 Mon Sep 17 00:00:00 2001 From: chunjaemin Date: Mon, 23 Feb 2026 15:12:17 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[DOCS/#18]=20README.md=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=82=AC=ED=95=AD=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/select/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/shared/ui/select/README.md b/src/shared/ui/select/README.md index c8c7109..5c30a6a 100644 --- a/src/shared/ui/select/README.md +++ b/src/shared/ui/select/README.md @@ -18,7 +18,7 @@ yarn add react-native-element-dropdown ``` ## import 방법 -- `import { Select } from "@/shared/ui";` +- `import { Select } from "@/shared/ui/select";` @@ -29,7 +29,7 @@ yarn add react-native-element-dropdown ```tsx import { useMemo, useState } from "react"; import { View, Text } from "react-native"; -import { Select } from "@/shared/ui"; +import { Select } from "@/shared/ui/select"; export default function Example() { const items = useMemo( @@ -113,8 +113,12 @@ const setValue = useLoginStore((s) => s.setCouncilType); `items`의 `disabled: true` 항목은: - 리스트에서 opacity가 낮게 보이고 +- 잠금 아이콘이 표시됩니다 - 선택이 무시됩니다(선택값이 바뀌지 않음) +> NOTE: `react-native-element-dropdown`는 item 단위 disabled를 공식 지원하지 않아, +> disabled 항목 탭 시 내부 선택 표시가 바뀔 수 있습니다. `Select`는 이 경우 표시값이 남지 않도록 내부적으로 강제 리마운트로 원복합니다. + ```tsx const items = [ { label: "총학생회", value: "university" },