sellmate-design-system-react 4.2.0 → 4.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +7 -1
- package/README.md +19 -3
- package/dist/components/SButton/README.md +2 -0
- package/dist/components/SDrawer/README.md +42 -0
- package/dist/components/SDrawer/SDrawer.d.ts +39 -0
- package/dist/components/SDrawer/index.d.ts +1 -0
- package/dist/components/SGhostButton/README.md +2 -0
- package/dist/components/SGnb/gnb.config.d.ts +11 -0
- package/dist/index.cjs +215 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +215 -15
- package/dist/index.js.map +1 -1
- package/dist/lib/global-overlay.d.ts +17 -0
- package/dist/llms-full.txt +47 -1
- package/dist/llms.txt +9 -3
- package/dist/styles.css +97 -9
- package/dist/theme.css +7 -0
- package/eslint/rules/no-off-scale-spacing.mjs +114 -35
- package/package.json +3 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 모달 위에 뜨는 전역 레이어(토스트·로딩)의 표식.
|
|
3
|
+
*
|
|
4
|
+
* 이 레이어들은 모달보다 위(Z_INDEX.toast/loading > Z_INDEX.modalHost)에 뜨지만 DOM 상으로는
|
|
5
|
+
* body 직속이라, Radix Dialog 입장에서는 '모달 바깥'이다. 표식이 없으면 토스트 닫기 버튼을
|
|
6
|
+
* 누르는 것만으로 바깥 클릭으로 처리돼 persistent 모달은 흔들리고 일반 모달은 닫혀버린다.
|
|
7
|
+
*/
|
|
8
|
+
export declare const GLOBAL_OVERLAY_ATTR = "data-s-global-overlay";
|
|
9
|
+
/** 전역 레이어 루트에 펼쳐 넣는다. */
|
|
10
|
+
export declare const globalOverlayProps: {
|
|
11
|
+
"data-s-global-overlay": string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Radix 의 outside 이벤트(pointerDownOutside/focusOutside/interactOutside)가
|
|
15
|
+
* 전역 레이어에서 비롯됐는지 판별한다. Radix 는 원본 이벤트를 `detail.originalEvent` 에 담는다.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isFromGlobalOverlay(event: Event): boolean;
|
package/dist/llms-full.txt
CHANGED
|
@@ -161,6 +161,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
161
161
|
- **간격 유틸리티는 `sd-` 접두를 붙인다** — `gap-sd-8`, `p-sd-16`, `mt-sd-12`. 숫자 = px 다.
|
|
162
162
|
접두를 빼면 Tailwind 기본 스케일이 적용된다(`gap-8` = 32px). 접두는 속성 뒤, 숫자 앞에 온다.
|
|
163
163
|
- 스케일: `2 4 6 8 10 12 16 19 20 22 24 28 32 36 40 44 48 56 60 62 80`
|
|
164
|
+
- **스케일에 없는 간격이 필요하면 임의 값(`p-[64px]`)으로 우회하지 않는다.** 가까운 스케일 값으로 맞추거나, 디자인상 그 값이어야 하면 **토큰 추가를 요청한다.** 이미 있는 토큰을 정확히 써야 할 때만 `gap-[var(--토큰명)]` 으로 참조한다.
|
|
164
165
|
- 형제 요소 간격은 margin 대신 부모의 `flex`/`grid` + `gap-sd-*`으로 잡는다.
|
|
165
166
|
- 시맨틱 간격 토큰 (텍스트 덩어리·요소 사이 기본 리듬):
|
|
166
167
|
|
|
@@ -498,6 +499,8 @@ const columns: STableColumn[] = [
|
|
|
498
499
|
| 테이블 **행 내부** 인라인 액션 | `xs` |
|
|
499
500
|
| 화면·목록 액션 (툴바, `STableBar`) | `sm` |
|
|
500
501
|
| **모달·위저드 푸터** | `md` |
|
|
502
|
+
| **페이지 하단 실행 버튼** (폼 저장·상세 수정 등) | `md` |
|
|
503
|
+
| 스크롤 시 헤더로 승격된 버튼 | `sm` (최대) |
|
|
501
504
|
|
|
502
505
|
`lg` 는 현재 사용처가 없다. 페이지 골격에서 쓰지 않는다.
|
|
503
506
|
|
|
@@ -630,11 +633,12 @@ export default function AppShell({ children }: { children: React.ReactNode }) {
|
|
|
630
633
|
| `header` | 상단바 배치 | 로고 폭 | `topContent` |
|
|
631
634
|
| --- | --- | --- | --- |
|
|
632
635
|
| `"fix"` (기본) | `[런처 · 로고 … 폴드]` — 상단바가 GNB 컬럼 안에 있고 폴드가 컬럼 오른쪽 끝 | 내용 폭 | **렌더되지 않는다** (놓을 자리가 없다) |
|
|
633
|
-
| `"full"` | `[런처 · 폴드 · 로고 · topContent]` — 상단바가 화면 전폭 | **140px 고정** | 로고 오른쪽 남는 폭 전체 |
|
|
636
|
+
| `"full"` | `[런처 · 폴드 · 로고 · topContent]` — 상단바가 화면 전폭 | **140px 고정** (런처 없으면 172px) | 로고 오른쪽 남는 폭 전체 |
|
|
634
637
|
|
|
635
638
|
- `topContent` 는 상단바 로고 오른쪽 슬롯이다. 전역 검색·계정 메뉴·알림처럼 **모든 페이지에 공통인 것만** 넣는다. 페이지별 액션은 여기가 아니라 §4-2 의 `STableBar` 로 간다.
|
|
636
639
|
- 슬롯이 남는 폭을 통째로 받으므로 **정렬은 안에서 직접 잡는다** (좌측 정렬 + 우측은 `ml-auto`).
|
|
637
640
|
- `header="full"` 에서 로고 자리는 140px 로 고정된다 — 로고 내용이 바뀌어도 `topContent` 시작점이 흔들리지 않게 하기 위함이다. 로고가 그보다 넓으면 잘리므로 이 폭에 맞춰 준비한다.
|
|
641
|
+
- `onLauncherClick` 을 주지 않으면 런처가 렌더되지 않고, 그 자리(버튼 + 간격)를 로고 슬롯이 이어받아 172px 가 된다. `topContent` 시작점은 런처 유무와 관계없이 같은 자리다.
|
|
638
642
|
|
|
639
643
|
```tsx
|
|
640
644
|
<SLayout type="box" header="full">
|
|
@@ -955,6 +959,8 @@ export default function ProductDetailPage() {
|
|
|
955
959
|
|
|
956
960
|
`gap-sd-13` 처럼 스케일 밖 값은 Tailwind v4 에서 **에러 없이 조용히 무시된다**. 반대로 접두를 빠뜨린 `gap-8` 은 Tailwind 기본 스케일이라 **32px 이 적용된다**. 둘 다 "간격이 왜 이러지" 로만 보이므로 반드시 `sd-` 접두 스케일 값을 쓴다. (린트는 기본값에서 앞쪽만 잡는다 — 뒤쪽까지 강제하려면 `requirePrefix: true`)
|
|
957
961
|
|
|
962
|
+
접두 누락은 **자동 수정되지 않는다.** `p-16` 은 지금 64px 로 동작하는 코드라 `p-sd-16`(16px)으로 바꾸면 여백이 달라지기 때문이다. 린트는 "숫자를 px 로 의도했나(`p-sd-16`)" 와 "현재 값을 유지할 건가(스케일에 있으면 그 값)" 를 제안으로 내놓으니 **의도에 맞는 쪽을 골라 적용한다.**
|
|
963
|
+
|
|
958
964
|
린트가 잡지 못하는 것(§3 컴포넌트 선택, §4 페이지 골격, 버튼 배치)은 §5 체크리스트로 직접 확인한다.
|
|
959
965
|
|
|
960
966
|
|
|
@@ -1269,6 +1275,7 @@ Tailwind 유틸리티는 아래 스케일에 있는 값만 사용한다. 리터
|
|
|
1269
1275
|
|
|
1270
1276
|
- [SActionModal](../SActionModal)
|
|
1271
1277
|
- [SConfirmModal](../SConfirmModal)
|
|
1278
|
+
- [SDrawer](../SDrawer)
|
|
1272
1279
|
- [SDropdownButton](../SDropdownButton)
|
|
1273
1280
|
- [SKeyValueTable](../SKeyValueTable)
|
|
1274
1281
|
- [SLoadingModal](../SLoadingModal)
|
|
@@ -1843,6 +1850,44 @@ Tailwind 유틸리티는 아래 스케일에 있는 값만 사용한다. 리터
|
|
|
1843
1850
|
|
|
1844
1851
|
---
|
|
1845
1852
|
|
|
1853
|
+
# SDrawer
|
|
1854
|
+
|
|
1855
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
1856
|
+
|
|
1857
|
+
### SDrawer
|
|
1858
|
+
|
|
1859
|
+
#### Props
|
|
1860
|
+
|
|
1861
|
+
| Prop | Type | Default | Description |
|
|
1862
|
+
|------|------|---------|-------------|
|
|
1863
|
+
| `open?` | `boolean` | — | 표시 여부 |
|
|
1864
|
+
| `persistent?` | `boolean` | `false` | true면 backdrop·ESC로 닫히지 않고 흔들림 효과를 준다. |
|
|
1865
|
+
| `title?` | `string` | `''` | 접근성 제목 및 헤더 제목 |
|
|
1866
|
+
| `width?` | `number \| string` | `572` | Drawer 너비. 기본값은 Figma drawer 기준 572px이다. |
|
|
1867
|
+
| `footerLeft?` | `ReactNode` | — | footer 좌측 슬롯 |
|
|
1868
|
+
| `button?` | `SDrawerButton` | — | 우측 기본 액션 버튼 |
|
|
1869
|
+
| `children?` | `ReactNode` | — | |
|
|
1870
|
+
| `className?` | `string` | — | |
|
|
1871
|
+
| `style?` | `CSSProperties` | — | |
|
|
1872
|
+
|
|
1873
|
+
#### Events
|
|
1874
|
+
|
|
1875
|
+
| Event | Type | Description |
|
|
1876
|
+
|-------|------|-------------|
|
|
1877
|
+
| `onOpenChange` | `(open: boolean) => void` | 표시 상태 변경 |
|
|
1878
|
+
| `onClose` | `() => void` | 닫기(X) 버튼 클릭 |
|
|
1879
|
+
|
|
1880
|
+
## Dependencies
|
|
1881
|
+
|
|
1882
|
+
### Depends on
|
|
1883
|
+
|
|
1884
|
+
- [SButton](../SButton)
|
|
1885
|
+
- [SGhostButton](../SGhostButton)
|
|
1886
|
+
|
|
1887
|
+
### Graph
|
|
1888
|
+
|
|
1889
|
+
---
|
|
1890
|
+
|
|
1846
1891
|
# SDropdownButton
|
|
1847
1892
|
|
|
1848
1893
|
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
@@ -2158,6 +2203,7 @@ Tailwind 유틸리티는 아래 스케일에 있는 값만 사용한다. 리터
|
|
|
2158
2203
|
- [SCalendar](../SCalendar)
|
|
2159
2204
|
- [SChip](../SChip)
|
|
2160
2205
|
- [SDateRangePicker](../SDateRangePicker)
|
|
2206
|
+
- [SDrawer](../SDrawer)
|
|
2161
2207
|
- [SFilePicker](../SFilePicker)
|
|
2162
2208
|
- [SGnb](../SGnb)
|
|
2163
2209
|
- [SGuide](../SGuide)
|
package/dist/llms.txt
CHANGED
|
@@ -162,6 +162,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
162
162
|
- **간격 유틸리티는 `sd-` 접두를 붙인다** — `gap-sd-8`, `p-sd-16`, `mt-sd-12`. 숫자 = px 다.
|
|
163
163
|
접두를 빼면 Tailwind 기본 스케일이 적용된다(`gap-8` = 32px). 접두는 속성 뒤, 숫자 앞에 온다.
|
|
164
164
|
- 스케일: `2 4 6 8 10 12 16 19 20 22 24 28 32 36 40 44 48 56 60 62 80`
|
|
165
|
+
- **스케일에 없는 간격이 필요하면 임의 값(`p-[64px]`)으로 우회하지 않는다.** 가까운 스케일 값으로 맞추거나, 디자인상 그 값이어야 하면 **토큰 추가를 요청한다.** 이미 있는 토큰을 정확히 써야 할 때만 `gap-[var(--토큰명)]` 으로 참조한다.
|
|
165
166
|
- 형제 요소 간격은 margin 대신 부모의 `flex`/`grid` + `gap-sd-*`으로 잡는다.
|
|
166
167
|
- 시맨틱 간격 토큰 (텍스트 덩어리·요소 사이 기본 리듬):
|
|
167
168
|
|
|
@@ -499,6 +500,8 @@ const columns: STableColumn[] = [
|
|
|
499
500
|
| 테이블 **행 내부** 인라인 액션 | `xs` |
|
|
500
501
|
| 화면·목록 액션 (툴바, `STableBar`) | `sm` |
|
|
501
502
|
| **모달·위저드 푸터** | `md` |
|
|
503
|
+
| **페이지 하단 실행 버튼** (폼 저장·상세 수정 등) | `md` |
|
|
504
|
+
| 스크롤 시 헤더로 승격된 버튼 | `sm` (최대) |
|
|
502
505
|
|
|
503
506
|
`lg` 는 현재 사용처가 없다. 페이지 골격에서 쓰지 않는다.
|
|
504
507
|
|
|
@@ -631,11 +634,12 @@ export default function AppShell({ children }: { children: React.ReactNode }) {
|
|
|
631
634
|
| `header` | 상단바 배치 | 로고 폭 | `topContent` |
|
|
632
635
|
| --- | --- | --- | --- |
|
|
633
636
|
| `"fix"` (기본) | `[런처 · 로고 … 폴드]` — 상단바가 GNB 컬럼 안에 있고 폴드가 컬럼 오른쪽 끝 | 내용 폭 | **렌더되지 않는다** (놓을 자리가 없다) |
|
|
634
|
-
| `"full"` | `[런처 · 폴드 · 로고 · topContent]` — 상단바가 화면 전폭 | **140px 고정** | 로고 오른쪽 남는 폭 전체 |
|
|
637
|
+
| `"full"` | `[런처 · 폴드 · 로고 · topContent]` — 상단바가 화면 전폭 | **140px 고정** (런처 없으면 172px) | 로고 오른쪽 남는 폭 전체 |
|
|
635
638
|
|
|
636
639
|
- `topContent` 는 상단바 로고 오른쪽 슬롯이다. 전역 검색·계정 메뉴·알림처럼 **모든 페이지에 공통인 것만** 넣는다. 페이지별 액션은 여기가 아니라 §4-2 의 `STableBar` 로 간다.
|
|
637
640
|
- 슬롯이 남는 폭을 통째로 받으므로 **정렬은 안에서 직접 잡는다** (좌측 정렬 + 우측은 `ml-auto`).
|
|
638
641
|
- `header="full"` 에서 로고 자리는 140px 로 고정된다 — 로고 내용이 바뀌어도 `topContent` 시작점이 흔들리지 않게 하기 위함이다. 로고가 그보다 넓으면 잘리므로 이 폭에 맞춰 준비한다.
|
|
642
|
+
- `onLauncherClick` 을 주지 않으면 런처가 렌더되지 않고, 그 자리(버튼 + 간격)를 로고 슬롯이 이어받아 172px 가 된다. `topContent` 시작점은 런처 유무와 관계없이 같은 자리다.
|
|
639
643
|
|
|
640
644
|
```tsx
|
|
641
645
|
<SLayout type="box" header="full">
|
|
@@ -956,6 +960,8 @@ export default function ProductDetailPage() {
|
|
|
956
960
|
|
|
957
961
|
`gap-sd-13` 처럼 스케일 밖 값은 Tailwind v4 에서 **에러 없이 조용히 무시된다**. 반대로 접두를 빠뜨린 `gap-8` 은 Tailwind 기본 스케일이라 **32px 이 적용된다**. 둘 다 "간격이 왜 이러지" 로만 보이므로 반드시 `sd-` 접두 스케일 값을 쓴다. (린트는 기본값에서 앞쪽만 잡는다 — 뒤쪽까지 강제하려면 `requirePrefix: true`)
|
|
958
962
|
|
|
963
|
+
접두 누락은 **자동 수정되지 않는다.** `p-16` 은 지금 64px 로 동작하는 코드라 `p-sd-16`(16px)으로 바꾸면 여백이 달라지기 때문이다. 린트는 "숫자를 px 로 의도했나(`p-sd-16`)" 와 "현재 값을 유지할 건가(스케일에 있으면 그 값)" 를 제안으로 내놓으니 **의도에 맞는 쪽을 골라 적용한다.**
|
|
964
|
+
|
|
959
965
|
린트가 잡지 못하는 것(§3 컴포넌트 선택, §4 페이지 골격, 버튼 배치)은 §5 체크리스트로 직접 확인한다.
|
|
960
966
|
|
|
961
967
|
|
|
@@ -1125,7 +1131,7 @@ Tailwind 유틸리티는 아래 스케일에 있는 값만 사용한다. 리터
|
|
|
1125
1131
|
--sys-color-link-accent
|
|
1126
1132
|
--sys-color-navigation-gnb-bg-dark
|
|
1127
1133
|
|
|
1128
|
-
## 3. 컴포넌트 인덱스 (
|
|
1134
|
+
## 3. 컴포넌트 인덱스 (62종)
|
|
1129
1135
|
|
|
1130
1136
|
**Props 는 이 문서에 없다.** 쓸 컴포넌트를 정했으면 그 컴포넌트의 README 를 읽는다.
|
|
1131
1137
|
|
|
@@ -1134,4 +1140,4 @@ Tailwind 유틸리티는 아래 스케일에 있는 값만 사용한다. 리터
|
|
|
1134
1140
|
예) SButton 을 쓴다면 `dist/components/SButton/README.md`.
|
|
1135
1141
|
prop 을 추측해서 쓰지 말고 반드시 README 로 확인한다 (분류별 용도는 §1 의 "전체 컴포넌트 인덱스" 참조).
|
|
1136
1142
|
|
|
1137
|
-
SActionModal · SBadge · SBarcodeInput · SButton · SCalendar · SCallout · SCard · SCheckbox · SChip · SChipInput · SCircleProgress · SConfirmModal · SDatePicker · SDateRangePicker · SDivider · SDraggableItem · SDraggableList · SDropdownButton · SExpansionItem · SExpansionList · SField · SFilePicker · SForm · SGhostButton · SGnb · SGuide · SIcon · SInput · SKeyValueTable · SLayout · SLinearProgress · SList · SListItem · SLoadingContainer · SLoadingModal · SModal · SNumberInput · SPage · SPagination · SPopover · SPopup · SPortal · SRadio · SRadioButton · SScrollArea · SSectionHeaderCard · SSelect · SStepper · SSwitch · STable · STableBar · STabs · STag · STextLink · STextarea · STimePicker · STimeRangePicker · SToast · SToggle · STooltip · STree
|
|
1143
|
+
SActionModal · SBadge · SBarcodeInput · SButton · SCalendar · SCallout · SCard · SCheckbox · SChip · SChipInput · SCircleProgress · SConfirmModal · SDatePicker · SDateRangePicker · SDivider · SDraggableItem · SDraggableList · SDrawer · SDropdownButton · SExpansionItem · SExpansionList · SField · SFilePicker · SForm · SGhostButton · SGnb · SGuide · SIcon · SInput · SKeyValueTable · SLayout · SLinearProgress · SList · SListItem · SLoadingContainer · SLoadingModal · SModal · SNumberInput · SPage · SPagination · SPopover · SPopup · SPortal · SRadio · SRadioButton · SScrollArea · SSectionHeaderCard · SSelect · SStepper · SSwitch · STable · STableBar · STabs · STag · STextLink · STextarea · STimePicker · STimeRangePicker · SToast · SToggle · STooltip · STree
|
package/dist/styles.css
CHANGED
|
@@ -377,6 +377,7 @@
|
|
|
377
377
|
--animate-modal-in: modal-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
378
378
|
--animate-modal-out: modal-out 0.2s ease-in;
|
|
379
379
|
--animate-modal-shake: modal-shake 0.3s ease-out;
|
|
380
|
+
--animate-drawer-shake: drawer-shake 0.22s ease-out;
|
|
380
381
|
--animate-floating-in-top: floating-in-top 0.35s ease-out;
|
|
381
382
|
--animate-floating-in-bottom: floating-in-bottom 0.35s ease-out;
|
|
382
383
|
--animate-floating-in-left: floating-in-left 0.35s ease-out;
|
|
@@ -751,6 +752,9 @@
|
|
|
751
752
|
.mt-8 {
|
|
752
753
|
margin-top: calc(var(--spacing) * 8);
|
|
753
754
|
}
|
|
755
|
+
.mt-9 {
|
|
756
|
+
margin-top: calc(var(--spacing) * 9);
|
|
757
|
+
}
|
|
754
758
|
.mt-\[8px\] {
|
|
755
759
|
margin-top: 8px;
|
|
756
760
|
}
|
|
@@ -772,6 +776,9 @@
|
|
|
772
776
|
.mt-sd-32 {
|
|
773
777
|
margin-top: var(--spacing-sd-32);
|
|
774
778
|
}
|
|
779
|
+
.mt-sd-36 {
|
|
780
|
+
margin-top: var(--spacing-sd-36);
|
|
781
|
+
}
|
|
775
782
|
.-mr-px {
|
|
776
783
|
margin-right: -1px;
|
|
777
784
|
}
|
|
@@ -1225,6 +1232,10 @@
|
|
|
1225
1232
|
--tw-translate-x: calc(1 / 2 * 100%);
|
|
1226
1233
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
1227
1234
|
}
|
|
1235
|
+
.translate-x-full {
|
|
1236
|
+
--tw-translate-x: 100%;
|
|
1237
|
+
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
1238
|
+
}
|
|
1228
1239
|
.-translate-y-1\/2 {
|
|
1229
1240
|
--tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
|
|
1230
1241
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
@@ -1247,6 +1258,9 @@
|
|
|
1247
1258
|
.animate-circle-spin {
|
|
1248
1259
|
animation: var(--animate-circle-spin);
|
|
1249
1260
|
}
|
|
1261
|
+
.animate-drawer-shake {
|
|
1262
|
+
animation: var(--animate-drawer-shake);
|
|
1263
|
+
}
|
|
1250
1264
|
.animate-linear-indeterminate {
|
|
1251
1265
|
animation: var(--animate-linear-indeterminate);
|
|
1252
1266
|
}
|
|
@@ -1532,6 +1546,9 @@
|
|
|
1532
1546
|
.gap-\[var\(--cmp-textinput-sm-gap\)\] {
|
|
1533
1547
|
gap: var(--cmp-textinput-sm-gap);
|
|
1534
1548
|
}
|
|
1549
|
+
.gap-\[var\(--토큰명\)\] {
|
|
1550
|
+
gap: var(--토큰명);
|
|
1551
|
+
}
|
|
1535
1552
|
.gap-sd-2 {
|
|
1536
1553
|
gap: var(--spacing-sd-2);
|
|
1537
1554
|
}
|
|
@@ -1824,6 +1841,9 @@
|
|
|
1824
1841
|
.\!border-\[\#93C4FF\] {
|
|
1825
1842
|
border-color: #93C4FF !important;
|
|
1826
1843
|
}
|
|
1844
|
+
.\!border-border-accentLight {
|
|
1845
|
+
border-color: var(--color-border-accentLight) !important;
|
|
1846
|
+
}
|
|
1827
1847
|
.border-\(--cmp-listDraggableItem-border-default\) {
|
|
1828
1848
|
border-color: var(--cmp-listDraggableItem-border-default);
|
|
1829
1849
|
}
|
|
@@ -1884,6 +1904,9 @@
|
|
|
1884
1904
|
.border-\[color\:var\(--cmp-datepicker-border-default\)\] {
|
|
1885
1905
|
border-color: var(--cmp-datepicker-border-default);
|
|
1886
1906
|
}
|
|
1907
|
+
.border-\[color\:var\(--cmp-drawer-body-border\)\] {
|
|
1908
|
+
border-color: var(--cmp-drawer-body-border);
|
|
1909
|
+
}
|
|
1887
1910
|
.border-\[color\:var\(--cmp-listBoxItem-depth1-border-color\)\] {
|
|
1888
1911
|
border-color: var(--cmp-listBoxItem-depth1-border-color);
|
|
1889
1912
|
}
|
|
@@ -2061,6 +2084,9 @@
|
|
|
2061
2084
|
.bg-\[var\(--cmp-datepicker-calendar-time-bg-selected\)\] {
|
|
2062
2085
|
background-color: var(--cmp-datepicker-calendar-time-bg-selected);
|
|
2063
2086
|
}
|
|
2087
|
+
.bg-\[var\(--cmp-drawer-bg\)\] {
|
|
2088
|
+
background-color: var(--cmp-drawer-bg);
|
|
2089
|
+
}
|
|
2064
2090
|
.bg-\[var\(--cmp-listBox-bg\)\] {
|
|
2065
2091
|
background-color: var(--cmp-listBox-bg);
|
|
2066
2092
|
}
|
|
@@ -2079,6 +2105,12 @@
|
|
|
2079
2105
|
.bg-\[var\(--cmp-modal-bg\)\] {
|
|
2080
2106
|
background-color: var(--cmp-modal-bg);
|
|
2081
2107
|
}
|
|
2108
|
+
.bg-\[var\(--cmp-overlay-footer-bg-default\)\] {
|
|
2109
|
+
background-color: var(--cmp-overlay-footer-bg-default);
|
|
2110
|
+
}
|
|
2111
|
+
.bg-\[var\(--cmp-overlay-header-bg\)\] {
|
|
2112
|
+
background-color: var(--cmp-overlay-header-bg);
|
|
2113
|
+
}
|
|
2082
2114
|
.bg-\[var\(--cmp-pagination-item-bg-selected\)\] {
|
|
2083
2115
|
background-color: var(--cmp-pagination-item-bg-selected);
|
|
2084
2116
|
}
|
|
@@ -2199,6 +2231,9 @@
|
|
|
2199
2231
|
.p-16 {
|
|
2200
2232
|
padding: calc(var(--spacing) * 16);
|
|
2201
2233
|
}
|
|
2234
|
+
.p-64 {
|
|
2235
|
+
padding: calc(var(--spacing) * 64);
|
|
2236
|
+
}
|
|
2202
2237
|
.p-\[2px\] {
|
|
2203
2238
|
padding: 2px;
|
|
2204
2239
|
}
|
|
@@ -2214,6 +2249,12 @@
|
|
|
2214
2249
|
.p-\[16px\] {
|
|
2215
2250
|
padding: 16px;
|
|
2216
2251
|
}
|
|
2252
|
+
.p-\[24px\] {
|
|
2253
|
+
padding: 24px;
|
|
2254
|
+
}
|
|
2255
|
+
.p-\[64px\] {
|
|
2256
|
+
padding: 64px;
|
|
2257
|
+
}
|
|
2217
2258
|
.p-\[var\(--cmp-datepicker-calendar-paddingXY\)\] {
|
|
2218
2259
|
padding: var(--cmp-datepicker-calendar-paddingXY);
|
|
2219
2260
|
}
|
|
@@ -2244,6 +2285,9 @@
|
|
|
2244
2285
|
.p-sd-32 {
|
|
2245
2286
|
padding: var(--spacing-sd-32);
|
|
2246
2287
|
}
|
|
2288
|
+
.p-sd-62 {
|
|
2289
|
+
padding: var(--spacing-sd-62);
|
|
2290
|
+
}
|
|
2247
2291
|
.px-\(--cmp-chipInput-md-paddingX\) {
|
|
2248
2292
|
padding-inline: var(--cmp-chipInput-md-paddingX);
|
|
2249
2293
|
}
|
|
@@ -2316,6 +2360,9 @@
|
|
|
2316
2360
|
.px-\[var\(--cmp-overlay-footer-paddingX\)\] {
|
|
2317
2361
|
padding-inline: var(--cmp-overlay-footer-paddingX);
|
|
2318
2362
|
}
|
|
2363
|
+
.px-\[var\(--cmp-overlay-header-paddingX\)\] {
|
|
2364
|
+
padding-inline: var(--cmp-overlay-header-paddingX);
|
|
2365
|
+
}
|
|
2319
2366
|
.px-\[var\(--cmp-pagination-paddingX\)\] {
|
|
2320
2367
|
padding-inline: var(--cmp-pagination-paddingX);
|
|
2321
2368
|
}
|
|
@@ -2988,6 +3035,10 @@
|
|
|
2988
3035
|
--tw-shadow: 5px 0 8px 0 var(--tw-shadow-color, rgba(34,34,34,0.1));
|
|
2989
3036
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
2990
3037
|
}
|
|
3038
|
+
.shadow-\[var\(--shadow-overlay\)\] {
|
|
3039
|
+
--tw-shadow: var(--shadow-overlay);
|
|
3040
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
3041
|
+
}
|
|
2991
3042
|
.shadow-none {
|
|
2992
3043
|
--tw-shadow: 0 0 #0000;
|
|
2993
3044
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
@@ -3065,6 +3116,10 @@
|
|
|
3065
3116
|
--tw-duration: 200ms;
|
|
3066
3117
|
transition-duration: 200ms;
|
|
3067
3118
|
}
|
|
3119
|
+
.duration-300 {
|
|
3120
|
+
--tw-duration: 300ms;
|
|
3121
|
+
transition-duration: 300ms;
|
|
3122
|
+
}
|
|
3068
3123
|
.duration-\[400ms\] {
|
|
3069
3124
|
--tw-duration: 400ms;
|
|
3070
3125
|
transition-duration: 400ms;
|
|
@@ -3394,13 +3449,6 @@
|
|
|
3394
3449
|
}
|
|
3395
3450
|
}
|
|
3396
3451
|
}
|
|
3397
|
-
.hover\:border-\[\#AAAAAA\] {
|
|
3398
|
-
&:hover {
|
|
3399
|
-
@media (hover: hover) {
|
|
3400
|
-
border-color: #AAAAAA;
|
|
3401
|
-
}
|
|
3402
|
-
}
|
|
3403
|
-
}
|
|
3404
3452
|
.hover\:border-\[color\:var\(--cmp-checkbox-unchecked-border-hover\)\] {
|
|
3405
3453
|
&:hover {
|
|
3406
3454
|
@media (hover: hover) {
|
|
@@ -3429,6 +3477,13 @@
|
|
|
3429
3477
|
}
|
|
3430
3478
|
}
|
|
3431
3479
|
}
|
|
3480
|
+
.hover\:border-border-strong {
|
|
3481
|
+
&:hover {
|
|
3482
|
+
@media (hover: hover) {
|
|
3483
|
+
border-color: var(--color-border-strong);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3432
3487
|
.hover\:\!bg-\[var\(--cmp-radioButton-bg-hover\)\] {
|
|
3433
3488
|
&:hover {
|
|
3434
3489
|
@media (hover: hover) {
|
|
@@ -3716,6 +3771,12 @@
|
|
|
3716
3771
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
3717
3772
|
}
|
|
3718
3773
|
}
|
|
3774
|
+
.data-\[state\=closed\]\:translate-x-full {
|
|
3775
|
+
&[data-state="closed"] {
|
|
3776
|
+
--tw-translate-x: 100%;
|
|
3777
|
+
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3719
3780
|
.data-\[state\=closed\]\:animate-modal-out {
|
|
3720
3781
|
&[data-state="closed"] {
|
|
3721
3782
|
animation: var(--animate-modal-out);
|
|
@@ -3810,6 +3871,12 @@
|
|
|
3810
3871
|
}
|
|
3811
3872
|
}
|
|
3812
3873
|
}
|
|
3874
|
+
.data-\[state\=open\]\:translate-x-0 {
|
|
3875
|
+
&[data-state="open"] {
|
|
3876
|
+
--tw-translate-x: 0;
|
|
3877
|
+
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3813
3880
|
.data-\[state\=open\]\:animate-modal-in {
|
|
3814
3881
|
&[data-state="open"] {
|
|
3815
3882
|
animation: var(--animate-modal-in);
|
|
@@ -3873,6 +3940,16 @@
|
|
|
3873
3940
|
transition-property: none;
|
|
3874
3941
|
}
|
|
3875
3942
|
}
|
|
3943
|
+
.md\:p-4 {
|
|
3944
|
+
@media (width >= 48rem) {
|
|
3945
|
+
padding: calc(var(--spacing) * 4);
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
.md\:p-sd-16 {
|
|
3949
|
+
@media (width >= 48rem) {
|
|
3950
|
+
padding: var(--spacing-sd-16);
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3876
3953
|
.md\:px-sd-24 {
|
|
3877
3954
|
@media (width >= 48rem) {
|
|
3878
3955
|
padding-inline: var(--spacing-sd-24);
|
|
@@ -4004,9 +4081,9 @@
|
|
|
4004
4081
|
border-left: 1px solid var(--sys-color-border-default);
|
|
4005
4082
|
}
|
|
4006
4083
|
}
|
|
4007
|
-
.\[\&\:has\(\>button\:hover\)\]\:border
|
|
4084
|
+
.\[\&\:has\(\>button\:hover\)\]\:border-border-strong {
|
|
4008
4085
|
&:has(>button:hover) {
|
|
4009
|
-
border-color:
|
|
4086
|
+
border-color: var(--color-border-strong);
|
|
4010
4087
|
}
|
|
4011
4088
|
}
|
|
4012
4089
|
.\[\&\>\*\:not\(\:last-child\)\]\:border-b {
|
|
@@ -5484,6 +5561,17 @@
|
|
|
5484
5561
|
transform: scale(1.01);
|
|
5485
5562
|
}
|
|
5486
5563
|
}
|
|
5564
|
+
@keyframes drawer-shake {
|
|
5565
|
+
0%, 100% {
|
|
5566
|
+
transform: translateX(0);
|
|
5567
|
+
}
|
|
5568
|
+
40% {
|
|
5569
|
+
transform: translateX(-3px);
|
|
5570
|
+
}
|
|
5571
|
+
70% {
|
|
5572
|
+
transform: translateX(2px);
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5487
5575
|
@keyframes floating-in-top {
|
|
5488
5576
|
from {
|
|
5489
5577
|
opacity: 0;
|
package/dist/theme.css
CHANGED
|
@@ -1743,6 +1743,7 @@
|
|
|
1743
1743
|
--animate-modal-in: modal-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
1744
1744
|
--animate-modal-out: modal-out 0.2s ease-in;
|
|
1745
1745
|
--animate-modal-shake: modal-shake 0.3s ease-out;
|
|
1746
|
+
--animate-drawer-shake: drawer-shake 0.22s ease-out;
|
|
1746
1747
|
|
|
1747
1748
|
/* floating(툴팁·팝오버) placement 방향별 슬라이드+페이드 (sd-floating-portal 포팅) */
|
|
1748
1749
|
--animate-floating-in-top: floating-in-top 0.35s ease-out;
|
|
@@ -1783,6 +1784,12 @@
|
|
|
1783
1784
|
50% { transform: scale(0.97); }
|
|
1784
1785
|
75% { transform: scale(1.01); }
|
|
1785
1786
|
}
|
|
1787
|
+
/* persistent Drawer에서 닫기 시도 시 사용하는 낮은 진폭의 흔들림 */
|
|
1788
|
+
@keyframes drawer-shake {
|
|
1789
|
+
0%, 100% { transform: translateX(0); }
|
|
1790
|
+
40% { transform: translateX(-3px); }
|
|
1791
|
+
70% { transform: translateX(2px); }
|
|
1792
|
+
}
|
|
1786
1793
|
/* floating(툴팁·팝오버): placement 방향에서 4px 슬라이드 + 페이드 (sd-floating-slide-* 포팅) */
|
|
1787
1794
|
@keyframes floating-in-top { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
|
|
1788
1795
|
@keyframes floating-in-bottom { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* 소비 앱이 원래 쓰던 Tailwind 코드이기 때문이다. 간격을 디자인 시스템 스케일로
|
|
10
10
|
* 통일하는 팀만 `requirePrefix: true` 로 켠다.
|
|
11
11
|
*
|
|
12
|
+
* 접두가 없는 클래스는 **깨진 코드가 아니라 다른 값으로 정상 동작하는 코드**다
|
|
13
|
+
* (`p-16` = 64px). 그래서 `sd-` 로 옮기는 것은 표기 정규화가 아니라 값 변경이고,
|
|
14
|
+
* 자동 수정(fix)이 아니라 제안(suggestion)으로만 낸다. 일괄 마이그레이션이
|
|
15
|
+
* 필요한 팀은 `unsafeFix: true` 로 명시적으로 켠다.
|
|
16
|
+
*
|
|
12
17
|
* AGENTS.md §2-2.
|
|
13
18
|
*/
|
|
14
19
|
import { collectClassTokens, stripVariants, stripModifiers } from "../lib/class-names.mjs";
|
|
@@ -23,28 +28,47 @@ const SPACING_PREFIXES = [
|
|
|
23
28
|
];
|
|
24
29
|
|
|
25
30
|
const ALLOWED = new Set([0, ...SPACING]);
|
|
31
|
+
const MAX = Math.max(...SPACING);
|
|
32
|
+
|
|
33
|
+
/** Tailwind 기본 스케일에서 `p-<n>` 이 실제로 만드는 px (`--spacing` = 0.25rem) */
|
|
34
|
+
const RENDERED_PX = (value) => value * 4;
|
|
26
35
|
|
|
27
36
|
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
28
37
|
|
|
38
|
+
/**
|
|
39
|
+
* 토큰 시작 경계 — 앞은 공백이거나 문자열 따옴표(= 문자열의 시작)여야 한다.
|
|
40
|
+
* `(?<![\w-])` 만 쓰면 `md:p-4 p-4` 에서 `p-4` 를 찾을 때 variant 안쪽(`md:` 뒤)이
|
|
41
|
+
* 먼저 걸려 엉뚱한 클래스를 고친다.
|
|
42
|
+
*/
|
|
43
|
+
const TOKEN_START = "(?<![^\\s'\"`])";
|
|
44
|
+
|
|
29
45
|
export default {
|
|
30
46
|
meta: {
|
|
31
47
|
type: "problem",
|
|
32
48
|
docs: {
|
|
33
49
|
description: "간격 유틸리티는 sd- 접두가 붙은 토큰 스케일 값만 사용",
|
|
34
50
|
},
|
|
51
|
+
// 스케일 밖 값(`gap-sd-13`)에는 fix 를 내지 않는다. `unsafeFix` 를 켠 팀의
|
|
52
|
+
// 접두 누락 수정에만 쓰이지만, 선언은 정적이어야 하므로 여기 남는다.
|
|
35
53
|
fixable: "code",
|
|
54
|
+
hasSuggestions: true,
|
|
36
55
|
schema: [
|
|
37
56
|
{
|
|
38
57
|
type: "object",
|
|
39
58
|
properties: {
|
|
40
59
|
/**
|
|
41
60
|
* 접두 없는 간격 유틸리티(`p-16`)도 잡는다. 기본 false.
|
|
42
|
-
*
|
|
43
|
-
* ⚠️ 자동 수정은 "클래스의 숫자 = px" 를 전제로 `p-16` → `p-sd-16` 로 바꾼다.
|
|
44
|
-
* 기본 Tailwind 스케일(`p-4` = 16px)로 쓰던 앱에서 켜면 여백이 1/4 로 줄어드니,
|
|
45
|
-
* 이전 버전의 디자인 시스템 스케일을 쓰던 앱에서만 켠다.
|
|
61
|
+
* 기본값에서는 두 해석(`p-sd-16` = 16px / 현재 값 유지)을 제안으로만 낸다.
|
|
46
62
|
*/
|
|
47
63
|
requirePrefix: { type: "boolean" },
|
|
64
|
+
/**
|
|
65
|
+
* 접두 누락을 "클래스의 숫자 = px" 로 단정하고 `p-16` → `p-sd-16` 자동 수정한다.
|
|
66
|
+
*
|
|
67
|
+
* ⚠️ **여백 값이 바뀐다.** 기본 Tailwind 스케일(`p-4` = 16px)로 쓰던 앱에서
|
|
68
|
+
* 켜면 여백이 1/4 로 줄어든다. 이전 버전의 디자인 시스템 스케일을 쓰던 앱을
|
|
69
|
+
* 한 번에 옮길 때만 켜고, 마이그레이션이 끝나면 다시 끈다.
|
|
70
|
+
*/
|
|
71
|
+
unsafeFix: { type: "boolean" },
|
|
48
72
|
},
|
|
49
73
|
additionalProperties: false,
|
|
50
74
|
},
|
|
@@ -52,32 +76,57 @@ export default {
|
|
|
52
76
|
messages: {
|
|
53
77
|
offScale:
|
|
54
78
|
"`{{token}}` 의 {{value}} 는 간격 스케일에 없어 스타일이 적용되지 않습니다. 가까운 값: {{nearest}}. (AGENTS.md §2-2)",
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"`{{token}}` 은
|
|
79
|
+
// 접두 누락 — 어느 해석이 스케일에 있느냐로 갈린다.
|
|
80
|
+
// `p-16` 은 지금 64px 이므로 "16px 의도" 와 "64px 유지" 가 서로 다른 결과다.
|
|
81
|
+
missingPrefixBoth:
|
|
82
|
+
"`{{token}}` 은 현재 {{actual}}px 입니다 (Tailwind 기본 스케일). {{value}}px 을 의도했다면 → `{{asLiteral}}` · {{actual}}px 을 유지하려면 → `{{asRendered}}` (AGENTS.md §2-2)",
|
|
83
|
+
missingPrefixLiteral:
|
|
84
|
+
"`{{token}}` 은 현재 {{actual}}px 입니다 (Tailwind 기본 스케일). {{value}}px 을 의도했다면 → `{{asLiteral}}`. {{actual}}px 유지는 간격 스케일에 값이 없습니다. {{fallback}} (AGENTS.md §2-2)",
|
|
85
|
+
missingPrefixRendered:
|
|
86
|
+
"`{{token}}` 은 현재 {{actual}}px 입니다 (Tailwind 기본 스케일). {{actual}}px 을 유지하려면 → `{{asRendered}}`. {{value}}px 은 간격 스케일에 없습니다. (AGENTS.md §2-2)",
|
|
87
|
+
missingPrefixNone:
|
|
88
|
+
"`{{token}}` 은 현재 {{actual}}px 입니다 (Tailwind 기본 스케일). {{value}}px·{{actual}}px 둘 다 간격 스케일에 없습니다. {{fallback}} (AGENTS.md §2-2)",
|
|
89
|
+
// 제안(suggestion) 설명 — 편집기 목록에 그대로 뜬다
|
|
90
|
+
useLiteral: "`{{to}}` 적용 — 숫자를 px 로 해석 ({{actual}}px → {{value}}px, 여백이 바뀝니다)",
|
|
91
|
+
useRendered: "`{{to}}` 적용 — 현재 {{actual}}px 유지",
|
|
59
92
|
},
|
|
60
93
|
},
|
|
61
94
|
|
|
62
95
|
create(context) {
|
|
63
96
|
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
64
97
|
const requirePrefix = context.options[0]?.requirePrefix ?? false;
|
|
98
|
+
const unsafeFix = context.options[0]?.unsafeFix ?? false;
|
|
65
99
|
|
|
66
100
|
/** 토큰 문자열을 소스에서 찾아 치환하는 fixer (없으면 null) */
|
|
67
101
|
const replaceToken = (valueNode, token, fixed) => (fixer) => {
|
|
68
102
|
const text = sourceCode.getText(valueNode);
|
|
69
|
-
const idx = text.search(new RegExp(
|
|
103
|
+
const idx = text.search(new RegExp(`${TOKEN_START}${escapeRegExp(token)}(?![\\w-])`));
|
|
70
104
|
if (idx === -1) return null;
|
|
71
105
|
const start = valueNode.range[0] + idx;
|
|
72
106
|
return fixer.replaceTextRange([start, start + token.length], fixed);
|
|
73
107
|
};
|
|
74
108
|
|
|
75
|
-
/**
|
|
76
|
-
const
|
|
77
|
-
[...ALLOWED]
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
109
|
+
/** 스케일을 value 와의 거리순으로 (동률이면 작은 값 먼저) */
|
|
110
|
+
const byDistance = (value) =>
|
|
111
|
+
[...ALLOWED].sort((a, b) => Math.abs(a - value) - Math.abs(b - value) || a - b);
|
|
112
|
+
|
|
113
|
+
/** 가장 가까운 값 하나 — 거리순 배열을 다시 정렬하면 안 된다 (64 → 62 지 60 이 아니다) */
|
|
114
|
+
const nearestOne = (value) => byDistance(value)[0];
|
|
115
|
+
|
|
116
|
+
/** 스케일에서 value 에 가장 가까운 값 두 개 (표시용이라 오름차순) */
|
|
117
|
+
const nearestOf = (value) => byDistance(value).slice(0, 2).sort((a, b) => a - b);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 대응 토큰이 없을 때의 안내. 기준은 클래스에 적힌 숫자가 아니라 **실제 렌더 px** 다.
|
|
121
|
+
* 스케일 최대를 넘으면 가까운 값을 권하는 것 자체가 무의미하므로 토큰 추가로 안내한다.
|
|
122
|
+
*/
|
|
123
|
+
const fallbackFor = (prefix, renderedPx) => {
|
|
124
|
+
if (renderedPx > MAX) {
|
|
125
|
+
return `간격 스케일 최대가 ${MAX}px 이라 대응 토큰이 없습니다 — 토큰 추가를 요청하세요.`;
|
|
126
|
+
}
|
|
127
|
+
const near = nearestOne(renderedPx);
|
|
128
|
+
return `가까운 \`${prefix}-sd-${near}\`(${near}px) 를 검토하거나 토큰 추가를 요청하세요.`;
|
|
129
|
+
};
|
|
81
130
|
|
|
82
131
|
return {
|
|
83
132
|
JSXAttribute(node) {
|
|
@@ -99,31 +148,61 @@ export default {
|
|
|
99
148
|
const value = Number(raw);
|
|
100
149
|
if (value === 0) continue; // p-0 은 스케일과 무관하게 0
|
|
101
150
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
151
|
+
const rendered = RENDERED_PX(value);
|
|
152
|
+
/** `md:p-4` → `md:p-sd-16` — variant·부호 접두는 그대로 두고 뒤만 갈아낀다 */
|
|
153
|
+
const rewrite = (px) =>
|
|
154
|
+
token.replace(
|
|
155
|
+
new RegExp(`${escapeRegExp(`${prefix}-${raw}`)}$`),
|
|
156
|
+
`${prefix}-sd-${px}`,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// 두 해석 — 숫자를 px 로 읽는 쪽(값이 바뀐다)과 현재 렌더 값을 지키는 쪽.
|
|
160
|
+
// 스케일에 없는 해석은 후보에서 빠진다 (sd- 를 붙여도 스타일이 안 생긴다).
|
|
161
|
+
const asLiteral = ALLOWED.has(value) ? rewrite(value) : null;
|
|
162
|
+
const asRendered = ALLOWED.has(rendered) ? rewrite(rendered) : null;
|
|
163
|
+
|
|
164
|
+
const data = {
|
|
165
|
+
token,
|
|
166
|
+
value: raw,
|
|
167
|
+
actual: String(rendered),
|
|
168
|
+
asLiteral: asLiteral ?? "",
|
|
169
|
+
asRendered: asRendered ?? "",
|
|
170
|
+
fallback: fallbackFor(prefix, rendered),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const suggest = [];
|
|
174
|
+
if (asLiteral) {
|
|
175
|
+
suggest.push({
|
|
176
|
+
messageId: "useLiteral",
|
|
177
|
+
data: { to: asLiteral, actual: String(rendered), value: raw },
|
|
178
|
+
fix: replaceToken(valueNode, token, asLiteral),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (asRendered) {
|
|
182
|
+
suggest.push({
|
|
183
|
+
messageId: "useRendered",
|
|
184
|
+
data: { to: asRendered, actual: String(rendered) },
|
|
185
|
+
fix: replaceToken(valueNode, token, asRendered),
|
|
114
186
|
});
|
|
115
|
-
continue;
|
|
116
187
|
}
|
|
117
188
|
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
189
|
+
const messageId = asLiteral
|
|
190
|
+
? asRendered
|
|
191
|
+
? "missingPrefixBoth"
|
|
192
|
+
: "missingPrefixLiteral"
|
|
193
|
+
: asRendered
|
|
194
|
+
? "missingPrefixRendered"
|
|
195
|
+
: "missingPrefixNone";
|
|
196
|
+
|
|
122
197
|
context.report({
|
|
123
198
|
node: valueNode,
|
|
124
|
-
messageId
|
|
125
|
-
data
|
|
126
|
-
|
|
199
|
+
messageId,
|
|
200
|
+
data,
|
|
201
|
+
suggest,
|
|
202
|
+
// 마이그레이션용 옵트인 — "숫자 = px" 를 단정하므로 여백 값이 바뀐다
|
|
203
|
+
...(unsafeFix && asLiteral
|
|
204
|
+
? { fix: replaceToken(valueNode, token, asLiteral) }
|
|
205
|
+
: {}),
|
|
127
206
|
});
|
|
128
207
|
continue;
|
|
129
208
|
}
|