sellmate-design-system-react 9.0.0-beta.31 → 9.0.0-beta.32
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/AGENTS.md +89 -12
- package/dist/components/SAccountListBox/AccountListBoxPortal.d.ts +31 -0
- package/dist/components/SAccountListBox/README.md +86 -0
- package/dist/components/SAccountListBox/SAccountListBox.d.ts +79 -0
- package/dist/components/SAccountListBox/SystemMenuPortal.d.ts +58 -0
- package/dist/components/SAccountListBox/accountListBox.config.d.ts +64 -0
- package/dist/components/SAccountListBox/index.d.ts +2 -0
- package/dist/components/SChipFilter/README.md +11 -7
- package/dist/components/SChipFilter/SChipFilter.d.ts +17 -8
- package/dist/components/SGnb/SGnb.d.ts +12 -0
- package/dist/components/SGnbSystem/README.md +27 -22
- package/dist/components/SGnbSystem/SGnbSystem.d.ts +33 -14
- package/dist/components/SIcon/README.md +2 -0
- package/dist/components/SIcon/icons.gen.d.ts +2 -0
- package/dist/components/SPortal/README.md +2 -0
- package/dist/components/SSystemActionButton/README.md +96 -9
- package/dist/components/SSystemActionButton/SSystemActionButton.d.ts +97 -15
- package/dist/components/SSystemActionButton/index.d.ts +1 -1
- package/dist/index.cjs +994 -377
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +989 -378
- package/dist/index.js.map +1 -1
- package/dist/llms-full.txt +303 -50
- package/dist/llms.txt +91 -14
- package/dist/styles.css +54 -26
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
| **입력 (폼)** | `SForm` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
|
|
31
31
|
| **날짜·시간** | `SCalendar` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` |
|
|
32
32
|
| **표·목록** | `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
|
|
33
|
-
| **레이아웃** | `SLayout` `SGnb` `SGnbSystem` `SPage`(제목 영역은 `header` prop) `SSectionHeaderCard` `SCard` `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
|
|
33
|
+
| **레이아웃** | `SLayout` `SGnb` `SGnbSystem` `SAccountListBox`(계정 행을 눌러 뜨는 계정 패널) `SPage`(제목 영역은 `header` prop) `SSectionHeaderCard` `SCard` `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
|
|
34
34
|
| **내비게이션** | `STabs` `SPagination` `SStepper` |
|
|
35
35
|
| **표시·상태** | `STag` `SBadge` `SIcon` `SImage` `SCallout` `SGuide` |
|
|
36
36
|
| **진행·로딩** | `SLinearProgress` `SCircleProgress` `SLoadingContainer` `SLoadingModal` |
|
|
@@ -154,7 +154,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
154
154
|
| **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SForm` `SSplitter` `SScrollArea` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `SCallout` `STabs` `SStepper` `SPagination` `SDivider` |
|
|
155
155
|
| **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SSystemActionButton` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SImage` `SLinearProgress` `SCircleProgress` |
|
|
156
156
|
| **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `STextLink` `SChip` |
|
|
157
|
-
| **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
|
|
157
|
+
| **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SAccountListBox`(계정 행에 붙어 뜬다 — 직접 띄우지 않는다) `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
|
|
158
158
|
|
|
159
159
|
여기에 화면을 차지하지 않는 **부트스트랩** 이 따로 있다 — `SModalOutlet` `SToastContainer` 는 앱 진입점에 한 번만 렌더한다 (§4-1).
|
|
160
160
|
|
|
@@ -536,6 +536,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
536
536
|
| 좌측 내비게이션을 만든다 | `SGnb` | §4-1 |
|
|
537
537
|
| 좌측 내비게이션 맨 아래(또는 전폭 상단바 오른쪽 끝)에 서비스·도메인·알림·설정·계정 묶음을 붙인다 | `SGnbSystem` | §4-1 |
|
|
538
538
|
| 앱 상단바 오른쪽 끝에 알림 벨·계정 이름을 둔다 | `SGnbSystem` (`SGnb` 의 `system` 슬롯 — 직접 만들지 않는다) | §4-1 |
|
|
539
|
+
| 계정 이름을 눌러 이메일·권한·계정 설정·언어 변경·로그아웃을 띄운다 | `SAccountListBox` (`SGnbSystem` 의 `account.listBox` 로 넘긴다 — 직접 띄우지 않는다) | §4-1 |
|
|
539
540
|
| 페이지 본문을 담는다 (패딩·스크롤) | `SPage` | §4-1 |
|
|
540
541
|
| 페이지 제목(+ 서브 텍스트·뒤로가기·우측 슬롯)을 만든다 | `SPage` 의 `header` prop | §4-1 |
|
|
541
542
|
| 제목 있는 섹션으로 묶는다 | `SSectionHeaderCard` | §3-7-8 |
|
|
@@ -1166,20 +1167,83 @@ const columns: STableColumn[] = [
|
|
|
1166
1167
|
|
|
1167
1168
|
| `type` | 선행 아이콘 | 라벨 | 우측 | 언제 |
|
|
1168
1169
|
| --- | --- | --- | --- | --- |
|
|
1169
|
-
| `account` | `user` 고정 | `label` | 없음 | 로그인한 계정을
|
|
1170
|
+
| `account` | `user` 고정 | `label` | 없음 | 로그인한 계정을 보여줄 때. 누르면 **언제나** 계정 리스트박스가 뜬다 — `option` 필수 (§3-5-8) |
|
|
1170
1171
|
| `select` | `option.icon` (필수) | `label` | 화살표 | 눌러서 하위 화면으로 들어갈 때 |
|
|
1171
1172
|
| `switch` | `option.icon` (필수) | `label` | `option.text` + 새로고침 | 지금 붙어 있는 대상을 갈아 끼울 때 |
|
|
1172
|
-
| `subSelect` | `option.icon` (필수) | `label` |
|
|
1173
|
-
| `status` | 없음 | 상태 태그(`
|
|
1173
|
+
| `subSelect` | `option.icon` (필수) | `label` | 지금 값 + 화살표 | 값을 보여주고 **눌러서 바꿀** 때. 누르면 언제나 메뉴가 뜬다 — `option.options` 필수 |
|
|
1174
|
+
| `status` | 없음 | 상태 태그(고른 칸의 `color`·`label`) | 화살표 | 상태를 보여주고 **눌러서 바꿀** 때. 누르면 언제나 메뉴가 뜬다 — `option.options` 필수 |
|
|
1174
1175
|
|
|
1175
1176
|
- **`SPage` 본문에 직접 놓지 않는다.** 폭을 가득 채우는 행이라 페이지에 바로 놓으면 표처럼 보인다. GNB system 패널·`SDrawer`·`SPopover` 처럼 **좁은 폭이 정해진 자리**에 쌓는다. 행 사이 간격은 `--cmp-gnb-system-panel-gap` 이다.
|
|
1176
|
-
- **GNB 의 system 자리(맨 아래 판 · 전폭 상단바)에 놓는 것이라면 이 버튼을 직접 배치하지 말고 `SGnbSystem` 의 `actions` 에 넘긴다** (§4-1). 패널의 배경·구분선·도메인 태그·알림·계정까지 한 벌로 그려주므로, 조각을 손으로 조립할 이유가 없다.
|
|
1177
|
+
- **GNB 의 system 자리(맨 아래 판 · 전폭 상단바)에 놓는 것이라면 이 버튼을 직접 배치하지 말고 `SGnbSystem` 의 `actions` 에 넘긴다** (§4-1). 패널의 배경·구분선·도메인 태그·알림·계정까지 한 벌로 그려주므로, 조각을 손으로 조립할 이유가 없다. **단 `type="account"` 는 `actions` 가 받지 않는다** — 계정 행은 패널에 하나뿐이고 자리도 맨 아래로 정해져 있어 `account` prop 이 따로 있다 (§3-5-8).
|
|
1177
1178
|
- **`color` 는 놓이는 표면을 따라간다** — 흰 면이면 `light`, 오션블루 면이면 `dark`, 네이비 면이면 `darker`. 배경과 다른 색을 고르면 버튼만 떠 보인다.
|
|
1178
1179
|
- **상태는 default·hover 둘뿐이다.** 토큰에 selected 가 없으므로 "지금 열려 있는 항목"을 이 버튼으로 표시하지 않는다. 선택 상태가 필요한 자리면 `SGnb` 메뉴나 `SListItem`(§3-7-6) 이다.
|
|
1179
1180
|
- **선행 아이콘이 비는 행은 없다.** `account` 는 `user` 고정, `status` 는 태그가 자리를 차지하고, 나머지는 `option.icon` 이 필수다.
|
|
1181
|
+
- **`account` 의 `option` 만 성격이 다르다** — 아이콘이 아니라 **누르면 뜨는 계정 리스트박스의 내용**이고, **필수다** (§3-5-8). 눌러도 아무것도 뜨지 않는 계정 행은 없다 — 계정 화면으로 넘기는 것은 이 행이 아니라 패널의 `accountSetting` 이 맡는다.
|
|
1180
1182
|
- **우측 아이콘은 `type` 이 정하므로 밖에서 바꾸지 않는다.** 화살표가 필요한데 문구가 없으면 `select`, 문구가 함께 필요하면 `subSelect` 다.
|
|
1183
|
+
- **`subSelect` · `status` 는 언제나 값 고르기 메뉴의 트리거다** — 지금 값이 적혀 있는 행이라 누르면 그 값을 바꾸는 메뉴가 뜬다. 그래서 `option.options` 가 **필수**이고, 메뉴는 **행 오른쪽으로**, 행 위쪽 끝에 맞춰 펼쳐진다 — 이 행은 판 안에 세로로 쌓이는 자리라 아래로 펴면 바로 다음 행을 덮는다. 고르면 `onChange(value)` 가 돌며 닫힌다. **메뉴를 손으로 만들지 않는다** — 뜨는 자리·닫히는 시점이 어긋난다. 계정 패널의 언어 변경 행(§3-5-8)도 같은 메뉴이지만, 그쪽은 판 안에 갇혀 있어 우측 값 아래로 편다.
|
|
1184
|
+
|
|
1185
|
+
```tsx
|
|
1186
|
+
<SSystemActionButton
|
|
1187
|
+
type="subSelect" label="작업 공간"
|
|
1188
|
+
option={{ icon: 'board', value: workspaceId, options: WORKSPACES, onChange: setWorkspace }}
|
|
1189
|
+
/>
|
|
1190
|
+
<SSystemActionButton
|
|
1191
|
+
type="status"
|
|
1192
|
+
option={{ value: status, options: STATUSES, onChange: setStatus }} // STATUSES: { value, label, color? }[]
|
|
1193
|
+
/>
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1196
|
+
- **`value` 를 함께 준다.** 우측 값(`subSelect`)·상태 태그(`status`)는 `options` 에서 같은 `value` 를 찾아 그 `label`(과 `status` 는 `color`)로 그린다. 못 찾으면 `value` 문자열이 그대로 노출되므로 반드시 맞춰 준다.
|
|
1197
|
+
- **눌러서 값 고르는 화면으로 보낼 것이면 `select` 다.** 그 타입은 행을 누르는 것 자체가 액션이라 `onClick` 만 돈다. 상태를 **보여주기만** 할 것이면 이 버튼이 아니라 `STag`(§3-7-2) 다 — 화살표가 붙는 행은 "눌러서 바꾼다"는 약속이다.
|
|
1198
|
+
- **태그의 색·이름을 밖에서 직접 주지 않는다** (`status`). `option.color`·`option.label` 은 없다 — 지금 상태가 가리키는 `options` 칸이 곧 태그다. 색이 상태의 뜻이라 둘이 어긋나면 안 되기 때문이다.
|
|
1199
|
+
- **상태 메뉴의 색은 칸마다 준다** — 점과 글자가 그 상태의 색으로 함께 물든다. 상태는 색 자체가 뜻이라 고르는 자리에서도 색을 보고 고른다.
|
|
1200
|
+
- `switch` 는 메뉴를 갖지 않는다 — 우측 문구가 "지금 값"이 아니라 보조 조작 문구라, 고를 목록이 있으면 그것은 `subSelect` 다.
|
|
1181
1201
|
- 높이·라운드·아이콘 크기·좌우 여백은 전부 토큰이 넣는다. 직접 주지 않는다.
|
|
1182
1202
|
|
|
1203
|
+
#### 3-5-8. `SAccountListBox` — 계정 행을 눌러 뜨는 계정 패널
|
|
1204
|
+
|
|
1205
|
+
**"지금 누구로 로그인해 있는가"와 "여기서 나간다"를 한 자리에 모은 패널**이다. 위에서부터 [사용자정보(이메일 · 이름 + 권한)] · [계정 설정] · [언어 변경] · [계정 로그아웃] 이다.
|
|
1206
|
+
|
|
1207
|
+
**직접 띄우지 않는다.** 계정 행에 붙여 여는 것은 두 곳이 맡는다 — 어느 쪽이든 뜨는 방향·닫히는 시점을 컴포넌트가 정하므로 앱이 팝오버를 조립하지 않는다. **두 곳 모두 이 내용이 필수다** — 계정 행은 예외 없이 이 패널의 트리거라, 눌러도 아무것도 뜨지 않는 계정 행은 만들 수 없다.
|
|
1208
|
+
|
|
1209
|
+
| 어디에 | 어떻게 |
|
|
1210
|
+
| --- | --- |
|
|
1211
|
+
| GNB 의 system 자리 (기본) | `SGnbSystem` 의 `account.listBox` — 필수 (§4-1) |
|
|
1212
|
+
| system 밖의 **좁은 세로 판 아래쪽** (SDrawer·SPopover 하단 등) | `SSystemActionButton type="account"` 의 `option` — 필수 |
|
|
1213
|
+
|
|
1214
|
+
```tsx
|
|
1215
|
+
<SGnbSystem
|
|
1216
|
+
account={{
|
|
1217
|
+
label: userName,
|
|
1218
|
+
listBox: {
|
|
1219
|
+
email: user.email,
|
|
1220
|
+
name: user.name,
|
|
1221
|
+
authority: user.authority, // 옵션
|
|
1222
|
+
accountSetting: { onClick: openAccountSetting }, // 옵션
|
|
1223
|
+
language: { // 옵션
|
|
1224
|
+
value: languageCode,
|
|
1225
|
+
options: LANGUAGES, // 주면 하위 메뉴가 펼쳐진다
|
|
1226
|
+
onChange: setLanguage,
|
|
1227
|
+
},
|
|
1228
|
+
logout: { onClick: signOut },
|
|
1229
|
+
},
|
|
1230
|
+
}}
|
|
1231
|
+
/>
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
- **`authority` · `accountSetting` · `language` 는 옵션이고, 사용자정보와 로그아웃 행은 늘 있다.** 이 패널이 존재하는 이유가 그 둘이다. 옵션을 빼면 그 줄이 통째로 사라지고 나머지가 위로 붙으므로, 줄을 지우려고 빈 문자열이나 빈 객체를 넣지 않는다 (`accountSetting={{}}` 은 "이 행을 쓴다" 는 뜻이다).
|
|
1235
|
+
- **권한 prop 은 `authority` 다 (`role` 아님).** `role` 은 DOM 의 ARIA 속성이라, 그 이름을 쓰면 소비 앱의 a11y lint 가 "최고 관리자" 를 ARIA 롤로 읽고 실패한다.
|
|
1236
|
+
- **`language` 를 쓰면 `value` 를 함께 준다.** 우측에 붙는 그 값이 "지금 무슨 언어인가" 다 — 없으면 우측이 비어 눌러야 알 수 있는 행이 된다.
|
|
1237
|
+
- **언어를 앱 안에서 바꾸면 `language.options` 를 준다.** 그 행이 우측 값 아래로 하위 메뉴를 펴고, 고르면 `onChange(value)` 가 돌며 하위 메뉴와 계정 패널이 함께 닫힌다. `value` 는 `options` 의 `value` 와 맞추면 되고, 우측에는 그 항목의 `label` 이 적힌다. **하위 메뉴를 손으로 만들지 않는다** — 뜨는 자리·닫히는 시점이 어긋난다.
|
|
1238
|
+
- **언어를 고르는 화면이 따로 있으면 `options` 를 주지 않는다.** 그러면 행을 누르는 것 자체가 액션이라 `onClick` 이 돌고 계정 패널이 닫힌다. 둘을 같이 주면 화면으로 가지 않고 메뉴만 펴진다.
|
|
1239
|
+
- 하위 메뉴가 열려 있는 동안 계정 패널은 닫히지 않는다 — 언어를 고르는 일이 아직 끝나지 않았기 때문이다.
|
|
1240
|
+
- **행 아이콘은 밖에서 바꾸지 않는다.** 무엇을 하는 행인지가 곧 그 행의 정체다. 서비스 문구가 다르면 `label` 만 바꾼다.
|
|
1241
|
+
- **폭을 늘리지 않는다.** `--cmp-gnb-system-accountListBox-width` 가 정하는 고정 폭이다 — GNB 폭에 맞춰 뜨는 패널이라 늘리면 행 여백이 어긋난다.
|
|
1242
|
+
- **화면 위쪽에 계정 행을 직접 세우지 않는다.** system 밖에 세운 계정 행의 패널은 **늘 행 위로** 펼쳐진다 — 방향을 바꾸는 prop 이 없으므로 위가 화면 끝인 자리(앱 상단바·헤더)에 두면 잘린다. 앱 상단바의 계정은 손으로 만들지 말고 `SGnb` 의 `system` 슬롯에 맡긴다(`header="full"` 이면 조각들이 상단바 오른쪽 끝으로 올라가고, 그 자리에서는 패널이 아래로 펼쳐진다 — §4-1).
|
|
1243
|
+
- **뜨는 방향은 prop 이 아니다 — 놓인 자리가 정한다.** 판이면 계정 행이 맨 아래에 있으므로 **위로 · 행 왼쪽 끝에 맞춰**, 전폭 상단바면 **아래로 · 오른쪽 끝에 맞춰** 펼친다(접힌 레일은 판과 같다). 방향을 바꾸는 prop 은 없으니 찾지 않는다 — 같은 계정 행이 앱마다 다른 방향으로 뜨면 안 되기 때문이다.
|
|
1244
|
+
- **`header="fix"` 에서 GNB 를 접거나 펴면 열려 있던 패널은 닫힌다.** 계정 행이 메뉴 컬럼 바닥에서 접힘 레일 바닥으로(또는 반대로) 옮겨 서기 때문이다 — 닫히는 것을 앱이 막을 수 없고, 막을 이유도 없다(누른 그 행이 화면에서 사라진다). 전폭 상단바(`header="full"`)는 접어도 계정 행이 제자리라 열린 채 남는다. **그 닫힘을 알아야 하면 `account.onOpenChange` 를 준다** — 열림을 따라 그리는 화면이 앱에 있을 때만 필요하다.
|
|
1245
|
+
- **로그아웃을 여기 말고 다른 곳에 또 두지 않는다.** 계정에서 나가는 길이 화면마다 다르면 사용자가 매번 찾는다.
|
|
1246
|
+
|
|
1183
1247
|
### 3-6. 영역 나누기 — SDivider vs SSplitter
|
|
1184
1248
|
|
|
1185
1249
|
| 상황 | 사용 |
|
|
@@ -1464,9 +1528,10 @@ const [selectedId, setSelectedId] = useState<string>();
|
|
|
1464
1528
|
|
|
1465
1529
|
- **기본은 `SKeyValueTable` 이다** (§4-2). 조건이 대여섯 개 이하로 고정이면 표로 펼쳐 두는 편이 한눈에 읽힌다.
|
|
1466
1530
|
- `SChipFilter` 는 조건을 **칩 한 줄**로 접고, "필터 추가" 로 필요한 것만 꺼내 쓰게 한다. 칩을 누르면 편집 팝오버가 열리고, 날짜는 프리셋(오늘·지난 7일·사용자 지정)으로 고른다. 조건 후보가 많은 목록 화면에서 필터가 화면을 세로로 잡아먹는 것을 막는 용도다.
|
|
1467
|
-
- 검색 실행 시점이 다르다 — `SKeyValueTable` 필터는 앱이 검색 버튼을 직접 놓지만, `SChipFilter` 는 편집 팝오버가 닫히거나 "검색" 을 누를 때 `onSearch` 가 값 맵과 함께 호출된다. 값이 바뀌지 않았으면 호출되지 않는다.
|
|
1531
|
+
- 검색 실행 시점이 다르다 — `SKeyValueTable` 필터는 앱이 검색 버튼을 직접 놓지만, `SChipFilter` 는 편집 팝오버가 닫히거나 "검색" 을 누를 때 `onSearch` 가 값 맵과 함께 호출된다. 값이 바뀌지 않았으면 호출되지 않는다. **단 마운트 후 첫 호출은 값이 처음 그대로여도 나간다** — 마운트 시 자동 조회하지 않는 화면(다이얼로그 등)에서 조건을 하나도 넣지 않고 누른 첫 "검색" 이 막히면 안 되기 때문이다. 그래서 **마운트 시 조회할지 말지는 앱이 정한다** — `SChipFilter` 는 마운트만으로 `onSearch` 를 부르지 않는다.
|
|
1532
|
+
- **keyword 칩은 입력창에 남은 텍스트까지 조회에 넣는다.** Enter 로 담지 않고 팝오버를 닫아도(="검색" 클릭·바깥 클릭·Esc·다른 칩으로 전환) 그 텍스트를 키워드로 확정한 뒤 조회한다. 앱이 따로 확정시킬 일은 없다.
|
|
1468
1533
|
- **노출할 칩을 앱이 계산하지 않는다.** 바에 놓이는 것은 `fixed`·`required` 필드와 **`value` 에 값이 들어 있는 필드**다. 노출 목록을 밖에서 넘기는 prop 은 없다. 쿼리스트링·서버 상태에서 조건을 복원하는 목록 화면도 **`value` 만 넘기면 칩이 함께 살아나고**, 그 조건을 빼면 칩도 함께 빠진다 — 칩과 조회 조건이 어긋날 자리가 없다. "필터 추가" 로 꺼낸 필터는 값을 넣기 전에도 자리를 지키지만, "검색 초기화" 를 누르거나 화면을 다시 그리면 사라진다. 걸린 조건이 없으니 문제되지 않는다.
|
|
1469
|
-
-
|
|
1534
|
+
- **`fixed`·`required` 이면서 후보가 하나뿐인 `select`·`select-multi` 는 자동으로 선택된다.** 고를 여지가 없는 목록이라 컴포넌트가 그 값을 채우고, 사용자가 고르지 않아도 `onValueChange` 가 그 값과 함께 호출된다 — 마운트 직후부터 그 조건이 값 맵에 들어 있다고 보고 조회를 짠다. 옵션이 API 응답이라 늦게 도착해도 도착한 시점에 채워지므로, 앱이 따로 채워 넣을 필요가 없다. 사용자가 그 값을 지우면 다시 채우지 않는다. **뺄 수 있는 필터는 후보가 하나여도 채우지 않는다** — 처음에 바에 없는 필터라, 채우면 보이지도 지울 수도 없는 조건이 검색에 걸린다(`defaultValue` 를 `fixed`·`required` 로 제한하는 것과 같은 이유). 후보가 하나뿐인 조건을 반드시 걸어야 하면 그 필드를 `fixed` 나 `required` 로 준다.
|
|
1470
1535
|
- **`fields` 는 항상 그룹 배열이다.** 묶을 것이 없어도 `[{ fields: [...] }]` 로 한 겹 감싼다. 함께 걸어야 하는 조건(예: 기간 중 하나는 필수)이 있으면 그 필드들만 별도 그룹으로 떼어 `rule` 을 준다 — 규칙을 못 채운 동안 경고 툴팁이 떠 있고 `onSearch` 가 막힌다. 그룹 앞 구분선은 `divider` 로 켠다. 검증 단위와 구분선은 별개라, 묶어서 검증만 하고 싶으면 `divider` 를 주지 않는다.
|
|
1471
1536
|
|
|
1472
1537
|
#### 3-7-12. 이미지 — SImage
|
|
@@ -1604,7 +1669,7 @@ import { SModalOutlet } from 'sellmate-design-system-react';
|
|
|
1604
1669
|
<SButton size="sm" color="neutral" outline label="도움말" className="ml-auto" onClick={openHelp} />
|
|
1605
1670
|
</div>
|
|
1606
1671
|
}
|
|
1607
|
-
system={<SGnbSystem alert={{ count: unreadCount, onClick: openAlerts }} account={{ label: userName,
|
|
1672
|
+
system={<SGnbSystem alert={{ count: unreadCount, onClick: openAlerts }} account={{ label: userName, listBox: accountPanel }} />}
|
|
1608
1673
|
/>
|
|
1609
1674
|
<SPage background="frame">{children}</SPage>
|
|
1610
1675
|
</SLayout>
|
|
@@ -1644,24 +1709,36 @@ import { SModalOutlet } from 'sellmate-design-system-react';
|
|
|
1644
1709
|
items={MENU} value={current} onValueChange={navigate} logo={<Logo />}
|
|
1645
1710
|
system={
|
|
1646
1711
|
<SGnbSystem
|
|
1647
|
-
/* 상단에 쌓는 행 — SSystemActionButton 의 props 배열이다 (§3-5).
|
|
1712
|
+
/* 상단에 쌓는 행 — SSystemActionButton 의 props 배열이다 (§3-5).
|
|
1713
|
+
color 는 넘기지 않고, 계정 행은 여기가 아니라 아래 account 로 준다 */
|
|
1648
1714
|
actions={[{ type: 'select', label: '서비스 전환', option: { icon: 'robot' }, onClick: openServices }]}
|
|
1649
1715
|
domain={domainName}
|
|
1650
1716
|
alert={{ count: unreadCount, onClick: openAlerts }}
|
|
1651
1717
|
setting={{ onClick: openSettings }}
|
|
1652
|
-
|
|
1718
|
+
/* 계정 행. listBox 를 주면 눌렀을 때 계정 패널이 뜬다 (§3-5-8) */
|
|
1719
|
+
account={{
|
|
1720
|
+
label: userName,
|
|
1721
|
+
listBox: {
|
|
1722
|
+
email: user.email, name: user.name, authority: user.authority,
|
|
1723
|
+
accountSetting: { onClick: openAccountSetting },
|
|
1724
|
+
language: { value: currentLanguage, onClick: openLanguage },
|
|
1725
|
+
logout: { onClick: signOut },
|
|
1726
|
+
},
|
|
1727
|
+
}}
|
|
1653
1728
|
/>
|
|
1654
1729
|
}
|
|
1655
1730
|
/>
|
|
1656
1731
|
```
|
|
1657
1732
|
|
|
1658
|
-
- **`color`·`folded
|
|
1733
|
+
- **`color`·`folded` 를 주지 않는다.** `system` 슬롯에 있으면 GNB 가 자기 색과 형태를 내려준다. 이 둘은 GNB 밖에서 단독으로 쓸 때만 준다.
|
|
1734
|
+
- **놓이는 자리는 prop 이 아니다.** 세로 판이냐 상단바 한 줄이냐는 GNB 의 `header` 가 정한다(fix → 판, full → 상단바). 고르는 prop 은 없으니 찾지 않는다 — 자리와 어긋난 형태를 세울 이유가 있는 화면이 없다.
|
|
1659
1735
|
- **각 조각은 해당 prop 을 줄 때만 나타난다 — 어떤 조합이어도 그것만으로 판이 성립한다.** 계정만, 알림만, 설정만, 알림+계정 … 서비스가 쓰는 것만 넘긴다. 조각을 빼려고 빈 문자열이나 빈 객체를 넣지 않는다(`setting={{}}` 은 "설정 버튼을 쓴다" 는 뜻이다).
|
|
1660
1736
|
- 구분선은 **위아래 양쪽에 내용이 있을 때만** 그어진다 — `actions` 만 넘겨도 바닥에 뜻 없는 선이 남지 않는다.
|
|
1661
1737
|
- 알림·설정은 **혼자 서면 폭을 다 먹고**, 둘이 나란히 서면 알림이 남는 폭을·설정이 제 폭을 갖는다. 어느 쪽이 빠져도 왼쪽 끝은 위아래 행과 맞으므로 앱이 정렬을 맞출 일이 없다.
|
|
1662
1738
|
- **켜진 조각이 하나도 없으면 아무것도 렌더되지 않는다.** 조건부로 조각이 다 빠지는 화면에서도 GNB 바닥에 빈 판이 남지 않는다.
|
|
1663
1739
|
- **`header="fix"` 를 접으면 알림·계정 아이콘만 남는다** — `actions`·`domain`·`setting` 은 48px 폭에 놓을 자리가 없어 렌더되지 않고, 알림 개수는 점 배지가 된다. **알림도 계정도 안 쓰는 조합이면 접힌 패널은 아예 렌더되지 않는다** — 접힌 상태에서도 반드시 눌러야 하는 것이 그 둘 밖에 있으면 `foldedTop`/`foldedFooter` 로 따로 준다.
|
|
1664
1740
|
- **`header="full"` 은 접어도 상단바가 남으므로 조각들도 그대로 남는다.** 대신 가로로 자리가 넉넉하지 않아 알림·설정이 라벨을 벗고 아이콘(알림은 개수까지)만 남는다 — 이건 컴포넌트가 알아서 하므로 앱이 라벨을 지우지 않는다.
|
|
1741
|
+
- **`account` 를 쓰면 `account.listBox` 가 필수다** (§3-5-8) — 계정 행을 누르면 이메일·이름·권한과 계정 설정·언어 변경·로그아웃이 예외 없이 뜬다. 뜨는 방향은 판이 놓인 자리가 정하므로 앱이 넘기지 않는다 — 판이면 계정 행 위로, 상단바면 아래로 펼치고, 접힌 레일도 판과 같다. `account.onClick` 은 패널을 여는 것 말고 따로 할 일(로깅 등)이 있을 때만 준다.
|
|
1665
1742
|
- **알림 개수는 `alert.count` 하나로 표현한다.** 1 이상이면 벨이 울리는 아이콘과 강조색으로 바뀐다 — 색은 GNB 색이 정하므로 앱이 직접 칠하지 않는다.
|
|
1666
1743
|
- **`header="fix"` + `useRail` 에서 하위 메뉴가 없는 레일 아이템이 활성이면 메뉴 컬럼째 사라지므로 이 패널도 함께 빠진다.** 항상 보여야 하는 계정·알림이라면 그런 레일 리프를 두지 않는다 — 또는 `header="full"` 로 두면 상단바에 남는다(그런 레일 리프가 있으면 GNB 가 `header` 를 `full` 로 강제한다).
|
|
1667
1744
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import { type SAccountListBoxContent } from './SAccountListBox';
|
|
3
|
+
/**
|
|
4
|
+
* 계정 행이 놓인 자리. 리스트박스가 어느 방향으로 펼쳐질지를 **이것만** 정한다.
|
|
5
|
+
* - `panel` — GNB 바닥의 세로 판(접힌 레일 포함). 계정 행이 맨 아래라 위로 편다.
|
|
6
|
+
* - `top` — 전폭 상단바 오른쪽 끝. 줄이 화면 맨 위라 아래로 편다.
|
|
7
|
+
*/
|
|
8
|
+
type AccountListBoxAnchor = 'panel' | 'top';
|
|
9
|
+
export declare const AccountListBoxAnchorProvider: import("react").Provider<AccountListBoxAnchor>;
|
|
10
|
+
/**
|
|
11
|
+
* 계정 리스트박스를 앵커에 붙여 띄우는 내부 래퍼.
|
|
12
|
+
*
|
|
13
|
+
* 계정 행은 GNB 판(펼침)·레일(접힘)·상단바 세 자리에 서고, 세 자리 모두 "누르면 같은 패널이
|
|
14
|
+
* 뜬다". 그 여는 방식(위치 · SPortal 설정 · 행을 고르면 닫기)을 한 곳에 모아 두어야 자리마다
|
|
15
|
+
* 동작이 갈리지 않는다. 공개 API 가 아니므로 index.ts 로 내보내지 않는다 —
|
|
16
|
+
* 앱은 `SSystemActionButton` 의 `option` 이나 `SGnbSystem` 의 `account.listBox` 로 닿는다.
|
|
17
|
+
*/
|
|
18
|
+
interface AccountListBoxPortalOptions {
|
|
19
|
+
/** 표시 여부 */
|
|
20
|
+
open: boolean;
|
|
21
|
+
/** 표시 상태 변경 */
|
|
22
|
+
onOpenChange: (open: boolean) => void;
|
|
23
|
+
/** 붙을 트리거 엘리먼트 */
|
|
24
|
+
anchorRef: RefObject<HTMLElement | null>;
|
|
25
|
+
/** 접근성 이름 */
|
|
26
|
+
ariaLabel?: string;
|
|
27
|
+
/** 패널에 담기는 내용 */
|
|
28
|
+
content: SAccountListBoxContent;
|
|
29
|
+
}
|
|
30
|
+
export declare function AccountListBoxPortal({ open, onOpenChange, anchorRef, ariaLabel, content, }: AccountListBoxPortalOptions): import("react").JSX.Element;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# SAccountListBox
|
|
2
|
+
|
|
3
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
4
|
+
|
|
5
|
+
### SAccountListBox
|
|
6
|
+
|
|
7
|
+
#### Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `email?` | `string` | — | 로그인한 계정의 이메일 |
|
|
12
|
+
| `name?` | `string` | — | 로그인한 계정의 이름 |
|
|
13
|
+
| `authority?` | `string` | — | 권한. 이름 오른쪽 끝에 붙는다. 없으면 이름만 남는다. (`role` 이 아닌 이유 — 그 이름은 DOM 의 ARIA 속성이라 소비 앱의 a11y lint 가 값을 ARIA 롤로 읽는다) |
|
|
14
|
+
| `accountSetting?` | `SAccountListBoxItem` | — | 계정 설정 행. 주지 않으면 행이 빠진다 |
|
|
15
|
+
| `language?` | `SAccountListBoxLanguage` | — | 언어 변경 행. 주지 않으면 행이 빠진다 |
|
|
16
|
+
| `logout?` | `SAccountListBoxItem` | — | 로그아웃 행. 이 행은 늘 있고, 라벨과 클릭만 받는다 |
|
|
17
|
+
|
|
18
|
+
## Types
|
|
19
|
+
|
|
20
|
+
### SAccountListBoxItem
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
/** 리스트박스의 한 행 — 라벨은 기본값이 있고 클릭만 앱이 준다 */
|
|
24
|
+
export interface SAccountListBoxItem {
|
|
25
|
+
/** 라벨 텍스트. 행마다 기본값이 있다 */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** 클릭 */
|
|
28
|
+
onClick?: () => void;
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### SAccountListBoxLanguage
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
/** 언어 변경 행 — 지금 쓰는 언어가 우측에 붙고, 고를 것이 있으면 눌러서 편다 */
|
|
36
|
+
export interface SAccountListBoxLanguage extends SAccountListBoxItem {
|
|
37
|
+
/**
|
|
38
|
+
* 지금 선택돼 있는 언어. 우측에 브랜드색으로 붙는다.
|
|
39
|
+
* `options` 를 함께 주면 그 목록에서 같은 `value` 를 찾아 `label` 로 적는다.
|
|
40
|
+
*/
|
|
41
|
+
value?: string;
|
|
42
|
+
/**
|
|
43
|
+
* 고를 수 있는 언어. 주면 행을 눌렀을 때 **하위 메뉴가 우측 아래로 펼쳐지고**,
|
|
44
|
+
* 그동안 계정 패널은 닫히지 않는다. 주지 않으면 행을 누르는 것 자체가 액션이라
|
|
45
|
+
* `onClick` 만 돌고 패널이 닫힌다 — 언어 화면을 따로 두는 앱이 그렇다.
|
|
46
|
+
*/
|
|
47
|
+
options?: SAccountListBoxLanguageOption[];
|
|
48
|
+
/** 하위 메뉴에서 언어를 고름. 고르면 하위 메뉴와 계정 패널이 함께 닫힌다 */
|
|
49
|
+
onChange?: (value: string) => void;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### SAccountListBoxLanguageOption
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
/** 언어 변경 하위 메뉴의 한 칸 */
|
|
57
|
+
export interface SAccountListBoxLanguageOption {
|
|
58
|
+
/** 값. `SAccountListBoxLanguage.value` 와 맞춰 지금 언어를 가린다 */
|
|
59
|
+
value: string;
|
|
60
|
+
/** 화면에 적히는 언어명 */
|
|
61
|
+
label: string;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Dependencies
|
|
66
|
+
|
|
67
|
+
### Used by
|
|
68
|
+
|
|
69
|
+
- [SGnbSystem](../SGnbSystem)
|
|
70
|
+
- [SSystemActionButton](../SSystemActionButton)
|
|
71
|
+
|
|
72
|
+
### Depends on
|
|
73
|
+
|
|
74
|
+
- [SIcon](../SIcon)
|
|
75
|
+
- [SPortal](../SPortal)
|
|
76
|
+
|
|
77
|
+
### Graph
|
|
78
|
+
|
|
79
|
+
```mermaid
|
|
80
|
+
graph TD;
|
|
81
|
+
SAccountListBox --> SIcon
|
|
82
|
+
SAccountListBox --> SPortal
|
|
83
|
+
SGnbSystem --> SAccountListBox
|
|
84
|
+
SSystemActionButton --> SAccountListBox
|
|
85
|
+
style SAccountListBox fill:#f9f,stroke:#333,stroke-width:4px
|
|
86
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type HTMLAttributes } from 'react';
|
|
2
|
+
/** 리스트박스의 한 행 — 라벨은 기본값이 있고 클릭만 앱이 준다 */
|
|
3
|
+
export interface SAccountListBoxItem {
|
|
4
|
+
/** 라벨 텍스트. 행마다 기본값이 있다 */
|
|
5
|
+
label?: string;
|
|
6
|
+
/** 클릭 */
|
|
7
|
+
onClick?: () => void;
|
|
8
|
+
}
|
|
9
|
+
/** 언어 변경 하위 메뉴의 한 칸 */
|
|
10
|
+
export interface SAccountListBoxLanguageOption {
|
|
11
|
+
/** 값. `SAccountListBoxLanguage.value` 와 맞춰 지금 언어를 가린다 */
|
|
12
|
+
value: string;
|
|
13
|
+
/** 화면에 적히는 언어명 */
|
|
14
|
+
label: string;
|
|
15
|
+
}
|
|
16
|
+
/** 언어 변경 행 — 지금 쓰는 언어가 우측에 붙고, 고를 것이 있으면 눌러서 편다 */
|
|
17
|
+
export interface SAccountListBoxLanguage extends SAccountListBoxItem {
|
|
18
|
+
/**
|
|
19
|
+
* 지금 선택돼 있는 언어. 우측에 브랜드색으로 붙는다.
|
|
20
|
+
* `options` 를 함께 주면 그 목록에서 같은 `value` 를 찾아 `label` 로 적는다.
|
|
21
|
+
*/
|
|
22
|
+
value?: string;
|
|
23
|
+
/**
|
|
24
|
+
* 고를 수 있는 언어. 주면 행을 눌렀을 때 **하위 메뉴가 우측 아래로 펼쳐지고**,
|
|
25
|
+
* 그동안 계정 패널은 닫히지 않는다. 주지 않으면 행을 누르는 것 자체가 액션이라
|
|
26
|
+
* `onClick` 만 돌고 패널이 닫힌다 — 언어 화면을 따로 두는 앱이 그렇다.
|
|
27
|
+
*/
|
|
28
|
+
options?: SAccountListBoxLanguageOption[];
|
|
29
|
+
/** 하위 메뉴에서 언어를 고름. 고르면 하위 메뉴와 계정 패널이 함께 닫힌다 */
|
|
30
|
+
onChange?: (value: string) => void;
|
|
31
|
+
}
|
|
32
|
+
export interface SAccountListBoxProps extends HTMLAttributes<HTMLDivElement> {
|
|
33
|
+
/** 로그인한 계정의 이메일 */
|
|
34
|
+
email?: string;
|
|
35
|
+
/** 로그인한 계정의 이름 */
|
|
36
|
+
name?: string;
|
|
37
|
+
/**
|
|
38
|
+
* 권한. 이름 오른쪽 끝에 붙는다. 없으면 이름만 남는다.
|
|
39
|
+
* (`role` 이 아닌 이유 — 그 이름은 DOM 의 ARIA 속성이라 소비 앱의 a11y lint 가 값을 ARIA 롤로 읽는다)
|
|
40
|
+
*/
|
|
41
|
+
authority?: string;
|
|
42
|
+
/** 계정 설정 행. 주지 않으면 행이 빠진다 */
|
|
43
|
+
accountSetting?: SAccountListBoxItem;
|
|
44
|
+
/** 언어 변경 행. 주지 않으면 행이 빠진다 */
|
|
45
|
+
language?: SAccountListBoxLanguage;
|
|
46
|
+
/** 로그아웃 행. 이 행은 늘 있고, 라벨과 클릭만 받는다 */
|
|
47
|
+
logout?: SAccountListBoxItem;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 리스트박스에 **담기는 내용**만 추린 것.
|
|
51
|
+
* 팝오버로 띄우는 쪽(`SSystemActionButton` 의 `option` · `SGnbSystem` 의 `account.listBox`)이
|
|
52
|
+
* 이 덩어리째 넘겨받으므로, 패널의 DOM 속성(className·style …)과 분리해 둔다.
|
|
53
|
+
*/
|
|
54
|
+
export type SAccountListBoxContent = Pick<SAccountListBoxProps, 'email' | 'name' | 'authority' | 'accountSetting' | 'language' | 'logout'>;
|
|
55
|
+
/**
|
|
56
|
+
* SAccountListBox — GNB system 의 계정 행을 눌렀을 때 뜨는 계정 리스트박스.
|
|
57
|
+
*
|
|
58
|
+
* 판에서는 계정 행이 맨 아래에 있어 **행 위로 · 행 왼쪽 끝에 맞춰**, 전폭 상단바에서는 **행 아래로 ·
|
|
59
|
+
* 오른쪽 끝에 맞춰** 펼쳐진다. 이 방향은 고정이라 앱이 고르지 않는다.
|
|
60
|
+
*
|
|
61
|
+
* 위에서부터 [사용자정보(이메일 · 이름 + 권한)] · [계정 설정] · [언어 변경] · [계정 로그아웃] 이다.
|
|
62
|
+
* **권한(`authority`) · 계정 설정 · 언어 변경은 옵션**이고, 사용자정보와 로그아웃은 늘 있다 — 이 패널이 존재하는
|
|
63
|
+
* 이유가 "지금 누구로 로그인해 있는가"와 "나가기"이기 때문이다. 옵션을 빼면 그 줄이 통째로 사라지고
|
|
64
|
+
* 나머지가 위로 붙는다.
|
|
65
|
+
*
|
|
66
|
+
* 폭은 토큰이 정하는 고정값(240px)이라 앱이 늘리지 않는다 — 이 패널은 GNB 폭에 맞춰 뜬다.
|
|
67
|
+
* 행 라벨·아이콘은 무엇을 하는 행인지가 곧 정체라 아이콘을 밖에서 바꾸지 않고, 라벨만 바꿀 수 있다.
|
|
68
|
+
*
|
|
69
|
+
* **언어 변경만 하위 메뉴를 가질 수 있다.** `language.options` 를 주면 그 행이 우측 값 아래로
|
|
70
|
+
* 목록을 펴고, 고를 때까지 계정 패널은 열려 있는다. 주지 않으면 행을 누르는 것 자체가 액션이라
|
|
71
|
+
* `onClick` 만 돌고 패널이 닫힌다 — 언어를 고르는 화면을 따로 두는 앱이 그렇다.
|
|
72
|
+
*
|
|
73
|
+
* **이 컴포넌트는 패널만 그린다.** 계정 행에 붙여 띄우는 것은 `SSystemActionButton` 의
|
|
74
|
+
* `type="account"` 이 맡으므로(`option` 에 이 내용을 넘긴다 — 필수다), 앱이 직접 팝오버를 조립할
|
|
75
|
+
* 일은 없다. 계정 행은 예외 없이 이 패널의 트리거다.
|
|
76
|
+
*
|
|
77
|
+
* 토큰은 `--cmp-gnb-system-accountListBox-*` · `--cmp-gnb-system-accountButton-*`.
|
|
78
|
+
*/
|
|
79
|
+
export declare const SAccountListBox: import("react").ForwardRefExoticComponent<SAccountListBoxProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import { type SPortalAlign, type SPortalPlacement } from '../SPortal';
|
|
3
|
+
import { type STagColor } from '../STag';
|
|
4
|
+
/**
|
|
5
|
+
* 시스템 메뉴의 한 칸.
|
|
6
|
+
*
|
|
7
|
+
* `color` 를 주면 그 칸은 **상태 칸**이 되어 점과 글자가 같은 색으로 물든다(시안) — 상태는 색 자체가
|
|
8
|
+
* 뜻이라, 고르는 자리에서도 그 색을 보고 고른다. 주지 않으면 글자만 있는 칸이고, 지금 값만
|
|
9
|
+
* 브랜드색으로 떠오른다.
|
|
10
|
+
*/
|
|
11
|
+
export interface SystemMenuOption {
|
|
12
|
+
/** 값. 지금 값과 맞춰 어느 칸이 선택돼 있는지를 가린다 */
|
|
13
|
+
value: string;
|
|
14
|
+
/** 화면에 적히는 이름 */
|
|
15
|
+
label: string;
|
|
16
|
+
/** 상태 칸의 색. 주면 점 + 같은 색 글자로 그린다 */
|
|
17
|
+
color?: STagColor;
|
|
18
|
+
}
|
|
19
|
+
interface SystemMenuPortalProps {
|
|
20
|
+
/** 표시 여부 */
|
|
21
|
+
open: boolean;
|
|
22
|
+
/** 표시 상태 변경 */
|
|
23
|
+
onOpenChange: (open: boolean) => void;
|
|
24
|
+
/** 붙을 자리 — 값 아래로 펼 때는 **바뀌는 값**(우측 값 묶음 · 상태 태그), 옆으로 펼 때는 행 자체 */
|
|
25
|
+
anchorRef: RefObject<HTMLElement | null>;
|
|
26
|
+
/** 뜨는 방향. 기본 `'bottom'`(앵커 아래) */
|
|
27
|
+
placement?: SPortalPlacement;
|
|
28
|
+
/** 교차축 정렬. 아래로 펼 때는 값이 놓인 쪽 끝, 옆으로 펼 때는 위쪽 끝(`start`) */
|
|
29
|
+
align: SPortalAlign;
|
|
30
|
+
/**
|
|
31
|
+
* 메뉴를 여는 행. 이 행 위의 클릭은 행이 스스로 토글하므로 여기서 닫지 않는다 —
|
|
32
|
+
* 앵커는 행이 아니라 그 안의 값이라, 값 밖(라벨 쪽)을 누르면 닫혔다가 곧바로 다시 열린다.
|
|
33
|
+
*/
|
|
34
|
+
triggerRef?: RefObject<HTMLElement | null>;
|
|
35
|
+
/** 접근성 이름 — 무엇을 고르는 메뉴인지 */
|
|
36
|
+
ariaLabel?: string;
|
|
37
|
+
/** 지금 값 */
|
|
38
|
+
value?: string;
|
|
39
|
+
/** 고를 수 있는 칸 */
|
|
40
|
+
options: SystemMenuOption[];
|
|
41
|
+
/** 칸을 고름 */
|
|
42
|
+
onSelect: (value: string) => void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* GNB system 의 값 고르기 메뉴.
|
|
46
|
+
*
|
|
47
|
+
* 계정 패널의 언어 변경 행 · `SSystemActionButton` 의 `subSelect` · `status` 가 모두 이것을 연다 —
|
|
48
|
+
* 세 자리 모두 "지금 값이 적혀 있고, 눌러서 다른 값으로 바꾼다" 는 같은 일이라, 뜨는 자리와 닫히는
|
|
49
|
+
* 시점을 한 곳에 모아 두지 않으면 자리마다 동작이 갈린다.
|
|
50
|
+
*
|
|
51
|
+
* **뜨는 자리는 부르는 쪽이 정한다.** 계정 패널 안의 행처럼 판 안에서 열리는 자리는 바꾸는 대상인
|
|
52
|
+
* 값 바로 아래로, 값이 놓인 쪽 끝에 맞춰 편다(패널 폭 안에 갇혀 있어 옆이 없다). 판 없이 행 하나로
|
|
53
|
+
* 서는 자리는 행 오른쪽으로 편다 — 행 아래는 다음 행이 이어지는 자리라 메뉴가 그 위를 덮는다.
|
|
54
|
+
*
|
|
55
|
+
* 공개 API 가 아니므로 index.ts 로 내보내지 않는다 — 앱은 각 행의 `options` 로 닿는다.
|
|
56
|
+
*/
|
|
57
|
+
export declare function SystemMenuPortal({ open, onOpenChange, anchorRef, placement, align, triggerRef, ariaLabel, value, options, onSelect, }: SystemMenuPortalProps): import("react").JSX.Element;
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** 행의 성격. default = 보통 행, negative = 되돌릴 수 없는 행(로그아웃) */
|
|
2
|
+
export declare const ACCOUNT_LIST_BOX_ITEM_TONES: readonly ["default", "negative"];
|
|
3
|
+
export type SAccountListBoxItemTone = (typeof ACCOUNT_LIST_BOX_ITEM_TONES)[number];
|
|
4
|
+
/** 행마다 정해져 있는 아이콘 — 무엇을 하는 행인지가 아이콘으로 읽히므로 밖에서 바꾸지 않는다 */
|
|
5
|
+
export declare const ACCOUNT_LIST_BOX_ICONS: {
|
|
6
|
+
readonly accountSetting: "account";
|
|
7
|
+
readonly language: "global";
|
|
8
|
+
readonly logout: "logout";
|
|
9
|
+
};
|
|
10
|
+
/** 행 라벨 기본값 — 시안 문구. 앱이 바꿀 수 있다 */
|
|
11
|
+
export declare const ACCOUNT_LIST_BOX_LABELS: {
|
|
12
|
+
readonly accountSetting: "계정 설정";
|
|
13
|
+
readonly language: "언어 변경";
|
|
14
|
+
readonly logout: "계정 로그아웃";
|
|
15
|
+
};
|
|
16
|
+
/** 행 성격별 색 */
|
|
17
|
+
export declare const ACCOUNT_LIST_BOX_TONE_CONFIG: Record<SAccountListBoxItemTone, {
|
|
18
|
+
bg: string;
|
|
19
|
+
bgHover: string;
|
|
20
|
+
text: string;
|
|
21
|
+
icon: string;
|
|
22
|
+
}>;
|
|
23
|
+
/** 치수·색 — 전부 토큰 참조 */
|
|
24
|
+
export declare const ACCOUNT_LIST_BOX_LAYOUT: {
|
|
25
|
+
/** 패널 고정 폭 */
|
|
26
|
+
readonly width: string;
|
|
27
|
+
/** 패널 배경 */
|
|
28
|
+
readonly bg: string;
|
|
29
|
+
/** 패널 라운드 */
|
|
30
|
+
readonly radius: string;
|
|
31
|
+
/** 사용자정보 영역 배경 */
|
|
32
|
+
readonly userdataBg: string;
|
|
33
|
+
/** 사용자정보 영역 좌우 여백 */
|
|
34
|
+
readonly userdataPaddingX: string;
|
|
35
|
+
/** 사용자정보 영역 상하 여백 */
|
|
36
|
+
readonly userdataPaddingY: string;
|
|
37
|
+
/** 이메일 글자색 */
|
|
38
|
+
readonly emailColor: string;
|
|
39
|
+
/** 이름 글자색 */
|
|
40
|
+
readonly nameColor: string;
|
|
41
|
+
/** 권한 글자색 */
|
|
42
|
+
readonly roleColor: string;
|
|
43
|
+
/** 행 묶음의 상하 여백 */
|
|
44
|
+
readonly listPaddingY: string;
|
|
45
|
+
/** 행 좌우 여백 */
|
|
46
|
+
readonly itemPaddingX: string;
|
|
47
|
+
/** 행 상하 여백 */
|
|
48
|
+
readonly itemPaddingY: string;
|
|
49
|
+
/** 행 아이콘 ↔ 라벨 간격 */
|
|
50
|
+
readonly itemGap: string;
|
|
51
|
+
/** 행 아이콘 크기 */
|
|
52
|
+
readonly itemIcon: string;
|
|
53
|
+
/** 우측 값 ↔ 화살표 간격 */
|
|
54
|
+
readonly selectDataGap: string;
|
|
55
|
+
/** 우측 화살표 크기 */
|
|
56
|
+
readonly selectDataIcon: string;
|
|
57
|
+
/** 우측 값 글자색 */
|
|
58
|
+
readonly selectDataText: string;
|
|
59
|
+
/** 시스템 메뉴에서 지금 값인 칸의 글자색 — 계정 행이 아니라 목록 항목이라
|
|
60
|
+
* listBoxItem 의 선택 색을 그대로 따른다(다른 목록의 선택 표시와 같은 색) */
|
|
61
|
+
readonly menuSelectedText: string;
|
|
62
|
+
/** 우측 화살표색 */
|
|
63
|
+
readonly selectDataIconColor: string;
|
|
64
|
+
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { SAccountListBox, type SAccountListBoxProps, type SAccountListBoxContent, type SAccountListBoxItem, type SAccountListBoxLanguage, type SAccountListBoxLanguageOption, } from './SAccountListBox';
|
|
2
|
+
export { ACCOUNT_LIST_BOX_ICONS, ACCOUNT_LIST_BOX_ITEM_TONES, ACCOUNT_LIST_BOX_LABELS, ACCOUNT_LIST_BOX_LAYOUT, ACCOUNT_LIST_BOX_TONE_CONFIG, type SAccountListBoxItemTone, } from './accountListBox.config';
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|-------|------|-------------|
|
|
23
23
|
| `onValueChange` | `(value: SChipFilterValueMap) => void` | 전체 값 변경 — 편집 중인 값이 바뀔 때마다(선택할 때마다) 호출된다. 실제 검색 실행은 onSearch를 쓴다 |
|
|
24
24
|
| `onFilterChange` | `(detail: SChipFilterChangeDetail) => void` | 개별 필터 값 변경 |
|
|
25
|
-
| `onSearch` | `(value: SChipFilterValueMap) => void` | 실제 검색을 실행할 시점 — 편집 팝오버의 "검색" 버튼을 누르거나 팝오버가 닫힐 때(바깥 클릭·Esc·다른 칩으로 전환 포함) 그 시점의 전체 값 맵과 함께 호출된다. 팝오버가 없는 필드(인라인 date 프리셋·custom, clearable ×, 검색 초기화)는 값이 바뀌는 즉시 호출된다. keyword 필터에서 Enter 로 키워드를 추가할 때도 그 즉시 호출된다 — 팝오버는 열린 채라 키워드를 이어서 더 넣을 수 있고, 넣을 때마다 조회가 갱신된다. dirty 체크가 기본 적용되어 있어 — 마지막으로 실제 검색이 실행된 값 맵과 비교해 하나라도 달라진 게 없으면(예: 팝오버를 열었다 아무것도 안 바꾸고 닫는 경우) 호출되지 않는다. fields를 그룹으로 넘겼다면 rule을 만족하지 못한 그룹이 있는 동안엔 onSearch가 호출되지 않는다 — 해당 그룹의 경고 툴팁은 이 시점과 무관하게 값이 비어 있는 동안 항상 실시간으로 떠 있다(별도로 validate()를 호출할 필요 없음). required 필드가 비어 있는 동안에도 마찬가지로 호출되지 않는다 — 정상 설정(내장 기본값 또는 defaultValue)이라면 원래 비워지지 않지만, defaultValue 없는 select-multi·keyword·custom에 required만 준 오설정에서는 "검색 초기화"·clearable이 값을 null로 비울 수 있어 이때의 안전장치다 |
|
|
25
|
+
| `onSearch` | `(value: SChipFilterValueMap) => void` | 실제 검색을 실행할 시점 — 편집 팝오버의 "검색" 버튼을 누르거나 팝오버가 닫힐 때(바깥 클릭·Esc·다른 칩으로 전환 포함) 그 시점의 전체 값 맵과 함께 호출된다. 팝오버가 없는 필드(인라인 date 프리셋·custom, clearable ×, 검색 초기화)는 값이 바뀌는 즉시 호출된다. keyword 필터에서 Enter 로 키워드를 추가할 때도 그 즉시 호출된다 — 팝오버는 열린 채라 키워드를 이어서 더 넣을 수 있고, 넣을 때마다 조회가 갱신된다. keyword 입력창에 Enter 없이 텍스트를 남긴 채 팝오버를 닫으면 그 텍스트도 키워드로 확정한 뒤 호출된다(input: 'csv'면 콤마로 쪼개는 규칙도 그대로 적용) — 사용자가 검색어를 넣고 "검색"을 누른 조작이 입력 확정 여부 때문에 사라지지 않게 한다. dirty 체크가 기본 적용되어 있어 — 마지막으로 실제 검색이 실행된 값 맵과 비교해 하나라도 달라진 게 없으면(예: 팝오버를 열었다 아무것도 안 바꾸고 닫는 경우) 호출되지 않는다. 단 마운트 후 첫 호출은 비교할 기준이 없어 값이 처음 그대로여도 나간다 — 마운트 시 자동 조회하지 않는 화면(다이얼로그 등)에서 사용자의 첫 "검색"이 막히지 않게 하기 위함이다. fields를 그룹으로 넘겼다면 rule을 만족하지 못한 그룹이 있는 동안엔 onSearch가 호출되지 않는다 — 해당 그룹의 경고 툴팁은 이 시점과 무관하게 값이 비어 있는 동안 항상 실시간으로 떠 있다(별도로 validate()를 호출할 필요 없음). required 필드가 비어 있는 동안에도 마찬가지로 호출되지 않는다 — 정상 설정(내장 기본값 또는 defaultValue)이라면 원래 비워지지 않지만, defaultValue 없는 select-multi·keyword·custom에 required만 준 오설정에서는 "검색 초기화"·clearable이 값을 null로 비울 수 있어 이때의 안전장치다 |
|
|
26
26
|
| `onReset` | `() => void` | "검색 초기화" 클릭 — 모든 필드가 기본값(또는 null)으로 리셋되고 "필터 추가"로 꺼낸 칩이 바에서 빠진 뒤 호출된다 |
|
|
27
27
|
| `onAddFilter` | `(key: string) => void` | "필터 추가" 목록에서 항목을 골랐을 때 호출된다(알림). 칩 노출은 컴포넌트가 알아서 하므로 이 콜백에서 따로 해줄 일은 없다 |
|
|
28
28
|
|
|
@@ -244,12 +244,16 @@ export type SChipFilterCustomValue = Record<string, unknown>;
|
|
|
244
244
|
export interface SChipFilterOptionsField extends SChipFilterFieldBase {
|
|
245
245
|
/** 고를 수 있는 후보 목록.
|
|
246
246
|
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
247
|
+
* **`fixed`·`required` 인 select·select-multi 에서 고를 수 있는 후보가 하나뿐이면 그 값이
|
|
248
|
+
* 자동으로 선택된다** — 사용자가 고르지 않아도 `onValueChange` 로 값이 올라온다
|
|
249
249
|
* (select 는 그 값, select-multi 는 그 값 하나짜리 배열). 옵션이 API 응답이라 늦게 도착해도
|
|
250
250
|
* 도착한 시점에 채워진다. 사용자가 그 값을 지우면 다시 채우지 않는다 —
|
|
251
251
|
* 다만 "검색 초기화"는 되돌려 놓는다(다른 기본값과 같은 규칙).
|
|
252
|
-
* 후보가 하나인데 비활성(`disabled`)이면 고를 수 없는 값이므로 채우지
|
|
252
|
+
* 후보가 하나인데 비활성(`disabled`)이면 고를 수 없는 값이므로 채우지 않는다.
|
|
253
|
+
*
|
|
254
|
+
* 뺄 수 있는 필터(= `fixed`·`required` 가 아닌 필드)는 후보가 하나여도 채우지 않는다 —
|
|
255
|
+
* 처음에 바에 없는 필터라 보이지도 지울 수도 없는 조건이 검색에 걸리기 때문이다.
|
|
256
|
+
* 후보가 하나뿐인 필터를 반드시 걸어야 한다면 `fixed` 나 `required` 로 준다 */
|
|
253
257
|
options?: SChipFilterOption[];
|
|
254
258
|
}
|
|
255
259
|
```
|
|
@@ -282,9 +286,9 @@ export interface SChipFilterFieldBase {
|
|
|
282
286
|
* 조용히 되살아난다. 처음부터 값이 정해져 있어야 하는 필터라면 그것이 `fixed`·`required` 다.
|
|
283
287
|
*
|
|
284
288
|
* required인데 지정하지 않으면 타입별 내장 기본값(select: 첫 번째 옵션,
|
|
285
|
-
* datepicker-day·datepicker-range: 오늘 날짜)을 대신 쓴다.
|
|
286
|
-
* 후보가 하나뿐일 때
|
|
287
|
-
*
|
|
289
|
+
* datepicker-day·datepicker-range: 오늘 날짜)을 대신 쓴다. `fixed`·`required` 인
|
|
290
|
+
* select·select-multi 는 후보가 하나뿐일 때 그 값이 기본값이 된다(options 참고) —
|
|
291
|
+
* defaultValue 를 함께 주면 그쪽이 이긴다. 인라인(radioButton) datepicker-range
|
|
288
292
|
* 필드는 팝오버 없이 바에 바로 노출되어 빈 상태로 둘 수 없으므로, 이 규칙과 무관하게 언제나
|
|
289
293
|
* 오늘이 채워진다 */
|
|
290
294
|
defaultValue?: SChipFilterValue;
|
|
@@ -64,9 +64,9 @@ export interface SChipFilterFieldBase {
|
|
|
64
64
|
* 조용히 되살아난다. 처음부터 값이 정해져 있어야 하는 필터라면 그것이 `fixed`·`required` 다.
|
|
65
65
|
*
|
|
66
66
|
* required인데 지정하지 않으면 타입별 내장 기본값(select: 첫 번째 옵션,
|
|
67
|
-
* datepicker-day·datepicker-range: 오늘 날짜)을 대신 쓴다.
|
|
68
|
-
* 후보가 하나뿐일 때
|
|
69
|
-
*
|
|
67
|
+
* datepicker-day·datepicker-range: 오늘 날짜)을 대신 쓴다. `fixed`·`required` 인
|
|
68
|
+
* select·select-multi 는 후보가 하나뿐일 때 그 값이 기본값이 된다(options 참고) —
|
|
69
|
+
* defaultValue 를 함께 주면 그쪽이 이긴다. 인라인(radioButton) datepicker-range
|
|
70
70
|
* 필드는 팝오버 없이 바에 바로 노출되어 빈 상태로 둘 수 없으므로, 이 규칙과 무관하게 언제나
|
|
71
71
|
* 오늘이 채워진다 */
|
|
72
72
|
defaultValue?: SChipFilterValue;
|
|
@@ -78,12 +78,16 @@ export interface SChipFilterFieldBase {
|
|
|
78
78
|
export interface SChipFilterOptionsField extends SChipFilterFieldBase {
|
|
79
79
|
/** 고를 수 있는 후보 목록.
|
|
80
80
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
81
|
+
* **`fixed`·`required` 인 select·select-multi 에서 고를 수 있는 후보가 하나뿐이면 그 값이
|
|
82
|
+
* 자동으로 선택된다** — 사용자가 고르지 않아도 `onValueChange` 로 값이 올라온다
|
|
83
83
|
* (select 는 그 값, select-multi 는 그 값 하나짜리 배열). 옵션이 API 응답이라 늦게 도착해도
|
|
84
84
|
* 도착한 시점에 채워진다. 사용자가 그 값을 지우면 다시 채우지 않는다 —
|
|
85
85
|
* 다만 "검색 초기화"는 되돌려 놓는다(다른 기본값과 같은 규칙).
|
|
86
|
-
* 후보가 하나인데 비활성(`disabled`)이면 고를 수 없는 값이므로 채우지
|
|
86
|
+
* 후보가 하나인데 비활성(`disabled`)이면 고를 수 없는 값이므로 채우지 않는다.
|
|
87
|
+
*
|
|
88
|
+
* 뺄 수 있는 필터(= `fixed`·`required` 가 아닌 필드)는 후보가 하나여도 채우지 않는다 —
|
|
89
|
+
* 처음에 바에 없는 필터라 보이지도 지울 수도 없는 조건이 검색에 걸리기 때문이다.
|
|
90
|
+
* 후보가 하나뿐인 필터를 반드시 걸어야 한다면 `fixed` 나 `required` 로 준다 */
|
|
87
91
|
options?: SChipFilterOption[];
|
|
88
92
|
}
|
|
89
93
|
/** 프리셋으로 기간을 고르는 필터 — datepicker-range·datepicker-statistics */
|
|
@@ -219,9 +223,14 @@ export interface SChipFilterProps {
|
|
|
219
223
|
/** 실제 검색을 실행할 시점 — 편집 팝오버의 "검색" 버튼을 누르거나 팝오버가 닫힐 때(바깥 클릭·Esc·다른
|
|
220
224
|
* 칩으로 전환 포함) 그 시점의 전체 값 맵과 함께 호출된다. 팝오버가 없는 필드(인라인 date 프리셋·custom,
|
|
221
225
|
* clearable ×, 검색 초기화)는 값이 바뀌는 즉시 호출된다. keyword 필터에서 Enter 로 키워드를 추가할 때도
|
|
222
|
-
* 그 즉시 호출된다 — 팝오버는 열린 채라 키워드를 이어서 더 넣을 수 있고, 넣을 때마다 조회가 갱신된다.
|
|
226
|
+
* 그 즉시 호출된다 — 팝오버는 열린 채라 키워드를 이어서 더 넣을 수 있고, 넣을 때마다 조회가 갱신된다.
|
|
227
|
+
* keyword 입력창에 Enter 없이 텍스트를 남긴 채 팝오버를 닫으면 그 텍스트도 키워드로 확정한 뒤
|
|
228
|
+
* 호출된다(input: 'csv'면 콤마로 쪼개는 규칙도 그대로 적용) — 사용자가 검색어를 넣고 "검색"을 누른
|
|
229
|
+
* 조작이 입력 확정 여부 때문에 사라지지 않게 한다. dirty 체크가 기본 적용되어 있어 — 마지막으로
|
|
223
230
|
* 실제 검색이 실행된 값 맵과 비교해 하나라도 달라진 게 없으면(예: 팝오버를 열었다 아무것도 안 바꾸고
|
|
224
|
-
* 닫는 경우) 호출되지 않는다.
|
|
231
|
+
* 닫는 경우) 호출되지 않는다. 단 마운트 후 첫 호출은 비교할 기준이 없어 값이 처음 그대로여도 나간다 —
|
|
232
|
+
* 마운트 시 자동 조회하지 않는 화면(다이얼로그 등)에서 사용자의 첫 "검색"이 막히지 않게 하기 위함이다.
|
|
233
|
+
* fields를 그룹으로 넘겼다면 rule을 만족하지 못한 그룹이 있는 동안엔
|
|
225
234
|
* onSearch가 호출되지 않는다 — 해당 그룹의 경고 툴팁은 이 시점과 무관하게 값이 비어 있는 동안 항상
|
|
226
235
|
* 실시간으로 떠 있다(별도로 validate()를 호출할 필요 없음). required 필드가 비어 있는 동안에도
|
|
227
236
|
* 마찬가지로 호출되지 않는다 — 정상 설정(내장 기본값 또는 defaultValue)이라면 원래 비워지지
|