@xonic0506/sonic-ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +154 -4
- package/README.md +87 -40
- package/dist/components/accordion.d.ts +30 -0
- package/dist/components/accordion.js +44 -0
- package/dist/components/file-upload.d.ts +57 -0
- package/dist/components/file-upload.js +238 -0
- package/dist/components/link.d.ts +9 -0
- package/dist/components/link.js +30 -0
- package/dist/components/table.d.ts +24 -0
- package/dist/components/table.js +46 -0
- package/dist/components/toggle-group.d.ts +36 -0
- package/dist/components/toggle-group.js +44 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -1
- package/dist/styles.css +2 -2
- package/package.json +6 -1
package/DESIGN.md
CHANGED
|
@@ -156,6 +156,7 @@ import { Plus } from 'lucide-react';
|
|
|
156
156
|
| 위치 | 값 |
|
|
157
157
|
|---|---|
|
|
158
158
|
| 페이지 콘텐츠 | 최대 너비 960, 가운데 정렬. 좌우 padding `space/4`(960 안쪽), 위아래 padding `space/10` |
|
|
159
|
+
| 사이드 내비가 있는 화면 | 헤더와 사이드 내비(너비 240)는 화면 전체를 쓰고, 960 규칙은 본문 영역에만 적용 |
|
|
159
160
|
| 섹션 사이 | `space/6` |
|
|
160
161
|
| Card 안쪽 padding / Header·Body·Footer 사이 | `space/5` / `space/4` |
|
|
161
162
|
| 폼 필드 사이 | `space/4` |
|
|
@@ -211,7 +212,7 @@ hover, focus는 **컴포넌트 variant로 만들지 않아요.** React prop이
|
|
|
211
212
|
| 상태 | Figma | 코드 |
|
|
212
213
|
|---|---|---|
|
|
213
214
|
| hover | Core 문서 "Interaction states" 매트릭스에 인스턴스 오버라이드로 그림 | `:hover` (disabled일 때는 적용 안 함) |
|
|
214
|
-
| focus | 같은 매트릭스. 2px `border/focus` 링, 간격 2px. invalid Input은 `border/danger` 링 | `:focus-visible` → `outline 2px`, `outline-offset 2px` (Tab
|
|
215
|
+
| focus | 같은 매트릭스. 2px `border/focus` 링, 간격 2px. invalid Input은 `border/danger` 링 | `:focus-visible` → `outline 2px`, `outline-offset 2px` (Tab, ToggleGroup 항목만 안쪽 −2px). 포커스 링을 그리는 요소에는 `outline-none`을 같이 쓰지 않아요(Tailwind v4에서 링이 사라져요) |
|
|
215
216
|
| press | 그리지 않음 | 구현 안 함 (v0.1) |
|
|
216
217
|
|
|
217
218
|
상태별 토큰은 §10 컴포넌트 스펙 표가 기준이에요. 화면 디자인에서는 hover, focus를 따로 그리지 않아요. 컴포넌트가 자동으로 처리해요.
|
|
@@ -220,7 +221,7 @@ hover, focus는 **컴포넌트 variant로 만들지 않아요.** React prop이
|
|
|
220
221
|
|
|
221
222
|
| 계층 | 위치 | 기준 | 예 |
|
|
222
223
|
|---|---|---|---|
|
|
223
|
-
| **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, Field, Skeleton, EmptyState, ErrorState |
|
|
224
|
+
| **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 |
|
|
224
225
|
| **Pattern** | sonic-ui `src/patterns/` | Core 조합이고 도메인을 모르지만, **두 프로젝트 이상에서 같은 형태로 복사됨** | (아직 없음) |
|
|
225
226
|
| **Domain** | 각 프로젝트 | 도메인 타입, API, 비즈니스 로직 중 하나라도 앎 | `UserProfileCard`, `ApiKeyList` |
|
|
226
227
|
|
|
@@ -406,7 +407,7 @@ const retryRef = useRef<HTMLButtonElement>(null);
|
|
|
406
407
|
| `description` | 설명 문구 (선택) |
|
|
407
408
|
| `error` | 에러 문구. `invalid`일 때만 보이고, 그때는 **설명 자리를 대신해요** |
|
|
408
409
|
| `invalid`, `disabled` | boolean. 안의 Input에 자동으로 전달돼요 (Base UI Field) |
|
|
409
|
-
| `children` | 입력 컨트롤 (
|
|
410
|
+
| `children` | 입력 컨트롤 (`Input`, `Textarea`, `Select`, `FileDropzone`) |
|
|
410
411
|
|
|
411
412
|
간격: 라벨 ↔ 입력 ↔ 설명·에러 `space/2`. 라벨 `label` + `text/default`(invalid여도 그대로), 설명 `caption` + `text/muted`, 에러 `caption` + `text/danger`. **Field 안의 Input에는 `invalid`를 따로 주지 않아요.** 입력 컨트롤과 설명 또는 에러는 `aria-describedby`로 연결돼요.
|
|
412
413
|
|
|
@@ -616,6 +617,150 @@ toast.add({ title: 'API 키를 만들었어요', description: '키는 지금 한
|
|
|
616
617
|
|
|
617
618
|
라벨 `label` + `text/default`, 오른쪽에 백분율 `mono-caption` + `text/muted`, 막대와 사이 `space/2`. 트랙 높이 4(`space/1`) `bg/subtle` `radius/full`, 채움 `bg/primary`이 `duration/normal`로 늘어나요. 끝이 정해진 작업(업로드, 내보내기)에만 써요. 끝을 알 수 없는 기다림은 Button `loading`이나 Skeleton을 써요.
|
|
618
619
|
|
|
620
|
+
### Link
|
|
621
|
+
|
|
622
|
+
```tsx
|
|
623
|
+
<Link href="/docs">DESIGN.md 보기</Link>
|
|
624
|
+
<Link href="https://base-ui.com" external>Base UI 문서</Link>
|
|
625
|
+
<Link render={<NextLink href="/docs" />}>문서</Link> // Next.js Link와 함께
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
| prop | 값 |
|
|
629
|
+
|---|---|
|
|
630
|
+
| `href` | 이동할 주소 |
|
|
631
|
+
| `external` | 새 탭으로 열고(`rel="noopener noreferrer"`) 오른쪽에 `external-link` 아이콘을 붙여요 |
|
|
632
|
+
| `render` | 다른 링크 컴포넌트로 렌더링 (예: Next.js `Link`) |
|
|
633
|
+
|
|
634
|
+
다른 페이지나 문서로 **이동**할 때 써요. 동작을 실행하면 Button을 써요. 글자 크기는 부모를 따르고, 색은 `text/default` + 밑줄(offset 2px)이에요. 강조는 한 곳에만 쓰는 원칙(§1)에 따라 링크에 코발트를 쓰지 않아요. hover는 `text/muted`, 포커스 링은 공통 규칙(2px, offset 2px)이에요. 외부 링크 아이콘은 16, 글자와 간격 `space/1`이에요.
|
|
635
|
+
|
|
636
|
+
### Table
|
|
637
|
+
|
|
638
|
+
```tsx
|
|
639
|
+
<Table>
|
|
640
|
+
<TableHeader>
|
|
641
|
+
<TableRow><TableHead>이름</TableHead><TableHead align="end">요청 수</TableHead></TableRow>
|
|
642
|
+
</TableHeader>
|
|
643
|
+
<TableBody>
|
|
644
|
+
<TableRow><TableCell>production</TableCell><TableCell align="end">12,480</TableCell></TableRow>
|
|
645
|
+
</TableBody>
|
|
646
|
+
</Table>
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
| prop | 값 |
|
|
650
|
+
|---|---|
|
|
651
|
+
| `TableHead`, `TableCell`의 `align` | `start` · `end` (숫자 열은 `end`) |
|
|
652
|
+
|
|
653
|
+
여러 항목의 같은 속성을 비교할 때 써요. 행 높이 40(`space/10`), 좌우 padding `space/3`, 행 아래 1px `border/default`. 머리글 `label` + `text/muted`, 본문 `body` + `text/default`, `align="end"`인 본문 칸은 숫자라서 `mono`예요. 줄무늬나 hover 바탕은 쓰지 않아요. 표가 부모보다 넓으면 가로로 스크롤해요.
|
|
654
|
+
|
|
655
|
+
### ToggleGroup
|
|
656
|
+
|
|
657
|
+
```tsx
|
|
658
|
+
<ToggleGroup value={view} onValueChange={setView} aria-label="보기 방식"> // 단일: string
|
|
659
|
+
<ToggleGroupItem value="list">목록</ToggleGroupItem>
|
|
660
|
+
<ToggleGroupItem value="card">카드</ToggleGroupItem>
|
|
661
|
+
</ToggleGroup>
|
|
662
|
+
<ToggleGroup multiple value={filters} onValueChange={setFilters}>…</ToggleGroup> // 다중: string[]
|
|
663
|
+
<ToggleGroupItem value="dark" iconOnly aria-label="다크 테마"><Moon /></ToggleGroupItem> // 아이콘만이면 iconOnly + aria-label 필수
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
| prop | 값 |
|
|
667
|
+
|---|---|
|
|
668
|
+
| `multiple` | 여러 개를 함께 누를 수 있음. 켜면 `value`·`onValueChange`가 배열이에요(타입으로 강제) |
|
|
669
|
+
| `value`, `defaultValue`, `onValueChange` | 단일은 `string`, 다중은 `string[]`. 단일은 항상 하나가 눌려 있어요(눌린 항목을 다시 눌러도 꺼지지 않음) |
|
|
670
|
+
| `disabled` | 그룹 전체 또는 항목 하나 |
|
|
671
|
+
| `ToggleGroupItem`의 `iconOnly` | 아이콘만 있는 24×24 항목. `aria-label`이 필수예요(타입으로 강제) |
|
|
672
|
+
|
|
673
|
+
같은 대상을 **보는 방식**을 바꿀 때 써요(목록/카드, 라이트/다크). 누르면 바로 적용돼요. 폼에서 값을 고를 때는 RadioGroup이나 Select를 써요. 바탕 `bg/subtle` + 안쪽 `space/1` + `radius/md`(전체 높이 32). 항목 높이 24(`space/6`), 좌우 `space/2`, `label`, `radius/sm`. 눌린 항목 `bg/surface` + 1px `border/default` + `text/default`, 나머지 `text/muted`(hover `text/default`). 아이콘만 있는 항목은 24×24, 아이콘 16이에요. 화살표 키로 항목 사이를 이동해요. 항목끼리 붙어 있어서 포커스 링은 Tab처럼 **안쪽**(offset −2px)에 그려요.
|
|
674
|
+
|
|
675
|
+
### Accordion
|
|
676
|
+
|
|
677
|
+
```tsx
|
|
678
|
+
<Accordion defaultValue={['limit']}>
|
|
679
|
+
<AccordionItem value="limit">
|
|
680
|
+
<AccordionTrigger>키는 몇 개까지 만들 수 있나요?</AccordionTrigger>
|
|
681
|
+
<AccordionPanel>프로젝트마다 10개까지 만들 수 있어요.</AccordionPanel>
|
|
682
|
+
</AccordionItem>
|
|
683
|
+
</Accordion>
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
| prop | 값 |
|
|
687
|
+
|---|---|
|
|
688
|
+
| `multiple` | 여러 항목을 함께 열 수 있음 (기본 `false`) |
|
|
689
|
+
| `value`, `defaultValue`, `onValueChange` | 열린 항목들의 `value` 배열 |
|
|
690
|
+
| `AccordionItem`의 `disabled` | 항목 하나를 비활성화 |
|
|
691
|
+
|
|
692
|
+
긴 내용을 제목만 먼저 보여 주고 필요할 때 펼칠 때 써요(자주 묻는 질문, 문서 사이드바 그룹). 꼭 읽어야 하는 내용은 접어 두지 않아요. 항목 아래 1px `border/default`. 트리거는 위아래 `space/3` + `body-strong`, 오른쪽 `chevron-down` 16(`text/muted`)이 열리면 180° 돌아요. 패널은 `body` + `text/muted`, 아래 `space/4`이고, 높이가 `duration/normal`로 변해요. 닫힌 내용도 브라우저 페이지 찾기로 찾아 열 수 있어요.
|
|
693
|
+
|
|
694
|
+
### FileDropzone / FileList
|
|
695
|
+
|
|
696
|
+
```tsx
|
|
697
|
+
const [items, setItems] = useState<FileItemData[]>(serverFiles.map(toItem)); // 서버 파일도 같은 모양으로
|
|
698
|
+
|
|
699
|
+
<Field label="첨부 파일" description="PDF, 10MB 이하, 5개까지" error="파일을 1개 이상 올려 주세요." invalid={submitted && !items.length}>
|
|
700
|
+
<FileDropzone
|
|
701
|
+
multiple accept=".pdf" maxSize={10_000_000} maxFiles={5 - items.length}
|
|
702
|
+
onFilesChange={(accepted, rejected) => { addItems(accepted); upload(accepted); }} // 업로드는 프로젝트가 해요
|
|
703
|
+
/>
|
|
704
|
+
</Field>
|
|
705
|
+
<FileList items={items} onRemove={remove} onRetry={retry} />
|
|
706
|
+
<FileList layout="grid" items={images} onRemove={remove} />
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
컴포넌트는 **고르기·검사·표시만** 해요. 실제 업로드(어느 API로, 어떤 인증으로), 진행률 계산, 재시도는 API를 아는 프로젝트가 하고, 그 결과를 `items`로 넘겨요(§7). 두 번째 프로젝트에서 같은 업로드 코드를 복사하게 되면 `useFileUpload` Pattern으로 올려요.
|
|
710
|
+
|
|
711
|
+
**FileDropzone**
|
|
712
|
+
|
|
713
|
+
| prop | 값 |
|
|
714
|
+
|---|---|
|
|
715
|
+
| `onFilesChange` | `(accepted: File[], rejected: FileRejection[]) => void` (필수) |
|
|
716
|
+
| `accept` | `.pdf`, `image/*`, `image/png` 같은 목록 (쉼표로 구분) |
|
|
717
|
+
| `multiple`, `maxFiles` | 여러 개 고르기와 최대 개수. `multiple`이 없으면 한 개만 받아요 |
|
|
718
|
+
| `maxSize` | 파일 하나의 최대 크기(바이트) |
|
|
719
|
+
| `variant` | `file` · `image` (아이콘과 기본 문구) |
|
|
720
|
+
| `title`, `description` | 영역 안 문구. 기본은 "파일을 끌어다 놓거나 눌러서 고르세요". Field 안에서는 조건을 Field `description`에 쓰고 영역 안 `description`은 비워요 |
|
|
721
|
+
| `invalid`, `disabled` | boolean |
|
|
722
|
+
|
|
723
|
+
- **다른 폼 항목처럼 Field로 감싸요.** Field의 라벨이 버튼 이름이 되고, 설명·에러·`invalid`·`disabled`가 자동으로 연결돼요(Base UI `Field.Control`). Field 없이 쓰면 영역 안 제목이 버튼 이름이 돼요.
|
|
724
|
+
- 에러는 두 종류예요. 고르는 순간 조건에 맞지 않은 파일은 **영역 안**에 바로 알려 주고, "1개 이상 올려 주세요"처럼 제출할 때 확인하는 규칙은 Field `error`로 **아래**에 보여 줘요.
|
|
725
|
+
- 영역 전체가 버튼이라 눌러도, Enter·Space를 눌러도 파일 선택 창이 열려요. 파일을 끌어다 놓아도 돼요.
|
|
726
|
+
- 조건(형식 `type`, 크기 `size`, 개수 `count`)에 맞지 않는 파일은 `rejected`로 넘기고, 영역 안에 고치는 방법을 기본 문구로 보여 줘요(예: "계약서.pdf: 10MB보다 큰 파일은 올릴 수 없어요"). 문구는 `rejected[].message`로 바꿀 수 있어요.
|
|
727
|
+
- 점선 1px `border/strong` + `bg/surface` + `radius/lg`, 위아래 `space/6`, 좌우 `space/4`, 요소 사이 `space/2`. 아이콘 24(`upload` · `image`, `text/muted`), 제목 `body-strong`, 조건 `caption` + `text/muted`. hover `bg/subtle`, 끌어다 놓는 중 `border/focus` + `bg/subtle`, 거부되면 `border/danger` + 문구 `text/danger`.
|
|
728
|
+
|
|
729
|
+
**FileList**
|
|
730
|
+
|
|
731
|
+
| prop | 값 |
|
|
732
|
+
|---|---|
|
|
733
|
+
| `items` | `FileItemData[]` (아래) |
|
|
734
|
+
| `layout` | `list` (일반 파일) · `grid` (이미지 썸네일) |
|
|
735
|
+
| `onRemove`, `onRetry` | `(id) => void`. 넘기면 삭제·다시 시도 버튼이 생겨요 |
|
|
736
|
+
|
|
737
|
+
```ts
|
|
738
|
+
interface FileItemData {
|
|
739
|
+
id: string;
|
|
740
|
+
name: string;
|
|
741
|
+
size?: number; // 바이트, "2.4 MB"로 표시
|
|
742
|
+
type?: string; // MIME, 이미지 여부 판단
|
|
743
|
+
status: 'uploading' | 'done' | 'error';
|
|
744
|
+
progress?: number; // 0~100, uploading일 때 Progress
|
|
745
|
+
error?: string; // error일 때 문구 (고치는 방법 포함)
|
|
746
|
+
url?: string; // done 파일을 열 주소 → 이름이 Link
|
|
747
|
+
previewUrl?: string; // 썸네일 주소
|
|
748
|
+
file?: File; // 방금 고른 로컬 파일
|
|
749
|
+
}
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
미리보기는 `previewUrl` 문자열 하나로 받아요.
|
|
753
|
+
|
|
754
|
+
| 출처 | `previewUrl` | 비고 |
|
|
755
|
+
|---|---|---|
|
|
756
|
+
| 서버 이미지 | `https://…/thumb.jpg` | 서버가 썸네일 주소를 따로 주는 게 가장 가벼워요 |
|
|
757
|
+
| 서버가 base64로 줌 | `data:image/png;base64,…` | 그대로 표시돼요. 응답이 커지니 썸네일 크기만 권장 |
|
|
758
|
+
| 방금 고른 파일 | 넣지 않고 `file`만 넘김 | 컴포넌트가 blob URL을 만들고, 목록에서 빠지면 해제해요 |
|
|
759
|
+
| 인증이 필요한 파일 | 프로젝트가 받아 만든 blob URL | `<img>`는 인증 헤더를 붙일 수 없어요 |
|
|
760
|
+
|
|
761
|
+
- `list`: `bg/surface` + 1px `border/default` + `radius/md`, padding·간격 `space/3`, 항목 사이 `space/2`. 아이콘 칸 32(`bg/subtle`, `file` 16), 이름 `body`(완료이고 `url`이 있으면 Link), 크기·진행률 `mono-caption` + `text/muted`. 업로드 중에는 라벨 없는 Progress, 에러면 `border/danger` + `caption` + `text/danger`. 버튼은 ghost `icon-sm`(`refresh-cw` 다시 시도, `x` 삭제).
|
|
762
|
+
- `grid`: 썸네일 96×96(`bg/subtle`, 1px `border/default`, `radius/md`, 안쪽 `space/1`), 항목 사이 `space/3`. 오른쪽 위 secondary `icon-sm` 버튼, 아래 Progress, 아래 이름 `caption` 한 줄 말줄임. 에러면 `border/danger` + `circle-alert`.
|
|
763
|
+
|
|
619
764
|
## 11. Figma ↔ 코드 매핑 (Code Connect 대체)
|
|
620
765
|
|
|
621
766
|
Sonic 팀은 Figma Pro 요금제라 Code Connect(Organization 이상)를 쓸 수 없어요. 아래 표로 대신합니다. Figma 컴포넌트를 코드로 옮길 때는 이 표의 컴포넌트를 그대로 써요.
|
|
@@ -647,6 +792,11 @@ Sonic 팀은 Figma Pro 요금제라 Code Connect(Organization 이상)를 쓸 수
|
|
|
647
792
|
| Toast | `57:3549` | `useToast().add({ tone, title, description })` (`showDescription` → `description`을 넘길지), 앱에는 `<ToastProvider>` | `src/components/toast.tsx` |
|
|
648
793
|
| Badge | `57:3498` | `<Badge tone variant>` (`children` → 자식 문구) | `src/components/badge.tsx` |
|
|
649
794
|
| Progress | `58:5093` | `<Progress value max label>` (`value` variant는 채움 미리보기, `value text`는 코드가 자동 표시) | `src/components/progress.tsx` |
|
|
650
|
-
|
|
|
795
|
+
| Link | `65:3733` | `<Link href external>` (`children` → 자식 문구) | `src/components/link.tsx` |
|
|
796
|
+
| Table Cell · Table | `65:3742` · `65:3743` | `Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell` (`type=header` → `TableHead`, `align` → 같은 prop) | `src/components/table.tsx` |
|
|
797
|
+
| Toggle Item · Toggle Group | `66:3760` · `66:3761` | `<ToggleGroup multiple>` + `<ToggleGroupItem value disabled>` (`pressed` → 현재 값, `content=icon`이면 `aria-label`) | `src/components/toggle-group.tsx` |
|
|
798
|
+
| Accordion Item · Accordion | `66:3792` · `66:3793` | `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionPanel` (`open` → 현재 값, `title`/`content` → 자식) | `src/components/accordion.tsx` |
|
|
799
|
+
| 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` |
|
|
800
|
+
| Icon (35개) | Foundations `Source icons` | `lucide-react`의 같은 이름 컴포넌트 (`Icon / chevron-down` → `<ChevronDown />`) | 쓰는 쪽에서 설치 |
|
|
651
801
|
|
|
652
802
|
Figma 속성 → props 변환 규칙: variant 속성은 같은 이름의 prop으로, 텍스트 속성 `children`은 자식으로, boolean `header`/`footer`는 해당 하위 컴포넌트를 넣을지 말지로 옮겨요. Button의 `iconLeft`/`iconRight`(+ `leftIcon`/`rightIcon` 교체)와 Button Icon의 `icon`은 prop이 아니라 **자식 순서**예요: `<Button><Plus />추가하기<ChevronDown /></Button>`.
|
package/README.md
CHANGED
|
@@ -1,65 +1,95 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="./docs/assets/sonic-mascot.webp" alt="Sonic 마스코트" width="240" />
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- AI 작업 규칙: [AI-RULES.md](./AI-RULES.md)
|
|
7
|
-
- Figma: [Sonic Design System](https://www.figma.com/design/XifnZsjqJWJqFmAmrM6p3u)
|
|
5
|
+
# Sonic UI
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/@xonic0506/sonic-ui)  [](LICENSE)
|
|
8
|
+
|
|
9
|
+
Figma를 원본으로 하는 개인 디자인 시스템이에요.
|
|
10
|
+
|
|
11
|
+
[Figma](https://www.figma.com/design/XifnZsjqJWJqFmAmrM6p3u) | [디자인 규칙 (DESIGN.md)](./DESIGN.md) | [AI 작업 규칙 (AI-RULES.md)](./AI-RULES.md)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
Sonic UI는 모든 사이드 프로젝트에서 같은 디자인을 쓰기 위해 만들었어요. 디자인 룰을 Figma와 DESIGN.md에 정해 두면, AI가 그 룰로 Figma 디자인을 만들고 이 패키지의 컴포넌트로 코드를 짜요.
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
Figma (원본) → DESIGN.md (룰) → @xonic0506/sonic-ui (코드) → 실제 프로젝트
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 특징
|
|
22
|
+
|
|
23
|
+
- Figma Variables가 그대로 CSS 변수가 돼요. `color/bg/primary`는 `--sn-color-bg-primary`이고, light/dark 테마를 함께 가져요.
|
|
24
|
+
- CSS를 미리 빌드해서 배포해요. 쓰는 쪽 프로젝트는 Tailwind가 있어도 되고 없어도 돼요.
|
|
25
|
+
- 모든 클래스에 `sn:` 접두사를 붙이고 `@layer sonic` 안에 넣어서, 쓰는 쪽 스타일과 부딪히지 않아요.
|
|
26
|
+
- [Base UI](https://base-ui.com) 위에 만들어서 키보드 조작, 포커스 관리, 화면 낭독기 지원이 기본으로 들어 있어요.
|
|
27
|
+
- 모든 파일에 `"use client"`가 붙어 있어서 Next.js 서버 컴포넌트에서도 바로 import할 수 있어요.
|
|
28
|
+
- 쓰는 쪽 AI가 읽을 규칙 문서(DESIGN.md, AI-RULES.md)와 디자인 lint 규칙을 함께 배포해요.
|
|
29
|
+
|
|
30
|
+
## 컴포넌트
|
|
31
|
+
|
|
32
|
+
| 분류 | 컴포넌트 |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| 기본 | Button, Input, Textarea, Text, Card, Avatar, Separator |
|
|
35
|
+
| 폼 | Field, Checkbox, CheckboxGroup, RadioGroup, Switch, Select (단일·다중) |
|
|
36
|
+
| 오버레이 | Dialog, AlertDialog, Menu, Popover, Tooltip |
|
|
37
|
+
| 탐색 | Tabs |
|
|
38
|
+
| 피드백·상태 | Toast, Badge, Progress, Skeleton, EmptyState, ErrorState |
|
|
39
|
+
|
|
40
|
+
컴포넌트별 props와 스펙은 [DESIGN.md §10](./DESIGN.md#10-컴포넌트-스펙-core)에 있어요.
|
|
41
|
+
|
|
42
|
+
## 시작하기
|
|
43
|
+
|
|
44
|
+
### 설치
|
|
10
45
|
|
|
11
46
|
```bash
|
|
12
47
|
npm i @xonic0506/sonic-ui
|
|
13
48
|
```
|
|
14
49
|
|
|
15
|
-
`react`, `react-dom` 18.2 이상(19 포함)이
|
|
50
|
+
`react`, `react-dom` 18.2 이상(19 포함)이 필요해요.
|
|
16
51
|
|
|
17
52
|
### 글꼴
|
|
18
53
|
|
|
19
|
-
|
|
54
|
+
IBM Plex Sans(400, 500, 600)와 IBM Plex Mono(400)를 프로젝트에서 불러와요. 없으면 시스템 글꼴로 대체돼요.
|
|
20
55
|
|
|
21
56
|
```html
|
|
22
57
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
23
58
|
```
|
|
24
59
|
|
|
25
|
-
|
|
60
|
+
> [!NOTE]
|
|
61
|
+
> 글꼴은 패키지에 들어 있지 않아요. sonic-ui는 글꼴 이름 `"IBM Plex Sans"`, `"IBM Plex Mono"`를 찾기 때문에, 이름을 바꾸는 방식(예: `next/font` 기본 설정)으로 불러오면 적용되지 않아요. 위 링크나 `@fontsource/ibm-plex-sans`처럼 원래 이름을 유지하는 방식을 써요.
|
|
26
62
|
|
|
27
|
-
|
|
63
|
+
### CSS 연결
|
|
28
64
|
|
|
29
|
-
|
|
65
|
+
Tailwind v4 프로젝트(권장)에서는 이렇게 연결해요.
|
|
30
66
|
|
|
31
67
|
```css
|
|
32
68
|
/* app/globals.css */
|
|
33
69
|
@layer theme, base, sonic, components, utilities;
|
|
34
70
|
@import "tailwindcss";
|
|
35
71
|
@import "@xonic0506/sonic-ui/styles.css";
|
|
36
|
-
@import "@xonic0506/sonic-ui/tailwind.css"; /* 선택: bg-primary, text-muted, rounded-md, text-body
|
|
72
|
+
@import "@xonic0506/sonic-ui/tailwind.css"; /* 선택: bg-primary, text-muted, rounded-md, text-body 같은 클래스 */
|
|
37
73
|
```
|
|
38
74
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- preflight가 버튼 배경, 글자 크기를 지우지 않아요.
|
|
42
|
-
- 쓰는 쪽 `className="bg-red-500"`은 항상 sonic-ui 기본 스타일을 이겨요.
|
|
75
|
+
> [!IMPORTANT]
|
|
76
|
+
> layer 순서를 지켜요. `sonic`이 Tailwind preflight(`base`)보다 뒤에 있어야 버튼 배경과 글자 크기가 지워지지 않고, 쓰는 쪽 `components`/`utilities`보다 앞에 있어야 `className="bg-red-500"` 같은 덮어쓰기가 항상 이겨요.
|
|
43
77
|
|
|
44
|
-
|
|
78
|
+
Tailwind가 없는 프로젝트에서는 CSS 파일 하나만 불러와요. 토큰은 `var(--sn-color-bg-primary)`처럼 CSS 변수로 써요.
|
|
45
79
|
|
|
46
80
|
```ts
|
|
47
81
|
import '@xonic0506/sonic-ui/styles.css';
|
|
48
82
|
```
|
|
49
83
|
|
|
50
|
-
|
|
84
|
+
### 앱 루트와 테마
|
|
51
85
|
|
|
52
|
-
|
|
86
|
+
앱 루트에 `isolation: isolate`를 지정해요. 앱의 z-index가 `body` 아래에 그려지는 Dialog, Toast를 가리지 않게 해 줘요.
|
|
53
87
|
|
|
54
88
|
```tsx
|
|
55
89
|
<div style={{ isolation: 'isolate' }}>{/* 앱 */}</div> // Tailwind: className="isolate"
|
|
56
90
|
```
|
|
57
91
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
### 테마
|
|
61
|
-
|
|
62
|
-
`<html data-theme="light">` 또는 `data-theme="dark"`로 바꿔요. 속성이 없으면 light예요. Portal로 `body`에 그려지는 Dialog에도 같은 테마가 적용돼요.
|
|
92
|
+
테마는 `<html data-theme="light">` 또는 `data-theme="dark"`로 바꿔요. 속성이 없으면 light이고, `body` 아래에 그려지는 팝업에도 같은 테마가 적용돼요.
|
|
63
93
|
|
|
64
94
|
## 사용
|
|
65
95
|
|
|
@@ -86,22 +116,26 @@ import { Button, Card, CardBody, CardHeader, CardTitle, Dialog, DialogClose, Dia
|
|
|
86
116
|
</Dialog>
|
|
87
117
|
```
|
|
88
118
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
Toast를 쓰려면 앱 최상단(Next.js는 `app/layout.tsx`)을 `<ToastProvider>`로 한 번 감싸고, 클라이언트 컴포넌트에서 `useToast().add({ title, tone })`로 띄워요.
|
|
119
|
+
하위 컴포넌트는 `Card.Header`가 아니라 `CardHeader`처럼 이름 있는 export로 써요. 서버 컴포넌트에서도 깨지지 않게 하기 위해서예요.
|
|
92
120
|
|
|
93
|
-
|
|
121
|
+
Toast는 앱 최상단(Next.js는 `app/layout.tsx`)을 `<ToastProvider>`로 한 번 감싸고, 클라이언트 컴포넌트에서 띄워요.
|
|
94
122
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@node_modules/@xonic0506/sonic-ui/DESIGN.md
|
|
99
|
-
@node_modules/@xonic0506/sonic-ui/AI-RULES.md
|
|
123
|
+
```tsx
|
|
124
|
+
const toast = useToast();
|
|
125
|
+
toast.add({ title: 'API 키를 만들었어요', tone: 'success' });
|
|
100
126
|
```
|
|
101
127
|
|
|
102
|
-
##
|
|
128
|
+
## AI와 함께 쓰기
|
|
129
|
+
|
|
130
|
+
> [!TIP]
|
|
131
|
+
> Claude Code를 쓰는 프로젝트라면 CLAUDE.md에 아래 두 줄을 추가해요. AI가 디자인 규칙을 읽고 sonic-ui 컴포넌트로 화면을 만들어요.
|
|
132
|
+
>
|
|
133
|
+
> ```md
|
|
134
|
+
> @node_modules/@xonic0506/sonic-ui/DESIGN.md
|
|
135
|
+
> @node_modules/@xonic0506/sonic-ui/AI-RULES.md
|
|
136
|
+
> ```
|
|
103
137
|
|
|
104
|
-
임의의 값 클래스, Tailwind 기본 팔레트, hex 색을 막는 ESLint flat config
|
|
138
|
+
임의의 값 클래스, Tailwind 기본 팔레트, hex 색을 막는 ESLint flat config도 제공해요. 켜는 것은 쓰는 쪽 설정에서 해요.
|
|
105
139
|
|
|
106
140
|
```js
|
|
107
141
|
// eslint.config.js
|
|
@@ -111,15 +145,28 @@ export default [/* … */, sonicDesign];
|
|
|
111
145
|
|
|
112
146
|
## 지원 브라우저
|
|
113
147
|
|
|
114
|
-
|
|
148
|
+
Chrome 111+, Edge 111+, Safari 16.4+, Firefox 128+. CSS `@layer`와 `@property`를 쓰기 때문에 Tailwind v4와 기준이 같아요.
|
|
115
149
|
|
|
116
150
|
## 개발
|
|
117
151
|
|
|
118
152
|
```bash
|
|
119
|
-
npm run build
|
|
120
|
-
npm test
|
|
153
|
+
npm run build # 토큰 생성 → JS(Vite) → 타입 → CSS(@layer sonic)
|
|
154
|
+
npm test # Vitest
|
|
121
155
|
npm run typecheck
|
|
122
|
-
npm run
|
|
156
|
+
npm run check:contrast # 글자와 바탕 대비 4.5:1 검사
|
|
157
|
+
npm run playground # Figma와 비교하는 화면 (build 먼저)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
.
|
|
162
|
+
├── src/components/ # Core 컴포넌트
|
|
163
|
+
├── src/patterns/ # Pattern 승격 기준 (두 번째 프로젝트에서 복사하게 될 때)
|
|
164
|
+
├── scripts/ # Figma 토큰 추출, CSS 빌드, 대비 검사
|
|
165
|
+
├── lint/ # 디자인 lint 규칙
|
|
166
|
+
├── playground/ # Figma와 비교하는 화면
|
|
167
|
+
├── fixtures/ # 쓰는 쪽 예제 (vite, Next.js)
|
|
168
|
+
└── DESIGN.md # 디자인 규칙 (패키지에 포함)
|
|
123
169
|
```
|
|
124
170
|
|
|
125
|
-
|
|
171
|
+
> [!NOTE]
|
|
172
|
+
> 디자인을 바꿀 때는 Figma → DESIGN.md → 토큰 추출 → 코드 순서를 지켜요. 자세한 운영 규칙은 [CLAUDE.md](./CLAUDE.md)에 있어요.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface AccordionProps {
|
|
3
|
+
/** 여러 항목을 함께 열 수 있음 (기본 false) */
|
|
4
|
+
multiple?: boolean;
|
|
5
|
+
/** 열린 항목들의 value */
|
|
6
|
+
value?: string[];
|
|
7
|
+
defaultValue?: string[];
|
|
8
|
+
onValueChange?: (value: string[]) => void;
|
|
9
|
+
className?: string;
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/** 닫힌 내용도 브라우저 페이지 찾기로 찾아 열 수 있어요 (hiddenUntilFound) */
|
|
13
|
+
export declare function Accordion({ onValueChange, className, ...props }: AccordionProps): React.JSX.Element;
|
|
14
|
+
export interface AccordionItemProps {
|
|
15
|
+
value?: string;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
className?: string;
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
}
|
|
20
|
+
export declare function AccordionItem({ className, ...props }: AccordionItemProps): React.JSX.Element;
|
|
21
|
+
export interface AccordionTriggerProps {
|
|
22
|
+
className?: string;
|
|
23
|
+
children?: React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
export declare function AccordionTrigger({ className, children }: AccordionTriggerProps): React.JSX.Element;
|
|
26
|
+
export interface AccordionPanelProps {
|
|
27
|
+
className?: string;
|
|
28
|
+
children?: React.ReactNode;
|
|
29
|
+
}
|
|
30
|
+
export declare function AccordionPanel({ className, children }: AccordionPanelProps): React.JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn as e } from "../lib/cn.js";
|
|
3
|
+
import "react";
|
|
4
|
+
import { ChevronDown as t } from "lucide-react";
|
|
5
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
6
|
+
import { Accordion as i } from "@base-ui/react/accordion";
|
|
7
|
+
//#region src/components/accordion.tsx
|
|
8
|
+
function a({ onValueChange: t, className: r, ...a }) {
|
|
9
|
+
return /* @__PURE__ */ n(i.Root, {
|
|
10
|
+
...a,
|
|
11
|
+
hiddenUntilFound: !0,
|
|
12
|
+
onValueChange: t && ((e) => t(e)),
|
|
13
|
+
className: e("sn:flex sn:flex-col sn:font-sans", r)
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function o({ className: t, ...r }) {
|
|
17
|
+
return /* @__PURE__ */ n(i.Item, {
|
|
18
|
+
...r,
|
|
19
|
+
className: e("sn:border-b sn:border-default sn:data-disabled:opacity-(--sn-opacity-disabled)", t)
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function s({ className: a, children: o }) {
|
|
23
|
+
return /* @__PURE__ */ n(i.Header, {
|
|
24
|
+
className: "sn:m-0",
|
|
25
|
+
children: /* @__PURE__ */ r(i.Trigger, {
|
|
26
|
+
className: e("sn:group sn:flex sn:w-full sn:items-center sn:gap-2 sn:rounded-sm sn:border-0 sn:bg-transparent sn:px-0 sn:py-3 sn:text-left sn:font-sans sn:text-body-strong sn:text-default sn:cursor-pointer sn:focus-visible:outline-2 sn:focus-visible:outline-offset-2 sn:focus-visible:outline-focus sn:data-disabled:cursor-not-allowed", a),
|
|
27
|
+
children: [/* @__PURE__ */ n("span", {
|
|
28
|
+
className: "sn:min-w-0 sn:flex-1",
|
|
29
|
+
children: o
|
|
30
|
+
}), /* @__PURE__ */ n(t, { className: "sn:size-4 sn:shrink-0 sn:text-muted sn:transition-transform sn:duration-(--sn-duration-normal) sn:ease-standard sn:group-data-panel-open:rotate-180 sn:motion-reduce:transition-none" })]
|
|
31
|
+
})
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function c({ className: t, children: r }) {
|
|
35
|
+
return /* @__PURE__ */ n(i.Panel, {
|
|
36
|
+
className: "sn:h-(--accordion-panel-height) sn:overflow-hidden sn:transition-[height] sn:duration-(--sn-duration-normal) sn:ease-standard sn:data-starting-style:h-0 sn:data-ending-style:h-0 sn:motion-reduce:transition-none",
|
|
37
|
+
children: /* @__PURE__ */ n("div", {
|
|
38
|
+
className: e("sn:pb-4 sn:font-sans sn:text-body sn:text-muted", t),
|
|
39
|
+
children: r
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { a as Accordion, o as AccordionItem, c as AccordionPanel, s as AccordionTrigger };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/** 바이트를 "2.4 MB"처럼 표시 */
|
|
3
|
+
export declare function formatBytes(bytes: number): string;
|
|
4
|
+
export interface FileRejection {
|
|
5
|
+
file: File;
|
|
6
|
+
reason: 'type' | 'size' | 'count';
|
|
7
|
+
/** 기본 문구. 바꾸려면 이 값 대신 직접 보여 주세요 */
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
export interface FileDropzoneProps {
|
|
11
|
+
onFilesChange: (accepted: File[], rejected: FileRejection[]) => void;
|
|
12
|
+
/** 예: ".pdf", "image/*", "image/png,image/jpeg" */
|
|
13
|
+
accept?: string;
|
|
14
|
+
multiple?: boolean;
|
|
15
|
+
/** 남은 개수. multiple일 때만 */
|
|
16
|
+
maxFiles?: number;
|
|
17
|
+
/** 파일 하나의 최대 크기(바이트) */
|
|
18
|
+
maxSize?: number;
|
|
19
|
+
variant?: 'file' | 'image';
|
|
20
|
+
/** 영역 안 안내 문구. Field 밖에서 쓰면 버튼 이름이 돼요 */
|
|
21
|
+
title?: React.ReactNode;
|
|
22
|
+
description?: React.ReactNode;
|
|
23
|
+
invalid?: boolean;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
name?: string;
|
|
26
|
+
id?: string;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Input처럼 Field로 감싸면 라벨·설명·에러·invalid·disabled가 연결돼요 */
|
|
30
|
+
export declare function FileDropzone({ onFilesChange, accept, multiple, maxFiles, maxSize, variant, title, description, invalid, disabled, name, id, className }: FileDropzoneProps): React.JSX.Element;
|
|
31
|
+
export interface FileItemData {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
/** 바이트 */
|
|
35
|
+
size?: number;
|
|
36
|
+
/** MIME. 이미지 여부 판단 */
|
|
37
|
+
type?: string;
|
|
38
|
+
status: 'uploading' | 'done' | 'error';
|
|
39
|
+
/** 0~100 */
|
|
40
|
+
progress?: number;
|
|
41
|
+
/** 무엇이 실패했고 어떻게 고치는지 */
|
|
42
|
+
error?: string;
|
|
43
|
+
/** 완료된 파일을 열 주소 */
|
|
44
|
+
url?: string;
|
|
45
|
+
/** 썸네일: 서버 주소, data URL, blob URL 모두 가능 */
|
|
46
|
+
previewUrl?: string;
|
|
47
|
+
/** 방금 고른 로컬 파일. previewUrl이 없으면 blob URL을 만들어 미리보기에 써요 */
|
|
48
|
+
file?: File;
|
|
49
|
+
}
|
|
50
|
+
export interface FileListProps {
|
|
51
|
+
items: FileItemData[];
|
|
52
|
+
layout?: 'list' | 'grid';
|
|
53
|
+
onRemove?: (id: string) => void;
|
|
54
|
+
onRetry?: (id: string) => void;
|
|
55
|
+
className?: string;
|
|
56
|
+
}
|
|
57
|
+
export declare function FileList({ items, layout, onRemove, onRetry, className }: FileListProps): React.JSX.Element;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn as e } from "../lib/cn.js";
|
|
3
|
+
import { Button as t } from "./button.js";
|
|
4
|
+
import { Progress as n } from "./progress.js";
|
|
5
|
+
import { Link as r } from "./link.js";
|
|
6
|
+
import * as i from "react";
|
|
7
|
+
import { CircleAlert as a, File as o, Image as s, RefreshCw as c, Upload as l, X as u } from "lucide-react";
|
|
8
|
+
import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
9
|
+
import { Field as m } from "@base-ui/react/field";
|
|
10
|
+
//#region src/components/file-upload.tsx
|
|
11
|
+
function h(e) {
|
|
12
|
+
if (e < 1024) return `${e} B`;
|
|
13
|
+
let t = [
|
|
14
|
+
"KB",
|
|
15
|
+
"MB",
|
|
16
|
+
"GB"
|
|
17
|
+
], n = e / 1024, r = 0;
|
|
18
|
+
for (; n >= 1024 && r < t.length - 1;) n /= 1024, r++;
|
|
19
|
+
return `${Number(n.toFixed(1))} ${t[r]}`;
|
|
20
|
+
}
|
|
21
|
+
function g(e, t) {
|
|
22
|
+
if (!t) return !0;
|
|
23
|
+
let n = e.name.toLowerCase(), r = e.type.toLowerCase();
|
|
24
|
+
return t.split(",").map((e) => e.trim().toLowerCase()).filter(Boolean).some((e) => e.startsWith(".") ? n.endsWith(e) : e.endsWith("/*") ? r.startsWith(e.slice(0, -1)) : r === e);
|
|
25
|
+
}
|
|
26
|
+
function _(e, { accept: t, maxSize: n, maxFiles: r, multiple: i }) {
|
|
27
|
+
let a = i ? r ?? Infinity : 1, o = [], s = [];
|
|
28
|
+
for (let r of e) g(r, t) ? n !== void 0 && r.size > n ? s.push({
|
|
29
|
+
file: r,
|
|
30
|
+
reason: "size",
|
|
31
|
+
message: `${r.name}: ${h(n)}보다 큰 파일은 올릴 수 없어요.`
|
|
32
|
+
}) : o.length >= a ? s.push({
|
|
33
|
+
file: r,
|
|
34
|
+
reason: "count",
|
|
35
|
+
message: a <= 0 ? "더 올릴 수 없어요. 파일을 삭제한 뒤 다시 골라 주세요." : `${a}개까지 올릴 수 있어요.`
|
|
36
|
+
}) : o.push(r) : s.push({
|
|
37
|
+
file: r,
|
|
38
|
+
reason: "type",
|
|
39
|
+
message: `${r.name}: 올릴 수 없는 형식이에요. ${t} 파일만 올려 주세요.`
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
accepted: o,
|
|
43
|
+
rejected: s
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function v({ onFilesChange: t, accept: n, multiple: r, maxFiles: a, maxSize: o, variant: c = "file", title: u, description: h, invalid: g, disabled: v, name: y, id: b, className: x }) {
|
|
47
|
+
let S = i.useRef(null), [C, w] = i.useState(!1), [T, E] = i.useState(), D = i.useId(), O = (e) => {
|
|
48
|
+
if (!e.length) return;
|
|
49
|
+
let { accepted: i, rejected: s } = _(e, {
|
|
50
|
+
accept: n,
|
|
51
|
+
maxSize: o,
|
|
52
|
+
maxFiles: a,
|
|
53
|
+
multiple: r
|
|
54
|
+
});
|
|
55
|
+
E(s.length ? s[0].message + (s.length > 1 ? ` (외 ${s.length - 1}개)` : "") : void 0), t(i, s);
|
|
56
|
+
}, k = g || !!T, A = c === "image" ? s : l;
|
|
57
|
+
return /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ p(m.Control, {
|
|
58
|
+
render: /* @__PURE__ */ f("button", { type: "button" }),
|
|
59
|
+
id: b,
|
|
60
|
+
disabled: v,
|
|
61
|
+
"aria-describedby": D,
|
|
62
|
+
"data-dragging": C ? "" : void 0,
|
|
63
|
+
"aria-invalid": g || void 0,
|
|
64
|
+
"data-invalid": k ? "" : void 0,
|
|
65
|
+
onClick: () => S.current?.click(),
|
|
66
|
+
onDragEnter: (e) => {
|
|
67
|
+
e.preventDefault(), v || w(!0);
|
|
68
|
+
},
|
|
69
|
+
onDragOver: (e) => e.preventDefault(),
|
|
70
|
+
onDragLeave: (e) => {
|
|
71
|
+
e.currentTarget.contains(e.relatedTarget) || w(!1);
|
|
72
|
+
},
|
|
73
|
+
onDrop: (e) => {
|
|
74
|
+
e.preventDefault(), w(!1), v || O([...e.dataTransfer.files]);
|
|
75
|
+
},
|
|
76
|
+
className: e("sn:group sn:box-border sn:flex sn:w-full sn:flex-col sn:items-center sn:gap-2 sn:rounded-lg sn:border sn:border-dashed sn:border-strong sn:bg-surface sn:px-4 sn:py-6 sn:font-sans sn:text-center sn:cursor-pointer sn:transition-colors sn:duration-(--sn-duration-fast) sn:ease-standard sn:hover:not-disabled:bg-subtle sn:data-dragging:border-focus sn:data-dragging:bg-subtle sn:data-invalid:border-danger sn:aria-invalid:border-danger sn:focus-visible:outline-2 sn:focus-visible:outline-offset-2 sn:focus-visible:outline-focus sn:disabled:cursor-not-allowed sn:disabled:opacity-(--sn-opacity-disabled) sn:motion-reduce:transition-none", x),
|
|
77
|
+
children: [
|
|
78
|
+
/* @__PURE__ */ f(A, { className: "sn:size-6 sn:text-muted sn:group-data-invalid:text-danger sn:group-aria-invalid:text-danger" }),
|
|
79
|
+
/* @__PURE__ */ f("span", {
|
|
80
|
+
className: "sn:text-body-strong sn:text-default",
|
|
81
|
+
children: u ?? (c === "image" ? "이미지를 끌어다 놓거나 눌러서 고르세요" : "파일을 끌어다 놓거나 눌러서 고르세요")
|
|
82
|
+
}),
|
|
83
|
+
/* @__PURE__ */ f("span", {
|
|
84
|
+
id: D,
|
|
85
|
+
"aria-live": "polite",
|
|
86
|
+
className: e("sn:text-caption", T ? "sn:text-danger" : "sn:text-muted"),
|
|
87
|
+
children: T ?? h
|
|
88
|
+
})
|
|
89
|
+
]
|
|
90
|
+
}), /* @__PURE__ */ f("input", {
|
|
91
|
+
ref: S,
|
|
92
|
+
type: "file",
|
|
93
|
+
hidden: !0,
|
|
94
|
+
name: y,
|
|
95
|
+
accept: n,
|
|
96
|
+
multiple: r,
|
|
97
|
+
disabled: v,
|
|
98
|
+
onChange: (e) => {
|
|
99
|
+
O([...e.target.files ?? []]), e.target.value = "";
|
|
100
|
+
}
|
|
101
|
+
})] });
|
|
102
|
+
}
|
|
103
|
+
function y(e) {
|
|
104
|
+
let [t, n] = i.useState(), r = (e.type ?? e.file?.type ?? "").startsWith("image/");
|
|
105
|
+
return i.useEffect(() => {
|
|
106
|
+
if (e.previewUrl || !e.file || !r) return;
|
|
107
|
+
let t = URL.createObjectURL(e.file);
|
|
108
|
+
return n(t), () => URL.revokeObjectURL(t);
|
|
109
|
+
}, [
|
|
110
|
+
e.previewUrl,
|
|
111
|
+
e.file,
|
|
112
|
+
r
|
|
113
|
+
]), e.previewUrl ?? t;
|
|
114
|
+
}
|
|
115
|
+
function b({ items: t, layout: n = "list", onRemove: r, onRetry: i, className: a }) {
|
|
116
|
+
let o = n === "grid" ? S : x;
|
|
117
|
+
return /* @__PURE__ */ f("ul", {
|
|
118
|
+
className: e("sn:m-0 sn:list-none sn:p-0 sn:font-sans", n === "grid" ? "sn:flex sn:flex-wrap sn:gap-3" : "sn:flex sn:flex-col sn:gap-2", a),
|
|
119
|
+
children: t.map((e) => /* @__PURE__ */ f(o, {
|
|
120
|
+
item: e,
|
|
121
|
+
onRemove: r,
|
|
122
|
+
onRetry: i
|
|
123
|
+
}, e.id))
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function x({ item: t, onRemove: i, onRetry: a }) {
|
|
127
|
+
let s = t.status === "error";
|
|
128
|
+
return /* @__PURE__ */ p("li", {
|
|
129
|
+
className: e("sn:box-border sn:flex sn:items-center sn:gap-3 sn:rounded-md sn:border sn:bg-surface sn:p-3", s ? "sn:border-danger" : "sn:border-default"),
|
|
130
|
+
children: [
|
|
131
|
+
/* @__PURE__ */ f("span", {
|
|
132
|
+
className: "sn:flex sn:size-8 sn:shrink-0 sn:items-center sn:justify-center sn:rounded-md sn:bg-subtle",
|
|
133
|
+
children: /* @__PURE__ */ f(o, { className: e("sn:size-4", s ? "sn:text-danger" : "sn:text-muted") })
|
|
134
|
+
}),
|
|
135
|
+
/* @__PURE__ */ p("div", {
|
|
136
|
+
className: "sn:flex sn:min-w-0 sn:flex-1 sn:flex-col sn:gap-1",
|
|
137
|
+
children: [
|
|
138
|
+
/* @__PURE__ */ p("div", {
|
|
139
|
+
className: "sn:flex sn:items-center sn:justify-between sn:gap-2",
|
|
140
|
+
children: [t.status === "done" && t.url ? /* @__PURE__ */ f(r, {
|
|
141
|
+
href: t.url,
|
|
142
|
+
target: "_blank",
|
|
143
|
+
rel: "noopener noreferrer",
|
|
144
|
+
className: "sn:min-w-0 sn:truncate sn:text-body",
|
|
145
|
+
children: t.name
|
|
146
|
+
}) : /* @__PURE__ */ f("span", {
|
|
147
|
+
className: "sn:min-w-0 sn:truncate sn:text-body sn:text-default",
|
|
148
|
+
children: t.name
|
|
149
|
+
}), /* @__PURE__ */ f("span", {
|
|
150
|
+
className: "sn:shrink-0 sn:font-mono sn:text-caption sn:text-muted",
|
|
151
|
+
children: t.status === "uploading" ? `${Math.round(t.progress ?? 0)}%` : t.size === void 0 ? null : h(t.size)
|
|
152
|
+
})]
|
|
153
|
+
}),
|
|
154
|
+
t.status === "uploading" && /* @__PURE__ */ f(n, {
|
|
155
|
+
value: t.progress ?? 0,
|
|
156
|
+
"aria-label": `${t.name} 올리는 중`
|
|
157
|
+
}),
|
|
158
|
+
s && t.error && /* @__PURE__ */ f("span", {
|
|
159
|
+
className: "sn:text-caption sn:text-danger",
|
|
160
|
+
children: t.error
|
|
161
|
+
})
|
|
162
|
+
]
|
|
163
|
+
}),
|
|
164
|
+
/* @__PURE__ */ f(C, {
|
|
165
|
+
item: t,
|
|
166
|
+
onRemove: i,
|
|
167
|
+
onRetry: a,
|
|
168
|
+
variant: "ghost"
|
|
169
|
+
})
|
|
170
|
+
]
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function S({ item: t, onRemove: r, onRetry: i }) {
|
|
174
|
+
let o = t.status === "error", c = y(t);
|
|
175
|
+
return /* @__PURE__ */ p("li", {
|
|
176
|
+
className: "sn:flex sn:w-24 sn:flex-col sn:gap-1",
|
|
177
|
+
children: [
|
|
178
|
+
/* @__PURE__ */ p("div", {
|
|
179
|
+
className: e("sn:relative sn:box-border sn:flex sn:size-24 sn:flex-col sn:justify-between sn:overflow-hidden sn:rounded-md sn:border sn:bg-subtle sn:p-1", o ? "sn:border-danger" : "sn:border-default"),
|
|
180
|
+
children: [
|
|
181
|
+
c && !o ? /* @__PURE__ */ f("img", {
|
|
182
|
+
src: c,
|
|
183
|
+
alt: t.name,
|
|
184
|
+
className: "sn:absolute sn:inset-0 sn:size-full sn:object-cover"
|
|
185
|
+
}) : /* @__PURE__ */ f("span", {
|
|
186
|
+
className: "sn:absolute sn:inset-0 sn:flex sn:items-center sn:justify-center",
|
|
187
|
+
children: o ? /* @__PURE__ */ f(a, { className: "sn:size-6 sn:text-danger" }) : /* @__PURE__ */ f(s, { className: "sn:size-6 sn:text-muted" })
|
|
188
|
+
}),
|
|
189
|
+
/* @__PURE__ */ f("div", {
|
|
190
|
+
className: "sn:relative sn:flex sn:justify-end",
|
|
191
|
+
children: /* @__PURE__ */ f(C, {
|
|
192
|
+
item: t,
|
|
193
|
+
onRemove: r,
|
|
194
|
+
onRetry: i,
|
|
195
|
+
variant: "secondary"
|
|
196
|
+
})
|
|
197
|
+
}),
|
|
198
|
+
t.status === "uploading" && /* @__PURE__ */ f("div", {
|
|
199
|
+
className: "sn:relative",
|
|
200
|
+
children: /* @__PURE__ */ f(n, {
|
|
201
|
+
value: t.progress ?? 0,
|
|
202
|
+
"aria-label": `${t.name} 올리는 중`
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
]
|
|
206
|
+
}),
|
|
207
|
+
/* @__PURE__ */ f("span", {
|
|
208
|
+
className: e("sn:truncate sn:text-caption", o ? "sn:text-danger" : "sn:text-muted"),
|
|
209
|
+
title: t.name,
|
|
210
|
+
children: t.name
|
|
211
|
+
}),
|
|
212
|
+
o && t.error && /* @__PURE__ */ f("span", {
|
|
213
|
+
className: "sn:sr-only",
|
|
214
|
+
children: t.error
|
|
215
|
+
})
|
|
216
|
+
]
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function C({ item: e, onRemove: n, onRetry: r, variant: i }) {
|
|
220
|
+
return /* @__PURE__ */ p("div", {
|
|
221
|
+
className: "sn:flex sn:shrink-0",
|
|
222
|
+
children: [e.status === "error" && r && /* @__PURE__ */ f(t, {
|
|
223
|
+
size: "icon-sm",
|
|
224
|
+
variant: i,
|
|
225
|
+
"aria-label": `${e.name} 다시 시도`,
|
|
226
|
+
onClick: () => r(e.id),
|
|
227
|
+
children: /* @__PURE__ */ f(c, {})
|
|
228
|
+
}), n && /* @__PURE__ */ f(t, {
|
|
229
|
+
size: "icon-sm",
|
|
230
|
+
variant: i,
|
|
231
|
+
"aria-label": `${e.name} 삭제`,
|
|
232
|
+
onClick: () => n(e.id),
|
|
233
|
+
children: /* @__PURE__ */ f(u, {})
|
|
234
|
+
})]
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
export { v as FileDropzone, b as FileList, h as formatBytes };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'className'> {
|
|
3
|
+
/** 새 탭으로 열고 external-link 아이콘을 붙여요 */
|
|
4
|
+
external?: boolean;
|
|
5
|
+
className?: string;
|
|
6
|
+
/** 다른 링크 컴포넌트로 렌더링 (예: render={<NextLink href="/docs" />}) */
|
|
7
|
+
render?: React.ReactElement<Record<string, unknown>>;
|
|
8
|
+
}
|
|
9
|
+
export declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn as e } from "../lib/cn.js";
|
|
3
|
+
import * as t from "react";
|
|
4
|
+
import { ExternalLink as n } from "lucide-react";
|
|
5
|
+
import { Fragment as r, jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
6
|
+
//#region src/components/link.tsx
|
|
7
|
+
var o = "sn:rounded-sm sn:text-default sn:underline sn:underline-offset-2 sn:transition-colors sn:duration-(--sn-duration-fast) sn:ease-standard sn:hover:text-muted sn:focus-visible:outline-2 sn:focus-visible:outline-offset-2 sn:focus-visible:outline-focus sn:motion-reduce:transition-none", s = t.forwardRef(function({ external: s, className: c, render: l, children: u, ...d }, f) {
|
|
8
|
+
let p = s ? /* @__PURE__ */ a(r, { children: [
|
|
9
|
+
u,
|
|
10
|
+
/* @__PURE__ */ i(n, { className: "sn:ml-1 sn:inline-block sn:size-4 sn:align-text-bottom" }),
|
|
11
|
+
/* @__PURE__ */ i("span", {
|
|
12
|
+
className: "sn:sr-only",
|
|
13
|
+
children: "(새 탭에서 열림)"
|
|
14
|
+
})
|
|
15
|
+
] }) : u, m = {
|
|
16
|
+
ref: f,
|
|
17
|
+
className: e(o, c),
|
|
18
|
+
...s && {
|
|
19
|
+
target: "_blank",
|
|
20
|
+
rel: "noopener noreferrer"
|
|
21
|
+
},
|
|
22
|
+
...d
|
|
23
|
+
};
|
|
24
|
+
return l ? t.cloneElement(l, m, p) : /* @__PURE__ */ i("a", {
|
|
25
|
+
...m,
|
|
26
|
+
children: p
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
export { s as Link };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/** 넓으면 가로로 스크롤해요 */
|
|
3
|
+
export declare const Table: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLTableElement>, "className"> & {
|
|
4
|
+
className?: string;
|
|
5
|
+
} & React.RefAttributes<HTMLTableElement>>;
|
|
6
|
+
export declare const TableHeader: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLTableSectionElement>, "className"> & {
|
|
7
|
+
className?: string;
|
|
8
|
+
} & React.RefAttributes<HTMLTableSectionElement>>;
|
|
9
|
+
export declare const TableBody: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLTableSectionElement>, "className"> & {
|
|
10
|
+
className?: string;
|
|
11
|
+
} & React.RefAttributes<HTMLTableSectionElement>>;
|
|
12
|
+
export declare const TableRow: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLTableRowElement>, "className"> & {
|
|
13
|
+
className?: string;
|
|
14
|
+
} & React.RefAttributes<HTMLTableRowElement>>;
|
|
15
|
+
export declare const TableHead: React.ForwardRefExoticComponent<Omit<React.TdHTMLAttributes<HTMLTableCellElement>, "align" | "className"> & {
|
|
16
|
+
/** 숫자 열은 end */
|
|
17
|
+
align?: 'start' | 'end';
|
|
18
|
+
className?: string;
|
|
19
|
+
} & React.RefAttributes<HTMLTableCellElement>>;
|
|
20
|
+
export declare const TableCell: React.ForwardRefExoticComponent<Omit<React.TdHTMLAttributes<HTMLTableCellElement>, "align" | "className"> & {
|
|
21
|
+
/** 숫자 열은 end */
|
|
22
|
+
align?: 'start' | 'end';
|
|
23
|
+
className?: string;
|
|
24
|
+
} & React.RefAttributes<HTMLTableCellElement>>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn as e } from "../lib/cn.js";
|
|
3
|
+
import * as t from "react";
|
|
4
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/table.tsx
|
|
6
|
+
var r = t.forwardRef(function({ className: t, ...r }, i) {
|
|
7
|
+
return /* @__PURE__ */ n("div", {
|
|
8
|
+
className: "sn:w-full sn:overflow-x-auto",
|
|
9
|
+
children: /* @__PURE__ */ n("table", {
|
|
10
|
+
ref: i,
|
|
11
|
+
className: e("sn:w-full sn:border-separate sn:border-spacing-0 sn:font-sans sn:text-body sn:text-default", t),
|
|
12
|
+
...r
|
|
13
|
+
})
|
|
14
|
+
});
|
|
15
|
+
}), i = t.forwardRef(function(e, t) {
|
|
16
|
+
return /* @__PURE__ */ n("thead", {
|
|
17
|
+
ref: t,
|
|
18
|
+
...e
|
|
19
|
+
});
|
|
20
|
+
}), a = t.forwardRef(function(e, t) {
|
|
21
|
+
return /* @__PURE__ */ n("tbody", {
|
|
22
|
+
ref: t,
|
|
23
|
+
...e
|
|
24
|
+
});
|
|
25
|
+
}), o = t.forwardRef(function({ className: e, ...t }, r) {
|
|
26
|
+
return /* @__PURE__ */ n("tr", {
|
|
27
|
+
ref: r,
|
|
28
|
+
className: e,
|
|
29
|
+
...t
|
|
30
|
+
});
|
|
31
|
+
}), s = "sn:box-border sn:h-10 sn:border-b sn:border-default sn:px-3 sn:py-0 sn:align-middle", c = t.forwardRef(function({ align: t = "start", className: r, ...i }, a) {
|
|
32
|
+
return /* @__PURE__ */ n("th", {
|
|
33
|
+
ref: a,
|
|
34
|
+
scope: "col",
|
|
35
|
+
className: e(s, "sn:text-label sn:text-muted", t === "end" ? "sn:text-right" : "sn:text-left", r),
|
|
36
|
+
...i
|
|
37
|
+
});
|
|
38
|
+
}), l = t.forwardRef(function({ align: t = "start", className: r, ...i }, a) {
|
|
39
|
+
return /* @__PURE__ */ n("td", {
|
|
40
|
+
ref: a,
|
|
41
|
+
className: e(s, t === "end" ? "sn:text-right sn:font-mono" : "sn:text-left", r),
|
|
42
|
+
...i
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
//#endregion
|
|
46
|
+
export { r as Table, a as TableBody, l as TableCell, c as TableHead, i as TableHeader, o as TableRow };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
interface ToggleGroupBaseProps {
|
|
3
|
+
disabled?: boolean;
|
|
4
|
+
className?: string;
|
|
5
|
+
'aria-label'?: string;
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
/** 단일은 항상 하나가 눌려 있어요(다시 눌러도 꺼지지 않음). multiple이면 값이 배열이에요 */
|
|
9
|
+
export type ToggleGroupProps = ToggleGroupBaseProps & ({
|
|
10
|
+
multiple?: false;
|
|
11
|
+
value?: string;
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
onValueChange?: (value: string) => void;
|
|
14
|
+
} | {
|
|
15
|
+
multiple: true;
|
|
16
|
+
value?: string[];
|
|
17
|
+
defaultValue?: string[];
|
|
18
|
+
onValueChange?: (value: string[]) => void;
|
|
19
|
+
});
|
|
20
|
+
export declare function ToggleGroup(props: ToggleGroupProps): React.JSX.Element;
|
|
21
|
+
interface ToggleGroupItemBaseProps {
|
|
22
|
+
value: string;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
className?: string;
|
|
25
|
+
children?: React.ReactNode;
|
|
26
|
+
}
|
|
27
|
+
/** 아이콘만 있는 항목은 읽을 글자가 없어서 aria-label이 필수예요 */
|
|
28
|
+
export type ToggleGroupItemProps = ToggleGroupItemBaseProps & ({
|
|
29
|
+
iconOnly?: false;
|
|
30
|
+
'aria-label'?: string;
|
|
31
|
+
} | {
|
|
32
|
+
iconOnly: true;
|
|
33
|
+
'aria-label': string;
|
|
34
|
+
});
|
|
35
|
+
export declare const ToggleGroupItem: React.ForwardRefExoticComponent<ToggleGroupItemProps & React.RefAttributes<HTMLButtonElement>>;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn as e } from "../lib/cn.js";
|
|
3
|
+
import * as t from "react";
|
|
4
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
5
|
+
import { ToggleGroup as r } from "@base-ui/react/toggle-group";
|
|
6
|
+
import { Toggle as i } from "@base-ui/react/toggle";
|
|
7
|
+
//#region src/components/toggle-group.tsx
|
|
8
|
+
var a = "sn:inline-flex sn:items-center sn:rounded-md sn:bg-subtle sn:p-1";
|
|
9
|
+
function o(i) {
|
|
10
|
+
let { disabled: o, className: s, children: c } = i, [l, u] = t.useState(i.multiple ? void 0 : i.defaultValue), d = {
|
|
11
|
+
disabled: o,
|
|
12
|
+
"aria-label": i["aria-label"],
|
|
13
|
+
className: e(a, s)
|
|
14
|
+
};
|
|
15
|
+
if (i.multiple) return /* @__PURE__ */ n(r, {
|
|
16
|
+
...d,
|
|
17
|
+
multiple: !0,
|
|
18
|
+
value: i.value,
|
|
19
|
+
defaultValue: i.defaultValue,
|
|
20
|
+
onValueChange: i.onValueChange && ((e) => i.onValueChange(e)),
|
|
21
|
+
children: c
|
|
22
|
+
});
|
|
23
|
+
let f = i.value === void 0 ? l : i.value;
|
|
24
|
+
return /* @__PURE__ */ n(r, {
|
|
25
|
+
...d,
|
|
26
|
+
value: f === void 0 ? [] : [f],
|
|
27
|
+
onValueChange: (e) => {
|
|
28
|
+
e.length && (u(e[0]), i.onValueChange?.(e[0]));
|
|
29
|
+
},
|
|
30
|
+
children: c
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
var s = "sn:box-border sn:inline-flex sn:h-6 sn:items-center sn:justify-center sn:gap-1 sn:rounded-sm sn:border sn:border-transparent sn:bg-transparent sn:px-2 sn:font-sans sn:text-label sn:text-muted sn:cursor-pointer sn:select-none sn:transition-colors sn:duration-(--sn-duration-fast) sn:ease-standard sn:hover:not-data-disabled:text-default sn:data-pressed:border-default sn:data-pressed:bg-surface sn:data-pressed:text-default sn:focus-visible:outline-2 sn:focus-visible:-outline-offset-2 sn:focus-visible:outline-focus sn:data-disabled:cursor-not-allowed sn:data-disabled:opacity-(--sn-opacity-disabled) sn:motion-reduce:transition-none sn:[&_svg]:size-4 sn:[&_svg]:shrink-0", c = t.forwardRef(function({ value: t, disabled: r, iconOnly: a, className: o, children: c, ...l }, u) {
|
|
34
|
+
return /* @__PURE__ */ n(i, {
|
|
35
|
+
ref: u,
|
|
36
|
+
value: t,
|
|
37
|
+
disabled: r,
|
|
38
|
+
"aria-label": l["aria-label"],
|
|
39
|
+
className: e(s, a && "sn:w-6 sn:px-0", o),
|
|
40
|
+
children: c
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
//#endregion
|
|
44
|
+
export { o as ToggleGroup, c as ToggleGroupItem };
|
package/dist/index.d.ts
CHANGED
|
@@ -22,3 +22,8 @@ export { Separator, type SeparatorProps } from './components/separator';
|
|
|
22
22
|
export { ToastProvider, useToast, type ToastProviderProps, type ToastOptions, type ToastTone } from './components/toast';
|
|
23
23
|
export { Badge, type BadgeProps } from './components/badge';
|
|
24
24
|
export { Progress, type ProgressProps } from './components/progress';
|
|
25
|
+
export { Link, type LinkProps } from './components/link';
|
|
26
|
+
export { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from './components/table';
|
|
27
|
+
export { ToggleGroup, ToggleGroupItem, type ToggleGroupProps, type ToggleGroupItemProps } from './components/toggle-group';
|
|
28
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionPanel, type AccordionProps, type AccordionItemProps } from './components/accordion';
|
|
29
|
+
export { FileDropzone, FileList, formatBytes, type FileDropzoneProps, type FileListProps, type FileItemData, type FileRejection } from './components/file-upload';
|
package/dist/index.js
CHANGED
|
@@ -23,4 +23,9 @@ import { Separator as Z } from "./components/separator.js";
|
|
|
23
23
|
import { ToastProvider as Q, useToast as $ } from "./components/toast.js";
|
|
24
24
|
import { Badge as ee } from "./components/badge.js";
|
|
25
25
|
import { Progress as te } from "./components/progress.js";
|
|
26
|
-
|
|
26
|
+
import { Link as ne } from "./components/link.js";
|
|
27
|
+
import { Table as re, TableBody as ie, TableCell as ae, TableHead as oe, TableHeader as se, TableRow as ce } from "./components/table.js";
|
|
28
|
+
import { ToggleGroup as le, ToggleGroupItem as ue } from "./components/toggle-group.js";
|
|
29
|
+
import { Accordion as de, AccordionItem as fe, AccordionPanel as pe, AccordionTrigger as me } from "./components/accordion.js";
|
|
30
|
+
import { FileDropzone as he, FileList as ge, formatBytes as _e } from "./components/file-upload.js";
|
|
31
|
+
export { de as Accordion, fe as AccordionItem, pe as AccordionPanel, me as AccordionTrigger, O as AlertDialog, k as AlertDialogClose, A as AlertDialogDescription, j as AlertDialogFooter, M as AlertDialogPopup, N as AlertDialogTitle, P as AlertDialogTrigger, g as Avatar, ee as Badge, e as Button, u as Card, d as CardBody, f as CardDescription, p as CardFooter, m as CardHeader, h as CardTitle, r as Checkbox, i as CheckboxGroup, x as Dialog, S as DialogClose, C as DialogDescription, w as DialogFooter, T as DialogPopup, E as DialogTitle, D as DialogTrigger, y as EmptyState, b as ErrorState, _ as Field, he as FileDropzone, ge as FileList, t as Input, ne as Link, F as Menu, I as MenuItem, L as MenuPopup, R as MenuSeparator, z as MenuTrigger, B as Popover, V as PopoverClose, H as PopoverDescription, U as PopoverPopup, W as PopoverTitle, G as PopoverTrigger, te as Progress, a as Radio, o as RadioGroup, c as Select, Z as Separator, v as Skeleton, s as Switch, q as Tab, re as Table, ie as TableBody, ae as TableCell, oe as TableHead, se as TableHeader, ce as TableRow, J as Tabs, Y as TabsList, X as TabsPanel, l as Text, n as Textarea, Q as ToastProvider, le as ToggleGroup, ue as ToggleGroupItem, K as Tooltip, _e as formatBytes, $ as useToast };
|
package/dist/styles.css
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/*! sonic-ui | 전체가 @layer sonic 안에 있어요. 쓰는 쪽: @layer theme, base, sonic, components, utilities; */
|
|
2
|
-
@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-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 theme;@layer utilities{.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-0{margin-bottom:0}.sn\:box-border{box-sizing:border-box}.sn\:block{display:block}.sn\:flex{display:flex}.sn\:hidden{display:none}.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-full{width:100%;height:100%}.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-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-full{height:100%}.sn\:h-px{height:1px}.sn\:max-h-\(--available-height\){max-height:var(--available-height)}.sn\:min-h-20{min-height:calc(var(--sn-space-1) * 20)}.sn\:w-\(--active-tab-width\){width:var(--active-tab-width)}.sn\:w-7{width:calc(var(--sn-space-1) * 7)}.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-px{width:1px}.sn\:max-w-\[calc\(100vw-2\*var\(--sn-space-4\)\)\]{max-width:calc(100vw - 2 * var(--sn-space-4))}.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\:flex-1{flex:1}.sn\:shrink-0{flex-shrink:0}.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\: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-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-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-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-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-1{padding-block:var(--sn-space-1)}.sn\:py-2{padding-block:calc(var(--sn-space-1) * 2)}.sn\:py-12{padding-block:calc(var(--sn-space-1) * 12)}.sn\:pt-1{padding-top:var(--sn-space-1)}.sn\:text-center{text-align:center}.sn\:text-left{text-align:left}.sn\:align-middle{vertical-align:middle}.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\:opacity-\(--sn-opacity-disabled\){opacity:var(--sn-opacity-disabled)}.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-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\: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\: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-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-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}}.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", 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-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 theme;@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-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-full{height:100%}.sn\:h-px{height:1px}.sn\:max-h-\(--available-height\){max-height:var(--available-height)}.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-px{width:1px}.sn\:max-w-\[calc\(100vw-2\*var\(--sn-space-4\)\)\]{max-width:calc(100vw - 2 * var(--sn-space-4))}.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\: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\: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\:pt-1{padding-top:var(--sn-space-1)}.sn\:pb-4{padding-bottom:calc(var(--sn-space-1) * 4)}.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}}.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", 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)}}}
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xonic0506/sonic-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Figma-first personal design system (Base UI + Tailwind v4, prebuilt CSS)",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/sonic0506/sonic-design-system.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/sonic0506/sonic-design-system#readme",
|
|
6
11
|
"type": "module",
|
|
7
12
|
"sideEffects": [
|
|
8
13
|
"**/*.css"
|