sh-ui-cli 0.98.0 → 0.109.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/data/changelog/versions.json +169 -0
- package/data/registry/react/components/scroll-area/index.module.tsx +71 -0
- package/data/registry/react/components/scroll-area/index.tailwind.tsx +54 -0
- package/data/registry/react/components/scroll-area/index.tsx +67 -0
- package/data/registry/react/components/scroll-area/styles.css +64 -0
- package/data/registry/react/components/scroll-area/styles.module.css +64 -0
- package/data/registry/react/components/sheet/index.module.tsx +93 -0
- package/data/registry/react/components/sheet/index.tailwind.tsx +120 -0
- package/data/registry/react/components/sheet/index.tsx +121 -0
- package/data/registry/react/components/sheet/styles.css +183 -0
- package/data/registry/react/components/sheet/styles.module.css +171 -0
- package/data/registry/react/registry.json +94 -0
- package/data/registry/react/tokens-used.json +86 -1
- package/data/summaries/react.json +3 -1
- package/data/tokens/src/primitives.json +8 -0
- package/data/tokens/src/semantic.json +36 -10
- package/package.json +1 -1
- package/src/add.mjs +39 -14
- package/src/create/cli-args.js +18 -5
- package/src/create/generator.js +116 -4
- package/src/create/index.mjs +3 -0
- package/src/create/plugins/nextIntl.js +3 -0
- package/src/create/theme/decode.js +3 -0
- package/src/create/theme/presets.js +45 -8
- package/src/css-bundle.mjs +60 -0
- package/src/mcp.mjs +47 -3
- package/src/remove.mjs +10 -1
- package/src/theme-extract.mjs +1 -0
- package/templates/nextjs-standalone/_arch/flat/app/globals.css +16 -4
- package/templates/nextjs-standalone/_arch/flat/lib/styles/tokens.css +35 -4
- package/templates/nextjs-standalone/_arch/fsd/src/shared/styles/tokens.css +35 -4
- package/templates/nextjs-standalone/_arch/mes/app/globals.css +16 -4
- package/templates/nextjs-standalone/_arch/mes/src/lib/styles/tokens.css +33 -2
- package/templates/nextjs-standalone/app/globals.css +16 -4
- package/templates/ui-app-template/src/styles/globals.css +16 -4
- package/templates/ui-app-template/src/styles/tokens.css +35 -4
- package/templates/vite-standalone/_arch/flat/src/lib/styles/globals.css +16 -0
- package/templates/vite-standalone/_arch/flat/src/lib/styles/tokens.css +35 -4
- package/templates/vite-standalone/_arch/fsd/src/shared/styles/globals.css +16 -0
- package/templates/vite-standalone/_arch/fsd/src/shared/styles/tokens.css +35 -4
|
@@ -2,6 +2,175 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$description": "sh-ui 릴리즈 노트 단일 소스. docs(React)와 showcase(Flutter)가 함께 읽는다. 새 릴리즈마다 맨 앞에 추가.",
|
|
4
4
|
"versions": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.109.1",
|
|
7
|
+
"date": "2026-05-22",
|
|
8
|
+
"title": "CLI/MCP npm 게시 동기화 — 0.99~0.109 토큰 업데이트 반영",
|
|
9
|
+
"type": "patch",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"**npm `sh-ui-cli` 가 0.98.1 에 멈춰 있던 문제 수정** — 0.99~0.109 의 토큰/스캐폴드 변경이 `packages/cli/src` 에는 반영됐지만 `packages/cli/package.json` version 범프 누락으로 npm 에 게시되지 않고 있었음. CLI 버전을 제품 버전과 일치(0.109.1)시켜 누락분을 일괄 반영.",
|
|
12
|
+
"**MCP 서버도 함께 갱신** — MCP 는 `sh-ui-cli` 패키지에 내장돼 함께 게시되므로, 이 릴리즈로 MCP 가 보고하는 버전과 번들된 토큰이 모두 최신화됨.",
|
|
13
|
+
"**publish.yml 에 태그/버전 일치 가드 추가** — 태그 `vX.Y.Z` 와 `packages/cli/package.json` version 이 다르면 게시를 즉시 실패시킴. 기존엔 \"이미 게시됨\" 스킵 로직이 버전 드리프트를 조용히 삼켜 11개 버전이 누락된 재발 원인을 차단."
|
|
14
|
+
],
|
|
15
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.109.1"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"version": "0.109.0",
|
|
19
|
+
"date": "2026-05-21",
|
|
20
|
+
"title": "다크 모드 — accent chroma 상향 (slate/rose/emerald/violet 프리셋)",
|
|
21
|
+
"type": "minor",
|
|
22
|
+
"highlights": [
|
|
23
|
+
"**dark accent 디폴트를 primary 보다 한 단계 lighter+saturated 톤으로** — Aifice 피드백 2.1 의 \"accent chroma 상향 (어두운 배경 위에서 highlight 가독성)\" 항목. 컬러 프리셋 4종 의 dark accent 가 primary (-400 톤) 보다 한 단계 lighter 한 -300 톤으로 이동:",
|
|
24
|
+
"- **slate**: `#818CF8` (indigo-400) → `#A5B4FC` (indigo-300) + accent-hover `#C7D2FE` (indigo-200)",
|
|
25
|
+
"- **rose**: `#FB7185` → `#FDA4AF` (rose-300) + accent-hover `#FECDD3` (rose-200)",
|
|
26
|
+
"- **emerald**: `#34D399` → `#6EE7B7` (emerald-300) + accent-hover `#A7F3D0` (emerald-200)",
|
|
27
|
+
"- **violet**: `#A78BFA` → `#C4B5FD` (violet-300) + accent-hover `#DDD6FE` (violet-200)",
|
|
28
|
+
"**`neutral` 은 변경 없음** (무채색이라 chroma 의미 없음). **light 모드도 변경 없음** (밝은 배경 위 saturated 톤은 그대로 OK). **primary 채도 보정 (desaturate)** 은 brand 톤 손상 위험 + 디자이너 손맛 필요로 미적용 — 별도 PR 권장.",
|
|
29
|
+
"**시각 영향** — dark mode + 컬러 프리셋 사용자가 `bg-accent` / `text-accent` utility 또는 `var(--accent)` 직접 호출 시 더 밝고 saturated 한 highlight 톤. accent 를 분리해 사용한 케이스 (링크 / 선택 상태) 만 영향, 미사용 사용자는 영향 없음. backward-compat 거의 유지."
|
|
30
|
+
],
|
|
31
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.109.0"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"version": "0.108.0",
|
|
35
|
+
"date": "2026-05-21",
|
|
36
|
+
"title": "토큰 — sidebar 디폴트를 background.subtle / .muted 와 동일 값으로 일치",
|
|
37
|
+
"type": "minor",
|
|
38
|
+
"highlights": [
|
|
39
|
+
"**sidebar 토큰 디폴트가 일반 surface 토큰과 일치** — light: `--sidebar-bg` 가 `{base}.50` (~#FAFAFA) → `{base}.100` (~#F5F5F5, = `--background-subtle`), `--sidebar-accent` 가 `{base}.100` → `{base}.200` (= `--background-muted`). 사용자 메시지 \"sidebar 만 분리된 어정쩡함\" 해소 (Aifice 피드백 2.6).",
|
|
40
|
+
"**대신 분리 능력은 유지** — sidebar 토큰 자체는 그대로 존재하므로 사용자가 sidebar 만 별 톤 (예: 어두운 sidebar on light 페이지) 으로 커스터마이즈하고 싶으면 `--sidebar-bg` 등 hex 만 별도 값으로 override. 즉 \"디폴트 일관 + 사용자 분리 가능\" 의 절충.",
|
|
41
|
+
"**dark 모드는 변경 없음** — sidebar.bg (`{base}.900`) 가 이미 background.subtle dark 값과 일치, sidebar.accent (`{base}.800`) 도 background.muted dark 와 일치. light 만 step 분리 (v0.107.0) 와 함께 일관성 회복.",
|
|
42
|
+
"**시각 영향** — light 모드에서 sidebar 가 약간 더 진한 톤 (#FAFAFA → #F5F5F5). 메인 콘텐츠 (#FFFFFF) 와 더 명확한 구분. v0.107.0 의 background-subtle/muted 분리와 같은 방향. dark 모드 영향 없음."
|
|
43
|
+
],
|
|
44
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.108.0"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"version": "0.107.0",
|
|
48
|
+
"date": "2026-05-21",
|
|
49
|
+
"title": "토큰 — light 모드 `background-subtle` / `background-muted` 한 step 더 분리",
|
|
50
|
+
"type": "minor",
|
|
51
|
+
"highlights": [
|
|
52
|
+
"**`--background-subtle` / `--background-muted` 시각 차이 명확화** — 기존 light 의 subtle (`{base}.50`, ~#FAFAFA) 과 muted (`{base}.100`, ~#F5F5F5) 가 99% 디스플레이에서 구분 안 되던 문제 (Aifice 피드백 2.2). 한 step 씩 더 진하게 이동 — subtle: `{base}.100` (#F5F5F5), muted: `{base}.200` (#E5E5E5). `bg < bg-subtle < bg-muted` 어두워지는 의도가 시각으로 드러남.",
|
|
53
|
+
"**dark 모드는 그대로** — 현재도 `.950 / .900 / .800` 으로 충분히 분리. 변경 없음.",
|
|
54
|
+
"**영향 범위** — semantic.json (light.background.subtle / .muted) + presets.js (NEUTRAL_LIGHT + slate light) + 6 templates 의 tokens.css `:root` (light 블록만) + apps/docs tokens.css. dark @media 와 `.dark` 블록은 미변경.",
|
|
55
|
+
"**시각 영향** — tabs/sidebar/pagination/code-editor 등 background-subtle 을 hover/active 표면으로 사용하는 컴포넌트에서 hover state 가 더 명확해진다. 인접 nested surface (예: card 안의 input group bg) 도 명확한 단계 분리. 사용자 base 색 (neutral/zinc/slate) 별로 동일 비율 차이 유지."
|
|
56
|
+
],
|
|
57
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.107.0"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"version": "0.106.0",
|
|
61
|
+
"date": "2026-05-21",
|
|
62
|
+
"title": "DX 묶음 — generator 가이드 주석 + 한국어 자연어 매핑 + radius scale 절대값화",
|
|
63
|
+
"type": "minor",
|
|
64
|
+
"highlights": [
|
|
65
|
+
"**(4.1) tokens.css 헤더에 재생성 가이드** — Generated 주석 다음에 `Input: sh-ui.config.json (theme block)` + `Regenerate: npx sh-ui-cli tokens upgrade --replace` 두 줄 추가. 사용자가 generated tokens.css 를 직접 수정해 다음 upgrade 시 손실되던 케이스 사전 안내. 6 templates + apps/docs 동기화 (Aifice 피드백 4.1).",
|
|
66
|
+
"**(4.2) `sh_ui_describe_init` 자연어 매핑 확장** — INIT_DESCRIPTIONS 에 `theme` 프리셋 5종 (neutral/slate/rose/emerald/violet) 한국어 풀이 + `keywordHints` 사전 신규. \"다크 모던\" / \"따뜻한 종이\" / \"한국 핀테크 스타일\" / \"기업 관리자\" 등 자연어 의도를 `base`/`radius`/`mode`/`theme` 조합으로 매핑하는 가이드 (Aifice 피드백 4.2).",
|
|
67
|
+
"**(2.5) radius scale 절대값화** — `--radius-sm/md/lg/xl/full` 을 tokens.css `:root` 에 직접 정의 (Tailwind 표준값 `0.25/0.5/0.75/1rem` + `9999px`). globals.css `@theme inline` 의 calc derive (`calc(var(--radius) - 2px)` 등) 도 같은 절대값으로 교체. 사용자가 `var(--radius-sm)` 직접 호출 또는 Tailwind `rounded-*` utility 양쪽이 동일 절대값 사용 (Aifice 피드백 2.5).",
|
|
68
|
+
"**⚠ 마이그 영향 (2.5)** — `--radius` (사용자 base) 변경 시 `rounded-md` 등 utility 가 더 이상 따라가지 않음 (이전 calc derive 동작). `--radius` 는 컴포넌트 default (input/button 등) 만 영향, utility 는 절대 scale 사용. 디폴트 (`--radius: 0.5rem`) 사용자는 `rounded-md` 값 동일 (0.5rem) — 실제 영향 거의 없음. base radius 변경한 사용자만 utility 결과가 변함."
|
|
69
|
+
],
|
|
70
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.106.0"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"version": "0.105.0",
|
|
74
|
+
"date": "2026-05-21",
|
|
75
|
+
"title": "next-intl plugin — `localePrefix: 'as-needed'` 디폴트",
|
|
76
|
+
"type": "minor",
|
|
77
|
+
"highlights": [
|
|
78
|
+
"**`next-intl` 플러그인의 routing 디폴트 변경** — `localePrefix: 'as-needed'` 추가. ko 는 prefix 없이 `/`, en 만 `/en/...` 로 라우팅 — 한국 사용자 베이스 + 영문 보조 페어가 흔한 패턴 (Aifice 피드백 3.3). 모든 locale 에 prefix 강제하고 싶으면 `'always'`, 절대 prefix 안 붙이면 `'never'` 로 routing.ts 한 줄 수정.",
|
|
79
|
+
"**기존 P3 항목은 이미 templates 에 구현돼 있음** — `formatDate` / `formatDateTime` / `formatPrice` util (ko-KR 고정 locale, SSR-safe) 과 ESLint `no-restricted-syntax` 룰 (raw `.toLocaleDateString()` / `.toLocaleString()` / `.toLocaleTimeString()` 인자 0개 호출 차단) 은 v0.93.x 부터 모든 next templates 의 `lib/utils/` 및 `eslint.config.js` 에 포함. 이번 릴리즈로 마지막 누락이었던 routing 디폴트만 보완.",
|
|
80
|
+
"**호환성** — 새 init 만 영향. 기존 사용자의 routing.ts 는 변경 없음. 회귀 테스트 1건 (smoke) 추가: `--plugins next-intl --arch flat` 시 routing.ts 에 `localePrefix: 'as-needed'` 포함."
|
|
81
|
+
],
|
|
82
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.105.0"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"version": "0.104.0",
|
|
86
|
+
"date": "2026-05-21",
|
|
87
|
+
"title": "토큰 — letter-spacing(`--tracking-*`) + 의도 shadow(`--shadow-popover/modal/toast`)",
|
|
88
|
+
"type": "minor",
|
|
89
|
+
"highlights": [
|
|
90
|
+
"**letter-spacing 토큰 신규** — `--tracking-tighter` / `--tracking-tight` / `--tracking-normal` / `--tracking-wide` / `--tracking-wider` 5단계. 한↔영 혼용에서 자간이 시각 톤을 결정짓는 만큼 토큰화 (Aifice 피드백 2.5). Tailwind utility `tracking-*` 도 함께 작동.",
|
|
91
|
+
"**의도 기반 shadow 토큰 신규** — 기존 size scale (`--shadow-sm/md/lg/xl/menu`) 에 더해 의도 시맨틱 `--shadow-popover` / `--shadow-modal` / `--shadow-toast` 추가. 컴포넌트별 inline box-shadow 박아쓰던 일관성 깨짐 케이스 해결. 디폴트 값은 각각 lg / xl / md 매핑이라 시각 변화 없음 — 사용자가 톤만 별도로 조정 가능.",
|
|
92
|
+
"**적용 범위** — `@sh-ui/tokens` semantic.json + primitives (letterSpacing 그룹 신규) + 6 templates 의 tokens.css :root + 6 templates 의 globals.css `@theme inline` + apps/docs/app/styles/tokens.css 동기화. 기존 사용자가 `var(--tracking-*)` / `var(--shadow-popover)` 직접 호출하거나 `tracking-*` Tailwind utility 로 활용 가능.",
|
|
93
|
+
"**out of scope** — radius scale 의 :root 직접 노출은 Tailwind v4 의 `@theme inline { --radius-sm: calc(...) }` derive 와 충돌 + 마이그 영향 우려로 별 PR 로 미룸 (현재도 Tailwind `rounded-*` utility 는 정상 작동). 회귀 영향 없음."
|
|
94
|
+
],
|
|
95
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.104.0"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"version": "0.103.0",
|
|
99
|
+
"date": "2026-05-21",
|
|
100
|
+
"title": "create — `--locale ko` 옵션으로 Pretendard 자동 적용",
|
|
101
|
+
"type": "minor",
|
|
102
|
+
"highlights": [
|
|
103
|
+
"**`--locale ko` 옵션 신규** — 한국어 사용자가 sh-ui init 직후 거의 100% 첫 작업이 `Pretendard 로 교체` (Aifice 피드백 3.1) 였던 흐름을 옵션 하나로 자동화. `sh-ui create my-app --locale ko` 또는 MCP `sh_ui_create_project({ locale: 'ko' })`.",
|
|
104
|
+
"**적용 방식** — 스캐폴드된 모든 globals.css 의 `sh-ui:external-imports-end` 마커 앞에 Pretendard CDN `@import` 추가 (Tailwind import 보다 먼저 — CSS spec 준수), 파일 끝에 `body { font-family: 'Pretendard Variable', ui-sans-serif, system-ui, ... }` rule 추가. monorepo 도 안전하게 모든 globals.css 재귀 처리.",
|
|
105
|
+
"**idempotent** — 이미 `'Pretendard Variable'` 가 있는 파일은 skip, 마커 없는 파일도 안전상 건드리지 않음. CDN 은 `cdn.jsdelivr.net/orioncactus/pretendard@v1.3.9` (SIL OFL 라이선스, 상용 OK).",
|
|
106
|
+
"**locale vs locales** — `--locale` (단수) 는 사용자 지역 디폴트 가정 (현재는 폰트). `--locales` (복수) 는 i18n 활성화 시 생성할 locale 코드 목록 (예: `ko,en`). 둘은 의미가 다르고 함께 사용 가능. CLI 에러 메시지에 차이 명시.",
|
|
107
|
+
"**검증** — pnpm test 420/420 통과 (413 → 420, +7). `cli-args` 4건 (값 검증) + `smoke` 3건 (실제 inject + 미지정 시 비활성 + idempotent) 추가."
|
|
108
|
+
],
|
|
109
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.103.0"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"version": "0.102.0",
|
|
113
|
+
"date": "2026-05-21",
|
|
114
|
+
"title": "create — nested .git 자동감지로 충돌 방지 + --no-git-init / --git-init 플래그",
|
|
115
|
+
"type": "minor",
|
|
116
|
+
"highlights": [
|
|
117
|
+
"**기존 git tree 안에서 `sh-ui create` 호출 시 nested .git 자동 스킵** — parent 디렉토리가 이미 git tree 안이면 `git init` 을 자동으로 건너뛰어 nested .git 충돌을 막는다. 모노레포 / 작업 트리 안에서 호출하는 흔한 시나리오에서 더 이상 nested repo 가 생기지 않는다 (Aifice 피드백 1.3).",
|
|
118
|
+
"**명시 옵션** — `--no-git-init` (CLI) / `gitInit:false` (MCP) 으로 무조건 스킵, `--git-init` / `gitInit:true` 로 nested 라도 강제 init. 자동 감지가 거슬리거나 nested 가 의도된 경우 override.",
|
|
119
|
+
"**감지 방식** — `git -C <parent> rev-parse --is-inside-work-tree` 실행. true 면 스킵 + 사용자에게 친절한 메시지 출력 (어느 parent repo 안인지 함께), 명령이 실패하면 (git 미설치 / parent 가 tree 밖) 트리 밖으로 간주해 init 시도. 안전 디폴트.",
|
|
120
|
+
"**적용 범위** — `sh_ui_create_project` MCP 툴 + CLI `create` 모든 경로 (next standalone / next monorepo / vite / flutter / dryRun 무관). 회귀 테스트 3 건 추가 (nested 자동 스킵 / force / 명시 스킵). 전체 테스트 413/413 통과."
|
|
121
|
+
],
|
|
122
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.102.0"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"version": "0.101.0",
|
|
126
|
+
"date": "2026-05-21",
|
|
127
|
+
"title": "sheet — side drawer 컴포넌트 신규 추가",
|
|
128
|
+
"type": "minor",
|
|
129
|
+
"highlights": [
|
|
130
|
+
"**`sheet` 컴포넌트 신규** — Base UI `Drawer` 위에 sh-ui 토큰 스타일을 얹은 composite. 헤더 알림함 · 글로벌 작업 큐 · 보조 설정 패널처럼 **사이드바와 무관한 위치**에서 슬라이드 인 하는 모달 시트가 필요한 케이스. 중앙 모달이 어울리는 흐름은 `Dialog`, 사이드바 인접 detail 패널은 `SidebarPanel` 그대로 사용 — 세 컴포넌트의 책임이 명확히 분리.",
|
|
131
|
+
"**API** — separate exports: `Sheet` / `SheetTrigger` / `SheetClose` / `SheetContent` / `SheetTitle` / `SheetDescription` / `SheetHeader` / `SheetFooter` / `SheetCloseX`. `<SheetContent side>` 으로 진입 방향 지정 — `\"right\"` (기본) / `\"left\"` / `\"top\"` / `\"bottom\"`. Trigger/Close 는 자체로 `<button>` 이라 다른 엘리먼트로 슬롯하려면 `render` prop.",
|
|
132
|
+
"**a11y & motion** — Base UI Drawer 의 포커스 트랩 / ESC 닫기 / 바깥 클릭 닫기 / 트리거 포커스 복귀 기본 동작. Title / Description 이 있으면 `aria-labelledby` / `aria-describedby` 자동. `prefers-reduced-motion` 사용자에게는 transform 트랜지션 제거 — 즉시 표시.",
|
|
133
|
+
"**styles** — backdrop 은 `rgba(0,0,0,0.25)` + `backdrop-filter: blur(8px)`. 시트 본체는 `--background` / `--border` / `--shadow-xl` / `--space-6` 토큰. side 별 transform (enter/exit 시 100% 이동) + 모서리 한 면만 border 노출로 화면에 \"붙은\" 느낌."
|
|
134
|
+
],
|
|
135
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.101.0"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"version": "0.100.0",
|
|
139
|
+
"date": "2026-05-21",
|
|
140
|
+
"title": "토큰 — `--accent` / `--accent-foreground` / `--accent-hover` 분리 노출 (옵셔널, primary 와 분리)",
|
|
141
|
+
"type": "minor",
|
|
142
|
+
"highlights": [
|
|
143
|
+
"**accent 토큰 그룹 신규 (옵셔널)** — `--accent` / `--accent-foreground` / `--accent-hover` 세 토큰을 정식 노출. primary 가 \"브랜드 action 색 (버튼·강조 액션)\" 이라면 accent 는 \"signature highlight (링크·선택 상태·hover bg)\". 기존엔 sidebar.accent 만 분리돼 있었고 일반 accent 가 없어 두 의미를 primary 에 묶어쓰던 케이스가 있었음 (Aifice 실사용 피드백).",
|
|
144
|
+
"**디폴트는 primary 와 동일 → 시각적 no-op** — 5 개 프리셋 (neutral/slate/rose/emerald/violet) 모두 light/dark 양쪽에 accent 디폴트가 primary 와 같은 값. 분리 의도가 없는 사용자는 아무 작업 없이도 기존 동작 그대로 유지. accent 를 분리하려면 tokens.css 에서 세 값만 override.",
|
|
145
|
+
"**적용 범위** — `@sh-ui/tokens` semantic.json + CLI 의 `theme-extract` / `decode` / `presets` / 6 개 templates 의 tokens.css + globals.css 의 `@theme inline` 매핑 + docs tokens.css + docs/create wizard 의 token 정의 + MCP `sh_ui_encode_theme` 스키마. 기존 base64 theme 은 OPTIONAL 키라 accent 없이도 정상 디코드 — 하위 호환.",
|
|
146
|
+
"**사용** — Tailwind utility `bg-accent` / `text-accent-foreground` 또는 CSS `var(--accent)` 그대로. 사이드바 hover/active bg (`--sidebar-accent`) 는 이 변경과 별개로 그대로 유지."
|
|
147
|
+
],
|
|
148
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.100.0"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"version": "0.99.0",
|
|
152
|
+
"date": "2026-05-21",
|
|
153
|
+
"title": "scroll-area — 커스텀 스크롤 컨테이너 신규 추가",
|
|
154
|
+
"type": "minor",
|
|
155
|
+
"highlights": [
|
|
156
|
+
"**`scroll-area` 컴포넌트 신규** — Base UI `ScrollArea` 위에 sh-ui 토큰 스타일을 얹은 composite. `<ScrollArea>` 하나로 viewport + scrollbar + thumb + corner 가 자동 구성되어 OS-native 스크롤바를 가리고 디자인 시스템 톤의 떠다니는 스크롤바를 보여준다. 사이드바·긴 리스트·채팅 메시지 영역처럼 워크스페이스 UI 전반에서 필요했던 패턴 (Aifice 실사용 피드백 반영).",
|
|
157
|
+
"**API** — `orientation: \"vertical\" | \"horizontal\" | \"both\"` (기본 vertical), `viewportClassName` 으로 viewport 의 패딩·flex 등 콘텐츠 레이아웃을 외곽 컨테이너 className 과 분리해 적용. 외곽 height/width 가 정해진 컨테이너 안에서 사용한다.",
|
|
158
|
+
"**a11y & motion** — Viewport 가 native 스크롤 컨테이너로 동작해 키보드 ↑/↓·PageUp/PageDown·Home/End 가 그대로 작동, focus 시 토큰 색 ring 표시, `prefers-reduced-motion` 시 fade in/out 트랜지션 제거. 스크롤바 thumb 색은 `--border-strong` 기본 + hover/scrolling 시 `--foreground-muted` 로 진해진다."
|
|
159
|
+
],
|
|
160
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.99.0"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"version": "0.98.1",
|
|
164
|
+
"date": "2026-05-19",
|
|
165
|
+
"title": "크로스컴포넌트 상대 import 를 aliases.components 로 재작성 — NodeNext 소비자 호환",
|
|
166
|
+
"type": "patch",
|
|
167
|
+
"highlights": [
|
|
168
|
+
"**크로스컴포넌트 import 가 이제 `aliases.components` 경유로 emit** — `sidebar`(→popover), `calendar`(→select), `date-picker`(→calendar), `markdown-editor`(→code-editor), `code-tabs`(→tabs/code-panel), `form-rhf`/`form-tanstack`/`form-yup`(→form) 등 형제 컴포넌트를 import 하던 모든 컴포넌트가, registry 의 `import … from \"../popover\"` 상대경로 대신 사용자 `sh-ui.config.json` 의 `aliases.components`(예: `@workspace/ui-core/components/popover`)로 재작성된다. `@SH_UI_UTILS@` → `aliases.utils` 치환과 동일한 add-time 변환.",
|
|
169
|
+
"**`moduleResolution: \"NodeNext\"`/`\"Node16\"` 소비자 깨짐 수정** — 상대경로/디렉터리 import 는 NodeNext 가 거부하고, 과거 일부 버전이 emit 한 `\"../popover/index.tsx\"` 의 명시적 `.tsx` 확장자는 TS5097 을 유발해 소비자가 `allowImportingTsExtensions`+`noEmit`(패키지 전체 declaration emit 비활성화) 같은 침습적 tsconfig 변경을 강요당했다. 이제 `\"Bundler\"`·`\"NodeNext\"` 양쪽에서 소비자 tsconfig 변경 없이 resolve 된다.",
|
|
170
|
+
"**add/remove 대칭 + registry 불변** — `remove` 의 unmodified 판정도 같은 재작성을 replay 해 형제-import 컴포넌트 삭제 시 '사용자 수정됨' false positive 가 나지 않는다. registry 소스·docs 듀얼카피·generated 산출물은 변경 없음(CLI 변환만). `aliases.components` 미설정 시 `@SH_UI_UTILS@` 와 동일한 톤의 친절 에러로 사전 안내. 회귀 단위 테스트 추가(전 스위트 407 통과, dual-copy drift 47 components 0)."
|
|
171
|
+
],
|
|
172
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.98.1"
|
|
173
|
+
},
|
|
5
174
|
{
|
|
6
175
|
"version": "0.98.0",
|
|
7
176
|
"date": "2026-05-16",
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ScrollArea as BaseScrollArea } from "@base-ui/react/scroll-area";
|
|
3
|
+
import styles from "./styles.module.css";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@SH_UI_UTILS@";
|
|
6
|
+
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
7
|
+
|
|
8
|
+
export interface ScrollAreaProps
|
|
9
|
+
extends WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseScrollArea.Root>> {
|
|
10
|
+
/**
|
|
11
|
+
* 스크롤 축. 콘텐츠가 양방향으로 넘치면 `"both"` 를 지정해 가로/세로 스크롤바를 함께 노출한다.
|
|
12
|
+
* @default "vertical"
|
|
13
|
+
*/
|
|
14
|
+
orientation?: "vertical" | "horizontal" | "both";
|
|
15
|
+
/**
|
|
16
|
+
* Viewport 컨테이너에 적용할 className.
|
|
17
|
+
*/
|
|
18
|
+
viewportClassName?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 콘텐츠가 넘칠 때 OS-native 스크롤바를 가리고 디자인 시스템 톤의 스크롤바를 떠다니게 보여주는 컨테이너.
|
|
23
|
+
*/
|
|
24
|
+
export const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
|
|
25
|
+
function ScrollArea(
|
|
26
|
+
{ className, viewportClassName, children, orientation = "vertical", ...props },
|
|
27
|
+
ref,
|
|
28
|
+
) {
|
|
29
|
+
const showVertical = orientation === "vertical" || orientation === "both";
|
|
30
|
+
const showHorizontal = orientation === "horizontal" || orientation === "both";
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<BaseScrollArea.Root
|
|
34
|
+
className={cn(styles["scroll-area__root"], className)}
|
|
35
|
+
{...props}
|
|
36
|
+
>
|
|
37
|
+
<BaseScrollArea.Viewport
|
|
38
|
+
ref={ref}
|
|
39
|
+
className={cn(styles["scroll-area__viewport"], viewportClassName)}
|
|
40
|
+
>
|
|
41
|
+
{children}
|
|
42
|
+
</BaseScrollArea.Viewport>
|
|
43
|
+
{showVertical && (
|
|
44
|
+
<BaseScrollArea.Scrollbar
|
|
45
|
+
orientation="vertical"
|
|
46
|
+
className={cn(
|
|
47
|
+
styles["scroll-area__scrollbar"],
|
|
48
|
+
styles["scroll-area__scrollbar--vertical"],
|
|
49
|
+
)}
|
|
50
|
+
>
|
|
51
|
+
<BaseScrollArea.Thumb className={styles["scroll-area__thumb"]} />
|
|
52
|
+
</BaseScrollArea.Scrollbar>
|
|
53
|
+
)}
|
|
54
|
+
{showHorizontal && (
|
|
55
|
+
<BaseScrollArea.Scrollbar
|
|
56
|
+
orientation="horizontal"
|
|
57
|
+
className={cn(
|
|
58
|
+
styles["scroll-area__scrollbar"],
|
|
59
|
+
styles["scroll-area__scrollbar--horizontal"],
|
|
60
|
+
)}
|
|
61
|
+
>
|
|
62
|
+
<BaseScrollArea.Thumb className={styles["scroll-area__thumb"]} />
|
|
63
|
+
</BaseScrollArea.Scrollbar>
|
|
64
|
+
)}
|
|
65
|
+
{orientation === "both" && (
|
|
66
|
+
<BaseScrollArea.Corner className={styles["scroll-area__corner"]} />
|
|
67
|
+
)}
|
|
68
|
+
</BaseScrollArea.Root>
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ScrollArea as BaseScrollArea } from "@base-ui/react/scroll-area";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@SH_UI_UTILS@";
|
|
5
|
+
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
6
|
+
|
|
7
|
+
export interface ScrollAreaProps
|
|
8
|
+
extends WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseScrollArea.Root>> {
|
|
9
|
+
orientation?: "vertical" | "horizontal" | "both";
|
|
10
|
+
viewportClassName?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
|
|
14
|
+
function ScrollArea(
|
|
15
|
+
{ className, viewportClassName, children, orientation = "vertical", ...props },
|
|
16
|
+
ref,
|
|
17
|
+
) {
|
|
18
|
+
const showVertical = orientation === "vertical" || orientation === "both";
|
|
19
|
+
const showHorizontal = orientation === "horizontal" || orientation === "both";
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<BaseScrollArea.Root className={cn("relative overflow-hidden", className)} {...props}>
|
|
23
|
+
<BaseScrollArea.Viewport
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn(
|
|
26
|
+
"h-full w-full outline-none [overscroll-behavior:contain] focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-ring focus-visible:[outline-offset:-2px]",
|
|
27
|
+
viewportClassName,
|
|
28
|
+
)}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</BaseScrollArea.Viewport>
|
|
32
|
+
{showVertical && (
|
|
33
|
+
<BaseScrollArea.Scrollbar
|
|
34
|
+
orientation="vertical"
|
|
35
|
+
className="flex w-2.5 touch-none select-none bg-transparent p-[2px] opacity-0 transition-opacity duration-150 hover:opacity-100 data-[hovering]:opacity-100 data-[scrolling]:opacity-100 motion-reduce:transition-none"
|
|
36
|
+
>
|
|
37
|
+
<BaseScrollArea.Thumb className="flex-1 rounded-full bg-border-strong transition-colors duration-100 hover:bg-foreground-muted [[data-scrolling]_&]:bg-foreground-muted motion-reduce:transition-none" />
|
|
38
|
+
</BaseScrollArea.Scrollbar>
|
|
39
|
+
)}
|
|
40
|
+
{showHorizontal && (
|
|
41
|
+
<BaseScrollArea.Scrollbar
|
|
42
|
+
orientation="horizontal"
|
|
43
|
+
className="flex h-2.5 flex-col touch-none select-none bg-transparent p-[2px] opacity-0 transition-opacity duration-150 hover:opacity-100 data-[hovering]:opacity-100 data-[scrolling]:opacity-100 motion-reduce:transition-none"
|
|
44
|
+
>
|
|
45
|
+
<BaseScrollArea.Thumb className="flex-1 rounded-full bg-border-strong transition-colors duration-100 hover:bg-foreground-muted [[data-scrolling]_&]:bg-foreground-muted motion-reduce:transition-none" />
|
|
46
|
+
</BaseScrollArea.Scrollbar>
|
|
47
|
+
)}
|
|
48
|
+
{orientation === "both" && (
|
|
49
|
+
<BaseScrollArea.Corner className="bg-transparent" />
|
|
50
|
+
)}
|
|
51
|
+
</BaseScrollArea.Root>
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ScrollArea as BaseScrollArea } from "@base-ui/react/scroll-area";
|
|
3
|
+
import "./styles.css";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@SH_UI_UTILS@";
|
|
6
|
+
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
7
|
+
|
|
8
|
+
export interface ScrollAreaProps
|
|
9
|
+
extends WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseScrollArea.Root>> {
|
|
10
|
+
/**
|
|
11
|
+
* 스크롤 축. 콘텐츠가 양방향으로 넘치면 `"both"` 를 지정해 가로/세로 스크롤바를 함께 노출한다.
|
|
12
|
+
* @default "vertical"
|
|
13
|
+
*/
|
|
14
|
+
orientation?: "vertical" | "horizontal" | "both";
|
|
15
|
+
/**
|
|
16
|
+
* Viewport 컨테이너에 적용할 className. 패딩·flex 같은 콘텐츠 레이아웃은 viewport 에 두는 것이 자연스럽다.
|
|
17
|
+
*/
|
|
18
|
+
viewportClassName?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 콘텐츠가 넘칠 때 OS-native 스크롤바를 가리고 디자인 시스템 톤의 스크롤바를 떠다니게 보여주는 컨테이너.
|
|
23
|
+
* `orientation` 으로 축을 지정하고, 콘텐츠 패딩 등은 `viewportClassName` 으로 viewport 에 둔다.
|
|
24
|
+
* 외부에서 height/width 를 줘야 스크롤이 발생한다 — 예: `<ScrollArea style={{ height: 240 }}>`.
|
|
25
|
+
*/
|
|
26
|
+
export const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
|
|
27
|
+
function ScrollArea(
|
|
28
|
+
{ className, viewportClassName, children, orientation = "vertical", ...props },
|
|
29
|
+
ref,
|
|
30
|
+
) {
|
|
31
|
+
const showVertical = orientation === "vertical" || orientation === "both";
|
|
32
|
+
const showHorizontal = orientation === "horizontal" || orientation === "both";
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<BaseScrollArea.Root
|
|
36
|
+
className={cn("sh-ui-scroll-area__root", className)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
<BaseScrollArea.Viewport
|
|
40
|
+
ref={ref}
|
|
41
|
+
className={cn("sh-ui-scroll-area__viewport", viewportClassName)}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</BaseScrollArea.Viewport>
|
|
45
|
+
{showVertical && (
|
|
46
|
+
<BaseScrollArea.Scrollbar
|
|
47
|
+
orientation="vertical"
|
|
48
|
+
className="sh-ui-scroll-area__scrollbar sh-ui-scroll-area__scrollbar--vertical"
|
|
49
|
+
>
|
|
50
|
+
<BaseScrollArea.Thumb className="sh-ui-scroll-area__thumb" />
|
|
51
|
+
</BaseScrollArea.Scrollbar>
|
|
52
|
+
)}
|
|
53
|
+
{showHorizontal && (
|
|
54
|
+
<BaseScrollArea.Scrollbar
|
|
55
|
+
orientation="horizontal"
|
|
56
|
+
className="sh-ui-scroll-area__scrollbar sh-ui-scroll-area__scrollbar--horizontal"
|
|
57
|
+
>
|
|
58
|
+
<BaseScrollArea.Thumb className="sh-ui-scroll-area__thumb" />
|
|
59
|
+
</BaseScrollArea.Scrollbar>
|
|
60
|
+
)}
|
|
61
|
+
{orientation === "both" && (
|
|
62
|
+
<BaseScrollArea.Corner className="sh-ui-scroll-area__corner" />
|
|
63
|
+
)}
|
|
64
|
+
</BaseScrollArea.Root>
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
.sh-ui-scroll-area__root {
|
|
2
|
+
position: relative;
|
|
3
|
+
overflow: hidden;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.sh-ui-scroll-area__viewport {
|
|
7
|
+
width: 100%;
|
|
8
|
+
height: 100%;
|
|
9
|
+
overscroll-behavior: contain;
|
|
10
|
+
outline: none;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.sh-ui-scroll-area__viewport:focus-visible {
|
|
14
|
+
outline: var(--border-width-strong) solid var(--ring);
|
|
15
|
+
outline-offset: -2px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.sh-ui-scroll-area__scrollbar {
|
|
19
|
+
display: flex;
|
|
20
|
+
touch-action: none;
|
|
21
|
+
user-select: none;
|
|
22
|
+
padding: 2px;
|
|
23
|
+
background: transparent;
|
|
24
|
+
opacity: 0;
|
|
25
|
+
transition: opacity 160ms ease;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.sh-ui-scroll-area__scrollbar--vertical {
|
|
29
|
+
width: 0.625rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.sh-ui-scroll-area__scrollbar--horizontal {
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
height: 0.625rem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.sh-ui-scroll-area__root:hover .sh-ui-scroll-area__scrollbar,
|
|
38
|
+
.sh-ui-scroll-area__scrollbar[data-hovering],
|
|
39
|
+
.sh-ui-scroll-area__scrollbar[data-scrolling] {
|
|
40
|
+
opacity: 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.sh-ui-scroll-area__thumb {
|
|
44
|
+
flex: 1;
|
|
45
|
+
background: var(--border-strong);
|
|
46
|
+
border-radius: 9999px;
|
|
47
|
+
transition: background-color 120ms ease;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.sh-ui-scroll-area__scrollbar:hover .sh-ui-scroll-area__thumb,
|
|
51
|
+
.sh-ui-scroll-area__scrollbar[data-scrolling] .sh-ui-scroll-area__thumb {
|
|
52
|
+
background: var(--foreground-muted);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.sh-ui-scroll-area__corner {
|
|
56
|
+
background: transparent;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@media (prefers-reduced-motion: reduce) {
|
|
60
|
+
.sh-ui-scroll-area__scrollbar,
|
|
61
|
+
.sh-ui-scroll-area__thumb {
|
|
62
|
+
transition: none;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
.scroll-area__root {
|
|
2
|
+
position: relative;
|
|
3
|
+
overflow: hidden;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.scroll-area__viewport {
|
|
7
|
+
width: 100%;
|
|
8
|
+
height: 100%;
|
|
9
|
+
overscroll-behavior: contain;
|
|
10
|
+
outline: none;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.scroll-area__viewport:focus-visible {
|
|
14
|
+
outline: var(--border-width-strong) solid var(--ring);
|
|
15
|
+
outline-offset: -2px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.scroll-area__scrollbar {
|
|
19
|
+
display: flex;
|
|
20
|
+
touch-action: none;
|
|
21
|
+
user-select: none;
|
|
22
|
+
padding: 2px;
|
|
23
|
+
background: transparent;
|
|
24
|
+
opacity: 0;
|
|
25
|
+
transition: opacity 160ms ease;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.scroll-area__scrollbar--vertical {
|
|
29
|
+
width: 0.625rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.scroll-area__scrollbar--horizontal {
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
height: 0.625rem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.scroll-area__root:hover .scroll-area__scrollbar,
|
|
38
|
+
.scroll-area__scrollbar[data-hovering],
|
|
39
|
+
.scroll-area__scrollbar[data-scrolling] {
|
|
40
|
+
opacity: 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.scroll-area__thumb {
|
|
44
|
+
flex: 1;
|
|
45
|
+
background: var(--border-strong);
|
|
46
|
+
border-radius: 9999px;
|
|
47
|
+
transition: background-color 120ms ease;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.scroll-area__scrollbar:hover .scroll-area__thumb,
|
|
51
|
+
.scroll-area__scrollbar[data-scrolling] .scroll-area__thumb {
|
|
52
|
+
background: var(--foreground-muted);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.scroll-area__corner {
|
|
56
|
+
background: transparent;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@media (prefers-reduced-motion: reduce) {
|
|
60
|
+
.scroll-area__scrollbar,
|
|
61
|
+
.scroll-area__thumb {
|
|
62
|
+
transition: none;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Drawer as BaseDrawer } from "@base-ui/react/drawer";
|
|
3
|
+
import styles from "./styles.module.css";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@SH_UI_UTILS@";
|
|
6
|
+
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 화면 가장자리에서 슬라이드 인 하는 side drawer 컨테이너.
|
|
10
|
+
*/
|
|
11
|
+
export const Sheet = BaseDrawer.Root;
|
|
12
|
+
|
|
13
|
+
export const SheetTrigger = BaseDrawer.Trigger;
|
|
14
|
+
export const SheetClose = BaseDrawer.Close;
|
|
15
|
+
|
|
16
|
+
/** 우상단에 배치되는 X 닫기 버튼. */
|
|
17
|
+
export function SheetCloseX({
|
|
18
|
+
className,
|
|
19
|
+
children,
|
|
20
|
+
...props
|
|
21
|
+
}: React.ButtonHTMLAttributes<HTMLButtonElement>) {
|
|
22
|
+
return (
|
|
23
|
+
<BaseDrawer.Close
|
|
24
|
+
className={cn(styles["sheet__close"], className)}
|
|
25
|
+
aria-label="닫기"
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
{children ?? "×"}
|
|
29
|
+
</BaseDrawer.Close>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function SheetHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
34
|
+
return <div className={cn(styles["sheet__header"], className)} {...props} />;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function SheetFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
38
|
+
return <div className={cn(styles["sheet__footer"], className)} {...props} />;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SheetContentProps
|
|
42
|
+
extends WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseDrawer.Popup>> {
|
|
43
|
+
/**
|
|
44
|
+
* Sheet 가 슬라이드 인 하는 방향.
|
|
45
|
+
* @default "right"
|
|
46
|
+
*/
|
|
47
|
+
side?: "right" | "left" | "top" | "bottom";
|
|
48
|
+
container?: React.ComponentPropsWithoutRef<typeof BaseDrawer.Portal>["container"];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const SheetContent = React.forwardRef<HTMLDivElement, SheetContentProps>(
|
|
52
|
+
function SheetContent({ className, children, side = "right", container, ...props }, ref) {
|
|
53
|
+
return (
|
|
54
|
+
<BaseDrawer.Portal container={container}>
|
|
55
|
+
<BaseDrawer.Backdrop className={styles["sheet__backdrop"]} />
|
|
56
|
+
<BaseDrawer.Popup
|
|
57
|
+
ref={ref}
|
|
58
|
+
data-side={side}
|
|
59
|
+
className={cn(styles["sheet__content"], className)}
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
</BaseDrawer.Popup>
|
|
64
|
+
</BaseDrawer.Portal>
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
export const SheetTitle = React.forwardRef<
|
|
70
|
+
HTMLHeadingElement,
|
|
71
|
+
WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseDrawer.Title>>
|
|
72
|
+
>(function SheetTitle({ className, ...props }, ref) {
|
|
73
|
+
return (
|
|
74
|
+
<BaseDrawer.Title
|
|
75
|
+
ref={ref}
|
|
76
|
+
className={cn(styles["sheet__title"], className)}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export const SheetDescription = React.forwardRef<
|
|
83
|
+
HTMLParagraphElement,
|
|
84
|
+
WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseDrawer.Description>>
|
|
85
|
+
>(function SheetDescription({ className, ...props }, ref) {
|
|
86
|
+
return (
|
|
87
|
+
<BaseDrawer.Description
|
|
88
|
+
ref={ref}
|
|
89
|
+
className={cn(styles["sheet__description"], className)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
});
|