@idbrnd/design-system 4.4.0 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @idbrnd/design-system
2
2
 
3
- ![Version](https://img.shields.io/static/v1?label=version&message=4.4.0&color=blue&style=flat-square) <!-- x-release-please-version -->
3
+ ![Version](https://img.shields.io/static/v1?label=version&message=4.5.1&color=blue&style=flat-square) <!-- x-release-please-version -->
4
4
  ![React](https://img.shields.io/badge/React-18.3.1-61DAFB?style=flat-square&logo=react&logoColor=white)
5
5
  ![Vite](https://img.shields.io/badge/Vite-7.3.0-646CFF?style=flat-square&logo=vite&logoColor=white)
6
6
  ![TypeScript](https://img.shields.io/badge/TypeScript-5.9.3-3178C6?style=flat-square&logo=typescript&logoColor=white)
@@ -60,6 +60,7 @@ export default function App() {
60
60
  - [EmptyState](#emptystate)
61
61
  - [Tooltip](#tooltip)
62
62
  - [Modal](#modal)
63
+ - [Popup](#popup)
63
64
  - [Select](#select)
64
65
  - [Dropdown](#dropdown)
65
66
  - [DatePicker](#datepicker)
@@ -907,6 +908,98 @@ const [open, setOpen] = useState(false);
907
908
  - `closeOnDimClick`은 기본값이 `false`입니다. 딤에서 누르기 시작해 모달 내부로 드래그하다 놓는 경우는 닫기로 처리하지 않습니다(누르기·떼기가 모두 딤 위에서 일어나야 함).
908
909
  - `navBar={false}`로 헤더 전체를 숨길 수 있고, `actionSlot`/`mainAction`/`subAction`을 모두 생략하면 액션 영역 자체가 렌더링되지 않습니다.
909
910
  - 닫힐 때 150ms 페이드+스케일 애니메이션 후 언마운트되며, `prefers-reduced-motion: reduce` 환경에서는 애니메이션이 비활성화됩니다.
911
+ - 열릴 때 다이얼로그로 옮겨간 포커스는 닫힐 때 열리기 직전에 포커스가 있던 요소(보통 트리거 버튼)로 되돌립니다. 액션 핸들러가 이미 포커스를 다른 곳으로 옮겼다면 덮어쓰지 않습니다. 트리거가 `react-modal` 같은 다른 모달 안에 있어도 그 모달의 ESC·포커스 트랩이 끊기지 않습니다.
912
+
913
+ ---
914
+
915
+ ### Popup
916
+
917
+ 간단한 경고나 짧은 메시지로 사용자의 즉각적인 확인을 받아야 할 때 화면 중앙에 노출되는 컴포넌트입니다. 제목·본문·액션 버튼(최대 2개)의 단순한 구조만 제공하며, 서비스 이탈·데이터 삭제처럼 치명적인 확인이 필요한 경우에 사용을 권장합니다. 이미지·리스트 같은 복잡한 콘텐츠는 `Modal`을 사용하세요. `open`/`onClose`로 제어하는 controlled 컴포넌트이며, `document.body`에 포털로 렌더링됩니다.
918
+
919
+ ```tsx
920
+ import { useState } from "react";
921
+ import { Popup, FillButton } from "@idbrnd/design-system";
922
+
923
+ const [open, setOpen] = useState(false);
924
+
925
+ <FillButton variant="error" onClick={() => setOpen(true)}>삭제</FillButton>
926
+ <Popup
927
+ open={open}
928
+ onClose={() => setOpen(false)}
929
+ heading="정말 삭제하시겠어요?"
930
+ body="삭제한 항목은 복구할 수 없습니다."
931
+ mainAction={{ label: "삭제", onClick: handleDelete, variant: "error" }}
932
+ subAction={{ label: "취소", onClick: () => setOpen(false) }}
933
+ />;
934
+ ```
935
+
936
+ **platform / variant 조합**
937
+
938
+ ```tsx
939
+ {/* text: 액션을 TextButton 으로 우측 정렬 */}
940
+ <Popup open={open} onClose={onClose} variant="text" heading="..." body="..." mainAction={...} subAction={...} />
941
+
942
+ {/* mobile: 버튼 large, basic 액션은 동일 너비로 분할 */}
943
+ <Popup open={open} onClose={onClose} platform="mobile" heading="..." body="..." mainAction={...} subAction={...} />
944
+ ```
945
+
946
+ **액션 영역 직접 구성**
947
+
948
+ ```tsx
949
+ <Popup
950
+ open={open}
951
+ onClose={onClose}
952
+ heading="담당자 지정이 필요한 사용자가 있습니다."
953
+ actionSlot={
954
+ <div style={{ display: "flex", flexDirection: "column", gap: 12, width: "100%" }}>
955
+ <FillButton widthType="fixed" onClick={handleAssignAll}>모두 담당자로 지정하고 발송</FillButton>
956
+ <OutlineButton widthType="fixed" onClick={handleOnlyManagers}>기존 담당자에게만 발송</OutlineButton>
957
+ </div>
958
+ }
959
+ />
960
+ ```
961
+
962
+ | Prop | 타입 | 기본값 | 설명 |
963
+ | ----------------- | ------------------------- | --------- | -------------------------------------------------------------------- |
964
+ | `open` | `boolean` | — | **(필수)** 팝업 표시 여부 (controlled) |
965
+ | `onClose` | `() => void` | — | **(필수)** ESC 키·딤 클릭으로 닫힐 때 호출 |
966
+ | `platform` | `"web"` \| `"mobile"` | `"web"` | 크기 프리셋 — `mobile`은 버튼 large, basic 액션을 동일 너비로 분할 |
967
+ | `variant` | `"basic"` \| `"text"` | `"basic"` | 액션 버튼 형태 — `basic`은 Fill/Outline 버튼, `text`는 TextButton 우측 정렬 |
968
+ | `heading` | `ReactNode` | — | 타이틀 (`body`와 둘 중 하나는 필수) |
969
+ | `body` | `ReactNode` | — | 본문 (`heading`과 둘 중 하나는 필수) |
970
+ | `mainAction` | `PopupAction` | — | 메인 액션 — 우측 배치, 시각적 위계 최상위 |
971
+ | `subAction` | `PopupAction` | — | 서브 액션 — 메인 액션 좌측 배치 |
972
+ | `actionSlot` | `ReactNode` | — | 지정 시 `mainAction`/`subAction` 대신 액션 영역을 통째로 교체 |
973
+ | `closeOnEsc` | `boolean` | `true` | ESC 키로 닫기 허용 |
974
+ | `closeOnDimClick` | `boolean` | `false` | 딤 클릭으로 닫기 허용 |
975
+ | `customStyle` | `CSSProperties` | — | 다이얼로그 루트에 적용할 추가 인라인 스타일 |
976
+ | `className` | `string` | — | 다이얼로그 루트에 추가할 클래스명 |
977
+
978
+ **`PopupAction`**
979
+
980
+ | 필드 | 타입 | 설명 |
981
+ | ---------- | ------------------------- | ------------------------------------------------------------------------ |
982
+ | `label` | `string` | **(필수)** 버튼 레이블 |
983
+ | `onClick` | `() => void` | **(필수)** 클릭 콜백 |
984
+ | `variant` | `"primary"` \| `"error"` | 메인 액션 색상 — 데이터 삭제 등 파괴적 행동 확인에는 `error`(빨강). 서브 액션에는 적용되지 않음 |
985
+ | `loading` | `boolean` | 로딩 스피너 표시 여부 |
986
+ | `disabled` | `boolean` | 비활성화 여부 |
987
+
988
+ **버튼 매핑**
989
+
990
+ | `variant` | `subAction` | `mainAction` |
991
+ | --------- | ------------------------- | -------------------- |
992
+ | `basic` | `OutlineButton` secondary | `FillButton` primary/error |
993
+ | `text` | `TextButton` assistive | `TextButton` primary |
994
+
995
+ 유의사항:
996
+
997
+ - `heading`과 `body` 중 하나는 반드시 넘겨야 합니다. 둘 다 없으면 개발 모드에서 `console.warn`이 출력됩니다(렌더는 막지 않음).
998
+ - 액션을 클릭해도 자동으로 닫히지 않습니다. `Modal`과 마찬가지로 호출자가 `onClick`에서 `onClose`를 호출하며, 비동기 액션은 `loading` 후 닫는 흐름을 그대로 쓸 수 있습니다.
999
+ - 너비는 콘텐츠에 맞춰 320~440px 범위에서 결정됩니다(`calc(100vw - 32px)` 이내).
1000
+ - 배경 스크롤 차단, 중첩 스택, "ESC는 최상단 오버레이만 닫힘"은 `Modal`과 같은 공용 로직을 사용합니다. `Modal` 위에 `Popup`을 띄우는 중첩도 지원합니다.
1001
+ - 열릴 때 다이얼로그로 옮겨간 포커스는 닫힐 때 열리기 직전에 포커스가 있던 요소(보통 트리거 버튼)로 되돌립니다. 액션 핸들러가 이미 포커스를 다른 곳으로 옮겼다면 덮어쓰지 않습니다. 트리거가 `react-modal` 같은 다른 모달 안에 있어도 그 모달의 ESC·포커스 트랩이 끊기지 않습니다.
1002
+ - 닫힐 때 150ms 페이드 애니메이션 후 언마운트되며, `prefers-reduced-motion: reduce` 환경에서는 애니메이션이 비활성화됩니다.
910
1003
 
911
1004
  ---
912
1005
 
@@ -0,0 +1,2 @@
1
+ import { type RefObject } from "react";
2
+ export declare function useRestoreFocus(open: boolean, dialogRef: RefObject<HTMLElement>): void;
@@ -0,0 +1,3 @@
1
+ import type { IconProps } from "../Icon.types";
2
+ declare const ListIcon: ({ size, color, style, ...props }: IconProps) => import("react/jsx-runtime").JSX.Element;
3
+ export default ListIcon;
@@ -135,6 +135,7 @@ export { default as IntervalUpDownIcon } from "./IntervalUpDownIcon";
135
135
  export { default as LineHorizontalIcon } from "./LineHorizontalIcon";
136
136
  export { default as LineHorizontalThickIcon } from "./LineHorizontalThickIcon";
137
137
  export { default as LinkIcon } from "./LinkIcon";
138
+ export { default as ListIcon } from "./ListIcon";
138
139
  export { default as LoadingIcon } from "./LoadingIcon";
139
140
  export { default as LockFillIcon } from "./LockFillIcon";
140
141
  export { default as LockIcon } from "./LockIcon";
@@ -136,6 +136,7 @@ export declare const iconMap: {
136
136
  readonly "line-horizontal": ({ size, color, style, ...props }: import("./Icon.types").IconProps) => import("react/jsx-runtime").JSX.Element;
137
137
  readonly "line-horizontal-thick": ({ size, color, style, ...props }: import("./Icon.types").IconProps) => import("react/jsx-runtime").JSX.Element;
138
138
  readonly link: ({ size, color, style, ...props }: import("./Icon.types").IconProps) => import("react/jsx-runtime").JSX.Element;
139
+ readonly list: ({ size, color, style, ...props }: import("./Icon.types").IconProps) => import("react/jsx-runtime").JSX.Element;
139
140
  readonly loading: ({ size, color, style, ...props }: import("./Icon.types").IconProps) => import("react/jsx-runtime").JSX.Element;
140
141
  readonly "lock-fill": ({ size, color, style, ...props }: import("./Icon.types").IconProps) => import("react/jsx-runtime").JSX.Element;
141
142
  readonly lock: ({ size, color, style, ...props }: import("./Icon.types").IconProps) => import("react/jsx-runtime").JSX.Element;