@t3q-ui/design-system 0.0.0-stage → 1.56.0
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/CHANGELOG.md +1977 -0
- package/LICENSE.md +88 -0
- package/README.md +464 -2
- package/cli/doctor.mjs +196 -0
- package/cli/lib.mjs +425 -0
- package/cli/setup.mjs +181 -0
- package/cli/t3q-design.mjs +78 -0
- package/dist/chunk-IPF3K4BS.js +4274 -0
- package/dist/chunk-IPF3K4BS.js.map +1 -0
- package/dist/chunk-MHAO45AL.cjs +4427 -0
- package/dist/chunk-MHAO45AL.cjs.map +1 -0
- package/dist/fonts.css +12 -0
- package/dist/index-Ci1Wh5sA.d.cts +253 -0
- package/dist/index-Ci1Wh5sA.d.ts +253 -0
- package/dist/index.cjs +10998 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +332 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +2554 -0
- package/dist/index.d.ts +2554 -0
- package/dist/index.js +10354 -0
- package/dist/index.js.map +1 -0
- package/dist/patterns/index.cjs +885 -0
- package/dist/patterns/index.cjs.map +1 -0
- package/dist/patterns/index.css +265 -0
- package/dist/patterns/index.css.map +1 -0
- package/dist/patterns/index.d.cts +373 -0
- package/dist/patterns/index.d.ts +373 -0
- package/dist/patterns/index.js +873 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/styles.css +2014 -0
- package/package.json +212 -4
- package/skills/tds/SKILL.md +91 -0
- package/skills/tds/examples/dashboard-page.tsx +344 -0
- package/skills/tds/examples/detail-page.tsx +388 -0
- package/skills/tds/examples/form-page.tsx +460 -0
- package/skills/tds/examples/list-page.tsx +275 -0
- package/skills/tds/reference/components.md +1424 -0
- package/skills/tds/rules.md +28 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode, ComponentProps } from 'react';
|
|
3
|
+
import { ColDef } from 'ag-grid-community';
|
|
4
|
+
import { B as BadgeProps, K as AgGridTableType, g as ButtonProps, G as PageHeaderProps, v as DrawerContent, l as DialogContent } from '../index-Ci1Wh5sA.js';
|
|
5
|
+
import { FieldValues, UseFormReturn } from 'react-hook-form';
|
|
6
|
+
import 'class-variance-authority';
|
|
7
|
+
import '@radix-ui/react-dialog';
|
|
8
|
+
import 'ag-grid-react';
|
|
9
|
+
|
|
10
|
+
/** 행을 눌러 보는 상세 — 목록 **오른쪽에 나란히** 그리는 패널. 표가 좁아지고 목록은 계속 보인다(덮는 Drawer 와 다름) */
|
|
11
|
+
type ListPageDetail = {
|
|
12
|
+
/** 패널 제목 (h2). 패널의 접근성 이름 */
|
|
13
|
+
title: ReactNode;
|
|
14
|
+
/** 제목 아래 한 줄 (id·이미지 등) */
|
|
15
|
+
description?: ReactNode;
|
|
16
|
+
/** 닫기 — 오른쪽 위 닫기 버튼과 Escape 가 부른다. 포커스는 옮기지 않는다(행에 남음) */
|
|
17
|
+
onClose: () => void;
|
|
18
|
+
/** 본문 (보통 MetaList + MetaRow) */
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
/** 버튼 줄 위 안내 한 줄 — 비활성 버튼의 사유 자리 (예: "실행 중인 환경만 중지할 수 있습니다"). 비활성 버튼에는 tooltip 이 뜨지 않는다 */
|
|
21
|
+
note?: ReactNode;
|
|
22
|
+
/** 버튼 줄 (오른쪽 정렬, 간격 8px) */
|
|
23
|
+
footer?: ReactNode;
|
|
24
|
+
/** 패널 폭. small 360 · medium 480(기본) — Drawer size 와 같은 값 */
|
|
25
|
+
width?: 'small' | 'medium';
|
|
26
|
+
};
|
|
27
|
+
/** 빈 결과의 제목·설명을 직접 줄 때의 형태 */
|
|
28
|
+
type ListPageEmptyContent = {
|
|
29
|
+
/** 무엇이 없는지 (예: "조건에 맞는 모델이 없습니다") */
|
|
30
|
+
title: ReactNode;
|
|
31
|
+
/** 다음에 할 수 있는 일 (예: "검색어나 필터를 바꾸거나 초기화해 보세요") */
|
|
32
|
+
description?: ReactNode;
|
|
33
|
+
};
|
|
34
|
+
/** 표 왼쪽 위의 빠른 필터(상태처럼 선택지 3~6개). 드롭다운과 달리 선택지·선택 상태·건수가 항상 보인다 */
|
|
35
|
+
type ListPageQuickFilter = {
|
|
36
|
+
/** 필터 대상 이름 (예: '상태'). 세그먼트 앞에 글자로 보이고 그룹의 접근성 이름이 된다 */
|
|
37
|
+
label: string;
|
|
38
|
+
/** 선택지. `count` 를 주면 항목 오른쪽에 건수 Badge — "지금 몇 건이 어떤 상태인가" (2026-10-11 진단에서 건수가 있을 때 두 평가자 모두 종합 선호가 올랐다) */
|
|
39
|
+
options: {
|
|
40
|
+
value: string;
|
|
41
|
+
label: string;
|
|
42
|
+
count?: number;
|
|
43
|
+
/** 건수 pill 뱃지의 색. 기본 secondary. **표의 상태 뱃지와 같은 tone**(`success`·`danger`·`warning`·`info`·`secondary`)을 주면 칩과 상태 열이 같은 색 언어가 된다(예제 list-page.tsx 의 `STATUS[s].tone`). "전체" 는 패턴이 primary 로 그린다 (2026-10-18 13차 D, 사용자 결정) */
|
|
44
|
+
countVariant?: NonNullable<BadgeProps['variant']>;
|
|
45
|
+
}[];
|
|
46
|
+
/** 선택된 값들 (다중 선택). 빈 배열 = 전체 */
|
|
47
|
+
value: string[];
|
|
48
|
+
/** 선택이 바뀔 때. 소비 앱은 `pagination.page` 를 1로 되돌리고 `filtersActive` 에 이 필터도 포함한다 */
|
|
49
|
+
onChange: (next: string[]) => void;
|
|
50
|
+
};
|
|
51
|
+
type ListPageProps<TRow> = {
|
|
52
|
+
/** 페이지 제목 (h1) */
|
|
53
|
+
title: string;
|
|
54
|
+
/** 제목 아래 설명 */
|
|
55
|
+
description?: ReactNode;
|
|
56
|
+
/** 제목 오른쪽 슬롯 (예: 새로고침 버튼·마지막 갱신 시각) */
|
|
57
|
+
titleRight?: ReactNode;
|
|
58
|
+
/** 주요 행동 버튼 (보통 1개). 제목 줄 오른쪽(PageHeader `actions`) — 2026-10-19 14차: 페이지 수준 행동은 제목 줄(Polaris 인덱스 페이지·GitHub Issues·기준선과 같은 자리). 1.22.0~1.47.4 의 필터 줄 왼쪽(SharePoint 계열)은 2026-10-13 UIClip(폐기된 자) 근거였다 */
|
|
59
|
+
actions?: ReactNode;
|
|
60
|
+
/** 검색·필터 컨트롤. 헤더 오른쪽 아래 (TableFilter*, ColumnFilter 등) */
|
|
61
|
+
filters?: ReactNode;
|
|
62
|
+
/** 필터를 되돌리는 핸들러. 주면 빠른 필터 줄(표 바로 위) 오른쪽 끝에 "필터 초기화" 외곽선 버튼이 **항상** 자리를 잡는다(빠른 필터·건수가 없으면 filters 줄 끝). 검색창은 그래서 표 오른쪽 끝까지 닿는다(2026-10-18 10차). 필터가 걸렸을 때만 조건부로 넘기지 말고(빈 결과에서 버튼이 튀어나와 배치가 바뀐다) 항상 넘기고 `filtersActive` 로 보일지를 정한다 */
|
|
63
|
+
onResetFilters?: () => void;
|
|
64
|
+
/** 필터가 하나라도 걸려 있는가. false 면 "필터 초기화" 버튼이 자리만 지킨 채 보이지 않는다(invisible) */
|
|
65
|
+
filtersActive?: boolean;
|
|
66
|
+
/** 주 식별자 열의 field. 그 열에 `type: 'primary'`(굵게, 행 hover 시 밑줄)를 붙인다. 기본값: `onRowClick` 이 있으면 `'name'`, 없으면 없음. 끄려면 `null` */
|
|
67
|
+
primaryField?: string | null;
|
|
68
|
+
/** 주 식별자 열 아래 **보조 줄**에 보일 행 필드(id·이미지·경로·설명, 점 표기 가능). 열을 하나 더 늘리는 대신 이름 아래 12px 회색 한 줄로 — dl-ui-v3 목록(이름 / 설명)과 같은 모양이고 열이 적어 잘림도 준다(2026-10-18 13차 ⑥). 주 열(`primaryField`, 기본 `name`)에 `cellRenderer` 가 이미 있으면 적용하지 않는다. 주면 행 높이 56(`tableProps.rowHeight` 가 우선) */
|
|
69
|
+
secondaryField?: string;
|
|
70
|
+
/** 표 위 제목. **쓰지 않는다** — 헤더 제목(h1)과 중복이고 빠른 필터 옆에서 "tightly packed" 로 감점(2026-10-18 10차, 사용자 결정). 남겨 둔 건 호환용. `pagination.total` 이 있으면 같은 줄 오른쪽에 "총 N개" 가 붙는다(제목·빠른 필터 둘 다 없으면 윗줄 자체를 안 그린다 — 2026-10-27) — 단 `quickFilter` 가 항목마다 `count` 를 보여 주면 "전체 N" 과 같은 숫자라 생략한다 */
|
|
71
|
+
tableTitle?: ReactNode;
|
|
72
|
+
/** 표 왼쪽 위(제목 줄)의 빠른 필터 — 상태처럼 선택지 3~6개의 다중 선택. ToggleGroup(붙어있는 세그먼트)으로 그려 드롭다운을 열지 않아도 선택지가 보인다. 선택지가 많거나 검색이 필요한 축은 `filters` 의 TableFilterMultiSelect 로. `tableTitle` 이 없으면 이 줄을 세그먼트가 대신한다(줄이 늘지 않음) */
|
|
73
|
+
quickFilter?: ListPageQuickFilter;
|
|
74
|
+
/** 상세가 열린 행의 id (`row.id` 와 비교). 그 행을 표의 선택 색으로 칠해 "지금 어느 행을 보고 있나" 를 보인다. 상세를 닫으면 null */
|
|
75
|
+
activeRowId?: string | number | null;
|
|
76
|
+
/** 행을 눌러 보는 상세 패널. 주면 목록 **오른쪽에 나란히** 그린다(표가 좁아지고 목록은 계속 보인다, 닫기 버튼·Escape). 닫으면 undefined 로. 2026-10-16(이슈 45) — 덮는 `<Drawer modal={false}>` 는 표를 좁히면 안 될 때의 대안 */
|
|
77
|
+
detail?: ListPageDetail;
|
|
78
|
+
/** 표 컬럼 정의 (ag-grid ColDef) */
|
|
79
|
+
columns: ColDef<TRow>[];
|
|
80
|
+
/** 현재 페이지 행. undefined 면 표가 로딩 오버레이를 보인다 */
|
|
81
|
+
rows?: TRow[];
|
|
82
|
+
/** true 면 표 자리에 TableSkeleton */
|
|
83
|
+
loading?: boolean;
|
|
84
|
+
/** 주면 표 대신 Alert(danger) 로 표시한다 */
|
|
85
|
+
error?: ReactNode;
|
|
86
|
+
/** 0건일 때 표 안에 그릴 안내. 패턴이 **한 줄 문장**(+ 필터가 걸려 있으면 링크형 "필터 초기화")으로 그린다 — 아이콘·상자는 그리지 않는다(1.29.0 의 상자는 2026-10-13 되돌림). 문자열 또는 `{ title, description }`(이어 붙여 한 줄). 버튼·아이콘은 넣지 않는다. 기본 "데이터가 없습니다" */
|
|
87
|
+
emptyState?: ReactNode | ListPageEmptyContent;
|
|
88
|
+
/** 페이지네이션. 없으면 그리지 않는다 */
|
|
89
|
+
pagination?: {
|
|
90
|
+
/** 전체 행 수 */
|
|
91
|
+
total: number;
|
|
92
|
+
/** 현재 페이지 (1부터) */
|
|
93
|
+
page: number;
|
|
94
|
+
/** 페이지당 행 수 */
|
|
95
|
+
pageSize: number;
|
|
96
|
+
/** 페이지 변경 */
|
|
97
|
+
onChange: (page: number) => void;
|
|
98
|
+
};
|
|
99
|
+
/** 정렬 변경. 값은 getSortedColumnState 규칙 ('name' | '-name' | '') */
|
|
100
|
+
onSortChange?: (sort: string) => void;
|
|
101
|
+
/** 행 클릭 */
|
|
102
|
+
onRowClick?: (row: TRow) => void;
|
|
103
|
+
/** AgGridTable 에 그대로 넘길 나머지 props (rowHeight, rowSelection 등). 표 높이는 기본 `domLayout='autoHeight'`(행 수만큼) — 고정 높이는 `domLayout='normal'` + `containerStyle` */
|
|
104
|
+
tableProps?: Omit<AgGridTableType, 'rowData' | 'columnDefs' | 'loading' | 'noRowsOverlayComponent' | 'onSortChanged' | 'onRowClicked'>;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* 목록 화면 패턴. PageHeader(제목·설명·주요 버튼·필터 줄) → AgGridTable → Pagination 을 한 번에 배치한다.
|
|
108
|
+
*
|
|
109
|
+
* 언제 쓰나: 실행환경·모델·파이프라인·데이터셋처럼 "검색/필터 → 표 → 상세 진입" 흐름이 있는 리소스 목록. 컬럼·행·필터 컨트롤·데이터 조회는 소비 앱이 넘기고, 패턴은 배치와 로딩·빈 결과·오류 상태만 책임진다.
|
|
110
|
+
* 언제 안 쓰나: 차트·요약 카드 중심의 대시보드, 한 항목의 상세 화면, 표가 없는 설정 화면.
|
|
111
|
+
* 대신/함께: 행 클릭 뒤 상세는 `onRowClick` 에서 상세 페이지로 이동하는 것이 기본이고, 목록 맥락을 유지해야 할 때는 `detail` 슬롯으로 목록 오른쪽에 **나란히** 연다(표가 좁아지고 목록은 계속 보인다, 닫기 버튼·Escape, 비활성 사유는 `detail.note`). 표를 좁히면 안 될 때만 `<Drawer modal={false}>`(덮는 옆 패널)로(어느 쪽인지는 화면 설계가 정한다). 필터가 바뀌면 소비 앱이 `pagination.page` 를 1로 되돌린다. `onResetFilters` 는 항상 넘기고 `filtersActive` 로 보일지를 정한다(필터가 걸린 채 0건이면 빈 결과 가운데에도 "필터 초기화" 가 나온다). 상세를 Drawer 로 열면 `activeRowId` 로 그 행을 표시한다. 주 식별자 열은 `primaryField`(행 클릭이 있으면 기본 `'name'`), 표 위 제목·건수는 `tableTitle`·`pagination.total`. 상태처럼 선택지가 3~6개인 다중 선택 필터는 `quickFilter` 로 표 왼쪽 위에 세그먼트로 둔다(드롭다운에 숨기지 않는다). 본문은 제목을 이름으로 가진 `section` landmark 다(소비 앱의 `<main>` 안에 둔다). 표는 한 페이지의 행을 다 보이도록 행 수만큼 자란다(`domLayout='autoHeight'` 기본) — 화면 높이에 맞춘 안쪽 스크롤이 필요하면 `tableProps` 로 `domLayout='normal'` 과 `containerStyle` 을 넘긴다.
|
|
112
|
+
*/
|
|
113
|
+
declare function ListPage<TRow>({ title, description, titleRight, actions, filters, onResetFilters, columns, rows, loading, error, emptyState, pagination, filtersActive, primaryField, secondaryField, tableTitle, quickFilter, activeRowId, detail, onSortChange, onRowClick, tableProps, }: ListPageProps<TRow>): react_jsx_runtime.JSX.Element;
|
|
114
|
+
|
|
115
|
+
/** 헤더 행동 버튼 하나. `children` 이 라벨(아이콘 포함), variant 는 슬롯이 정한다 */
|
|
116
|
+
type DetailPageAction = Omit<ButtonProps, 'variant' | 'onClick'> & {
|
|
117
|
+
/** 클릭. 좁은 헤더에서 "더보기" 메뉴 항목으로 접혔을 때는 항목 선택 */
|
|
118
|
+
onClick?: () => void;
|
|
119
|
+
};
|
|
120
|
+
type DetailPageProps = Pick<PageHeaderProps, 'title' | 'description' | 'status' | 'meta' | 'titleRight' | 'breadcrumb' | 'tabs' | 'activeTab' | 'onTabChange'> & {
|
|
121
|
+
/** 주 행동 1개 (예: 배포·새 추론 서비스). `primaryBold` 로 맨 오른쪽에 그린다 */
|
|
122
|
+
primaryAction?: DetailPageAction;
|
|
123
|
+
/** 보조 행동들 (예: 다운로드·복제·새 버전). `secondary` 로 주 행동 왼쪽에 준 순서대로 — 2개까지만 보이고 3번째부터는 "더보기" 메뉴로 접힌다(2026-10-19 14차) */
|
|
124
|
+
secondaryActions?: DetailPageAction[];
|
|
125
|
+
/** 삭제. 항상 "더보기" 메뉴 안(구분선 아래 danger 항목)에 둔다 — 빨간 버튼을 첫 화면에 펼치지 않는다(2026-10-19 14차). 확인 Dialog 는 소비 앱이 onClick 에서 연다 */
|
|
126
|
+
deleteAction?: DetailPageAction;
|
|
127
|
+
/** 버튼 묶음을 직접 구성할 때만 (드롭다운·툴팁 등). 주면 위 세 슬롯 대신 이것을 그대로 그린다 */
|
|
128
|
+
actions?: ReactNode;
|
|
129
|
+
/** **쓰지 않는다**(2026-10-18 11차, 사용자 결정) — 본문 위 안내 한 줄이 4라운드 연속 "떠 있다" 로 감점. 비활성 버튼은 비활성으로만 두고, 상태 자체의 안내가 필요하면 `banner`. 호환용으로 남긴 prop */
|
|
130
|
+
actionsNote?: ReactNode;
|
|
131
|
+
/** 헤더 아래, 본문 위에 놓는 알림 슬롯 (예: 삭제 예정·권한 안내 Alert). 상태와 무관하게 항상 그린다 */
|
|
132
|
+
banner?: ReactNode;
|
|
133
|
+
/** true 면 본문 자리에 Skeleton. error·notFound 보다 우선 */
|
|
134
|
+
loading?: boolean;
|
|
135
|
+
/** 주면 본문 대신 오류 상자(ErrorState: 아이콘·"불러오지 못했습니다"·이 내용). `onRetry` 가 있으면 "다시 시도" 버튼을 함께 그린다 */
|
|
136
|
+
error?: ReactNode;
|
|
137
|
+
/** error 상태의 "다시 시도" 클릭 */
|
|
138
|
+
onRetry?: () => void;
|
|
139
|
+
/** 항목이 없을 때 본문 대신 없음 상자(EmptyState — 상자 일러스트, 2026-10-13 부터 목록·대시보드와 같은 그림). true 면 기본 문구 "항목을 찾을 수 없습니다." + 안내 한 줄 */
|
|
140
|
+
notFound?: ReactNode;
|
|
141
|
+
/** **호환용** — 새 화면은 핵심 정보를 헤더 `meta`(`MetaGroup columns={3}`)에 둔다(2026-10-27 사용자 결정 A: 오른쪽 패널은 내용이 짧아 첫 화면 아래가 비는 것이 홀드아웃 9~12 연속 지적, 2026-10-30 요약 상자 대신 메타 자리 하나로). 주면 넓은 화면(xl)에서 본문 2 : 패널 1, 좁은 화면에서는 본문 아래 */
|
|
142
|
+
aside?: ReactNode;
|
|
143
|
+
/** 패널 제목(Panel 머리글, 기본 "기본 정보"). 패턴이 그리므로 `aside` 안에 같은 제목을 다시 쓰지 않는다(2026-10-27 홀드아웃 12: "작업 정보" 가 연달아 두 번) */
|
|
144
|
+
asideTitle?: ReactNode;
|
|
145
|
+
/** 본문 폭. full = 가로 전체(정보 탭), panel = 가운데 900px (설정 탭). 기본 full */
|
|
146
|
+
width?: 'full' | 'panel';
|
|
147
|
+
/** 본문 (현재 탭 내용). 탭 상태와 라우팅은 소비 앱이 가진다 */
|
|
148
|
+
children?: ReactNode;
|
|
149
|
+
};
|
|
150
|
+
declare function DetailPage({ primaryAction, secondaryActions, deleteAction, actions, actionsNote, banner, loading, error, onRetry, notFound, aside, asideTitle, width, children, ...header }: DetailPageProps): react_jsx_runtime.JSX.Element;
|
|
151
|
+
|
|
152
|
+
/** FormPage · FormDrawer · FormDialog 가 공유하는 props */
|
|
153
|
+
type FormShellProps<TValues extends FieldValues> = {
|
|
154
|
+
/** 제목 */
|
|
155
|
+
title: string;
|
|
156
|
+
/** 제목 아래 설명 */
|
|
157
|
+
description?: ReactNode;
|
|
158
|
+
/** react-hook-form `useForm()` 반환값. 검증 스키마·기본값은 소비 앱이 소유한다 */
|
|
159
|
+
form: UseFormReturn<TValues>;
|
|
160
|
+
/** 검증 통과 후 호출. Promise 를 돌려주면 끝날 때까지 제출 중 상태가 된다 */
|
|
161
|
+
onSubmit: (values: TValues) => void | Promise<void>;
|
|
162
|
+
/** 제출 버튼 라벨. 기본 "저장" */
|
|
163
|
+
submitLabel?: string;
|
|
164
|
+
/** 취소 버튼 라벨. 기본 "취소" */
|
|
165
|
+
cancelLabel?: string;
|
|
166
|
+
/** 외부 저장 중 상태(뮤테이션 등). 버튼 loading·disabled, 취소·닫기 차단 */
|
|
167
|
+
submitting?: boolean;
|
|
168
|
+
/** 서버 오류 등 폼 전체 오류. 버튼 위에 Alert(danger) 로 표시 */
|
|
169
|
+
error?: ReactNode;
|
|
170
|
+
/**
|
|
171
|
+
* 제출 버튼 활성 규칙. `always`(기본) = 제출 중이 아니면 항상 눌리고, 누르면 react-hook-form 이 검증해 틀린 항목마다 FormMessage 를 보인다.
|
|
172
|
+
* `whenValid` = 모든 입력이 유효해질 때까지 잠근다(2026-10-12 전 기본). "누르면 검증해 이유 표시" 가 흔한 폼 관례라 기본을 바꿨다
|
|
173
|
+
*/
|
|
174
|
+
submitMode?: 'always' | 'whenValid';
|
|
175
|
+
/** FormSection 으로 묶은 FormField 들 */
|
|
176
|
+
children: ReactNode;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
type FormPageProps<TValues extends FieldValues> = FormShellProps<TValues> & Pick<PageHeaderProps, 'breadcrumb' | 'titleRight'> & {
|
|
180
|
+
/** 취소 버튼 핸들러. 없으면 취소 버튼을 그리지 않는다 */
|
|
181
|
+
onCancel?: () => void;
|
|
182
|
+
/** true 면 폼 맨 위에 "* 표시는 필수 입력 항목입니다" 안내 한 줄 */
|
|
183
|
+
requiredLegend?: boolean;
|
|
184
|
+
/** 본문 바탕. 'plain'(기본)은 흰 캔버스에 섹션 구분선. 'subtle' 은 연한 회색 캔버스 위에 폼 전체를 흰 Card 하나로 — UIClip 폼 진단(evals/metrics-spec §16.7)용 옵트인이며 캔버스 정책(DESIGN.md)은 흰색 그대로다 */
|
|
185
|
+
canvas?: 'plain' | 'subtle';
|
|
186
|
+
/** 폼 열(720px)의 자리. 'start'(기본)는 헤더 제목과 같은 왼쪽 축에서 시작, 'center' 는 본문 가운데(2026-10-16 이슈 47 전의 모양 — 제목은 왼쪽, 폼은 가운데라 축이 어긋난다고 원문 심판 10회 감점). 2026-10-26 22차부터 헤더 내용과 본문이 같은 가운데 폭 컨테이너(패널 있으면 1152, 없으면 720) 안에 들어가므로 이 값의 효과는 거의 없다(호환용). `aside` 가 있으면 폼 열은 왼쪽 */
|
|
187
|
+
align?: 'start' | 'center';
|
|
188
|
+
/** 폼 오른쪽의 검토·요약 패널(입력값 요약, 비용 미리보기 등). 넓은 화면에서 폼 열 옆에 세로선으로 구분돼 화면을 따라 내려오고(sticky), 좁으면 폼 아래로. **취소·제출 버튼은 이 패널 바닥**에 놓인다(폼 끝에는 없음 — 2026-10-20 16차 D1, 긴 폼 옆에서 패널 아래가 비고 제출이 멀다는 두 심판 지적). FormPage 를 flex 로 감싸 옆에 패널을 두면 헤더가 화면 폭을 다 못 써 구분선이 끊긴다 — 반드시 이 슬롯으로 */
|
|
189
|
+
aside?: ReactNode;
|
|
190
|
+
/** 검토 패널 제목(h2). 기본 '검토' */
|
|
191
|
+
asideTitle?: ReactNode;
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* 독립 화면 폼 패턴. PageHeader(제목·설명) → FormSection 들(사이에 연한 구분선, 왼쪽 축 720px) → 오류 → 취소/저장(FormFooter; `aside` 가 있으면 패널 바닥). **헤더 내용과 본문은 같은 가운데 폭 컨테이너**에 둔다 — 검토 패널이 있으면 `--max-w-page-content`(1152 = 720 + 24 + 408), 없으면 폼 폭 720. 띠(헤더 배경·아래 선)는 전폭 그대로(2026-10-26 22차: 전폭 왼쪽 정렬에서는 긴 폼 옆 짧은 패널 아래가 "unused space" 로 감점됐고, 내용을 가운데 폭에 가둔 변형만 원문 심판 layout 3.5 → 4.0 — 기준선 폼들과 같은 구조). 검토·요약 패널(`aside`)은 **항상** 폼 옆에 둔다 — 입력값 요약·예상 비용처럼 제출 전에 확인할 것(2026-10-17 8차 C2, 예제 form-page.tsx).
|
|
195
|
+
*
|
|
196
|
+
* 언제 쓰나: 스크롤이 생기는 긴 폼 — 섹션 3개 이상, 한 화면을 넘기거나, 여러 단계. 전용 URL 을 가진 생성·수정 화면의 기본. 검증 스키마·기본값·payload 는 소비 앱의 `useForm` 이 소유한다.
|
|
197
|
+
* 언제 안 쓰나: 목록 맥락을 유지해야 하고 Drawer 한 화면 안에 들어가는 폼(섹션 1~2개)은 FormDrawer, 필드 1~4개의 짧은 입력은 FormDialog. 긴 폼을 Drawer·Dialog 에 넣지 않는다.
|
|
198
|
+
* 대신/함께: 머리는 목록·상세·대시보드와 같은 PageHeader(h1)라 FormSection 제목은 h2 가 된다. 바탕은 흰색이 기본이고 `canvas='subtle'` 은 측정용 옵트인(회색 캔버스 + 카드)이다. 저장 버튼은 기본(`submitMode='always'`)으로 항상 눌리고 누르면 검증해 틀린 항목마다 FormMessage 를 보인다 — 유효해질 때까지 잠그려면 `submitMode='whenValid'`. `onSubmit` 이 Promise 면 끝날 때까지 loading — 다만 제출 중 상태는 앱이 `submitting` 으로도 **코드에 드러내라**(접근성·심사 모두 코드에서 보여야 한다, 2026-10-16). 숫자 입력의 단위는 `TextField elemAfterInput='개'`. 선택지 목록을 못 불러왔으면 폼을 지우지 말고 그 필드 자리에 ErrorState 를 둔다.
|
|
199
|
+
*/
|
|
200
|
+
declare function FormPage<TValues extends FieldValues>({ title, description, breadcrumb, titleRight, form, onSubmit, onCancel, submitLabel, cancelLabel, submitting, submitMode, error, requiredLegend, canvas, align, aside, asideTitle, children, }: FormPageProps<TValues>): react_jsx_runtime.JSX.Element;
|
|
201
|
+
|
|
202
|
+
type FormDrawerProps<TValues extends FieldValues> = FormShellProps<TValues> & {
|
|
203
|
+
/** 열림 상태 (controlled) */
|
|
204
|
+
open: boolean;
|
|
205
|
+
/** 열림 상태 변경. 제출 중에는 닫기 요청(false)이 전달되지 않는다 */
|
|
206
|
+
onOpenChange: (open: boolean) => void;
|
|
207
|
+
/** 패널이 나오는 방향. 기본 right */
|
|
208
|
+
side?: ComponentProps<typeof DrawerContent>['side'];
|
|
209
|
+
/** 패널 너비. 기본 large */
|
|
210
|
+
size?: ComponentProps<typeof DrawerContent>['size'];
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* 사이드 패널 폼 패턴. 목록 화면 위에 오른쪽 Drawer 로 생성·수정 폼을 띄운다. 제목 → FormSection 들(스크롤) → 오류 → 취소/저장.
|
|
214
|
+
*
|
|
215
|
+
* 언제 쓰나: 목록 맥락을 유지해야 하고 **Drawer 한 화면 안에 들어가는** 폼(섹션 1~2개 — 예: 태그·설명·담당자 수정, 짧은 생성).
|
|
216
|
+
* 언제 안 쓰나: 필드 1~4개의 짧은 입력은 FormDialog. 스크롤이 생기거나 섹션이 셋 이상이거나 여러 단계인 긴 폼(실행환경·파이프라인·모델 생성)은 FormPage — 긴 폼을 Drawer 에 넣지 않는다(2026-10-13, Carbon·Shopify 관례).
|
|
217
|
+
* 대신/함께: 제출 중에는 Esc·오버레이·취소·닫기 버튼으로 닫히지 않는다. 닫을 때 폼을 reset 할지는 소비 앱이 `onOpenChange` 에서 정한다(초안을 보존하려면 reset 하지 않는다).
|
|
218
|
+
*/
|
|
219
|
+
declare function FormDrawer<TValues extends FieldValues>({ open, onOpenChange, side, size, title, description, form, onSubmit, submitLabel, cancelLabel, submitting, error, submitMode, children, }: FormDrawerProps<TValues>): react_jsx_runtime.JSX.Element;
|
|
220
|
+
|
|
221
|
+
type FormDialogProps<TValues extends FieldValues> = FormShellProps<TValues> & {
|
|
222
|
+
/** 열림 상태 (controlled) */
|
|
223
|
+
open: boolean;
|
|
224
|
+
/** 열림 상태 변경. 제출 중에는 닫기 요청(false)이 전달되지 않는다 */
|
|
225
|
+
onOpenChange: (open: boolean) => void;
|
|
226
|
+
/** 모달 너비. 기본 small */
|
|
227
|
+
width?: ComponentProps<typeof DialogContent>['width'];
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* 모달 폼 패턴. 제목 → 필드 → 오류 → 취소/저장.
|
|
231
|
+
*
|
|
232
|
+
* 언제 쓰나: 이름 변경, 태그 추가처럼 필드 1~4개로 끝나는 짧은 입력.
|
|
233
|
+
* 언제 안 쓰나: 목록 맥락이 필요한 한 화면짜리 폼은 FormDrawer, 스크롤이 생기는 긴 폼은 FormPage, 되돌릴 수 없는 확인만 받을 때는 AlertDialog.
|
|
234
|
+
* 대신/함께: 제출 중에는 닫기 버튼이 사라지고 Esc·오버레이·취소로도 닫히지 않는다.
|
|
235
|
+
*/
|
|
236
|
+
declare function FormDialog<TValues extends FieldValues>({ open, onOpenChange, width, title, description, form, onSubmit, submitLabel, cancelLabel, submitting, error, submitMode, children, }: FormDialogProps<TValues>): react_jsx_runtime.JSX.Element;
|
|
237
|
+
|
|
238
|
+
type DashboardStat = {
|
|
239
|
+
/** 타일 라벨 (예: 실행 중인 학습) */
|
|
240
|
+
label: ReactNode;
|
|
241
|
+
/** 큰 숫자 */
|
|
242
|
+
value: ReactNode;
|
|
243
|
+
/** 숫자 아래 보조 설명 (예: 어제보다 +2) */
|
|
244
|
+
caption?: ReactNode;
|
|
245
|
+
/** 주면 타일 전체가 이 주소로 가는 링크가 된다 */
|
|
246
|
+
href?: string;
|
|
247
|
+
/** 사용률 같은 비율(0~1). 주면 숫자 아래에 진행 막대를 그린다 (예: 172/192 코어 → 0.9) */
|
|
248
|
+
ratio?: number;
|
|
249
|
+
/** 값의 색. warning·danger 면 경고 아이콘이 붙고 숫자·막대가 그 색이 된다 (예: 사용률 80% 초과). 기본 default */
|
|
250
|
+
tone?: 'default' | 'warning' | 'danger';
|
|
251
|
+
};
|
|
252
|
+
type DashboardLayoutProps = Pick<PageHeaderProps, 'title' | 'description' | 'titleRight' | 'breadcrumb'> & {
|
|
253
|
+
/** 주요 행동 버튼. 제목 줄 오른쪽 (ListPage·DetailPage 와 같은 자리, 2026-10-19 14차) */
|
|
254
|
+
actions?: ReactNode;
|
|
255
|
+
/** 맨 위 요약 타일. 2열, 넓은 화면(xl)에서 4열, 간격 12px */
|
|
256
|
+
stats?: DashboardStat[];
|
|
257
|
+
/** true 면 타일 숫자 자리에 Skeleton. 패널 로딩은 각 DashboardPanel 의 `loading` */
|
|
258
|
+
loading?: boolean;
|
|
259
|
+
/** 주면 타일·섹션 대신 오류 상자(ErrorState). `onRetry` 가 있으면 "다시 시도" 버튼 */
|
|
260
|
+
error?: ReactNode;
|
|
261
|
+
/** error 상태의 "다시 시도" 클릭 */
|
|
262
|
+
onRetry?: () => void;
|
|
263
|
+
/** 보여 줄 데이터가 전혀 없을 때 타일·섹션 대신 없음 상자. true 면 기본 문구 "데이터가 없습니다." 패널 하나만 비었으면 DashboardPanel 의 `empty` */
|
|
264
|
+
empty?: ReactNode;
|
|
265
|
+
/** DashboardSection 들. 세로로 32px 간격 */
|
|
266
|
+
children?: ReactNode;
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* 대시보드 화면 패턴. PageHeader → 요약 타일(`stats`) → DashboardSection(제목 + 패널 그리드 또는 표) 을 세로로 배치한다.
|
|
270
|
+
*
|
|
271
|
+
* 언제 쓰나: 프로젝트 홈·리소스 모니터링처럼 "지금 상태를 숫자·차트로 한눈에" 보여 주고 각 섹션에서 세부 화면으로 들어가는 화면. 값·시계열·갱신 주기는 소비 앱이 가진다.
|
|
272
|
+
* 언제 안 쓰나: 한 리소스의 목록(→ ListPage)·상세(→ DetailPage), 설정 입력(→ FormPage). 섹션 하나뿐이면 PageHeader + Card 로 충분하다.
|
|
273
|
+
* 대신/함께: 패널 내용(차트·표)은 `DashboardPanel` 의 children 으로 넣고 차트 라이브러리는 소비 앱이 고른다. 폴링·갱신 시각 표시는 `titleRight` 슬롯에 `PageHeaderRefreshButton` 으로. 표 섹션은 DashboardSection 안에 Table 또는 AgGridTable 을 그대로 넣는다. 사용률 타일은 `ratio`(진행 막대)·`tone`(80% 초과면 warning) 을 준다. 전체를 못 불러왔으면 `error`(+`onRetry`), 데이터가 전혀 없으면 `empty` — 빨간 Alert·`<div>` 로 직접 만들지 않는다.
|
|
274
|
+
*/
|
|
275
|
+
declare function DashboardLayout({ actions, stats, loading, error, onRetry, empty, children, ...header }: DashboardLayoutProps): react_jsx_runtime.JSX.Element;
|
|
276
|
+
type DashboardSectionProps = {
|
|
277
|
+
/** 섹션 제목 (h2) */
|
|
278
|
+
title: ReactNode;
|
|
279
|
+
/** 제목 오른쪽 슬롯 (예: "전체 보기" 버튼) */
|
|
280
|
+
action?: ReactNode;
|
|
281
|
+
/** 주면 children(DashboardPanel 들)을 **하나의 Card** 안에 1px 구분선으로 나눈 그리드로 그린다. 2 면 넓은 화면(xl)에서 2열. 없으면 children 을 그대로(표 등) */
|
|
282
|
+
columns?: 1 | 2;
|
|
283
|
+
/** columns 가 있으면 DashboardPanel 들, 없으면 표 등 아무 내용 */
|
|
284
|
+
children?: ReactNode;
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* DashboardLayout 의 한 섹션. 제목(h2)·오른쪽 행동 → 패널 그리드(Card 하나 안에 구분선) 또는 표.
|
|
288
|
+
*
|
|
289
|
+
* 언제 쓰나: 대시보드에서 주제 하나(리소스 사용량, 최근 학습 …)를 묶을 때. 차트 패널 여러 개면 `columns={2}` — 패널마다 카드가 아니라 **큰 카드 하나를 구분선으로 나눈다**(dl-ui-v3 리소스 모니터링 관례).
|
|
290
|
+
* 언제 안 쓰나: 대시보드 밖의 섹션 제목(→ FormSection·CardHeader).
|
|
291
|
+
* 대신/함께: 표 섹션은 `columns` 없이 children 에 Table 을 바로 넣고 `action` 에 "전체 보기" 링크 버튼을 둔다.
|
|
292
|
+
*/
|
|
293
|
+
declare function DashboardSection({ title, action, columns, children }: DashboardSectionProps): react_jsx_runtime.JSX.Element;
|
|
294
|
+
type DashboardPanelProps = {
|
|
295
|
+
/** 패널 제목 (h3) */
|
|
296
|
+
title: ReactNode;
|
|
297
|
+
/** 제목 옆 현재값 (큰 글자) */
|
|
298
|
+
value?: ReactNode;
|
|
299
|
+
/** 현재값 단위 (예: %, GB) */
|
|
300
|
+
unit?: ReactNode;
|
|
301
|
+
/** 현재값 옆 보조 수치 (예: 할당 8 / 사용 3) */
|
|
302
|
+
stats?: {
|
|
303
|
+
label: ReactNode;
|
|
304
|
+
value: ReactNode;
|
|
305
|
+
}[];
|
|
306
|
+
/** true 면 내용 자리에 Skeleton. `empty` 보다 우선 */
|
|
307
|
+
loading?: boolean;
|
|
308
|
+
/** 주면 내용 대신 표시. true 면 기본 문구 "데이터가 없습니다." */
|
|
309
|
+
empty?: ReactNode;
|
|
310
|
+
/** 내용 (차트·표). 차트 라이브러리는 소비 앱이 고른다 */
|
|
311
|
+
children?: ReactNode;
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
314
|
+
* DashboardSection(`columns`) 의 칸 하나. 제목·현재값·단위·보조 수치 머리 → 내용(차트·표). 자체 테두리는 없고 섹션 Card 가 칸 사이를 구분선으로 나눈다.
|
|
315
|
+
*
|
|
316
|
+
* 언제 쓰나: 시계열 차트나 작은 표 하나에 현재값을 붙여 보여 줄 때. 항상 `columns` 를 준 DashboardSection 안에 둔다.
|
|
317
|
+
* 언제 안 쓰나: 숫자 하나만 보여 줄 때(→ DashboardLayout 의 `stats` 타일), 상세 화면의 정보 묶음(→ 제목 + Separator + MetaRow).
|
|
318
|
+
* 대신/함께: 데이터가 없으면 `empty`, 불러오는 중이면 `loading` 을 준다. 내용 높이는 children 이 정한다.
|
|
319
|
+
*/
|
|
320
|
+
declare function DashboardPanel({ title, value, unit, stats, loading, empty, children, }: DashboardPanelProps): react_jsx_runtime.JSX.Element;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* 패턴 공통 로딩 자리표시. 스크린리더에는 "불러오는 중" 으로 읽힌다.
|
|
324
|
+
*
|
|
325
|
+
* 언제 쓰나: 패턴 밖에서 본문 하나를 통째로 기다릴 때(패턴 안에서는 `loading` prop 이 이것을 그린다).
|
|
326
|
+
* 언제 안 쓰나: 표 안의 행 로딩(→ AgGridTable 의 로딩 오버레이), 버튼 안의 진행(→ Button `loading`).
|
|
327
|
+
* 대신/함께: 줄 수는 `lines` 로. 자리 크기를 실제 내용과 비슷하게 맞추면 로딩 끝에 화면이 덜 튄다.
|
|
328
|
+
*/
|
|
329
|
+
declare function LoadingState({ className, lines }: {
|
|
330
|
+
className?: string;
|
|
331
|
+
lines?: number;
|
|
332
|
+
}): react_jsx_runtime.JSX.Element;
|
|
333
|
+
/**
|
|
334
|
+
* 패턴 공통 빈 결과·없음 안내. 가운데 정렬. 아이콘 → 제목 → 설명 → 행동 버튼 순.
|
|
335
|
+
* 제목만 있으면 한 줄 안내, 설명까지 있으면 "무엇이 없고 다음에 뭘 하나" 를 두 줄로 — 2026-10-10 진단에서 두 평가자가 모두 더 낫다고 본 구성.
|
|
336
|
+
*
|
|
337
|
+
* 언제 쓰나: 목록이 0건, 선택지가 없음, 항목이 삭제됨처럼 "보여 줄 것이 없다" 를 알릴 때. 패턴(ListPage·DetailPage·DashboardLayout·DashboardPanel)은 `emptyState`·`notFound`·`empty` prop 으로 이것을 그린다.
|
|
338
|
+
* 언제 안 쓰나: 불러오기 실패(→ ErrorState), 아직 불러오는 중(→ LoadingState), 입력 검증 실패(→ FormMessage).
|
|
339
|
+
* 대신/함께: `<div>` 에 문장만 넣어 직접 만들지 않는다. 다음 행동이 있으면 `action` 에 버튼 하나(예: 필터 초기화·새로 만들기).
|
|
340
|
+
*/
|
|
341
|
+
declare function EmptyState({ className, children, icon, title, description, action, }: {
|
|
342
|
+
className?: string;
|
|
343
|
+
/** 제목 자리(`title` 과 같다). 기존 호출부 호환용 */
|
|
344
|
+
children?: ReactNode;
|
|
345
|
+
/** 제목 위 그림. 기본은 상자 일러스트 `EmptyBoxIllustration`(라이트·다크 토큰, 2026-10-13 — dl-ui-v3 사용자 정보 화면의 빈 패널과 같은 그림). 장식이라 스크린리더에는 읽히지 않는다 */
|
|
346
|
+
icon?: ReactNode;
|
|
347
|
+
/** 무엇이 없는지 한 줄 (예: "조건에 맞는 모델이 없습니다") */
|
|
348
|
+
title?: ReactNode;
|
|
349
|
+
/** 다음에 할 수 있는 일 (예: "검색어나 필터를 바꾸거나 초기화해 보세요") */
|
|
350
|
+
description?: ReactNode;
|
|
351
|
+
/** 설명 아래 행동 버튼 (예: 필터 초기화). ag-grid 오버레이 안에서도 눌리게 pointer-events 를 연다 */
|
|
352
|
+
action?: ReactNode;
|
|
353
|
+
}): react_jsx_runtime.JSX.Element;
|
|
354
|
+
/**
|
|
355
|
+
* 패턴 공통 오류 안내. 빈 결과 상자와 같은 배치(아이콘 → 제목 → 설명 → 다시 시도)에 오류 색 아이콘. `role='alert'`.
|
|
356
|
+
*
|
|
357
|
+
* 언제 쓰나: 화면·패널 본문을 통째로 못 불러왔을 때(패턴은 `error` prop 으로 이것을 그린다).
|
|
358
|
+
* 언제 안 쓰나: 폼 섹션 안의 부분 실패(선택지 목록 로딩 → Alert(danger) + AlertActions 의 다시 시도 — 상자가 섹션을 밀어내지 않게, 2026-10-13), 제출 실패·서버 응답 오류처럼 화면은 그대로 두고 알려야 할 때(→ Alert(danger)·Toast), 입력 검증 실패(→ FormMessage).
|
|
359
|
+
* 대신/함께: 빨간 Alert 줄로 본문을 대체하지 않는다 — 2026-10-12 블라인드 평가에서 가운데 상자가 일관되게 선호됐다. `onRetry` 를 주면 "다시 시도" 버튼이 붙는다. 원인·코드가 있으면 `description` 에 한 줄로.
|
|
360
|
+
*/
|
|
361
|
+
declare function ErrorState({ className, title, description, onRetry, retryLabel, }: {
|
|
362
|
+
className?: string;
|
|
363
|
+
/** 무엇이 실패했는지 한 줄. 기본 "불러오지 못했습니다" */
|
|
364
|
+
title?: ReactNode;
|
|
365
|
+
/** 원인·안내 (예: "서버에 연결할 수 없습니다. 잠시 후 다시 시도해 주세요.") */
|
|
366
|
+
description?: ReactNode;
|
|
367
|
+
/** 주면 "다시 시도" 버튼이 붙는다 */
|
|
368
|
+
onRetry?: () => void;
|
|
369
|
+
/** 다시 시도 버튼 라벨. 기본 "다시 시도" */
|
|
370
|
+
retryLabel?: ReactNode;
|
|
371
|
+
}): react_jsx_runtime.JSX.Element;
|
|
372
|
+
|
|
373
|
+
export { DashboardLayout, type DashboardLayoutProps, DashboardPanel, type DashboardPanelProps, DashboardSection, type DashboardSectionProps, type DashboardStat, DetailPage, type DetailPageAction, type DetailPageProps, EmptyState, ErrorState, FormDialog, type FormDialogProps, FormDrawer, type FormDrawerProps, FormPage, type FormPageProps, type FormShellProps, ListPage, type ListPageDetail, type ListPageEmptyContent, type ListPageProps, type ListPageQuickFilter, LoadingState };
|