@xonic0506/sonic-ui 0.5.0 → 0.6.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/DESIGN.md +110 -4
- package/dist/components/button.d.ts +2 -0
- package/dist/components/button.js +6 -6
- package/dist/components/data-grid.d.ts +88 -0
- package/dist/components/data-grid.js +415 -0
- package/dist/components/pagination.d.ts +18 -0
- package/dist/components/pagination.js +101 -0
- package/dist/components/table.d.ts +19 -2
- package/dist/components/table.js +50 -26
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -4
- package/dist/lib/format.d.ts +8 -0
- package/dist/lib/format.js +20 -0
- package/dist/styles.css +2 -2
- package/dist/tailwind.css +1 -0
- package/package.json +6 -1
package/DESIGN.md
CHANGED
|
@@ -70,6 +70,7 @@ Figma Variable 이름이 그대로 CSS 변수가 돼요. `/`는 `-`로 바꾸고
|
|
|
70
70
|
| `color/text/warning` | amber/700 `#85520A` | amber/300 `#EEC46A` | 주의 문구, 아이콘 |
|
|
71
71
|
| `color/border/warning` | amber/600 | amber/400 | 주의 강조 테두리 |
|
|
72
72
|
| `color/bg/danger-subtle` | red/50 `#FCEDEC` | red/950 `#2B1311` | danger Badge(subtle) |
|
|
73
|
+
| `color/bg/selected` | cobalt/50 `#EEF1FF` | cobalt/950 `#18214D` | DataGrid 선택된 행 (hover `bg/subtle`과 구분) |
|
|
73
74
|
|
|
74
75
|
글자와 바탕 조합은 모두 WCAG 4.5:1 이상이에요. 토큰을 바꾸면 `npm run check:contrast`로 다시 확인해요(배포 전 검사에도 들어 있어요).
|
|
75
76
|
|
|
@@ -254,7 +255,7 @@ hover, focus는 **컴포넌트 variant로 만들지 않아요.** React prop이
|
|
|
254
255
|
|
|
255
256
|
| 계층 | 위치 | 기준 | 예 |
|
|
256
257
|
|---|---|---|---|
|
|
257
|
-
| **Core** | sonic-ui `src/components/` | 더 나눌 수 없고, 도메인을 전혀 모름 | Button, Input, Textarea, Checkbox, RadioGroup, Switch, Select, Text, Card, Avatar, Dialog, AlertDialog, Menu, Popover, Tooltip, Tabs, Separator, Toast, Badge, Progress, Link, Table, ToggleGroup, Accordion, FileDropzone, FileList, Field, Skeleton, EmptyState, ErrorState, ErrorPage |
|
|
258
|
+
| **Core** | sonic-ui `src/components/` | 더 나눌 수 없고, 도메인을 전혀 모름 | Button, Input, Textarea, Checkbox, RadioGroup, Switch, Select, Text, Card, Avatar, Dialog, AlertDialog, Menu, Popover, Tooltip, Tabs, Separator, Toast, Badge, Progress, Link, Table, DataGrid, Pagination, ToggleGroup, Accordion, FileDropzone, FileList, Field, Skeleton, EmptyState, ErrorState, ErrorPage |
|
|
258
259
|
| **Pattern** | sonic-ui `src/patterns/` | Core 조합이고 도메인을 모르지만, **두 프로젝트 이상에서 같은 형태로 복사됨** | (아직 없음) |
|
|
259
260
|
| **Domain** | 각 프로젝트 | 도메인 타입, API, 비즈니스 로직 중 하나라도 앎 | `UserProfileCard`, `ApiKeyList` |
|
|
260
261
|
|
|
@@ -302,6 +303,7 @@ Figma Core 페이지의 컴포넌트 속성 이름 = React props 이름이에요
|
|
|
302
303
|
| `disabled` | boolean | `false` |
|
|
303
304
|
| `children` | 버튼 문구. 아이콘은 문구 앞뒤에 lucide 컴포넌트로 넣음 | — |
|
|
304
305
|
| `loading` | boolean. 스피너 + 클릭 막기 + `aria-busy` | `false` |
|
|
306
|
+
| `focusableWhenDisabled` | boolean. 비활성이 돼도 포커스를 유지해요(`aria-disabled`). 누른 뒤 비활성이 되는 버튼에 써요(Pagination의 처음·마지막) | `false` |
|
|
305
307
|
| `aria-label` | icon 크기일 때 **필수** (타입으로 강제) | — |
|
|
306
308
|
|
|
307
309
|
| | sm | md | lg |
|
|
@@ -716,8 +718,110 @@ toast.add({ title: 'API 키를 만들었어요', description: '키는 지금 한
|
|
|
716
718
|
| prop | 값 |
|
|
717
719
|
|---|---|
|
|
718
720
|
| `TableHead`, `TableCell`의 `align` | `start` · `end` (숫자 열은 `end`) |
|
|
721
|
+
| `TableHead`의 `sort`, `onSort` | `asc` · `desc` · `none`. `onSort`를 넘기면 머리글이 버튼이 되고 `aria-sort`가 붙어요 |
|
|
722
|
+
| `TableRow`의 `selected`, `interactive`, `dragging` | 선택 `bg/selected`, hover `bg/subtle`(interactive일 때만), 이동 중 모양 |
|
|
723
|
+
| `TableHead`의 `handle` | 컬럼 이동 손잡이. hover·포커스 때만 보이고 정렬 반대쪽 끝에 놓여요 (DataGrid가 씀) |
|
|
724
|
+
| `TableHead`, `TableCell`의 `pinned` | `left` · `right`. 가로 스크롤 중에도 붙어 있고 경계에 1px `border/default`. 여러 열을 고정하면 `style.left`·`style.right`로 위치를 넘겨요 |
|
|
719
725
|
|
|
720
|
-
여러 항목의 같은 속성을 비교할 때 써요. 행 높이 40(`space/10`), 좌우 padding `space/3`, 행 아래 1px `border/default`. 머리글 `label` + `text/muted`, 본문 `body` + `text/default`, `align="end"`인 본문 칸은 숫자라서 `mono`예요.
|
|
726
|
+
여러 항목의 같은 속성을 비교할 때 써요. 행 높이 40(`space/10`), 좌우 padding `space/3`, 행 아래 1px `border/default`. 머리글 `label` + `text/muted`, 본문 `body` + `text/default`, `align="end"`인 본문 칸은 숫자라서 `mono`예요. 줄무늬는 쓰지 않고, hover 바탕은 `interactive` 행에만 써요. 표가 부모보다 넓으면 가로로 스크롤해요.
|
|
727
|
+
|
|
728
|
+
위 상태 props는 모양만 바꿔요. 정렬·선택·이동 상태를 직접 관리하기 번거로우면 DataGrid를 써요. 정렬된 머리글은 `text/default` + `arrow-up`/`arrow-down` 16(간격 `space/1`)이고, 칸끼리 붙어 있어서 포커스 링은 안쪽(offset −2px)에 그려요.
|
|
729
|
+
|
|
730
|
+
### DataGrid
|
|
731
|
+
|
|
732
|
+
```tsx
|
|
733
|
+
<DataGrid
|
|
734
|
+
aria-label="API 키"
|
|
735
|
+
rows={keys}
|
|
736
|
+
getRowId={row => row.id}
|
|
737
|
+
columns={[
|
|
738
|
+
{ field: 'name', header: '이름', type: 'text', pinned: 'left', width: 160, sortable: true },
|
|
739
|
+
{ field: 'requests', header: '요청 수', type: 'number', sortable: true },
|
|
740
|
+
{ field: 'status', header: '상태', type: 'badge', tones: { active: 'success', revoked: 'danger' }, labels: { active: '활성', revoked: '폐기됨' } },
|
|
741
|
+
{ field: 'createdAt', header: '만든 날', type: 'date' },
|
|
742
|
+
{ field: 'enabled', header: '사용', type: 'switch', onChange: (row, on) => toggle(row.id, on) },
|
|
743
|
+
{ id: 'actions', type: 'actions', pinned: 'right', actions: [
|
|
744
|
+
{ label: '이름 바꾸기', icon: Pencil, onClick: rename },
|
|
745
|
+
{ label: '삭제', icon: Trash2, tone: 'danger', onClick: confirmDelete },
|
|
746
|
+
] },
|
|
747
|
+
]}
|
|
748
|
+
selection={selected} onSelectionChange={setSelected}
|
|
749
|
+
onRowOrderChange={ids => saveOrder(ids)}
|
|
750
|
+
loading={isLoading}
|
|
751
|
+
emptyState={<EmptyState title="API 키가 없어요" action={<Button>API 키 만들기</Button>} />}
|
|
752
|
+
/>
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
정렬, 행 선택, 좌/우 고정, 컬럼·행 이동이 필요한 목록에 써요. 보여 주기만 하는 표는 Table을 써요. 모양은 Table이 그리고, 상태는 DataGrid가 관리해요(TanStack Table, dnd-kit. 쓰는 쪽은 설치하지 않아요).
|
|
756
|
+
|
|
757
|
+
| prop | 값 |
|
|
758
|
+
|---|---|
|
|
759
|
+
| `rows`, `getRowId` | 행 데이터와 행 id. `rows`에는 **원본 값**을 넣어요(숫자는 number, 날짜는 ISO 문자열) |
|
|
760
|
+
| `columns` | 열 정의 배열. `type`에 따라 필요한 속성이 타입으로 강제돼요(아래 표) |
|
|
761
|
+
| `aria-label` | 표 이름 (필수) |
|
|
762
|
+
| `sorting`, `defaultSorting`, `onSortingChange` | `{ id, desc }` 또는 `null`. 한 번에 한 열만 정렬해요 |
|
|
763
|
+
| `selection`, `onSelectionChange` | 선택된 행 id 배열. `onSelectionChange`를 넘기면 맨 앞에 체크박스 열이 생겨요 |
|
|
764
|
+
| `columnOrder`, `onColumnOrderChange` | 열 id 순서. `onColumnOrderChange`를 넘기면 머리글을 끌어서 옮길 수 있어요 |
|
|
765
|
+
| `onRowOrderChange` | `(ids) => void`. 넘기면 핸들 열이 생겨요. 정렬 중에는 핸들을 숨겨요 |
|
|
766
|
+
| `loading` | Skeleton 행 5개 + `aria-busy` |
|
|
767
|
+
| `emptyState`, `errorState` | 행이 없을 때, 불러오기 실패했을 때 본문 자리에 보여 줄 EmptyState·ErrorState |
|
|
768
|
+
| `pagination` | 넘기면 표 아래에 Pagination이 붙어요. `{ page, defaultPage, onPageChange, pageSize, defaultPageSize, onPageSizeChange, pageSizeOptions, showFirstLast, rowCount }` (모두 선택, 페이지는 1부터) |
|
|
769
|
+
|
|
770
|
+
| `type` | 보여 주는 것 | 추가 속성 |
|
|
771
|
+
|---|---|---|
|
|
772
|
+
| `text` | `body` | — |
|
|
773
|
+
| `number` | `mono`, 오른쪽 정렬, 천 단위 쉼표 | — |
|
|
774
|
+
| `date` · `datetime` | `mono`, `YYYY-MM-DD` · `YYYY-MM-DD HH:mm:ss` (브라우저 현지 시간) | — |
|
|
775
|
+
| `badge` | Badge subtle | `tones`(값 → tone, 필수), `labels`(값 → 문구). 없는 값은 neutral |
|
|
776
|
+
| `switch` | Switch, 누르면 바로 적용 | `onChange(row, checked)` (필수) |
|
|
777
|
+
| `actions` | 1개면 ghost `icon-sm` 버튼, 2개 이상이면 `ellipsis` Menu | `actions: { label, icon?, tone?, onClick(row) }[]` (필수) |
|
|
778
|
+
|
|
779
|
+
공통 열 속성: `header`, `width`(px), `pinned`(`left` · `right`), `sortable`. 고정 열에는 `width`를 꼭 줘요(다음 고정 열 위치 계산에 써요). `select`·`handle` 열은 너비 40이고, 있으면 항상 왼쪽에 고정돼요.
|
|
780
|
+
|
|
781
|
+
- 행 hover `bg/subtle`, 선택 `bg/selected`. 고정 열 경계는 1px `border/default`이고 그림자는 쓰지 않아요(§1 원칙 4).
|
|
782
|
+
- 컬럼 이동 중인 머리글은 `bg/subtle`, 행 이동 중인 행은 `bg/surface` + 1px `border/default` + `radius/md`. 놓을 위치는 2px `border/focus` 선이에요.
|
|
783
|
+
- 핸들은 `grip-vertical` 16(`text/muted`, hover `text/default`)이고 바깥 2px 포커스 링을 그려요. 키보드로도 옮겨요: 핸들에 포커스 → Space로 들기 → 화살표 → Space로 놓기(Esc 취소). 화면 낭독기 안내는 한국어 해요체예요.
|
|
784
|
+
- 위험한 actions는 바로 실행하지 말고 `onClick`에서 AlertDialog를 열어요(§10 Menu).
|
|
785
|
+
- 행이 많으면(100개 초과) 보이는 행만 그려요. 이때는 `className`으로 높이를 정해 주세요(예: `h-96`). 그 안에서 세로로 스크롤돼요.
|
|
786
|
+
- Next.js: `columns`에 함수가 들어 있어서 서버 컴포넌트에서 넘길 수 없어요. DataGrid는 클라이언트 컴포넌트 안에서 정의해요.
|
|
787
|
+
|
|
788
|
+
**페이지로 나누기**
|
|
789
|
+
|
|
790
|
+
```tsx
|
|
791
|
+
<DataGrid rows={allKeys} pagination={{ defaultPageSize: 20 }} … /> // 클라이언트: rows 전체를 DataGrid가 나눠요
|
|
792
|
+
<DataGrid rows={pageRows} pagination={{ page, onPageChange: setPage, pageSize, onPageSizeChange: setPageSize, rowCount: total }}
|
|
793
|
+
sorting={sort} onSortingChange={setSort} … /> // 서버: 현재 페이지 rows + 전체 개수
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
- `rowCount`가 없으면 클라이언트 모드예요. 정렬하거나 페이지당 행 수를 바꾸면 1페이지로 돌아가요.
|
|
797
|
+
- `rowCount`를 넘기면 서버 모드예요. 받은 `rows`를 그대로 보여 주고, 정렬도 서버가 해요(`onSortingChange`로 받아 다시 불러와요). 한 페이지만 정렬하면 결과가 틀리기 때문이에요.
|
|
798
|
+
- 전체 선택 체크박스는 **현재 페이지** 행만 골라요. 다른 페이지에서 고른 행은 그대로 남아요.
|
|
799
|
+
- 행 이동은 현재 페이지 안에서만 돼요. 페이지네이션을 켜면 가상 스크롤은 쓰지 않아요.
|
|
800
|
+
- 행이 없거나 불러오기 실패면 Pagination을 숨겨요.
|
|
801
|
+
|
|
802
|
+
### Pagination
|
|
803
|
+
|
|
804
|
+
```tsx
|
|
805
|
+
<Pagination page={page} pageCount={20} onPageChange={setPage} />
|
|
806
|
+
<Pagination page={page} pageCount={20} onPageChange={setPage} pageSize={pageSize} onPageSizeChange={setPageSize} showFirstLast={false} />
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
| prop | 값 | 기본값 |
|
|
810
|
+
|---|---|---|
|
|
811
|
+
| `page`, `onPageChange` | 현재 페이지(1부터)와 바뀔 때 | (필수) |
|
|
812
|
+
| `pageCount` | 전체 페이지 수 | (필수) |
|
|
813
|
+
| `pageSize`, `onPageSizeChange` | 넘기면 왼쪽에 "페이지당 행" Select가 생겨요 | — |
|
|
814
|
+
| `pageSizeOptions` | 페이지당 행 수 목록 | `[10, 20, 50]` |
|
|
815
|
+
| `showFirstLast` | 처음·마지막 버튼 | `true` |
|
|
816
|
+
| `aria-label` | 페이지 이동 영역 이름 | "페이지" |
|
|
817
|
+
|
|
818
|
+
목록을 페이지로 나눌 때 써요. Table이나 카드 목록과 함께 쓰고, DataGrid는 `pagination` prop으로 붙여요.
|
|
819
|
+
|
|
820
|
+
- 왼쪽에 페이지당 행 수(라벨 `label` + `text/muted`, Select는 글자 너비에 맞춰요, 라벨과 `space/2`), 오른쪽에 `« ‹ 1 … 4 5 6 … 20 › »`. 둘 사이 `space/4`, 버튼 사이 `space/1`.
|
|
821
|
+
- 번호 칸 28×28(`space/7`), `radius/md`, `mono`. 현재 페이지는 `bg/subtle` + `text/default` + `aria-current="page"`이고 강조색은 쓰지 않아요(§1 원칙 2). 다른 번호는 `text/muted`, hover `bg/subtle` + `text/default`. 포커스 링은 바깥 2px(offset 2px).
|
|
822
|
+
- 번호는 처음·마지막과 현재 양옆 1개만 보이고, 나머지는 `…`로 줄여요. 항상 최대 7칸이라 너비가 흔들리지 않아요.
|
|
823
|
+
- 처음·이전·다음·마지막은 ghost `icon-sm`(`chevrons-left`, `chevron-left`, `chevron-right`, `chevrons-right`)이고, 첫 페이지에서는 처음·이전이, 마지막 페이지에서는 다음·마지막이 비활성이에요.
|
|
824
|
+
- 좁은 화면(640 미만)에서는 번호 대신 `4 / 20`(`mono-caption` + `text/muted`)만 보이고, 행 수 라벨도 숨겨요(Select 이름은 `aria-label`로 남아요). Figma `viewport=mobile`.
|
|
721
825
|
|
|
722
826
|
### ToggleGroup
|
|
723
827
|
|
|
@@ -862,11 +966,13 @@ Sonic 팀은 Figma Pro 요금제라 Code Connect(Organization 이상)를 쓸 수
|
|
|
862
966
|
| Badge | `57:3498` | `<Badge tone variant>` (`children` → 자식 문구) | `src/components/badge.tsx` |
|
|
863
967
|
| Progress | `58:5093` | `<Progress value max label>` (`value` variant는 채움 미리보기, `value text`는 코드가 자동 표시) | `src/components/progress.tsx` |
|
|
864
968
|
| Link | `65:3733` | `<Link href external>` (`children` → 자식 문구) | `src/components/link.tsx` |
|
|
865
|
-
| Table Cell · Table | `65:3742` · `65:3743` | `Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell` (`type=header` → `TableHead`, `align` → 같은 prop) | `src/components/table.tsx` |
|
|
969
|
+
| Table Cell · Table | `65:3742` · `65:3743` | `Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell` (`type=header` → `TableHead`, `align`·`sort`·`pinned` → 같은 prop, `state=hover·selected` → `TableRow`의 `interactive`·`selected`) | `src/components/table.tsx` |
|
|
970
|
+
| Pagination Item · Pagination | `138:5684` · `138:5747` | `<Pagination page pageCount onPageChange pageSize onPageSizeChange showFirstLast>` (`state=current` → 현재 `page`, `viewport` → prop 아님, 좁은 화면은 코드가 자동 처리) | `src/components/pagination.tsx` |
|
|
971
|
+
| Data Grid Cell | `130:6234` | `<DataGrid columns>`의 `columns[].type` (`type` 값이 같음. `select`·`handle`은 `onSelectionChange`·`onRowOrderChange`로 생기는 열) | `src/components/data-grid.tsx` |
|
|
866
972
|
| Toggle Item · Toggle Group | `66:3760` · `66:3761` | `<ToggleGroup multiple>` + `<ToggleGroupItem value disabled>` (`pressed` → 현재 값, `content=icon`이면 `aria-label`) | `src/components/toggle-group.tsx` |
|
|
867
973
|
| Accordion Item · Accordion | `66:3792` · `66:3793` | `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionPanel` (`open` → 현재 값, `title`/`content` → 자식) | `src/components/accordion.tsx` |
|
|
868
974
|
| File Dropzone · File Item | `71:4191` · `72:4251` | `<FileDropzone variant accept multiple maxSize maxFiles onFilesChange>` (`state` → 끌어다 놓기·거부·`disabled`), `<FileList layout items onRemove onRetry>` (`status` → `items[].status`) | `src/components/file-upload.tsx` |
|
|
869
975
|
| Illustration (5개) | Foundations `Source illustrations` | 제품 화면은 EmptyState·ErrorState·ErrorPage 안에서 써요. 문서 화면은 `illustrations['error-404']`로 URL(§3.7) | `src/illustrations/*.webp` |
|
|
870
|
-
| Icon (
|
|
976
|
+
| Icon (42개) | Foundations `Source icons` | `lucide-react`의 같은 이름 컴포넌트 (`Icon / chevron-down` → `<ChevronDown />`) | 쓰는 쪽에서 설치 |
|
|
871
977
|
|
|
872
978
|
Figma 속성 → props 변환 규칙: variant 속성은 같은 이름의 prop으로, 텍스트 속성 `children`은 자식으로, boolean `header`/`footer`는 해당 하위 컴포넌트를 넣을지 말지로 옮겨요. Button의 `iconLeft`/`iconRight`(+ `leftIcon`/`rightIcon` 교체)와 Button Icon의 `icon`은 prop이 아니라 **자식 순서**예요: `<Button><Plus />추가하기<ChevronDown /></Button>`.
|
|
@@ -6,6 +6,8 @@ interface ButtonBaseProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElem
|
|
|
6
6
|
render?: React.ReactElement;
|
|
7
7
|
/** 진행 중: 스피너 표시, 클릭 막기, 포커스 유지, aria-busy */
|
|
8
8
|
loading?: boolean;
|
|
9
|
+
/** 비활성이 돼도 포커스를 잃지 않아요. 누른 뒤 비활성이 되는 버튼에 써요 (예: 마지막 페이지) */
|
|
10
|
+
focusableWhenDisabled?: boolean;
|
|
9
11
|
}
|
|
10
12
|
/** 아이콘만 있는 버튼은 읽을 글자가 없어서 aria-label이 필수예요 */
|
|
11
13
|
export type ButtonProps = ButtonBaseProps & ({
|
|
@@ -6,22 +6,22 @@ import { Button as r } from "@base-ui/react/button";
|
|
|
6
6
|
import { LoaderCircle as i } from "lucide-react";
|
|
7
7
|
import { Fragment as a, jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
8
8
|
//#region src/components/button.tsx
|
|
9
|
-
var c = n.forwardRef(function({ variant: n, size: c, className: l, render: u, loading: d, disabled: f,
|
|
10
|
-
let
|
|
9
|
+
var c = n.forwardRef(function({ variant: n, size: c, className: l, render: u, loading: d, disabled: f, focusableWhenDisabled: p, children: m, ...h }, g) {
|
|
10
|
+
let _ = /* @__PURE__ */ o(i, { className: "sn:animate-[sn-spin_var(--sn-duration-loop)_linear_infinite] sn:motion-reduce:animate-none" });
|
|
11
11
|
return /* @__PURE__ */ o(r, {
|
|
12
|
-
ref:
|
|
12
|
+
ref: g,
|
|
13
13
|
render: u,
|
|
14
14
|
nativeButton: !u || u.type === "button",
|
|
15
15
|
disabled: f || d,
|
|
16
|
-
focusableWhenDisabled: d,
|
|
16
|
+
focusableWhenDisabled: d || p,
|
|
17
17
|
"aria-busy": d || void 0,
|
|
18
18
|
"data-loading": d ? "" : void 0,
|
|
19
19
|
className: e(t({
|
|
20
20
|
variant: n,
|
|
21
21
|
size: c
|
|
22
22
|
}), l),
|
|
23
|
-
...
|
|
24
|
-
children: d && c?.startsWith("icon-") ?
|
|
23
|
+
...h,
|
|
24
|
+
children: d && c?.startsWith("icon-") ? _ : d ? /* @__PURE__ */ s(a, { children: [_, m] }) : m
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
27
|
//#endregion
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type RowData } from '@tanstack/react-table';
|
|
3
|
+
import { type BadgeProps } from './badge';
|
|
4
|
+
type Field<Row> = Extract<keyof Row, string>;
|
|
5
|
+
type BadgeTone = NonNullable<BadgeProps['tone']>;
|
|
6
|
+
interface ColumnBase {
|
|
7
|
+
header?: string;
|
|
8
|
+
/** px. 고정 열에는 꼭 줘요 (다음 고정 열 위치 계산) */
|
|
9
|
+
width?: number;
|
|
10
|
+
pinned?: 'left' | 'right';
|
|
11
|
+
}
|
|
12
|
+
interface FieldColumn<Row> extends ColumnBase {
|
|
13
|
+
field: Field<Row>;
|
|
14
|
+
/** 기본은 field */
|
|
15
|
+
id?: string;
|
|
16
|
+
sortable?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface DataGridAction<Row> {
|
|
19
|
+
label: string;
|
|
20
|
+
icon?: React.ComponentType<{
|
|
21
|
+
className?: string;
|
|
22
|
+
}>;
|
|
23
|
+
/** danger는 맨 아래 구분선 뒤에 놓여요. 실행 전에 AlertDialog로 확인해요 */
|
|
24
|
+
tone?: 'default' | 'danger';
|
|
25
|
+
onClick: (row: Row) => void;
|
|
26
|
+
}
|
|
27
|
+
/** Figma Data Grid Cell의 type과 같은 이름이에요 */
|
|
28
|
+
export type DataGridColumn<Row> = (FieldColumn<Row> & {
|
|
29
|
+
type: 'text' | 'number' | 'date' | 'datetime';
|
|
30
|
+
}) | (FieldColumn<Row> & {
|
|
31
|
+
type: 'badge';
|
|
32
|
+
tones: Record<string, BadgeTone>;
|
|
33
|
+
labels?: Record<string, string>;
|
|
34
|
+
}) | (FieldColumn<Row> & {
|
|
35
|
+
type: 'switch';
|
|
36
|
+
onChange: (row: Row, checked: boolean) => void;
|
|
37
|
+
}) | (ColumnBase & {
|
|
38
|
+
type: 'actions';
|
|
39
|
+
id: string;
|
|
40
|
+
actions: DataGridAction<Row>[];
|
|
41
|
+
});
|
|
42
|
+
export interface DataGridSort {
|
|
43
|
+
id: string;
|
|
44
|
+
desc: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface DataGridPagination {
|
|
47
|
+
/** 현재 페이지 (1부터) */
|
|
48
|
+
page?: number;
|
|
49
|
+
defaultPage?: number;
|
|
50
|
+
onPageChange?: (page: number) => void;
|
|
51
|
+
pageSize?: number;
|
|
52
|
+
/** 기본 20 */
|
|
53
|
+
defaultPageSize?: number;
|
|
54
|
+
onPageSizeChange?: (pageSize: number) => void;
|
|
55
|
+
pageSizeOptions?: number[];
|
|
56
|
+
showFirstLast?: boolean;
|
|
57
|
+
/** 서버에서 나눌 때 전체 행 수 */
|
|
58
|
+
rowCount?: number;
|
|
59
|
+
}
|
|
60
|
+
export interface DataGridProps<Row> {
|
|
61
|
+
/** 원본 값을 넣어요 (숫자는 number, 날짜는 ISO 문자열) */
|
|
62
|
+
rows: Row[];
|
|
63
|
+
getRowId: (row: Row) => string;
|
|
64
|
+
columns: DataGridColumn<Row>[];
|
|
65
|
+
'aria-label': string;
|
|
66
|
+
sorting?: DataGridSort | null;
|
|
67
|
+
defaultSorting?: DataGridSort | null;
|
|
68
|
+
onSortingChange?: (sorting: DataGridSort | null) => void;
|
|
69
|
+
/** 선택된 행 id. onSelectionChange를 넘기면 체크박스 열이 생겨요 */
|
|
70
|
+
selection?: string[];
|
|
71
|
+
onSelectionChange?: (ids: string[]) => void;
|
|
72
|
+
/** 열 id 순서. onColumnOrderChange를 넘기면 머리글을 끌어서 옮길 수 있어요 */
|
|
73
|
+
columnOrder?: string[];
|
|
74
|
+
onColumnOrderChange?: (ids: string[]) => void;
|
|
75
|
+
/** 넘기면 핸들 열이 생겨요. 정렬 중에는 핸들을 숨겨요 */
|
|
76
|
+
onRowOrderChange?: (ids: string[]) => void;
|
|
77
|
+
loading?: boolean;
|
|
78
|
+
emptyState?: React.ReactNode;
|
|
79
|
+
errorState?: React.ReactNode;
|
|
80
|
+
/** 넘기면 표 아래에 Pagination이 붙어요. rowCount를 넘기면 서버 모드(받은 rows를 그대로, 정렬도 서버) */
|
|
81
|
+
pagination?: DataGridPagination;
|
|
82
|
+
/** 행이 많으면 보이는 행만 그려요. 이때 높이를 정해 주세요 (예: h-96) */
|
|
83
|
+
className?: string;
|
|
84
|
+
}
|
|
85
|
+
/** Figma Data Grid Cell의 type 목록 (select·handle은 props로 생기는 열) */
|
|
86
|
+
export declare const dataGridCellTypes: readonly ['text', 'number', 'date', 'datetime', 'badge', 'switch', 'actions', 'select', 'handle'];
|
|
87
|
+
export declare function DataGrid<Row extends RowData>({ rows, getRowId, columns, 'aria-label': ariaLabel, sorting, defaultSorting, onSortingChange, selection, onSelectionChange, columnOrder, onColumnOrderChange, onRowOrderChange, loading, emptyState, errorState, pagination, className, }: DataGridProps<Row>): React.JSX.Element;
|
|
88
|
+
export {};
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn as e } from "../lib/cn.js";
|
|
3
|
+
import { Button as t } from "./button.js";
|
|
4
|
+
import { Checkbox as n } from "./checkbox.js";
|
|
5
|
+
import { Switch as r } from "./switch.js";
|
|
6
|
+
import { Skeleton as i } from "./skeleton.js";
|
|
7
|
+
import { Menu as a, MenuItem as o, MenuPopup as s, MenuSeparator as c, MenuTrigger as l } from "./menu.js";
|
|
8
|
+
import { Badge as u } from "./badge.js";
|
|
9
|
+
import { TableBody as d, TableCell as f, TableHead as p, TableHeader as ee, TableRow as m, tableClass as te } from "./table.js";
|
|
10
|
+
import { formatDate as h, formatDateTime as g, formatNumber as _ } from "../lib/format.js";
|
|
11
|
+
import { Pagination as ne } from "./pagination.js";
|
|
12
|
+
import * as v from "react";
|
|
13
|
+
import { Ellipsis as y, GripVertical as b } from "lucide-react";
|
|
14
|
+
import { Fragment as re, jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
15
|
+
import { columnOrderingFeature as C, columnPinningFeature as w, columnSizingFeature as T, createPaginatedRowModel as ie, createSortedRowModel as E, rowPaginationFeature as D, rowSortingFeature as O, tableFeatures as k, useTable as ae } from "@tanstack/react-table";
|
|
16
|
+
import { useVirtualizer as oe } from "@tanstack/react-virtual";
|
|
17
|
+
import { DndContext as se, KeyboardSensor as ce, PointerSensor as le, closestCenter as ue, useSensor as de, useSensors as fe } from "@dnd-kit/core";
|
|
18
|
+
import { SortableContext as pe, arrayMove as me, horizontalListSortingStrategy as he, sortableKeyboardCoordinates as ge, useSortable as A, verticalListSortingStrategy as _e } from "@dnd-kit/sortable";
|
|
19
|
+
//#region src/components/data-grid.tsx
|
|
20
|
+
var j = "__select", M = "__handle", ve = 100, ye = k({
|
|
21
|
+
rowSortingFeature: O,
|
|
22
|
+
sortedRowModel: E(),
|
|
23
|
+
rowPaginationFeature: D,
|
|
24
|
+
paginatedRowModel: ie(),
|
|
25
|
+
columnOrderingFeature: C,
|
|
26
|
+
columnSizingFeature: T,
|
|
27
|
+
columnPinningFeature: w
|
|
28
|
+
}), be = (e, t, n) => {
|
|
29
|
+
let r = e.getValue(n), i = t.getValue(n);
|
|
30
|
+
return r == null || i == null ? r == null ? i == null ? 0 : -1 : 1 : typeof r == "number" && typeof i == "number" ? r - i : typeof r == "boolean" ? Number(r) - Number(i) : String(r).localeCompare(String(i), "ko", { numeric: !0 });
|
|
31
|
+
}, N = (e) => "field" in e ? e.id ?? e.field : e.id, xe = {
|
|
32
|
+
before: "sn:shadow-[inset_0_2px_0_var(--sn-color-border-focus)]",
|
|
33
|
+
after: "sn:shadow-[inset_0_-2px_0_var(--sn-color-border-focus)]"
|
|
34
|
+
}, P = {
|
|
35
|
+
before: "sn:shadow-[inset_2px_0_0_var(--sn-color-border-focus)]",
|
|
36
|
+
after: "sn:shadow-[inset_-2px_0_0_var(--sn-color-border-focus)]"
|
|
37
|
+
}, F = "sn:flex sn:size-4 sn:cursor-grab sn:items-center sn:justify-center sn:rounded-md sn:border-0 sn:bg-transparent sn:p-0 sn:text-muted sn:hover:text-default sn:focus-visible:outline-2 sn:focus-visible:outline-offset-2 sn:focus-visible:outline-focus sn:[&_svg]:size-4";
|
|
38
|
+
function I(e, t) {
|
|
39
|
+
let n = (e) => t().indexOf(String(e)) + 1;
|
|
40
|
+
return {
|
|
41
|
+
onDragStart: ({ active: t }) => `${e}을 들었어요. 지금 ${n(t.id)}번째예요.`,
|
|
42
|
+
onDragOver: ({ over: e }) => e ? `${n(e.id)}번째 위치예요.` : "놓을 수 없는 위치예요.",
|
|
43
|
+
onDragEnd: ({ over: e }) => e ? `${n(e.id)}번째에 놓았어요.` : "옮기지 않았어요.",
|
|
44
|
+
onDragCancel: () => "옮기기를 취소했어요."
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
var Se = { draggable: "Space로 들고, 화살표로 옮긴 다음 Space로 놓아요. Esc로 취소해요." };
|
|
48
|
+
function Ce(e, t) {
|
|
49
|
+
let [n, r] = v.useState(null);
|
|
50
|
+
return {
|
|
51
|
+
drop: n,
|
|
52
|
+
onDragOver: ({ active: t, over: n }) => {
|
|
53
|
+
if (!n || n.id === t.id) return r(null);
|
|
54
|
+
r({
|
|
55
|
+
id: String(n.id),
|
|
56
|
+
side: e.indexOf(String(n.id)) > e.indexOf(String(t.id)) ? "after" : "before"
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
onDragEnd: ({ active: n, over: i }) => {
|
|
60
|
+
r(null), i && i.id !== n.id && t(me(e, e.indexOf(String(n.id)), e.indexOf(String(i.id))));
|
|
61
|
+
},
|
|
62
|
+
onDragCancel: () => r(null)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function L(e, t, n) {
|
|
66
|
+
let [r, i] = v.useState(t);
|
|
67
|
+
return [e === void 0 ? r : e, (t) => {
|
|
68
|
+
e === void 0 && i(t), n?.(t);
|
|
69
|
+
}];
|
|
70
|
+
}
|
|
71
|
+
function we(e, t) {
|
|
72
|
+
let n = e.getIsPinned(), r = t || n ? e.getSize() : void 0;
|
|
73
|
+
return {
|
|
74
|
+
width: r,
|
|
75
|
+
minWidth: r,
|
|
76
|
+
left: n === "start" ? e.getStart("start") : void 0,
|
|
77
|
+
right: n === "end" ? e.getAfter("end") : void 0
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function Te({ actions: e, row: n }) {
|
|
81
|
+
if (e.length === 1) {
|
|
82
|
+
let [r] = e, i = r.icon;
|
|
83
|
+
return i ? /* @__PURE__ */ x(t, {
|
|
84
|
+
size: "icon-sm",
|
|
85
|
+
variant: "ghost",
|
|
86
|
+
"aria-label": r.label,
|
|
87
|
+
onClick: () => r.onClick(n),
|
|
88
|
+
children: /* @__PURE__ */ x(i, {})
|
|
89
|
+
}) : /* @__PURE__ */ x(t, {
|
|
90
|
+
size: "sm",
|
|
91
|
+
variant: "ghost",
|
|
92
|
+
onClick: () => r.onClick(n),
|
|
93
|
+
children: r.label
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
let r = e.filter((e) => e.tone !== "danger"), i = e.filter((e) => e.tone === "danger"), u = (e) => {
|
|
97
|
+
let t = e.icon;
|
|
98
|
+
return /* @__PURE__ */ S(o, {
|
|
99
|
+
tone: e.tone,
|
|
100
|
+
onClick: () => e.onClick(n),
|
|
101
|
+
children: [t && /* @__PURE__ */ x(t, {}), e.label]
|
|
102
|
+
}, e.label);
|
|
103
|
+
};
|
|
104
|
+
return /* @__PURE__ */ S(a, { children: [/* @__PURE__ */ x(l, {
|
|
105
|
+
render: /* @__PURE__ */ x(t, {
|
|
106
|
+
size: "icon-sm",
|
|
107
|
+
variant: "ghost",
|
|
108
|
+
"aria-label": "더 보기"
|
|
109
|
+
}),
|
|
110
|
+
children: /* @__PURE__ */ x(y, {})
|
|
111
|
+
}), /* @__PURE__ */ S(s, { children: [
|
|
112
|
+
r.map(u),
|
|
113
|
+
r.length > 0 && i.length > 0 && /* @__PURE__ */ x(c, {}),
|
|
114
|
+
i.map(u)
|
|
115
|
+
] })] });
|
|
116
|
+
}
|
|
117
|
+
function Ee(e, t, n) {
|
|
118
|
+
switch (e.type) {
|
|
119
|
+
case "text": return n == null ? "" : String(n);
|
|
120
|
+
case "number": return _(n);
|
|
121
|
+
case "date": return h(n);
|
|
122
|
+
case "datetime": return g(n);
|
|
123
|
+
case "badge": {
|
|
124
|
+
let t = n == null ? "" : String(n);
|
|
125
|
+
return /* @__PURE__ */ x(u, {
|
|
126
|
+
tone: e.tones[t] ?? "neutral",
|
|
127
|
+
children: e.labels?.[t] ?? t
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
case "switch": return /* @__PURE__ */ x(r, {
|
|
131
|
+
checked: !!n,
|
|
132
|
+
onCheckedChange: (n) => e.onChange(t, n),
|
|
133
|
+
children: /* @__PURE__ */ x("span", {
|
|
134
|
+
className: "sn:sr-only",
|
|
135
|
+
children: e.header
|
|
136
|
+
})
|
|
137
|
+
});
|
|
138
|
+
case "actions": return /* @__PURE__ */ x(Te, {
|
|
139
|
+
actions: e.actions,
|
|
140
|
+
row: t
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function De({ id: t, label: n, drop: r, className: i, ...a }) {
|
|
145
|
+
let { setNodeRef: o, setActivatorNodeRef: s, attributes: c, listeners: l, isDragging: u } = A({
|
|
146
|
+
id: t,
|
|
147
|
+
attributes: { roleDescription: "옮길 수 있는 열" }
|
|
148
|
+
});
|
|
149
|
+
return /* @__PURE__ */ x(p, {
|
|
150
|
+
ref: o,
|
|
151
|
+
className: e(i, u && "sn:bg-subtle", r && P[r]),
|
|
152
|
+
handle: /* @__PURE__ */ x("button", {
|
|
153
|
+
type: "button",
|
|
154
|
+
ref: s,
|
|
155
|
+
...c,
|
|
156
|
+
...l,
|
|
157
|
+
"aria-label": `${n} 열 옮기기`,
|
|
158
|
+
className: F,
|
|
159
|
+
children: /* @__PURE__ */ x(b, {})
|
|
160
|
+
}),
|
|
161
|
+
...a
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function Oe({ id: e, draggable: t, renderCells: n, ...r }) {
|
|
165
|
+
let { setNodeRef: i, setActivatorNodeRef: a, attributes: o, listeners: s, isDragging: c } = A({
|
|
166
|
+
id: e,
|
|
167
|
+
disabled: !t,
|
|
168
|
+
attributes: { roleDescription: "옮길 수 있는 행" }
|
|
169
|
+
}), l = t ? /* @__PURE__ */ x("button", {
|
|
170
|
+
type: "button",
|
|
171
|
+
ref: a,
|
|
172
|
+
...o,
|
|
173
|
+
...s,
|
|
174
|
+
"aria-label": "행 옮기기",
|
|
175
|
+
className: F,
|
|
176
|
+
children: /* @__PURE__ */ x(b, {})
|
|
177
|
+
}) : null;
|
|
178
|
+
return /* @__PURE__ */ x(m, {
|
|
179
|
+
ref: i,
|
|
180
|
+
interactive: !0,
|
|
181
|
+
dragging: c,
|
|
182
|
+
...r,
|
|
183
|
+
children: n(l)
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function ke({ rows: t, getRowId: r, columns: a, "aria-label": o, sorting: s, defaultSorting: c = null, onSortingChange: l, selection: u, onSelectionChange: h, columnOrder: g, onColumnOrderChange: _, onRowOrderChange: y, loading: b, emptyState: C, errorState: w, pagination: T, className: ie }) {
|
|
187
|
+
let E = u !== void 0 || h !== void 0, D = y !== void 0, O = _ !== void 0, [k, me] = L(s, c, l), [A, P] = L(u, [], h), [F, Te] = L(g, a.map(N), _), [ke, R] = L(T?.page, T?.defaultPage ?? 1, T?.onPageChange), [z, Ae] = L(T?.pageSize, T?.defaultPageSize ?? 20, T?.onPageSizeChange), B = T?.rowCount !== void 0, je = T ? Math.max(1, Math.ceil((B ? T.rowCount : t.length) / z)) : 1, V = Math.min(ke, je), Me = v.useId(), H = v.useMemo(() => new Map(a.map((e) => [N(e), e])), [a]), Ne = v.useMemo(() => {
|
|
188
|
+
let e = [];
|
|
189
|
+
E && e.push({
|
|
190
|
+
id: j,
|
|
191
|
+
size: 40,
|
|
192
|
+
enableSorting: !1
|
|
193
|
+
}), D && e.push({
|
|
194
|
+
id: M,
|
|
195
|
+
size: 40,
|
|
196
|
+
enableSorting: !1
|
|
197
|
+
});
|
|
198
|
+
for (let t of a) {
|
|
199
|
+
let n = t.width ?? (t.type === "actions" ? 56 : void 0);
|
|
200
|
+
e.push("field" in t ? {
|
|
201
|
+
id: N(t),
|
|
202
|
+
accessorFn: (e) => e[t.field],
|
|
203
|
+
enableSorting: !!t.sortable,
|
|
204
|
+
sortFn: be,
|
|
205
|
+
...n && { size: n }
|
|
206
|
+
} : {
|
|
207
|
+
id: t.id,
|
|
208
|
+
enableSorting: !1,
|
|
209
|
+
...n && { size: n }
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return e;
|
|
213
|
+
}, [
|
|
214
|
+
a,
|
|
215
|
+
E,
|
|
216
|
+
D
|
|
217
|
+
]), Pe = v.useMemo(() => ({
|
|
218
|
+
start: [
|
|
219
|
+
...E ? [j] : [],
|
|
220
|
+
...D ? [M] : [],
|
|
221
|
+
...a.filter((e) => e.pinned === "left").map(N)
|
|
222
|
+
],
|
|
223
|
+
end: a.filter((e) => e.pinned === "right").map(N)
|
|
224
|
+
}), [
|
|
225
|
+
a,
|
|
226
|
+
E,
|
|
227
|
+
D
|
|
228
|
+
]), U = ae({
|
|
229
|
+
features: ye,
|
|
230
|
+
columns: Ne,
|
|
231
|
+
data: t,
|
|
232
|
+
getRowId: (e) => r(e),
|
|
233
|
+
state: {
|
|
234
|
+
sorting: k ? [k] : [],
|
|
235
|
+
columnOrder: F,
|
|
236
|
+
columnPinning: Pe,
|
|
237
|
+
pagination: {
|
|
238
|
+
pageIndex: V - 1,
|
|
239
|
+
pageSize: T ? z : t.length
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
manualPagination: !T || B,
|
|
243
|
+
manualSorting: B
|
|
244
|
+
}), W = [
|
|
245
|
+
...U.getStartFlatHeaders(),
|
|
246
|
+
...U.getCenterFlatHeaders(),
|
|
247
|
+
...U.getEndFlatHeaders()
|
|
248
|
+
], G = U.getRowModel().rows, Fe = t.map(r), K = G.map((e) => e.id), q = U.getCenterFlatHeaders().map((e) => e.column.id), Ie = D && !k, Le = v.useRef(null), J = !T && G.length > ve, Re = oe({
|
|
249
|
+
count: J ? G.length : 0,
|
|
250
|
+
getScrollElement: () => Le.current,
|
|
251
|
+
estimateSize: () => 40,
|
|
252
|
+
overscan: 8
|
|
253
|
+
}), Y = J ? Re.getVirtualItems() : null, ze = Y ? Y.map((e) => G[e.index]) : G, Be = Y?.[0]?.start ?? 0, X = Y ? Re.getTotalSize() - (Y.at(-1)?.end ?? 0) : 0, Ve = typeof document > "u" ? void 0 : document.body, He = fe(de(le, { activationConstraint: { distance: 4 } }), de(ce, { coordinateGetter: ge })), Z = Ce(Fe, (e) => y?.(e)), Q = Ce(q, (e) => Te([...F.filter((t) => !e.includes(t)), ...e])), Ue = (e) => {
|
|
254
|
+
me(k?.id === e ? k.desc ? null : {
|
|
255
|
+
id: e,
|
|
256
|
+
desc: !0
|
|
257
|
+
} : {
|
|
258
|
+
id: e,
|
|
259
|
+
desc: !1
|
|
260
|
+
}), T && V !== 1 && R(1);
|
|
261
|
+
}, We = K.length > 0 && K.every((e) => A.includes(e)), Ge = !We && K.some((e) => A.includes(e)), Ke = (e) => P([...A.filter((e) => !K.includes(e)), ...e ? K : []]), qe = (t) => {
|
|
262
|
+
let { column: r } = t, i = r.id, a = r.getIsPinned(), o = a === "start" ? "left" : a === "end" ? "right" : void 0, s = {
|
|
263
|
+
pinned: o,
|
|
264
|
+
style: we(r, i === j || i === M || H.get(i)?.width !== void 0),
|
|
265
|
+
className: e("sn:sticky sn:top-0 sn:z-20 sn:bg-surface", o && "sn:z-30")
|
|
266
|
+
};
|
|
267
|
+
if (i === j) return /* @__PURE__ */ x(p, {
|
|
268
|
+
...s,
|
|
269
|
+
children: /* @__PURE__ */ x(n, {
|
|
270
|
+
checked: We,
|
|
271
|
+
indeterminate: Ge,
|
|
272
|
+
onCheckedChange: Ke,
|
|
273
|
+
children: /* @__PURE__ */ x("span", {
|
|
274
|
+
className: "sn:sr-only",
|
|
275
|
+
children: "모든 행 선택"
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
}, i);
|
|
279
|
+
if (i === M) return /* @__PURE__ */ x(p, {
|
|
280
|
+
...s,
|
|
281
|
+
children: /* @__PURE__ */ x("span", {
|
|
282
|
+
className: "sn:sr-only",
|
|
283
|
+
children: "순서"
|
|
284
|
+
})
|
|
285
|
+
}, i);
|
|
286
|
+
let c = H.get(i), l = c.header ?? "", u = "sortable" in c && c.sortable ? {
|
|
287
|
+
sort: k?.id === i ? k.desc ? "desc" : "asc" : "none",
|
|
288
|
+
onSort: () => Ue(i)
|
|
289
|
+
} : {}, d = c.type === "actions" && !c.header ? /* @__PURE__ */ x("span", {
|
|
290
|
+
className: "sn:sr-only",
|
|
291
|
+
children: "동작"
|
|
292
|
+
}) : l, f = c.type === "number" || c.type === "actions" ? "end" : "start";
|
|
293
|
+
return O && !o ? /* @__PURE__ */ x(De, {
|
|
294
|
+
id: i,
|
|
295
|
+
label: l || "동작",
|
|
296
|
+
align: f,
|
|
297
|
+
drop: Q.drop?.id === i ? Q.drop.side : void 0,
|
|
298
|
+
...s,
|
|
299
|
+
...u,
|
|
300
|
+
children: d
|
|
301
|
+
}, i) : /* @__PURE__ */ x(p, {
|
|
302
|
+
align: f,
|
|
303
|
+
...s,
|
|
304
|
+
...u,
|
|
305
|
+
children: d
|
|
306
|
+
}, i);
|
|
307
|
+
}, Je = (e) => /* @__PURE__ */ x(m, { children: /* @__PURE__ */ x(f, {
|
|
308
|
+
colSpan: W.length,
|
|
309
|
+
className: "sn:h-auto sn:p-0",
|
|
310
|
+
children: e
|
|
311
|
+
}) }), $;
|
|
312
|
+
return $ = w ? Je(w) : b ? Array.from({ length: 5 }, (e, t) => /* @__PURE__ */ x(m, { children: W.map((e) => /* @__PURE__ */ x(f, { children: e.column.id === j || e.column.id === M ? null : /* @__PURE__ */ x(i, {
|
|
313
|
+
shape: "text",
|
|
314
|
+
className: "sn:w-2/3"
|
|
315
|
+
}) }, e.id)) }, t)) : G.length === 0 ? C ? Je(C) : null : /* @__PURE__ */ S(re, { children: [
|
|
316
|
+
Be > 0 && /* @__PURE__ */ x("tr", {
|
|
317
|
+
"aria-hidden": !0,
|
|
318
|
+
style: { height: Be }
|
|
319
|
+
}),
|
|
320
|
+
ze.map((t) => {
|
|
321
|
+
let r = A.includes(t.id), i = Z.drop?.id === t.id ? xe[Z.drop.side] : void 0, a = [
|
|
322
|
+
...t.getStartVisibleCells(),
|
|
323
|
+
...t.getCenterVisibleCells(),
|
|
324
|
+
...t.getEndVisibleCells()
|
|
325
|
+
];
|
|
326
|
+
return /* @__PURE__ */ x(Oe, {
|
|
327
|
+
id: t.id,
|
|
328
|
+
draggable: Ie,
|
|
329
|
+
selected: r,
|
|
330
|
+
"aria-selected": E ? r : void 0,
|
|
331
|
+
"aria-rowindex": J ? t.index + 2 : void 0,
|
|
332
|
+
renderCells: (o) => a.map((a) => {
|
|
333
|
+
let { column: s } = a, c = s.id, l = s.getIsPinned(), u = l === "start" ? "left" : l === "end" ? "right" : void 0, d = H.get(c), p;
|
|
334
|
+
return p = c === j ? /* @__PURE__ */ x(n, {
|
|
335
|
+
checked: r,
|
|
336
|
+
onCheckedChange: (e) => P(e ? [...A, t.id] : A.filter((e) => e !== t.id)),
|
|
337
|
+
children: /* @__PURE__ */ x("span", {
|
|
338
|
+
className: "sn:sr-only",
|
|
339
|
+
children: "행 선택"
|
|
340
|
+
})
|
|
341
|
+
}) : c === M ? o : Ee(d, t.original, a.getValue()), /* @__PURE__ */ x(f, {
|
|
342
|
+
pinned: u,
|
|
343
|
+
style: we(s, c === j || c === M || d?.width !== void 0),
|
|
344
|
+
align: d?.type === "number" ? "end" : "start",
|
|
345
|
+
className: e((d?.type === "date" || d?.type === "datetime") && "sn:font-mono", d?.type === "actions" && "sn:text-right", i),
|
|
346
|
+
children: p
|
|
347
|
+
}, a.id);
|
|
348
|
+
})
|
|
349
|
+
}, t.id);
|
|
350
|
+
}),
|
|
351
|
+
X > 0 && /* @__PURE__ */ x("tr", {
|
|
352
|
+
"aria-hidden": !0,
|
|
353
|
+
style: { height: X }
|
|
354
|
+
})
|
|
355
|
+
] }), /* @__PURE__ */ S("div", {
|
|
356
|
+
className: e("sn:flex sn:w-full sn:flex-col sn:gap-4", ie),
|
|
357
|
+
children: [/* @__PURE__ */ x("div", {
|
|
358
|
+
ref: Le,
|
|
359
|
+
className: "sn:relative sn:min-h-0 sn:w-full sn:flex-1 sn:overflow-auto",
|
|
360
|
+
children: /* @__PURE__ */ S("table", {
|
|
361
|
+
"aria-label": o,
|
|
362
|
+
"aria-busy": b || void 0,
|
|
363
|
+
"aria-rowcount": J ? G.length + 1 : void 0,
|
|
364
|
+
className: e(te, "sn:bg-surface"),
|
|
365
|
+
children: [/* @__PURE__ */ x(se, {
|
|
366
|
+
id: `DndDescribedBy-${Me}-columns`,
|
|
367
|
+
sensors: He,
|
|
368
|
+
collisionDetection: ue,
|
|
369
|
+
accessibility: {
|
|
370
|
+
container: Ve,
|
|
371
|
+
announcements: I("열", () => q),
|
|
372
|
+
screenReaderInstructions: Se
|
|
373
|
+
},
|
|
374
|
+
onDragOver: Q.onDragOver,
|
|
375
|
+
onDragEnd: Q.onDragEnd,
|
|
376
|
+
onDragCancel: Q.onDragCancel,
|
|
377
|
+
children: /* @__PURE__ */ x(ee, { children: /* @__PURE__ */ x(pe, {
|
|
378
|
+
items: O ? q : [],
|
|
379
|
+
strategy: he,
|
|
380
|
+
children: /* @__PURE__ */ x(m, { children: W.map(qe) })
|
|
381
|
+
}) })
|
|
382
|
+
}), /* @__PURE__ */ x(se, {
|
|
383
|
+
id: `DndDescribedBy-${Me}-rows`,
|
|
384
|
+
sensors: He,
|
|
385
|
+
collisionDetection: ue,
|
|
386
|
+
accessibility: {
|
|
387
|
+
container: Ve,
|
|
388
|
+
announcements: I("행", () => K),
|
|
389
|
+
screenReaderInstructions: Se
|
|
390
|
+
},
|
|
391
|
+
onDragOver: Z.onDragOver,
|
|
392
|
+
onDragEnd: Z.onDragEnd,
|
|
393
|
+
onDragCancel: Z.onDragCancel,
|
|
394
|
+
children: /* @__PURE__ */ x(pe, {
|
|
395
|
+
items: Ie ? K : [],
|
|
396
|
+
strategy: _e,
|
|
397
|
+
children: /* @__PURE__ */ x(d, { children: $ })
|
|
398
|
+
})
|
|
399
|
+
})]
|
|
400
|
+
})
|
|
401
|
+
}), T && !w && (b || G.length > 0) && /* @__PURE__ */ x(ne, {
|
|
402
|
+
page: V,
|
|
403
|
+
pageCount: je,
|
|
404
|
+
onPageChange: R,
|
|
405
|
+
pageSize: z,
|
|
406
|
+
onPageSizeChange: (e) => {
|
|
407
|
+
Ae(e), V !== 1 && R(1);
|
|
408
|
+
},
|
|
409
|
+
pageSizeOptions: T.pageSizeOptions,
|
|
410
|
+
showFirstLast: T.showFirstLast
|
|
411
|
+
})]
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
//#endregion
|
|
415
|
+
export { ke as DataGrid };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface PaginationProps {
|
|
3
|
+
/** 현재 페이지 (1부터) */
|
|
4
|
+
page: number;
|
|
5
|
+
pageCount: number;
|
|
6
|
+
onPageChange: (page: number) => void;
|
|
7
|
+
/** onPageSizeChange를 넘기면 왼쪽에 페이지당 행 수 Select가 생겨요 */
|
|
8
|
+
pageSize?: number;
|
|
9
|
+
onPageSizeChange?: (pageSize: number) => void;
|
|
10
|
+
pageSizeOptions?: number[];
|
|
11
|
+
/** 처음·마지막 버튼 */
|
|
12
|
+
showFirstLast?: boolean;
|
|
13
|
+
'aria-label'?: string;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
/** 처음·마지막 + 현재 양옆 1개, 나머지는 줄임표. 항상 최대 7칸이라 너비가 흔들리지 않아요 */
|
|
17
|
+
export declare function getPageItems(page: number, pageCount: number): Array<number | 'ellipsis'>;
|
|
18
|
+
export declare function Pagination({ page, pageCount, onPageChange, pageSize, onPageSizeChange, pageSizeOptions, showFirstLast, 'aria-label': ariaLabel, className, }: PaginationProps): React.JSX.Element;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn as e } from "../lib/cn.js";
|
|
3
|
+
import { Button as t } from "./button.js";
|
|
4
|
+
import { Select as n } from "./select.js";
|
|
5
|
+
import "react";
|
|
6
|
+
import { ChevronLeft as r, ChevronRight as i, ChevronsLeft as a, ChevronsRight as o } from "lucide-react";
|
|
7
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
8
|
+
//#region src/components/pagination.tsx
|
|
9
|
+
function l(e, t) {
|
|
10
|
+
return t <= 7 ? Array.from({ length: t }, (e, t) => t + 1) : e <= 4 ? [
|
|
11
|
+
1,
|
|
12
|
+
2,
|
|
13
|
+
3,
|
|
14
|
+
4,
|
|
15
|
+
5,
|
|
16
|
+
"ellipsis",
|
|
17
|
+
t
|
|
18
|
+
] : e >= t - 3 ? [
|
|
19
|
+
1,
|
|
20
|
+
"ellipsis",
|
|
21
|
+
...Array.from({ length: 5 }, (e, n) => t - 4 + n)
|
|
22
|
+
] : [
|
|
23
|
+
1,
|
|
24
|
+
"ellipsis",
|
|
25
|
+
e - 1,
|
|
26
|
+
e,
|
|
27
|
+
e + 1,
|
|
28
|
+
"ellipsis",
|
|
29
|
+
t
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
var u = "sn:box-border sn:flex sn:h-7 sn:min-w-7 sn:items-center sn:justify-center sn:rounded-md sn:border-0 sn:px-1 sn:font-mono sn:text-body";
|
|
33
|
+
function d({ page: d, pageCount: f, onPageChange: p, pageSize: m, onPageSizeChange: h, pageSizeOptions: g = [
|
|
34
|
+
10,
|
|
35
|
+
20,
|
|
36
|
+
50
|
|
37
|
+
], showFirstLast: _ = !0, "aria-label": v = "페이지", className: y }) {
|
|
38
|
+
let b = Math.max(1, f), x = Math.min(Math.max(1, d), b), S = (e) => e !== x && p(e), C = (e, n, r, i) => /* @__PURE__ */ s(t, {
|
|
39
|
+
size: "icon-sm",
|
|
40
|
+
variant: "ghost",
|
|
41
|
+
"aria-label": e,
|
|
42
|
+
disabled: r,
|
|
43
|
+
focusableWhenDisabled: !0,
|
|
44
|
+
onClick: () => S(n),
|
|
45
|
+
children: /* @__PURE__ */ s(i, {})
|
|
46
|
+
});
|
|
47
|
+
return /* @__PURE__ */ c("div", {
|
|
48
|
+
className: e("sn:flex sn:items-center sn:justify-between sn:gap-4 sn:font-sans", y),
|
|
49
|
+
children: [h && /* @__PURE__ */ c("div", {
|
|
50
|
+
className: "sn:flex sn:items-center sn:gap-2",
|
|
51
|
+
children: [/* @__PURE__ */ s("span", {
|
|
52
|
+
"aria-hidden": !0,
|
|
53
|
+
className: "sn:hidden sn:text-label sn:text-muted sn:sm:inline",
|
|
54
|
+
children: "페이지당 행"
|
|
55
|
+
}), /* @__PURE__ */ s(n, {
|
|
56
|
+
"aria-label": "페이지당 행",
|
|
57
|
+
className: "sn:w-auto",
|
|
58
|
+
options: g.map((e) => ({
|
|
59
|
+
value: String(e),
|
|
60
|
+
label: String(e)
|
|
61
|
+
})),
|
|
62
|
+
value: m === void 0 ? null : String(m),
|
|
63
|
+
onValueChange: (e) => e && h(Number(e))
|
|
64
|
+
})]
|
|
65
|
+
}), /* @__PURE__ */ c("nav", {
|
|
66
|
+
"aria-label": v,
|
|
67
|
+
className: "sn:ml-auto sn:flex sn:items-center sn:gap-1",
|
|
68
|
+
children: [
|
|
69
|
+
_ && C("처음 페이지", 1, x <= 1, a),
|
|
70
|
+
C("이전 페이지", x - 1, x <= 1, r),
|
|
71
|
+
/* @__PURE__ */ s("ul", {
|
|
72
|
+
className: "sn:m-0 sn:hidden sn:list-none sn:items-center sn:gap-1 sn:p-0 sn:sm:flex",
|
|
73
|
+
children: l(x, b).map((t, n) => t === "ellipsis" ? /* @__PURE__ */ s("li", {
|
|
74
|
+
"aria-hidden": !0,
|
|
75
|
+
className: e(u, "sn:text-muted"),
|
|
76
|
+
children: "…"
|
|
77
|
+
}, `e${n}`) : /* @__PURE__ */ s("li", { children: /* @__PURE__ */ s("button", {
|
|
78
|
+
type: "button",
|
|
79
|
+
"aria-label": `${t}페이지`,
|
|
80
|
+
"aria-current": t === x ? "page" : void 0,
|
|
81
|
+
onClick: () => S(t),
|
|
82
|
+
className: e(u, "sn:cursor-pointer sn:transition-colors sn:duration-(--sn-duration-fast) sn:ease-standard sn:motion-reduce:transition-none", "sn:focus-visible:outline-2 sn:focus-visible:outline-offset-2 sn:focus-visible:outline-focus", t === x ? "sn:bg-subtle sn:text-default" : "sn:bg-transparent sn:text-muted sn:hover:bg-subtle sn:hover:text-default"),
|
|
83
|
+
children: t
|
|
84
|
+
}) }, t))
|
|
85
|
+
}),
|
|
86
|
+
/* @__PURE__ */ c("span", {
|
|
87
|
+
className: "sn:px-1 sn:font-mono sn:text-caption sn:text-muted sn:sm:hidden",
|
|
88
|
+
children: [
|
|
89
|
+
x,
|
|
90
|
+
" / ",
|
|
91
|
+
b
|
|
92
|
+
]
|
|
93
|
+
}),
|
|
94
|
+
C("다음 페이지", x + 1, x >= b, i),
|
|
95
|
+
_ && C("마지막 페이지", b, x >= b, o)
|
|
96
|
+
]
|
|
97
|
+
})]
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { d as Pagination, l as getPageItems };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
type Pinned = 'left' | 'right';
|
|
3
|
+
export declare const tableClass = "sn:w-full sn:border-separate sn:border-spacing-0 sn:font-sans sn:text-body sn:text-default";
|
|
2
4
|
/** 넓으면 가로로 스크롤해요 */
|
|
3
5
|
export declare const Table: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLTableElement>, "className"> & {
|
|
4
6
|
className?: string;
|
|
@@ -11,14 +13,29 @@ export declare const TableBody: React.ForwardRefExoticComponent<Omit<React.HTMLA
|
|
|
11
13
|
} & React.RefAttributes<HTMLTableSectionElement>>;
|
|
12
14
|
export declare const TableRow: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLTableRowElement>, "className"> & {
|
|
13
15
|
className?: string;
|
|
16
|
+
} & {
|
|
17
|
+
/** 선택된 행 (bg/selected) */
|
|
18
|
+
selected?: boolean;
|
|
19
|
+
/** hover 바탕을 보여 줘요 (DataGrid처럼 행을 고르거나 옮길 때) */
|
|
20
|
+
interactive?: boolean;
|
|
21
|
+
/** 옮기는 중인 행 */
|
|
22
|
+
dragging?: boolean;
|
|
14
23
|
} & React.RefAttributes<HTMLTableRowElement>>;
|
|
15
|
-
export declare const TableHead: React.ForwardRefExoticComponent<Omit<React.
|
|
16
|
-
/** 숫자 열은 end */
|
|
24
|
+
export declare const TableHead: React.ForwardRefExoticComponent<Omit<React.ThHTMLAttributes<HTMLTableCellElement>, "align" | "className"> & {
|
|
17
25
|
align?: 'start' | 'end';
|
|
26
|
+
pinned?: Pinned;
|
|
27
|
+
/** 정렬 표시. onSort를 넘기면 머리글이 버튼이 돼요 */
|
|
28
|
+
sort?: 'asc' | 'desc' | 'none';
|
|
29
|
+
onSort?: () => void;
|
|
30
|
+
/** 컬럼 이동 손잡이. hover·포커스 때만 보이고, 정렬 반대쪽 끝에 놓여요 */
|
|
31
|
+
handle?: React.ReactNode;
|
|
18
32
|
className?: string;
|
|
19
33
|
} & React.RefAttributes<HTMLTableCellElement>>;
|
|
20
34
|
export declare const TableCell: React.ForwardRefExoticComponent<Omit<React.TdHTMLAttributes<HTMLTableCellElement>, "align" | "className"> & {
|
|
21
35
|
/** 숫자 열은 end */
|
|
22
36
|
align?: 'start' | 'end';
|
|
37
|
+
/** 가로 스크롤 중에도 붙어 있어요. 여러 열을 고정하면 style.left·right로 위치를 넘겨요 */
|
|
38
|
+
pinned?: Pinned;
|
|
23
39
|
className?: string;
|
|
24
40
|
} & React.RefAttributes<HTMLTableCellElement>>;
|
|
41
|
+
export {};
|
package/dist/components/table.js
CHANGED
|
@@ -1,46 +1,70 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn as e } from "../lib/cn.js";
|
|
3
3
|
import * as t from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { ArrowDown as n, ArrowUp as r } from "lucide-react";
|
|
5
|
+
import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
5
6
|
//#region src/components/table.tsx
|
|
6
|
-
var
|
|
7
|
-
return /* @__PURE__ */
|
|
7
|
+
var s = "sn:w-full sn:border-separate sn:border-spacing-0 sn:font-sans sn:text-body sn:text-default", c = t.forwardRef(function({ className: t, ...n }, r) {
|
|
8
|
+
return /* @__PURE__ */ a("div", {
|
|
8
9
|
className: "sn:w-full sn:overflow-x-auto",
|
|
9
|
-
children: /* @__PURE__ */
|
|
10
|
-
ref:
|
|
11
|
-
className: e(
|
|
12
|
-
...
|
|
10
|
+
children: /* @__PURE__ */ a("table", {
|
|
11
|
+
ref: r,
|
|
12
|
+
className: e(s, t),
|
|
13
|
+
...n
|
|
13
14
|
})
|
|
14
15
|
});
|
|
15
|
-
}),
|
|
16
|
-
return /* @__PURE__ */
|
|
16
|
+
}), l = t.forwardRef(function(e, t) {
|
|
17
|
+
return /* @__PURE__ */ a("thead", {
|
|
17
18
|
ref: t,
|
|
18
19
|
...e
|
|
19
20
|
});
|
|
20
|
-
}),
|
|
21
|
-
return /* @__PURE__ */
|
|
21
|
+
}), u = t.forwardRef(function(e, t) {
|
|
22
|
+
return /* @__PURE__ */ a("tbody", {
|
|
22
23
|
ref: t,
|
|
23
24
|
...e
|
|
24
25
|
});
|
|
25
|
-
}),
|
|
26
|
-
return /* @__PURE__ */
|
|
27
|
-
ref:
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
}), d = t.forwardRef(function({ selected: t, interactive: n, dragging: r, className: i, ...o }, s) {
|
|
27
|
+
return /* @__PURE__ */ a("tr", {
|
|
28
|
+
ref: s,
|
|
29
|
+
"data-selected": t ? "" : void 0,
|
|
30
|
+
"data-interactive": n ? "" : void 0,
|
|
31
|
+
"data-dragging": r ? "" : void 0,
|
|
32
|
+
className: e("sn:group/row sn:data-interactive:hover:bg-subtle sn:data-selected:bg-selected", "sn:data-dragging:bg-surface sn:data-dragging:outline sn:data-dragging:outline-1 sn:data-dragging:-outline-offset-1 sn:data-dragging:outline-default", i),
|
|
33
|
+
...o
|
|
30
34
|
});
|
|
31
|
-
}),
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
}), f = "sn:box-border sn:h-10 sn:border-b sn:border-default sn:px-3 sn:py-0 sn:align-middle", p = {
|
|
36
|
+
left: "sn:sticky sn:left-0 sn:z-10 sn:border-r sn:border-r-default sn:bg-surface",
|
|
37
|
+
right: "sn:sticky sn:right-0 sn:z-10 sn:border-l sn:border-l-default sn:bg-surface"
|
|
38
|
+
}, m = "sn:group-data-interactive/row:group-hover/row:bg-subtle sn:group-data-selected/row:bg-selected", h = t.forwardRef(function({ align: t = "start", sort: s, onSort: c, handle: l, pinned: u, className: d, children: m, ...h }, g) {
|
|
39
|
+
let _ = s === "asc" || s === "desc", v = _ ? /* @__PURE__ */ o(i, { children: [m, /* @__PURE__ */ a(s === "asc" ? r : n, {
|
|
40
|
+
className: "sn:size-4 sn:shrink-0",
|
|
41
|
+
"aria-hidden": !0
|
|
42
|
+
})] }) : m, y = e("sn:flex sn:h-10 sn:w-full sn:items-center sn:gap-1 sn:px-3", t === "end" && "sn:justify-end");
|
|
43
|
+
return /* @__PURE__ */ o("th", {
|
|
44
|
+
ref: g,
|
|
34
45
|
scope: "col",
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
"aria-sort": s === "asc" ? "ascending" : s === "desc" ? "descending" : s === "none" ? "none" : void 0,
|
|
47
|
+
className: e(f, !!l && "sn:group/head sn:relative", "sn:text-label", _ ? "sn:text-default" : "sn:text-muted", t === "end" ? "sn:text-right" : "sn:text-left", u && p[u], (c || _) && "sn:px-0", d),
|
|
48
|
+
...h,
|
|
49
|
+
children: [c ? /* @__PURE__ */ a("button", {
|
|
50
|
+
type: "button",
|
|
51
|
+
onClick: c,
|
|
52
|
+
className: e(y, "sn:cursor-pointer sn:border-0 sn:bg-transparent sn:[font:inherit] sn:text-inherit sn:hover:text-default", "sn:focus-visible:outline-2 sn:focus-visible:-outline-offset-2 sn:focus-visible:outline-focus"),
|
|
53
|
+
children: v
|
|
54
|
+
}) : _ ? /* @__PURE__ */ a("span", {
|
|
55
|
+
className: y,
|
|
56
|
+
children: v
|
|
57
|
+
}) : m, l ? /* @__PURE__ */ a("span", {
|
|
58
|
+
className: e("sn:absolute sn:inset-y-0 sn:flex sn:items-center sn:opacity-0 sn:group-hover/head:opacity-100 sn:focus-within:opacity-100", t === "end" ? "sn:left-1" : "sn:right-1"),
|
|
59
|
+
children: l
|
|
60
|
+
}) : null]
|
|
37
61
|
});
|
|
38
|
-
}),
|
|
39
|
-
return /* @__PURE__ */
|
|
40
|
-
ref:
|
|
41
|
-
className: e(
|
|
62
|
+
}), g = t.forwardRef(function({ align: t = "start", pinned: n, className: r, ...i }, o) {
|
|
63
|
+
return /* @__PURE__ */ a("td", {
|
|
64
|
+
ref: o,
|
|
65
|
+
className: e(f, t === "end" ? "sn:text-right sn:font-mono" : "sn:text-left", n && [p[n], m].join(" "), r),
|
|
42
66
|
...i
|
|
43
67
|
});
|
|
44
68
|
});
|
|
45
69
|
//#endregion
|
|
46
|
-
export {
|
|
70
|
+
export { c as Table, u as TableBody, g as TableCell, h as TableHead, l as TableHeader, d as TableRow, s as tableClass };
|
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,9 @@ export { Badge, type BadgeProps } from './components/badge';
|
|
|
27
27
|
export { Progress, type ProgressProps } from './components/progress';
|
|
28
28
|
export { Link, type LinkProps } from './components/link';
|
|
29
29
|
export { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from './components/table';
|
|
30
|
+
export { DataGrid, type DataGridProps, type DataGridColumn, type DataGridAction, type DataGridSort, type DataGridPagination } from './components/data-grid';
|
|
31
|
+
export { Pagination, type PaginationProps } from './components/pagination';
|
|
32
|
+
export { formatNumber, formatDate, formatDateTime } from './lib/format';
|
|
30
33
|
export { ToggleGroup, ToggleGroupItem, type ToggleGroupProps, type ToggleGroupItemProps } from './components/toggle-group';
|
|
31
34
|
export { Accordion, AccordionItem, AccordionTrigger, AccordionPanel, type AccordionProps, type AccordionItemProps } from './components/accordion';
|
|
32
35
|
export { FileDropzone, FileList, formatBytes, type FileDropzoneProps, type FileListProps, type FileItemData, type FileRejection } from './components/file-upload';
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,10 @@ import { Badge as re } from "./components/badge.js";
|
|
|
27
27
|
import { Progress as ie } from "./components/progress.js";
|
|
28
28
|
import { Link as ae } from "./components/link.js";
|
|
29
29
|
import { Table as oe, TableBody as se, TableCell as ce, TableHead as le, TableHeader as ue, TableRow as de } from "./components/table.js";
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
|
|
30
|
+
import { formatDate as fe, formatDateTime as pe, formatNumber as me } from "./lib/format.js";
|
|
31
|
+
import { Pagination as he } from "./components/pagination.js";
|
|
32
|
+
import { DataGrid as ge } from "./components/data-grid.js";
|
|
33
|
+
import { ToggleGroup as _e, ToggleGroupItem as ve } from "./components/toggle-group.js";
|
|
34
|
+
import { Accordion as ye, AccordionItem as be, AccordionPanel as xe, AccordionTrigger as Se } from "./components/accordion.js";
|
|
35
|
+
import { FileDropzone as Ce, FileList as we, formatBytes as Te } from "./components/file-upload.js";
|
|
36
|
+
export { ye as Accordion, be as AccordionItem, xe as AccordionPanel, Se as AccordionTrigger, j as AlertDialog, M as AlertDialogClose, N as AlertDialogDescription, P as AlertDialogFooter, F as AlertDialogPopup, I as AlertDialogTitle, L as AlertDialogTrigger, _ as Avatar, re as Badge, t as Button, d as Card, f as CardBody, p as CardDescription, m as CardFooter, h as CardHeader, g as CardTitle, i as Checkbox, a as CheckboxGroup, ge as DataGrid, w as Dialog, T as DialogClose, E as DialogDescription, D as DialogFooter, O as DialogPopup, k as DialogTitle, A as DialogTrigger, x as EmptyState, C as ErrorPage, S as ErrorState, v as Field, Ce as FileDropzone, we as FileList, n as Input, ae as Link, R as Menu, z as MenuItem, B as MenuPopup, V as MenuSeparator, H as MenuTrigger, he as Pagination, U as Popover, W as PopoverClose, G as PopoverDescription, K as PopoverPopup, q as PopoverTitle, J as PopoverTrigger, ie as Progress, o as Radio, s as RadioGroup, l as Select, ee as Separator, y as Skeleton, c as Switch, X as Tab, oe as Table, se as TableBody, ce as TableCell, le as TableHead, ue as TableHeader, de as TableRow, Z as Tabs, Q as TabsList, $ as TabsPanel, u as Text, r as Textarea, te as ToastProvider, _e as ToggleGroup, ve as ToggleGroupItem, Y as Tooltip, e as buttonVariants, Te as formatBytes, fe as formatDate, pe as formatDateTime, me as formatNumber, b as illustrations, ne as useToast };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type DateInput = string | number | Date | null | undefined;
|
|
2
|
+
/** 12480 → "12,480" */
|
|
3
|
+
export declare function formatNumber(value: number | null | undefined): string;
|
|
4
|
+
/** YYYY-MM-DD (브라우저 현지 시간) */
|
|
5
|
+
export declare function formatDate(value: DateInput): string;
|
|
6
|
+
/** YYYY-MM-DD HH:mm:ss (브라우저 현지 시간) */
|
|
7
|
+
export declare function formatDateTime(value: DateInput): string;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/lib/format.ts
|
|
2
|
+
var e = new Intl.NumberFormat("ko-KR"), t = (e) => String(e).padStart(2, "0"), n = (e) => {
|
|
3
|
+
if (e == null || e === "") return null;
|
|
4
|
+
let t = e instanceof Date ? e : new Date(e);
|
|
5
|
+
return Number.isNaN(t.getTime()) ? null : t;
|
|
6
|
+
};
|
|
7
|
+
function r(t) {
|
|
8
|
+
return t == null || Number.isNaN(t) ? "" : e.format(t);
|
|
9
|
+
}
|
|
10
|
+
function i(e) {
|
|
11
|
+
if (typeof e == "string" && /^\d{4}-\d{2}-\d{2}$/.test(e)) return e;
|
|
12
|
+
let r = n(e);
|
|
13
|
+
return r ? `${r.getFullYear()}-${t(r.getMonth() + 1)}-${t(r.getDate())}` : "";
|
|
14
|
+
}
|
|
15
|
+
function a(e) {
|
|
16
|
+
let r = n(e);
|
|
17
|
+
return r ? `${i(r)} ${t(r.getHours())}:${t(r.getMinutes())}:${t(r.getSeconds())}` : "";
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { i as formatDate, a as formatDateTime, r as formatNumber };
|
package/dist/styles.css
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/*! sonic-ui | 전체가 @layer sonic 안에 있어요. 쓰는 쪽: @layer theme, base, sonic, components, utilities; */
|
|
2
|
-
@property --tw-border-spacing-x{syntax:"<length>";inherits:false;initial-value:0}@property --tw-border-spacing-y{syntax:"<length>";inherits:false;initial-value:0}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@layer sonic{/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
3
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-duration:initial;--tw-ease:initial;--tw-outline-style:solid;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer utilities{.sn\:sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.sn\:absolute{position:absolute}.sn\:fixed{position:fixed}.sn\:relative{position:relative}.sn\:inset-0{inset:0}.sn\:top-1\/2{top:50%}.sn\:right-4{right:calc(var(--sn-space-1) * 4)}.sn\:bottom-4{bottom:calc(var(--sn-space-1) * 4)}.sn\:bottom-\[-1px\]{bottom:-1px}.sn\:left-0{left:0}.sn\:left-1\/2{left:50%}.sn\:z-50{z-index:50}.sn\:m-0{margin:0}.sn\:my-1{margin-block:var(--sn-space-1)}.sn\:-mt-2{margin-top:calc(var(--sn-space-1) * -2)}.sn\:-mt-3{margin-top:calc(var(--sn-space-1) * -3)}.sn\:mt-2{margin-top:calc(var(--sn-space-1) * 2)}.sn\:-mb-px{margin-bottom:-1px}.sn\:mb-0{margin-bottom:0}.sn\:ml-1{margin-left:var(--sn-space-1)}.sn\:box-border{box-sizing:border-box}.sn\:block{display:block}.sn\:flex{display:flex}.sn\:hidden{display:none}.sn\:inline-block{display:inline-block}.sn\:inline-flex{display:inline-flex}.sn\:size-2{width:calc(var(--sn-space-1) * 2);height:calc(var(--sn-space-1) * 2)}.sn\:size-3{width:calc(var(--sn-space-1) * 3);height:calc(var(--sn-space-1) * 3)}.sn\:size-4{width:calc(var(--sn-space-1) * 4);height:calc(var(--sn-space-1) * 4)}.sn\:size-6{width:calc(var(--sn-space-1) * 6);height:calc(var(--sn-space-1) * 6)}.sn\:size-7{width:calc(var(--sn-space-1) * 7);height:calc(var(--sn-space-1) * 7)}.sn\:size-8{width:calc(var(--sn-space-1) * 8);height:calc(var(--sn-space-1) * 8)}.sn\:size-10{width:calc(var(--sn-space-1) * 10);height:calc(var(--sn-space-1) * 10)}.sn\:size-12{width:calc(var(--sn-space-1) * 12);height:calc(var(--sn-space-1) * 12)}.sn\:size-24{width:calc(var(--sn-space-1) * 24);height:calc(var(--sn-space-1) * 24)}.sn\:size-full{width:100%;height:100%}.sn\:h-\(--accordion-panel-height\){height:var(--accordion-panel-height)}.sn\:h-0\.5{height:calc(var(--sn-space-1) * .5)}.sn\:h-1{height:var(--sn-space-1)}.sn\:h-3{height:calc(var(--sn-space-1) * 3)}.sn\:h-4{height:calc(var(--sn-space-1) * 4)}.sn\:h-5{height:calc(var(--sn-space-1) * 5)}.sn\:h-6{height:calc(var(--sn-space-1) * 6)}.sn\:h-7{height:calc(var(--sn-space-1) * 7)}.sn\:h-8{height:calc(var(--sn-space-1) * 8)}.sn\:h-10{height:calc(var(--sn-space-1) * 10)}.sn\:h-16{height:calc(var(--sn-space-1) * 16)}.sn\:h-auto{height:auto}.sn\:h-full{height:100%}.sn\:h-px{height:1px}.sn\:max-h-\(--available-height\){max-height:var(--available-height)}.sn\:max-h-\[calc\(100dvh-2\*var\(--sn-space-4\)\)\]{max-height:calc(100dvh - 2 * var(--sn-space-4))}.sn\:min-h-20{min-height:calc(var(--sn-space-1) * 20)}.sn\:w-\(--active-tab-width\){width:var(--active-tab-width)}.sn\:w-6{width:calc(var(--sn-space-1) * 6)}.sn\:w-7{width:calc(var(--sn-space-1) * 7)}.sn\:w-24{width:calc(var(--sn-space-1) * 24)}.sn\:w-80{width:calc(var(--sn-space-1) * 80)}.sn\:w-90{width:calc(var(--sn-space-1) * 90)}.sn\:w-110{width:calc(var(--sn-space-1) * 110)}.sn\:w-full{width:100%}.sn\:w-max{width:max-content}.sn\:w-px{width:1px}.sn\:max-w-\[calc\(100vw-2\*var\(--sn-space-4\)\)\]{max-width:calc(100vw - 2 * var(--sn-space-4))}.sn\:max-w-full{max-width:100%}.sn\:min-w-\(--anchor-width\){min-width:var(--anchor-width)}.sn\:min-w-0{min-width:0}.sn\:min-w-48{min-width:calc(var(--sn-space-1) * 48)}.sn\:min-w-full{min-width:100%}.sn\:flex-1{flex:1}.sn\:shrink-0{flex-shrink:0}.sn\:border-separate{border-collapse:separate}.sn\:border-spacing-0{--tw-border-spacing-x:0px;--tw-border-spacing-y:0px;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.sn\:origin-\(--transform-origin\){transform-origin:var(--transform-origin)}.sn\:-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:translate-x-\(--active-tab-left\){--tw-translate-x:var(--active-tab-left);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:animate-\[sn-pulse_var\(--sn-duration-loop\)_ease-in-out_infinite\]{animation:sn-pulse var(--sn-duration-loop) ease-in-out infinite}.sn\:animate-\[sn-spin_var\(--sn-duration-loop\)_linear_infinite\]{animation:sn-spin var(--sn-duration-loop) linear infinite}.sn\:cursor-default{cursor:default}.sn\:cursor-not-allowed{cursor:not-allowed}.sn\:cursor-pointer{cursor:pointer}.sn\:resize-y{resize:vertical}.sn\:\[scrollbar-width\:none\]{scrollbar-width:none}.sn\:list-none{list-style-type:none}.sn\:flex-col{flex-direction:column}.sn\:flex-col-reverse{flex-direction:column-reverse}.sn\:flex-wrap{flex-wrap:wrap}.sn\:items-center{align-items:center}.sn\:items-start{align-items:flex-start}.sn\:justify-between{justify-content:space-between}.sn\:justify-center{justify-content:center}.sn\:justify-end{justify-content:flex-end}.sn\:gap-1{gap:var(--sn-space-1)}.sn\:gap-2{gap:calc(var(--sn-space-1) * 2)}.sn\:gap-3{gap:calc(var(--sn-space-1) * 3)}.sn\:gap-4{gap:calc(var(--sn-space-1) * 4)}.sn\:gap-6{gap:calc(var(--sn-space-1) * 6)}.sn\:gap-10{gap:calc(var(--sn-space-1) * 10)}.sn\:self-stretch{align-self:stretch}.sn\:truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.sn\:overflow-hidden{overflow:hidden}.sn\:overflow-x-auto{overflow-x:auto}.sn\:overflow-y-auto{overflow-y:auto}.sn\:rounded-full{border-radius:var(--sn-radius-full)}.sn\:rounded-lg{border-radius:var(--sn-radius-lg)}.sn\:rounded-md{border-radius:var(--sn-radius-md)}.sn\:rounded-sm{border-radius:var(--sn-radius-sm)}.sn\:border{border-style:var(--tw-border-style);border-width:1px}.sn\:border-0{border-style:var(--tw-border-style);border-width:0}.sn\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.sn\:border-dashed{--tw-border-style:dashed;border-style:dashed}.sn\:border-danger{border-color:var(--sn-color-border-danger)}.sn\:border-default{border-color:var(--sn-color-border-default)}.sn\:border-strong{border-color:var(--sn-color-border-strong)}.sn\:border-transparent{border-color:#0000}.sn\:bg-\(--sn-color-border-default\){background-color:var(--sn-color-border-default)}.sn\:bg-\(--sn-color-text-on-primary\){background-color:var(--sn-color-text-on-primary)}.sn\:bg-canvas{background-color:var(--sn-color-bg-canvas)}.sn\:bg-danger{background-color:var(--sn-color-bg-danger)}.sn\:bg-danger-subtle{background-color:var(--sn-color-bg-danger-subtle)}.sn\:bg-inverse{background-color:var(--sn-color-bg-inverse)}.sn\:bg-overlay{background-color:var(--sn-color-bg-overlay)}.sn\:bg-primary{background-color:var(--sn-color-bg-primary)}.sn\:bg-subtle{background-color:var(--sn-color-bg-subtle)}.sn\:bg-success{background-color:var(--sn-color-bg-success)}.sn\:bg-success-subtle{background-color:var(--sn-color-bg-success-subtle)}.sn\:bg-surface{background-color:var(--sn-color-bg-surface)}.sn\:bg-transparent{background-color:#0000}.sn\:bg-warning{background-color:var(--sn-color-bg-warning)}.sn\:bg-warning-subtle{background-color:var(--sn-color-bg-warning-subtle)}.sn\:object-cover{object-fit:cover}.sn\:p-0{padding:0}.sn\:p-1{padding:var(--sn-space-1)}.sn\:p-3{padding:calc(var(--sn-space-1) * 3)}.sn\:p-4{padding:calc(var(--sn-space-1) * 4)}.sn\:p-5{padding:calc(var(--sn-space-1) * 5)}.sn\:p-6{padding:calc(var(--sn-space-1) * 6)}.sn\:p-px{padding:1px}.sn\:px-0{padding-inline:0}.sn\:px-2{padding-inline:calc(var(--sn-space-1) * 2)}.sn\:px-3{padding-inline:calc(var(--sn-space-1) * 3)}.sn\:px-4{padding-inline:calc(var(--sn-space-1) * 4)}.sn\:px-6{padding-inline:calc(var(--sn-space-1) * 6)}.sn\:py-0{padding-block:0}.sn\:py-1{padding-block:var(--sn-space-1)}.sn\:py-2{padding-block:calc(var(--sn-space-1) * 2)}.sn\:py-3{padding-block:calc(var(--sn-space-1) * 3)}.sn\:py-6{padding-block:calc(var(--sn-space-1) * 6)}.sn\:py-12{padding-block:calc(var(--sn-space-1) * 12)}.sn\:py-16{padding-block:calc(var(--sn-space-1) * 16)}.sn\:pt-1{padding-top:var(--sn-space-1)}.sn\:pb-4{padding-bottom:calc(var(--sn-space-1) * 4)}.sn\:pb-px{padding-bottom:1px}.sn\:text-center{text-align:center}.sn\:text-left{text-align:left}.sn\:text-right{text-align:right}.sn\:align-middle{vertical-align:middle}.sn\:align-text-bottom{vertical-align:text-bottom}.sn\:font-mono{font-family:var(--sn-font-mono)}.sn\:font-sans{font-family:var(--sn-font-sans)}.sn\:text-body{font-size:var(--sn-text-body);line-height:var(--tw-leading,var(--sn-text-body--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-body--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-body--font-weight))}.sn\:text-body-strong{font-size:var(--sn-text-body-strong);line-height:var(--tw-leading,var(--sn-text-body-strong--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-body-strong--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-body-strong--font-weight))}.sn\:text-caption{font-size:var(--sn-text-caption);line-height:var(--tw-leading,var(--sn-text-caption--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-caption--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-caption--font-weight))}.sn\:text-display{font-size:var(--sn-text-display);line-height:var(--tw-leading,var(--sn-text-display--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-display--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-display--font-weight))}.sn\:text-label{font-size:var(--sn-text-label);line-height:var(--tw-leading,var(--sn-text-label--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-label--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-label--font-weight))}.sn\:text-title{font-size:var(--sn-text-title);line-height:var(--tw-leading,var(--sn-text-title--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-title--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-title--font-weight))}.sn\:whitespace-nowrap{white-space:nowrap}.sn\:text-danger{color:var(--sn-color-text-danger)}.sn\:text-default{color:var(--sn-color-text-default)}.sn\:text-inverse{color:var(--sn-color-text-inverse)}.sn\:text-muted{color:var(--sn-color-text-muted)}.sn\:text-on-danger{color:var(--sn-color-text-on-danger)}.sn\:text-on-primary{color:var(--sn-color-text-on-primary)}.sn\:text-on-success{color:var(--sn-color-text-on-success)}.sn\:text-on-warning{color:var(--sn-color-text-on-warning)}.sn\:text-success{color:var(--sn-color-text-success)}.sn\:text-warning{color:var(--sn-color-text-warning)}.sn\:tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.sn\:underline{text-decoration-line:underline}.sn\:underline-offset-2{text-underline-offset:2px}.sn\:opacity-\(--sn-opacity-disabled\){opacity:var(--sn-opacity-disabled)}.sn\:transition-\[height\]{transition-property:height;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-\[opacity\,scale\]{transition-property:opacity,scale;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-\[opacity\,translate\]{transition-property:opacity,translate;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-\[translate\,width\]{transition-property:translate,width;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:duration-\(--sn-duration-fast\){--tw-duration:var(--sn-duration-fast);transition-duration:var(--sn-duration-fast)}.sn\:duration-\(--sn-duration-normal\){--tw-duration:var(--sn-duration-normal);transition-duration:var(--sn-duration-normal)}.sn\:ease-standard{--tw-ease:var(--sn-easing-standard);transition-timing-function:var(--sn-easing-standard)}.sn\:outline-none{--tw-outline-style:none;outline-style:none}.sn\:select-none{-webkit-user-select:none;user-select:none}.sn\:group-aria-invalid\:text-danger:is(:where(.sn\:group)[aria-invalid=true] *),.sn\:group-data-invalid\:text-danger:is(:where(.sn\:group)[data-invalid] *){color:var(--sn-color-text-danger)}.sn\:group-data-panel-open\:rotate-180:is(:where(.sn\:group)[data-panel-open] *),.sn\:group-data-popup-open\:rotate-180:is(:where(.sn\:group)[data-popup-open] *){rotate:180deg}.sn\:group-data-selected\:border-transparent:is(:where(.sn\:group)[data-selected] *){border-color:#0000}.sn\:group-data-selected\:bg-primary:is(:where(.sn\:group)[data-selected] *){background-color:var(--sn-color-bg-primary)}.sn\:placeholder\:text-muted::placeholder{color:var(--sn-color-text-muted)}@media (hover:hover){.sn\:hover\:text-muted:hover{color:var(--sn-color-text-muted)}.sn\:hover\:not-disabled\:bg-subtle:hover:not(:disabled){background-color:var(--sn-color-bg-subtle)}.sn\:hover\:not-data-disabled\:border-strong:hover:not([data-disabled]){border-color:var(--sn-color-border-strong)}.sn\:hover\:not-data-disabled\:bg-primary-hover:hover:not([data-disabled]){background-color:var(--sn-color-bg-primary-hover)}.sn\:hover\:not-data-disabled\:bg-subtle:hover:not([data-disabled]){background-color:var(--sn-color-bg-subtle)}.sn\:hover\:not-data-disabled\:text-default:hover:not([data-disabled]){color:var(--sn-color-text-default)}}.sn\:focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.sn\:focus-visible\:-outline-offset-2:focus-visible{outline-offset:calc(2px * -1)}.sn\:focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.sn\:focus-visible\:outline-focus:focus-visible{outline-color:var(--sn-color-border-focus)}.sn\:disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.sn\:disabled\:opacity-\(--sn-opacity-disabled\):disabled{opacity:var(--sn-opacity-disabled)}.sn\:aria-invalid\:border-danger[aria-invalid=true]{border-color:var(--sn-color-border-danger)}@media (hover:hover){.sn\:hover\:aria-invalid\:border-danger:hover[aria-invalid=true]{border-color:var(--sn-color-border-danger)}}.sn\:aria-invalid\:focus-visible\:outline-danger[aria-invalid=true]:focus-visible{outline-color:var(--sn-color-border-danger)}.sn\:data-active\:text-default[data-active]{color:var(--sn-color-text-default)}.sn\:data-checked\:translate-x-3[data-checked]{--tw-translate-x:calc(var(--sn-space-1) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:data-checked\:border-transparent[data-checked]{border-color:#0000}.sn\:data-checked\:bg-\(--sn-color-text-on-primary\)[data-checked]{background-color:var(--sn-color-text-on-primary)}.sn\:data-checked\:bg-primary[data-checked]{background-color:var(--sn-color-bg-primary)}.sn\:data-disabled\:cursor-not-allowed[data-disabled]{cursor:not-allowed}.sn\:data-disabled\:opacity-\(--sn-opacity-disabled\)[data-disabled]{opacity:var(--sn-opacity-disabled)}.sn\:data-disabled\:not-data-loading\:cursor-not-allowed[data-disabled]:not([data-loading]){cursor:not-allowed}.sn\:data-disabled\:not-data-loading\:opacity-\(--sn-opacity-disabled\)[data-disabled]:not([data-loading]){opacity:var(--sn-opacity-disabled)}.sn\:data-dragging\:border-focus[data-dragging]{border-color:var(--sn-color-border-focus)}.sn\:data-dragging\:bg-subtle[data-dragging]{background-color:var(--sn-color-bg-subtle)}.sn\:data-ending-style\:h-0[data-ending-style]{height:0}.sn\:data-ending-style\:translate-y-2[data-ending-style]{--tw-translate-y:calc(var(--sn-space-1) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:data-ending-style\:scale-98[data-ending-style]{--tw-scale-x:98%;--tw-scale-y:98%;--tw-scale-z:98%;scale:var(--tw-scale-x) var(--tw-scale-y)}.sn\:data-ending-style\:opacity-0[data-ending-style]{opacity:0}.sn\:data-highlighted\:bg-subtle[data-highlighted]{background-color:var(--sn-color-bg-subtle)}.sn\:data-indeterminate\:border-transparent[data-indeterminate]{border-color:#0000}.sn\:data-indeterminate\:bg-primary[data-indeterminate]{background-color:var(--sn-color-bg-primary)}.sn\:data-invalid\:border-danger[data-invalid]{border-color:var(--sn-color-border-danger)}.sn\:data-invalid\:focus-visible\:outline-danger[data-invalid]:focus-visible{outline-color:var(--sn-color-border-danger)}.sn\:data-limited\:hidden[data-limited]{display:none}.sn\:data-loading\:cursor-progress[data-loading]{cursor:progress}.sn\:data-placeholder\:text-muted[data-placeholder]{color:var(--sn-color-text-muted)}.sn\:data-popup-open\:border-focus[data-popup-open]{border-color:var(--sn-color-border-focus)}.sn\:data-pressed\:border-default[data-pressed]{border-color:var(--sn-color-border-default)}.sn\:data-pressed\:bg-surface[data-pressed]{background-color:var(--sn-color-bg-surface)}.sn\:data-pressed\:text-default[data-pressed]{color:var(--sn-color-text-default)}.sn\:data-starting-style\:h-0[data-starting-style]{height:0}.sn\:data-starting-style\:translate-y-2[data-starting-style]{--tw-translate-y:calc(var(--sn-space-1) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:data-starting-style\:scale-98[data-starting-style]{--tw-scale-x:98%;--tw-scale-y:98%;--tw-scale-z:98%;scale:var(--tw-scale-x) var(--tw-scale-y)}.sn\:data-starting-style\:opacity-0[data-starting-style]{opacity:0}@media (prefers-reduced-motion:reduce){.sn\:motion-reduce\:animate-none{animation:none}.sn\:motion-reduce\:transition-none{transition-property:none}}@media not all and (min-width:40rem){.sn\:max-sm\:flex-col-reverse{flex-direction:column-reverse}.sn\:max-sm\:text-\[16px\]{font-size:16px}:is(.sn\:max-sm\:\*\:h-10>*){height:calc(var(--sn-space-1) * 10)}:is(.sn\:max-sm\:\*\:w-full>*){width:100%}}@media (pointer:coarse){.sn\:pointer-coarse\:hidden{display:none}.sn\:pointer-coarse\:inline{display:inline}}.sn\:\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.sn\:\[\&_svg\]\:size-3 svg{width:calc(var(--sn-space-1) * 3);height:calc(var(--sn-space-1) * 3)}.sn\:\[\&_svg\]\:size-4 svg{width:calc(var(--sn-space-1) * 4);height:calc(var(--sn-space-1) * 4)}.sn\:\[\&_svg\]\:size-5 svg{width:calc(var(--sn-space-1) * 5);height:calc(var(--sn-space-1) * 5)}.sn\:\[\&_svg\]\:size-6 svg{width:calc(var(--sn-space-1) * 6);height:calc(var(--sn-space-1) * 6)}.sn\:\[\&_svg\]\:shrink-0 svg{flex-shrink:0}}:root{--sn-space-0:0px;--sn-space-1:4px;--sn-space-2:8px;--sn-space-3:12px;--sn-space-4:16px;--sn-space-5:20px;--sn-space-6:24px;--sn-space-7:28px;--sn-space-8:32px;--sn-space-10:40px;--sn-space-12:48px;--sn-space-16:64px;--sn-radius-sm:2px;--sn-radius-md:4px;--sn-radius-lg:6px;--sn-radius-full:9999px;--sn-stroke-default:1px;--sn-stroke-focus:2px;--sn-duration-fast:.12s;--sn-duration-normal:.2s;--sn-easing-standard:cubic-bezier(.2, 0, 0, 1);--sn-duration-loop:1s;--sn-opacity-disabled:.45;--sn-font-sans:"IBM Plex Sans KR", ui-sans-serif, system-ui, sans-serif;--sn-font-mono:"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--sn-text-display:32px;--sn-text-display--line-height:40px;--sn-text-display--font-weight:600;--sn-text-display--letter-spacing:-.02em;--sn-text-title:20px;--sn-text-title--line-height:28px;--sn-text-title--font-weight:600;--sn-text-title--letter-spacing:-.01em;--sn-text-body:14px;--sn-text-body--line-height:20px;--sn-text-body--font-weight:400;--sn-text-body--letter-spacing:0em;--sn-text-body-strong:14px;--sn-text-body-strong--line-height:20px;--sn-text-body-strong--font-weight:500;--sn-text-body-strong--letter-spacing:0em;--sn-text-label:13px;--sn-text-label--line-height:16px;--sn-text-label--font-weight:500;--sn-text-label--letter-spacing:0em;--sn-text-caption:12px;--sn-text-caption--line-height:16px;--sn-text-caption--font-weight:400;--sn-text-caption--letter-spacing:0em;--sn-text-mono:14px;--sn-text-mono--line-height:20px;--sn-text-mono--font-weight:400;--sn-text-mono--letter-spacing:0em;--sn-text-mono-caption:12px;--sn-text-mono-caption--line-height:16px;--sn-text-mono-caption--font-weight:400;--sn-text-mono-caption--letter-spacing:0em}:root,[data-theme=light]{color-scheme:light;--sn-color-bg-canvas:#f4f6f8;--sn-color-bg-surface:#fff;--sn-color-bg-subtle:#e6eaee;--sn-color-bg-primary:#2340d0;--sn-color-bg-primary-hover:#1b33ae;--sn-color-bg-danger:#c8312b;--sn-color-bg-overlay:#0e121680;--sn-color-text-default:#0e1216;--sn-color-text-muted:#5b6672;--sn-color-text-on-primary:#fff;--sn-color-text-on-danger:#fff;--sn-color-text-danger:#c8312b;--sn-color-border-default:#d5dbe1;--sn-color-border-strong:#8d99a6;--sn-color-border-focus:#2340d0;--sn-color-border-danger:#c8312b;--sn-color-bg-inverse:#0e1216;--sn-color-text-inverse:#fff;--sn-color-bg-success:#16804f;--sn-color-text-on-success:#fff;--sn-color-bg-success-subtle:#eaf7f0;--sn-color-text-success:#0f6640;--sn-color-bg-warning:#e3a43a;--sn-color-text-on-warning:#0e1216;--sn-color-bg-warning-subtle:#fcf5e6;--sn-color-text-warning:#85520a;--sn-color-bg-danger-subtle:#fcedec;--sn-color-border-success:#16804f;--sn-color-border-warning:#a86c12}[data-theme=dark]{color-scheme:dark;--sn-color-bg-canvas:#0e1216;--sn-color-bg-surface:#161b21;--sn-color-bg-subtle:#1e252d;--sn-color-bg-primary:#6e86ff;--sn-color-bg-primary-hover:#8c9fff;--sn-color-bg-danger:#f06a5f;--sn-color-bg-overlay:#0e121680;--sn-color-text-default:#e6eaee;--sn-color-text-muted:#8d99a6;--sn-color-text-on-primary:#0e1216;--sn-color-text-on-danger:#0e1216;--sn-color-text-danger:#f06a5f;--sn-color-border-default:#2a323b;--sn-color-border-strong:#5b6672;--sn-color-border-focus:#6e86ff;--sn-color-border-danger:#f06a5f;--sn-color-bg-inverse:#e6eaee;--sn-color-text-inverse:#0e1216;--sn-color-bg-success:#3db882;--sn-color-text-on-success:#0e1216;--sn-color-bg-success-subtle:#0b2218;--sn-color-text-success:#6bd1a0;--sn-color-bg-warning:#e3a43a;--sn-color-text-on-warning:#0e1216;--sn-color-bg-warning-subtle:#24190a;--sn-color-text-warning:#eec46a;--sn-color-bg-danger-subtle:#2b1311;--sn-color-border-success:#3db882;--sn-color-border-warning:#e3a43a}@keyframes sn-pulse{50%{opacity:.5}}@keyframes sn-spin{to{transform:rotate(360deg)}}}
|
|
2
|
+
@property --tw-border-spacing-x{syntax:"<length>";inherits:false;initial-value:0}@property --tw-border-spacing-y{syntax:"<length>";inherits:false;initial-value:0}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@layer sonic{/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
3
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-outline-style:solid;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer utilities{.sn\:sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.sn\:absolute{position:absolute}.sn\:fixed{position:fixed}.sn\:relative{position:relative}.sn\:sticky{position:sticky}.sn\:inset-0{inset:0}.sn\:inset-y-0{inset-block:0}.sn\:top-0{top:0}.sn\:top-1\/2{top:50%}.sn\:right-0{right:0}.sn\:right-1{right:var(--sn-space-1)}.sn\:right-4{right:calc(var(--sn-space-1) * 4)}.sn\:bottom-4{bottom:calc(var(--sn-space-1) * 4)}.sn\:bottom-\[-1px\]{bottom:-1px}.sn\:left-0{left:0}.sn\:left-1{left:var(--sn-space-1)}.sn\:left-1\/2{left:50%}.sn\:z-10{z-index:10}.sn\:z-20{z-index:20}.sn\:z-30{z-index:30}.sn\:z-50{z-index:50}.sn\:m-0{margin:0}.sn\:my-1{margin-block:var(--sn-space-1)}.sn\:-mt-2{margin-top:calc(var(--sn-space-1) * -2)}.sn\:-mt-3{margin-top:calc(var(--sn-space-1) * -3)}.sn\:mt-2{margin-top:calc(var(--sn-space-1) * 2)}.sn\:-mb-px{margin-bottom:-1px}.sn\:mb-0{margin-bottom:0}.sn\:ml-1{margin-left:var(--sn-space-1)}.sn\:ml-auto{margin-left:auto}.sn\:box-border{box-sizing:border-box}.sn\:block{display:block}.sn\:flex{display:flex}.sn\:hidden{display:none}.sn\:inline-block{display:inline-block}.sn\:inline-flex{display:inline-flex}.sn\:size-2{width:calc(var(--sn-space-1) * 2);height:calc(var(--sn-space-1) * 2)}.sn\:size-3{width:calc(var(--sn-space-1) * 3);height:calc(var(--sn-space-1) * 3)}.sn\:size-4{width:calc(var(--sn-space-1) * 4);height:calc(var(--sn-space-1) * 4)}.sn\:size-6{width:calc(var(--sn-space-1) * 6);height:calc(var(--sn-space-1) * 6)}.sn\:size-7{width:calc(var(--sn-space-1) * 7);height:calc(var(--sn-space-1) * 7)}.sn\:size-8{width:calc(var(--sn-space-1) * 8);height:calc(var(--sn-space-1) * 8)}.sn\:size-10{width:calc(var(--sn-space-1) * 10);height:calc(var(--sn-space-1) * 10)}.sn\:size-12{width:calc(var(--sn-space-1) * 12);height:calc(var(--sn-space-1) * 12)}.sn\:size-24{width:calc(var(--sn-space-1) * 24);height:calc(var(--sn-space-1) * 24)}.sn\:size-full{width:100%;height:100%}.sn\:h-\(--accordion-panel-height\){height:var(--accordion-panel-height)}.sn\:h-0\.5{height:calc(var(--sn-space-1) * .5)}.sn\:h-1{height:var(--sn-space-1)}.sn\:h-3{height:calc(var(--sn-space-1) * 3)}.sn\:h-4{height:calc(var(--sn-space-1) * 4)}.sn\:h-5{height:calc(var(--sn-space-1) * 5)}.sn\:h-6{height:calc(var(--sn-space-1) * 6)}.sn\:h-7{height:calc(var(--sn-space-1) * 7)}.sn\:h-8{height:calc(var(--sn-space-1) * 8)}.sn\:h-10{height:calc(var(--sn-space-1) * 10)}.sn\:h-16{height:calc(var(--sn-space-1) * 16)}.sn\:h-96{height:calc(var(--sn-space-1) * 96)}.sn\:h-auto{height:auto}.sn\:h-full{height:100%}.sn\:h-px{height:1px}.sn\:max-h-\(--available-height\){max-height:var(--available-height)}.sn\:max-h-\[calc\(100dvh-2\*var\(--sn-space-4\)\)\]{max-height:calc(100dvh - 2 * var(--sn-space-4))}.sn\:min-h-0{min-height:0}.sn\:min-h-20{min-height:calc(var(--sn-space-1) * 20)}.sn\:w-\(--active-tab-width\){width:var(--active-tab-width)}.sn\:w-2\/3{width:66.6667%}.sn\:w-6{width:calc(var(--sn-space-1) * 6)}.sn\:w-7{width:calc(var(--sn-space-1) * 7)}.sn\:w-24{width:calc(var(--sn-space-1) * 24)}.sn\:w-80{width:calc(var(--sn-space-1) * 80)}.sn\:w-90{width:calc(var(--sn-space-1) * 90)}.sn\:w-110{width:calc(var(--sn-space-1) * 110)}.sn\:w-auto{width:auto}.sn\:w-full{width:100%}.sn\:w-max{width:max-content}.sn\:w-px{width:1px}.sn\:max-w-\[calc\(100vw-2\*var\(--sn-space-4\)\)\]{max-width:calc(100vw - 2 * var(--sn-space-4))}.sn\:max-w-full{max-width:100%}.sn\:min-w-\(--anchor-width\){min-width:var(--anchor-width)}.sn\:min-w-0{min-width:0}.sn\:min-w-7{min-width:calc(var(--sn-space-1) * 7)}.sn\:min-w-48{min-width:calc(var(--sn-space-1) * 48)}.sn\:min-w-full{min-width:100%}.sn\:flex-1{flex:1}.sn\:shrink-0{flex-shrink:0}.sn\:border-separate{border-collapse:separate}.sn\:border-spacing-0{--tw-border-spacing-x:0px;--tw-border-spacing-y:0px;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.sn\:origin-\(--transform-origin\){transform-origin:var(--transform-origin)}.sn\:-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:translate-x-\(--active-tab-left\){--tw-translate-x:var(--active-tab-left);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:animate-\[sn-pulse_var\(--sn-duration-loop\)_ease-in-out_infinite\]{animation:sn-pulse var(--sn-duration-loop) ease-in-out infinite}.sn\:animate-\[sn-spin_var\(--sn-duration-loop\)_linear_infinite\]{animation:sn-spin var(--sn-duration-loop) linear infinite}.sn\:cursor-default{cursor:default}.sn\:cursor-grab{cursor:grab}.sn\:cursor-not-allowed{cursor:not-allowed}.sn\:cursor-pointer{cursor:pointer}.sn\:resize-y{resize:vertical}.sn\:\[scrollbar-width\:none\]{scrollbar-width:none}.sn\:list-none{list-style-type:none}.sn\:flex-col{flex-direction:column}.sn\:flex-col-reverse{flex-direction:column-reverse}.sn\:flex-wrap{flex-wrap:wrap}.sn\:items-center{align-items:center}.sn\:items-start{align-items:flex-start}.sn\:justify-between{justify-content:space-between}.sn\:justify-center{justify-content:center}.sn\:justify-end{justify-content:flex-end}.sn\:gap-1{gap:var(--sn-space-1)}.sn\:gap-2{gap:calc(var(--sn-space-1) * 2)}.sn\:gap-3{gap:calc(var(--sn-space-1) * 3)}.sn\:gap-4{gap:calc(var(--sn-space-1) * 4)}.sn\:gap-6{gap:calc(var(--sn-space-1) * 6)}.sn\:gap-10{gap:calc(var(--sn-space-1) * 10)}.sn\:self-stretch{align-self:stretch}.sn\:truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.sn\:overflow-auto{overflow:auto}.sn\:overflow-hidden{overflow:hidden}.sn\:overflow-x-auto{overflow-x:auto}.sn\:overflow-y-auto{overflow-y:auto}.sn\:rounded-full{border-radius:var(--sn-radius-full)}.sn\:rounded-lg{border-radius:var(--sn-radius-lg)}.sn\:rounded-md{border-radius:var(--sn-radius-md)}.sn\:rounded-sm{border-radius:var(--sn-radius-sm)}.sn\:border{border-style:var(--tw-border-style);border-width:1px}.sn\:border-0{border-style:var(--tw-border-style);border-width:0}.sn\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.sn\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.sn\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.sn\:border-dashed{--tw-border-style:dashed;border-style:dashed}.sn\:border-danger{border-color:var(--sn-color-border-danger)}.sn\:border-default{border-color:var(--sn-color-border-default)}.sn\:border-strong{border-color:var(--sn-color-border-strong)}.sn\:border-transparent{border-color:#0000}.sn\:border-r-default{border-right-color:var(--sn-color-border-default)}.sn\:border-l-default{border-left-color:var(--sn-color-border-default)}.sn\:bg-\(--sn-color-border-default\){background-color:var(--sn-color-border-default)}.sn\:bg-\(--sn-color-text-on-primary\){background-color:var(--sn-color-text-on-primary)}.sn\:bg-canvas{background-color:var(--sn-color-bg-canvas)}.sn\:bg-danger{background-color:var(--sn-color-bg-danger)}.sn\:bg-danger-subtle{background-color:var(--sn-color-bg-danger-subtle)}.sn\:bg-inverse{background-color:var(--sn-color-bg-inverse)}.sn\:bg-overlay{background-color:var(--sn-color-bg-overlay)}.sn\:bg-primary{background-color:var(--sn-color-bg-primary)}.sn\:bg-subtle{background-color:var(--sn-color-bg-subtle)}.sn\:bg-success{background-color:var(--sn-color-bg-success)}.sn\:bg-success-subtle{background-color:var(--sn-color-bg-success-subtle)}.sn\:bg-surface{background-color:var(--sn-color-bg-surface)}.sn\:bg-transparent{background-color:#0000}.sn\:bg-warning{background-color:var(--sn-color-bg-warning)}.sn\:bg-warning-subtle{background-color:var(--sn-color-bg-warning-subtle)}.sn\:object-cover{object-fit:cover}.sn\:p-0{padding:0}.sn\:p-1{padding:var(--sn-space-1)}.sn\:p-3{padding:calc(var(--sn-space-1) * 3)}.sn\:p-4{padding:calc(var(--sn-space-1) * 4)}.sn\:p-5{padding:calc(var(--sn-space-1) * 5)}.sn\:p-6{padding:calc(var(--sn-space-1) * 6)}.sn\:p-px{padding:1px}.sn\:px-0{padding-inline:0}.sn\:px-1{padding-inline:var(--sn-space-1)}.sn\:px-2{padding-inline:calc(var(--sn-space-1) * 2)}.sn\:px-3{padding-inline:calc(var(--sn-space-1) * 3)}.sn\:px-4{padding-inline:calc(var(--sn-space-1) * 4)}.sn\:px-6{padding-inline:calc(var(--sn-space-1) * 6)}.sn\:py-0{padding-block:0}.sn\:py-1{padding-block:var(--sn-space-1)}.sn\:py-2{padding-block:calc(var(--sn-space-1) * 2)}.sn\:py-3{padding-block:calc(var(--sn-space-1) * 3)}.sn\:py-6{padding-block:calc(var(--sn-space-1) * 6)}.sn\:py-12{padding-block:calc(var(--sn-space-1) * 12)}.sn\:py-16{padding-block:calc(var(--sn-space-1) * 16)}.sn\:pt-1{padding-top:var(--sn-space-1)}.sn\:pb-4{padding-bottom:calc(var(--sn-space-1) * 4)}.sn\:pb-px{padding-bottom:1px}.sn\:text-center{text-align:center}.sn\:text-left{text-align:left}.sn\:text-right{text-align:right}.sn\:align-middle{vertical-align:middle}.sn\:align-text-bottom{vertical-align:text-bottom}.sn\:font-mono{font-family:var(--sn-font-mono)}.sn\:font-sans{font-family:var(--sn-font-sans)}.sn\:text-body{font-size:var(--sn-text-body);line-height:var(--tw-leading,var(--sn-text-body--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-body--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-body--font-weight))}.sn\:text-body-strong{font-size:var(--sn-text-body-strong);line-height:var(--tw-leading,var(--sn-text-body-strong--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-body-strong--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-body-strong--font-weight))}.sn\:text-caption{font-size:var(--sn-text-caption);line-height:var(--tw-leading,var(--sn-text-caption--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-caption--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-caption--font-weight))}.sn\:text-display{font-size:var(--sn-text-display);line-height:var(--tw-leading,var(--sn-text-display--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-display--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-display--font-weight))}.sn\:text-label{font-size:var(--sn-text-label);line-height:var(--tw-leading,var(--sn-text-label--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-label--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-label--font-weight))}.sn\:text-title{font-size:var(--sn-text-title);line-height:var(--tw-leading,var(--sn-text-title--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-title--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-title--font-weight))}.sn\:whitespace-nowrap{white-space:nowrap}.sn\:text-danger{color:var(--sn-color-text-danger)}.sn\:text-default{color:var(--sn-color-text-default)}.sn\:text-inherit{color:inherit}.sn\:text-inverse{color:var(--sn-color-text-inverse)}.sn\:text-muted{color:var(--sn-color-text-muted)}.sn\:text-on-danger{color:var(--sn-color-text-on-danger)}.sn\:text-on-primary{color:var(--sn-color-text-on-primary)}.sn\:text-on-success{color:var(--sn-color-text-on-success)}.sn\:text-on-warning{color:var(--sn-color-text-on-warning)}.sn\:text-success{color:var(--sn-color-text-success)}.sn\:text-warning{color:var(--sn-color-text-warning)}.sn\:tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.sn\:underline{text-decoration-line:underline}.sn\:underline-offset-2{text-underline-offset:2px}.sn\:opacity-\(--sn-opacity-disabled\){opacity:var(--sn-opacity-disabled)}.sn\:opacity-0{opacity:0}.sn\:shadow-\[inset_-2px_0_0_var\(--sn-color-border-focus\)\]{--tw-shadow:inset -2px 0 0 var(--tw-shadow-color,var(--sn-color-border-focus));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.sn\:shadow-\[inset_0_-2px_0_var\(--sn-color-border-focus\)\]{--tw-shadow:inset 0 -2px 0 var(--tw-shadow-color,var(--sn-color-border-focus));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.sn\:shadow-\[inset_0_2px_0_var\(--sn-color-border-focus\)\]{--tw-shadow:inset 0 2px 0 var(--tw-shadow-color,var(--sn-color-border-focus));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.sn\:shadow-\[inset_2px_0_0_var\(--sn-color-border-focus\)\]{--tw-shadow:inset 2px 0 0 var(--tw-shadow-color,var(--sn-color-border-focus));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.sn\:transition-\[height\]{transition-property:height;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-\[opacity\,scale\]{transition-property:opacity,scale;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-\[opacity\,translate\]{transition-property:opacity,translate;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-\[translate\,width\]{transition-property:translate,width;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:duration-\(--sn-duration-fast\){--tw-duration:var(--sn-duration-fast);transition-duration:var(--sn-duration-fast)}.sn\:duration-\(--sn-duration-normal\){--tw-duration:var(--sn-duration-normal);transition-duration:var(--sn-duration-normal)}.sn\:ease-standard{--tw-ease:var(--sn-easing-standard);transition-timing-function:var(--sn-easing-standard)}.sn\:outline-none{--tw-outline-style:none;outline-style:none}.sn\:select-none{-webkit-user-select:none;user-select:none}.sn\:\[font\:inherit\]{font:inherit}@media (hover:hover){.sn\:group-hover\/head\:opacity-100:is(:where(.sn\:group\/head):hover *){opacity:1}}.sn\:group-aria-invalid\:text-danger:is(:where(.sn\:group)[aria-invalid=true] *){color:var(--sn-color-text-danger)}@media (hover:hover){.sn\:group-data-interactive\/row\:group-hover\/row\:bg-subtle:is(:where(.sn\:group\/row)[data-interactive] *):is(:where(.sn\:group\/row):hover *){background-color:var(--sn-color-bg-subtle)}}.sn\:group-data-invalid\:text-danger:is(:where(.sn\:group)[data-invalid] *){color:var(--sn-color-text-danger)}.sn\:group-data-panel-open\:rotate-180:is(:where(.sn\:group)[data-panel-open] *),.sn\:group-data-popup-open\:rotate-180:is(:where(.sn\:group)[data-popup-open] *){rotate:180deg}.sn\:group-data-selected\:border-transparent:is(:where(.sn\:group)[data-selected] *){border-color:#0000}.sn\:group-data-selected\:bg-primary:is(:where(.sn\:group)[data-selected] *){background-color:var(--sn-color-bg-primary)}.sn\:group-data-selected\/row\:bg-selected:is(:where(.sn\:group\/row)[data-selected] *){background-color:var(--sn-color-bg-selected)}.sn\:placeholder\:text-muted::placeholder{color:var(--sn-color-text-muted)}.sn\:focus-within\:opacity-100:focus-within{opacity:1}@media (hover:hover){.sn\:hover\:bg-subtle:hover{background-color:var(--sn-color-bg-subtle)}.sn\:hover\:text-default:hover{color:var(--sn-color-text-default)}.sn\:hover\:text-muted:hover{color:var(--sn-color-text-muted)}.sn\:hover\:not-disabled\:bg-subtle:hover:not(:disabled){background-color:var(--sn-color-bg-subtle)}.sn\:hover\:not-data-disabled\:border-strong:hover:not([data-disabled]){border-color:var(--sn-color-border-strong)}.sn\:hover\:not-data-disabled\:bg-primary-hover:hover:not([data-disabled]){background-color:var(--sn-color-bg-primary-hover)}.sn\:hover\:not-data-disabled\:bg-subtle:hover:not([data-disabled]){background-color:var(--sn-color-bg-subtle)}.sn\:hover\:not-data-disabled\:text-default:hover:not([data-disabled]){color:var(--sn-color-text-default)}}.sn\:focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.sn\:focus-visible\:-outline-offset-2:focus-visible{outline-offset:calc(2px * -1)}.sn\:focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.sn\:focus-visible\:outline-focus:focus-visible{outline-color:var(--sn-color-border-focus)}.sn\:disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.sn\:disabled\:opacity-\(--sn-opacity-disabled\):disabled{opacity:var(--sn-opacity-disabled)}.sn\:aria-invalid\:border-danger[aria-invalid=true]{border-color:var(--sn-color-border-danger)}@media (hover:hover){.sn\:hover\:aria-invalid\:border-danger:hover[aria-invalid=true]{border-color:var(--sn-color-border-danger)}}.sn\:aria-invalid\:focus-visible\:outline-danger[aria-invalid=true]:focus-visible{outline-color:var(--sn-color-border-danger)}.sn\:data-active\:text-default[data-active]{color:var(--sn-color-text-default)}.sn\:data-checked\:translate-x-3[data-checked]{--tw-translate-x:calc(var(--sn-space-1) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:data-checked\:border-transparent[data-checked]{border-color:#0000}.sn\:data-checked\:bg-\(--sn-color-text-on-primary\)[data-checked]{background-color:var(--sn-color-text-on-primary)}.sn\:data-checked\:bg-primary[data-checked]{background-color:var(--sn-color-bg-primary)}.sn\:data-disabled\:cursor-not-allowed[data-disabled]{cursor:not-allowed}.sn\:data-disabled\:opacity-\(--sn-opacity-disabled\)[data-disabled]{opacity:var(--sn-opacity-disabled)}.sn\:data-disabled\:not-data-loading\:cursor-not-allowed[data-disabled]:not([data-loading]){cursor:not-allowed}.sn\:data-disabled\:not-data-loading\:opacity-\(--sn-opacity-disabled\)[data-disabled]:not([data-loading]){opacity:var(--sn-opacity-disabled)}.sn\:data-dragging\:border-focus[data-dragging]{border-color:var(--sn-color-border-focus)}.sn\:data-dragging\:bg-subtle[data-dragging]{background-color:var(--sn-color-bg-subtle)}.sn\:data-dragging\:bg-surface[data-dragging]{background-color:var(--sn-color-bg-surface)}.sn\:data-dragging\:outline[data-dragging],.sn\:data-dragging\:outline-1[data-dragging]{outline-style:var(--tw-outline-style);outline-width:1px}.sn\:data-dragging\:-outline-offset-1[data-dragging]{outline-offset:calc(1px * -1)}.sn\:data-dragging\:outline-default[data-dragging]{outline-color:var(--sn-color-border-default)}.sn\:data-ending-style\:h-0[data-ending-style]{height:0}.sn\:data-ending-style\:translate-y-2[data-ending-style]{--tw-translate-y:calc(var(--sn-space-1) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:data-ending-style\:scale-98[data-ending-style]{--tw-scale-x:98%;--tw-scale-y:98%;--tw-scale-z:98%;scale:var(--tw-scale-x) var(--tw-scale-y)}.sn\:data-ending-style\:opacity-0[data-ending-style]{opacity:0}.sn\:data-highlighted\:bg-subtle[data-highlighted]{background-color:var(--sn-color-bg-subtle)}.sn\:data-indeterminate\:border-transparent[data-indeterminate]{border-color:#0000}.sn\:data-indeterminate\:bg-primary[data-indeterminate]{background-color:var(--sn-color-bg-primary)}@media (hover:hover){.sn\:data-interactive\:hover\:bg-subtle[data-interactive]:hover{background-color:var(--sn-color-bg-subtle)}}.sn\:data-invalid\:border-danger[data-invalid]{border-color:var(--sn-color-border-danger)}.sn\:data-invalid\:focus-visible\:outline-danger[data-invalid]:focus-visible{outline-color:var(--sn-color-border-danger)}.sn\:data-limited\:hidden[data-limited]{display:none}.sn\:data-loading\:cursor-progress[data-loading]{cursor:progress}.sn\:data-placeholder\:text-muted[data-placeholder]{color:var(--sn-color-text-muted)}.sn\:data-popup-open\:border-focus[data-popup-open]{border-color:var(--sn-color-border-focus)}.sn\:data-pressed\:border-default[data-pressed]{border-color:var(--sn-color-border-default)}.sn\:data-pressed\:bg-surface[data-pressed]{background-color:var(--sn-color-bg-surface)}.sn\:data-pressed\:text-default[data-pressed]{color:var(--sn-color-text-default)}.sn\:data-selected\:bg-selected[data-selected]{background-color:var(--sn-color-bg-selected)}.sn\:data-starting-style\:h-0[data-starting-style]{height:0}.sn\:data-starting-style\:translate-y-2[data-starting-style]{--tw-translate-y:calc(var(--sn-space-1) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:data-starting-style\:scale-98[data-starting-style]{--tw-scale-x:98%;--tw-scale-y:98%;--tw-scale-z:98%;scale:var(--tw-scale-x) var(--tw-scale-y)}.sn\:data-starting-style\:opacity-0[data-starting-style]{opacity:0}@media (prefers-reduced-motion:reduce){.sn\:motion-reduce\:animate-none{animation:none}.sn\:motion-reduce\:transition-none{transition-property:none}}@media not all and (min-width:40rem){.sn\:max-sm\:flex-col-reverse{flex-direction:column-reverse}.sn\:max-sm\:text-\[16px\]{font-size:16px}:is(.sn\:max-sm\:\*\:h-10>*){height:calc(var(--sn-space-1) * 10)}:is(.sn\:max-sm\:\*\:w-full>*){width:100%}}@media (min-width:40rem){.sn\:sm\:flex{display:flex}.sn\:sm\:hidden{display:none}.sn\:sm\:inline{display:inline}}@media (pointer:coarse){.sn\:pointer-coarse\:hidden{display:none}.sn\:pointer-coarse\:inline{display:inline}}.sn\:\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.sn\:\[\&_svg\]\:size-3 svg{width:calc(var(--sn-space-1) * 3);height:calc(var(--sn-space-1) * 3)}.sn\:\[\&_svg\]\:size-4 svg{width:calc(var(--sn-space-1) * 4);height:calc(var(--sn-space-1) * 4)}.sn\:\[\&_svg\]\:size-5 svg{width:calc(var(--sn-space-1) * 5);height:calc(var(--sn-space-1) * 5)}.sn\:\[\&_svg\]\:size-6 svg{width:calc(var(--sn-space-1) * 6);height:calc(var(--sn-space-1) * 6)}.sn\:\[\&_svg\]\:shrink-0 svg{flex-shrink:0}}:root{--sn-space-0:0px;--sn-space-1:4px;--sn-space-2:8px;--sn-space-3:12px;--sn-space-4:16px;--sn-space-5:20px;--sn-space-6:24px;--sn-space-7:28px;--sn-space-8:32px;--sn-space-10:40px;--sn-space-12:48px;--sn-space-16:64px;--sn-radius-sm:2px;--sn-radius-md:4px;--sn-radius-lg:6px;--sn-radius-full:9999px;--sn-stroke-default:1px;--sn-stroke-focus:2px;--sn-duration-fast:.12s;--sn-duration-normal:.2s;--sn-easing-standard:cubic-bezier(.2, 0, 0, 1);--sn-duration-loop:1s;--sn-opacity-disabled:.45;--sn-font-sans:"IBM Plex Sans KR", ui-sans-serif, system-ui, sans-serif;--sn-font-mono:"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--sn-text-display:32px;--sn-text-display--line-height:40px;--sn-text-display--font-weight:600;--sn-text-display--letter-spacing:-.02em;--sn-text-title:20px;--sn-text-title--line-height:28px;--sn-text-title--font-weight:600;--sn-text-title--letter-spacing:-.01em;--sn-text-body:14px;--sn-text-body--line-height:20px;--sn-text-body--font-weight:400;--sn-text-body--letter-spacing:0em;--sn-text-body-strong:14px;--sn-text-body-strong--line-height:20px;--sn-text-body-strong--font-weight:500;--sn-text-body-strong--letter-spacing:0em;--sn-text-label:13px;--sn-text-label--line-height:16px;--sn-text-label--font-weight:500;--sn-text-label--letter-spacing:0em;--sn-text-caption:12px;--sn-text-caption--line-height:16px;--sn-text-caption--font-weight:400;--sn-text-caption--letter-spacing:0em;--sn-text-mono:14px;--sn-text-mono--line-height:20px;--sn-text-mono--font-weight:400;--sn-text-mono--letter-spacing:0em;--sn-text-mono-caption:12px;--sn-text-mono-caption--line-height:16px;--sn-text-mono-caption--font-weight:400;--sn-text-mono-caption--letter-spacing:0em}:root,[data-theme=light]{color-scheme:light;--sn-color-bg-canvas:#f4f6f8;--sn-color-bg-surface:#fff;--sn-color-bg-subtle:#e6eaee;--sn-color-bg-primary:#2340d0;--sn-color-bg-primary-hover:#1b33ae;--sn-color-bg-danger:#c8312b;--sn-color-bg-overlay:#0e121680;--sn-color-text-default:#0e1216;--sn-color-text-muted:#5b6672;--sn-color-text-on-primary:#fff;--sn-color-text-on-danger:#fff;--sn-color-text-danger:#c8312b;--sn-color-border-default:#d5dbe1;--sn-color-border-strong:#8d99a6;--sn-color-border-focus:#2340d0;--sn-color-border-danger:#c8312b;--sn-color-bg-inverse:#0e1216;--sn-color-text-inverse:#fff;--sn-color-bg-success:#16804f;--sn-color-text-on-success:#fff;--sn-color-bg-success-subtle:#eaf7f0;--sn-color-text-success:#0f6640;--sn-color-bg-warning:#e3a43a;--sn-color-text-on-warning:#0e1216;--sn-color-bg-warning-subtle:#fcf5e6;--sn-color-text-warning:#85520a;--sn-color-bg-danger-subtle:#fcedec;--sn-color-border-success:#16804f;--sn-color-border-warning:#a86c12;--sn-color-bg-selected:#eef1ff}[data-theme=dark]{color-scheme:dark;--sn-color-bg-canvas:#0e1216;--sn-color-bg-surface:#161b21;--sn-color-bg-subtle:#1e252d;--sn-color-bg-primary:#6e86ff;--sn-color-bg-primary-hover:#8c9fff;--sn-color-bg-danger:#f06a5f;--sn-color-bg-overlay:#0e121680;--sn-color-text-default:#e6eaee;--sn-color-text-muted:#8d99a6;--sn-color-text-on-primary:#0e1216;--sn-color-text-on-danger:#0e1216;--sn-color-text-danger:#f06a5f;--sn-color-border-default:#2a323b;--sn-color-border-strong:#5b6672;--sn-color-border-focus:#6e86ff;--sn-color-border-danger:#f06a5f;--sn-color-bg-inverse:#e6eaee;--sn-color-text-inverse:#0e1216;--sn-color-bg-success:#3db882;--sn-color-text-on-success:#0e1216;--sn-color-bg-success-subtle:#0b2218;--sn-color-text-success:#6bd1a0;--sn-color-bg-warning:#e3a43a;--sn-color-text-on-warning:#0e1216;--sn-color-bg-warning-subtle:#24190a;--sn-color-text-warning:#eec46a;--sn-color-bg-danger-subtle:#2b1311;--sn-color-border-success:#3db882;--sn-color-border-warning:#e3a43a;--sn-color-bg-selected:#18214d}@keyframes sn-pulse{50%{opacity:.5}}@keyframes sn-spin{to{transform:rotate(360deg)}}}
|
package/dist/tailwind.css
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xonic0506/sonic-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Figma-first personal design system (Base UI + Tailwind v4, prebuilt CSS)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -46,6 +46,11 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@base-ui/react": "^1.9.0",
|
|
49
|
+
"@dnd-kit/core": "^6.3.1",
|
|
50
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
51
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
52
|
+
"@tanstack/react-table": "^9.3.1",
|
|
53
|
+
"@tanstack/react-virtual": "^3.14.22",
|
|
49
54
|
"class-variance-authority": "^0.7.1",
|
|
50
55
|
"lucide-react": "^1.59.0",
|
|
51
56
|
"tailwind-merge": "^3.7.0"
|