sh-ui-cli 0.59.8 → 0.61.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.
Files changed (94) hide show
  1. package/data/changelog/versions.json +53 -0
  2. package/data/registry/flutter/widgets/sh_ui_input.dart +0 -79
  3. package/data/registry/react/components/input/index.module.tsx +0 -70
  4. package/data/registry/react/components/input/index.tailwind.tsx +0 -53
  5. package/data/registry/react/components/input/index.tsx +0 -70
  6. package/data/registry/react/components/input/index.vanilla-extract.tsx +0 -63
  7. package/data/summaries/react.json +1 -1
  8. package/package.json +2 -2
  9. package/src/create/architectures/flat.js +1 -1
  10. package/src/create/generator.js +717 -17
  11. package/src/create/index.mjs +1 -1
  12. package/src/create/plugins/authJwt.js +51 -1
  13. package/src/create/plugins/nextIntl.js +171 -17
  14. package/src/create/plugins/sentry.js +43 -23
  15. package/src/mcp.mjs +2 -2
  16. package/templates/flutter-standalone/sh-ui.config.json +0 -1
  17. package/templates/monorepo/README.md +14 -5
  18. package/templates/monorepo/packages/eslint-config/flat.js +71 -0
  19. package/templates/monorepo/packages/eslint-config/fsd.js +0 -21
  20. package/templates/monorepo/packages/eslint-config/package.json +2 -3
  21. package/templates/monorepo/packages/typescript-config/package.json +6 -1
  22. package/templates/monorepo/packages/ui/ui-core/tsconfig.json +1 -1
  23. package/templates/monorepo/pnpm-workspace.yaml +2 -1
  24. package/templates/nextjs-app/.env.example +3 -2
  25. package/templates/nextjs-app/Dockerfile +36 -5
  26. package/templates/nextjs-app/README.md +9 -7
  27. package/templates/nextjs-app/_arch/flat/app/api/proxy/[...path]/route.ts +1 -1
  28. package/templates/nextjs-app/_arch/flat/app/layout.tsx +2 -2
  29. package/templates/nextjs-app/_arch/flat/components/common/PrefetchBoundary/index.tsx +1 -1
  30. package/templates/nextjs-app/_arch/flat/components/layouts/RootLayout.tsx +2 -0
  31. package/templates/nextjs-app/_arch/flat/components/providers/GlobalProvider/index.tsx +3 -3
  32. package/templates/nextjs-app/_arch/flat/components/providers/theme/ThemeProvider.tsx +12 -0
  33. package/templates/nextjs-app/_arch/flat/eslint.config.js +10 -0
  34. package/templates/nextjs-app/_arch/flat/lib/api/clientFetch.ts +4 -4
  35. package/templates/nextjs-app/_arch/flat/lib/api/errorMessages.ts +37 -0
  36. package/templates/nextjs-app/_arch/flat/lib/hooks/useAppMutation.ts +14 -7
  37. package/templates/nextjs-app/_arch/flat/lib/test/renderWithProviders.tsx +32 -7
  38. package/templates/nextjs-app/_arch/flat/lib/utils/formatDate.ts +4 -0
  39. package/templates/nextjs-app/_arch/flat/lib/utils/formatPrice.ts +13 -5
  40. package/templates/nextjs-app/_arch/flat/lib/utils/getQueryClient.ts +1 -1
  41. package/templates/nextjs-app/_arch/flat/tsconfig.json +0 -1
  42. package/templates/nextjs-app/_arch/fsd/app/api/proxy/[...path]/route.ts +1 -1
  43. package/templates/nextjs-app/_arch/fsd/app/layout.tsx +2 -2
  44. package/templates/nextjs-app/_arch/fsd/src/app/layouts/RootLayout.tsx +2 -0
  45. package/templates/nextjs-app/_arch/fsd/src/app/providers/GlobalProvider/index.tsx +3 -3
  46. package/templates/nextjs-app/_arch/fsd/src/app/providers/theme/ThemeProvider.tsx +12 -0
  47. package/templates/nextjs-app/_arch/fsd/src/shared/api/clientFetch.ts +4 -4
  48. package/templates/nextjs-app/_arch/fsd/src/shared/api/errorMessages.ts +37 -0
  49. package/templates/nextjs-app/_arch/fsd/src/shared/hooks/useAppMutation.ts +14 -7
  50. package/templates/nextjs-app/_arch/fsd/src/shared/lib/formatDate.ts +4 -0
  51. package/templates/nextjs-app/_arch/fsd/src/shared/lib/formatPrice.ts +13 -5
  52. package/templates/nextjs-app/_arch/fsd/src/shared/lib/getQueryClient.ts +1 -1
  53. package/templates/nextjs-app/_arch/fsd/src/shared/test/renderWithProviders.tsx +32 -7
  54. package/templates/nextjs-app/_arch/fsd/src/shared/ui/PrefetchBoundary/index.tsx +1 -1
  55. package/templates/nextjs-app/vitest.config.ts +4 -0
  56. package/templates/nextjs-standalone/.env.example +3 -2
  57. package/templates/nextjs-standalone/Dockerfile +35 -0
  58. package/templates/nextjs-standalone/_arch/flat/app/api/proxy/[...path]/route.ts +1 -1
  59. package/templates/nextjs-standalone/_arch/flat/app/layout.tsx +2 -2
  60. package/templates/nextjs-standalone/_arch/flat/components/common/PrefetchBoundary/index.tsx +1 -1
  61. package/templates/nextjs-standalone/_arch/flat/components/layouts/RootLayout.tsx +2 -0
  62. package/templates/nextjs-standalone/_arch/flat/components/providers/GlobalProvider/index.tsx +3 -3
  63. package/templates/nextjs-standalone/_arch/flat/components/providers/theme/ThemeProvider.tsx +12 -0
  64. package/templates/nextjs-standalone/_arch/flat/eslint.config.js +123 -0
  65. package/templates/nextjs-standalone/_arch/flat/lib/api/clientFetch.ts +4 -4
  66. package/templates/nextjs-standalone/_arch/flat/lib/api/errorMessages.ts +37 -0
  67. package/templates/nextjs-standalone/_arch/flat/lib/hooks/useAppMutation.ts +14 -7
  68. package/templates/nextjs-standalone/_arch/flat/lib/test/renderWithProviders.tsx +32 -7
  69. package/templates/nextjs-standalone/_arch/flat/lib/utils/formatDate.ts +4 -0
  70. package/templates/nextjs-standalone/_arch/flat/lib/utils/formatPrice.ts +13 -5
  71. package/templates/nextjs-standalone/_arch/flat/lib/utils/getQueryClient.ts +1 -1
  72. package/templates/nextjs-standalone/_arch/flat/tsconfig.json +1 -2
  73. package/templates/nextjs-standalone/_arch/fsd/app/api/proxy/[...path]/route.ts +1 -1
  74. package/templates/nextjs-standalone/_arch/fsd/app/layout.tsx +2 -2
  75. package/templates/nextjs-standalone/_arch/fsd/src/app/layouts/RootLayout.tsx +2 -0
  76. package/templates/nextjs-standalone/_arch/fsd/src/app/providers/GlobalProvider/index.tsx +3 -3
  77. package/templates/nextjs-standalone/_arch/fsd/src/app/providers/theme/ThemeProvider.tsx +12 -0
  78. package/templates/nextjs-standalone/_arch/fsd/src/shared/api/clientFetch.ts +4 -4
  79. package/templates/nextjs-standalone/_arch/fsd/src/shared/api/errorMessages.ts +37 -0
  80. package/templates/nextjs-standalone/_arch/fsd/src/shared/hooks/useAppMutation.ts +14 -7
  81. package/templates/nextjs-standalone/_arch/fsd/src/shared/lib/formatDate.ts +4 -0
  82. package/templates/nextjs-standalone/_arch/fsd/src/shared/lib/formatPrice.ts +13 -5
  83. package/templates/nextjs-standalone/_arch/fsd/src/shared/lib/getQueryClient.ts +1 -1
  84. package/templates/nextjs-standalone/_arch/fsd/src/shared/test/renderWithProviders.tsx +32 -7
  85. package/templates/nextjs-standalone/_arch/fsd/src/shared/ui/PrefetchBoundary/index.tsx +1 -1
  86. package/templates/nextjs-standalone/eslint.config.js +0 -15
  87. package/templates/nextjs-standalone/package.json +0 -2
  88. package/templates/ui-app-template/package.json +2 -2
  89. package/templates/ui-app-template/postcss.config.mjs +1 -1
  90. package/templates/monorepo/.eslintrc.js +0 -8
  91. package/templates/nextjs-app/_arch/flat/components/providers/theme/ThemeProviders.tsx +0 -12
  92. package/templates/nextjs-app/_arch/fsd/src/app/providers/theme/ThemeProviders.tsx +0 -12
  93. package/templates/nextjs-standalone/_arch/flat/components/providers/theme/ThemeProviders.tsx +0 -12
  94. package/templates/nextjs-standalone/_arch/fsd/src/app/providers/theme/ThemeProviders.tsx +0 -12
@@ -2,6 +2,59 @@
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.61.0",
7
+ "date": "2026-05-07",
8
+ "title": "create CLI 대규모 정비 — cssFramework 진짜 분기 + i18n hooks + 49건 결함 수정",
9
+ "type": "minor",
10
+ "highlights": [
11
+ "**`--css plain` / `--css css-modules` 가 진짜로 분기 emit** — 이전엔 `sh-ui.config.json` 의 cssFramework 필드만 바뀌고 페이지/스타일은 모두 Tailwind 동일했음. 이제 plain 은 `tailwindcss` 의존 없음 + `page.tsx` 가 inline style, css-modules 는 `page.module.css` + class import. sentry 의 `error.tsx`, monorepo root `.prettierrc` / `package.json` 까지 cssFramework 별로 정리.",
12
+ "**i18n-aware 포맷 hook + error code mapping 신규** — next-intl 활성 시 `useFormatDate()` / `useFormatPrice(currency)` hook emit (현재 locale 자동). 새 `errorMessages.ts` + `resolveErrorMessage()` 가 backend raw 메시지 직접 노출 대신 코드별 안전 메시지로 변환. `useAppMutation` 이 자동 사용.",
13
+ "**플러그인 emit 결함 일괄 수정** — auth-jwt 가 redirect 만 시키고 sign-in 페이지를 안 만들어 무한 404 루프에 빠지던 문제 (placeholder 페이지 emit). `[locale]/error.tsx` 가 i18n messages 를 사용 안 해 dead code 였던 것 (`useTranslations` + `Link` from i18n navigation). `clientFetch` 의 `/sign-in` redirect 가 locale prefix 무시해 루프 위험이던 것 (regex 매칭).",
14
+ "**fs-extra → Node native fs/promises 마이그레이션** — long-running MCP daemon 환경에서 fs-extra v11 의 internal state 누적으로 `fs.copy` filter 가 부분 실패하던 회귀를 native `fs.cp` 로 우회. `ensureArchCleanup` + `assertArchOverlayApplied` invariant 가드 추가.",
15
+ "**작은 정리 모음** — `.eslintrc.js` (legacy ESLint 8) 제거, `Dockerfile` multi-stage build 로 교체, `getQueryClient` default → named export, `ThemeProviders` → `ThemeProvider` rename, deps 알파벳 정렬, flat arch 전용 `eslint-config/flat` 신설, Flutter `sh-ui.config.json` 의 의미 없는 `cssFramework` 필드 제거. 96 combo install + typecheck + build 회귀 가드 통과."
16
+ ],
17
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.61.0"
18
+ },
19
+ {
20
+ "version": "0.60.0",
21
+ "date": "2026-05-06",
22
+ "title": "BREAKING — PasswordInput 제거, InputGroup 레시피로 강등",
23
+ "type": "minor",
24
+ "highlights": [
25
+ "**`PasswordInput` (React) / `ShUiPasswordInput` (Flutter) export 제거** — 표시/숨김 토글만 들어 있던 얇은 wrapper 였고, 아이콘 교체·왼쪽 자물쇠 같은 변형 요청을 받기 시작하면 prop 폭발 → `InputGroup` + `InputAdornment` 합성으로 대체.",
26
+ "**Input 페이지 신규 섹션 “비밀번호 토글 (레시피)”** — 우측 눈 아이콘 / 좌측 자물쇠 + 우측 토글 두 가지 라이브 데모. `aria-pressed`, `tabIndex={-1}`, `InputAdornment interactive` 등 a11y 디테일을 그대로 노출해 사용자가 자신의 코드로 복사해 자유롭게 변형.",
27
+ "**마이그레이션** — `<PasswordInput placeholder=\"…\" />` → `<Input type=\"password\" placeholder=\"…\" />` (단순 비밀번호 입력) 또는 docs 의 InputGroup 레시피를 복붙(토글이 필요한 경우). Flutter 는 `ShUiInput(obscureText: true)`.",
28
+ "**왜 컴포넌트가 아니라 레시피인가** — 토글 자체에는 알고리즘 가치가 없고 사용자마다 아이콘·위치·동작 요구가 갈린다. 같은 모듈의 NumberInput / PhoneInput / BusinessNumberInput 은 포맷·체크섬 로직이 자산이라 컴포넌트로 유지."
29
+ ],
30
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.60.0"
31
+ },
32
+ {
33
+ "version": "0.59.10",
34
+ "date": "2026-05-06",
35
+ "title": "fix — docs 우측 TOC · 사이드바 active 하이라이트 (i18n locale prefix)",
36
+ "type": "patch",
37
+ "highlights": [
38
+ "**우측 목차(TOC)가 컴포넌트 페이지에서 사라진 문제 수정** — i18n 도입 후 `usePathname()` 이 `/en/components/...` / `/ko/components/...` 를 반환하는데, `TocSlot` 가드가 여전히 `/components/` prefix 만 검사해 항상 false → TOC 가 mount 되지 않았다.",
39
+ "**사이드바 active 하이라이트 동시 복구** — `app-sidebar.tsx` 의 `componentsActive` / `pluginsActive` / `architecturesActive` 와 `isActive(href)` 모두 raw pathname 으로 비교하던 걸 locale prefix 를 벗긴 `withoutLocale` 기준으로 통일.",
40
+ "**가드 방식** — `pathname.replace(/^\\/[a-z]{2}(?=\\/|$)/, \"\")` 로 두 글자 locale 코드만 안전하게 제거 (`/en` / `/ko` 외 라우트는 그대로 통과).",
41
+ "**검증** — `/ko/components/button`, `/en/components/card`, `/ko/architectures/fsd` 세 경로에서 TOC 표시 여부와 사이드바 `data-active` 가 의도대로 동작하는지 브라우저로 확인."
42
+ ],
43
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.59.10"
44
+ },
45
+ {
46
+ "version": "0.59.9",
47
+ "date": "2026-05-06",
48
+ "title": "fix — next-intl standalone 빌드 (globals.css 상대 경로 보정)",
49
+ "type": "patch",
50
+ "highlights": [
51
+ "**`sh-ui-cli create --plugins next-intl --structure standalone` prod 빌드 폭발 수정** — next-intl 플러그인이 `app/layout.tsx` 를 `app/[locale]/layout.tsx` 로 이동하면서 보존하는 side-effect import (`import './globals.css';`) 의 상대 경로를 보정하지 않아, 한 단계 깊어진 새 위치에서 `./globals.css` 가 미해결 모듈이 됐다. `pnpm build` (`next build`) 가 `Module not found: Can't resolve './globals.css'` 로 실패.",
52
+ "**경로 보정** — `nextIntl.js` 의 `contentFn` 에서 보존되는 side-effect import 의 `./` / `../` 접두사를 디렉토리 깊이 변화(1단계)만큼 끌어올림. 절대 경로(`/x`) 와 모듈명(`polyfills`) 은 그대로.",
53
+ "**회귀 가드 강화** — smoke 시나리오 3 의 globals.css 검사가 `/[^'\"]*globals\\.css/` 단순 존재만 봤어서 v0.59.8 까지 누락. `import '../globals.css';` 정확 매치 + `import './globals.css';` 부재로 강화.",
54
+ "**전수 검증** — 36-combo 스캐폴드 매트릭스(structure × arch × css × plugins) 의 standalone+all-plugins 6 케이스 모두 fix 후 prod 빌드 성공 확인. monorepo+all-plugins 는 원래부터 정상."
55
+ ],
56
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.59.9"
57
+ },
5
58
  {
6
59
  "version": "0.59.8",
7
60
  "date": "2026-05-06",
@@ -158,85 +158,6 @@ class _ShUiInputState extends State<ShUiInput> {
158
158
  }
159
159
  }
160
160
 
161
- /* ───────── ShUiPasswordInput ───────── */
162
-
163
- class ShUiPasswordInput extends StatefulWidget {
164
- final TextEditingController? controller;
165
- final String? placeholder;
166
- final bool enabled;
167
- final bool invalid;
168
- final bool hideToggle;
169
- final ValueChanged<String>? onChanged;
170
- final ValueChanged<String>? onSubmitted;
171
- final FocusNode? focusNode;
172
-
173
- const ShUiPasswordInput({
174
- super.key,
175
- this.controller,
176
- this.placeholder,
177
- this.enabled = true,
178
- this.invalid = false,
179
- this.hideToggle = false,
180
- this.onChanged,
181
- this.onSubmitted,
182
- this.focusNode,
183
- });
184
-
185
- @override
186
- State<ShUiPasswordInput> createState() => _ShUiPasswordInputState();
187
- }
188
-
189
- class _ShUiPasswordInputState extends State<ShUiPasswordInput> {
190
- bool _visible = false;
191
-
192
- @override
193
- Widget build(BuildContext context) {
194
- return ShUiInput(
195
- controller: widget.controller,
196
- placeholder: widget.placeholder,
197
- enabled: widget.enabled,
198
- invalid: widget.invalid,
199
- obscureText: !_visible,
200
- onChanged: widget.onChanged,
201
- onSubmitted: widget.onSubmitted,
202
- focusNode: widget.focusNode,
203
- suffix: widget.hideToggle
204
- ? null
205
- : _ToggleButton(
206
- visible: _visible,
207
- onToggle: () => setState(() => _visible = !_visible),
208
- ),
209
- );
210
- }
211
- }
212
-
213
- class _ToggleButton extends StatelessWidget {
214
- final bool visible;
215
- final VoidCallback onToggle;
216
- const _ToggleButton({required this.visible, required this.onToggle});
217
-
218
- @override
219
- Widget build(BuildContext context) {
220
- final shUi = Theme.of(context).extension<ShUiTheme>() ?? ShUiTheme.light;
221
- return Semantics(
222
- button: true,
223
- label: visible ? '비밀번호 숨기기' : '비밀번호 표시',
224
- child: InkWell(
225
- onTap: onToggle,
226
- borderRadius: BorderRadius.circular(shUi.radius.defaultRadius - 2),
227
- child: Padding(
228
- padding: EdgeInsets.all(shUi.spacing.s2),
229
- child: Icon(
230
- visible ? Icons.visibility_off_outlined : Icons.visibility_outlined,
231
- size: 18,
232
- color: shUi.colors.foregroundMuted,
233
- ),
234
- ),
235
- ),
236
- );
237
- }
238
- }
239
-
240
161
  /* ───────── ShUiNumberInput ─────────
241
162
  * 정수 입력 + 천 단위 콤마(옵션). onChanged는 num? 콜백.
242
163
  * blur 시 min/max로 clamp.
@@ -158,76 +158,6 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
158
158
  );
159
159
  Input.displayName = "Input";
160
160
 
161
- /* ───────── PasswordInput ───────── */
162
-
163
- function EyeIcon() {
164
- return (
165
- <svg viewBox="0 0 20 20" width="16" height="16" fill="none" aria-hidden>
166
- <path
167
- d="M2 10s3-5.5 8-5.5S18 10 18 10s-3 5.5-8 5.5S2 10 2 10Z"
168
- stroke="currentColor"
169
- strokeWidth="1.5"
170
- />
171
- <circle cx="10" cy="10" r="2.25" stroke="currentColor" strokeWidth="1.5" />
172
- </svg>
173
- );
174
- }
175
-
176
- function EyeOffIcon() {
177
- return (
178
- <svg viewBox="0 0 20 20" width="16" height="16" fill="none" aria-hidden>
179
- <path
180
- d="M3 3l14 14M8 5a8 8 0 0 1 2-.3c5 0 8 5.3 8 5.3a13 13 0 0 1-2.3 2.9M12 12a2.5 2.5 0 0 1-3.4-3.4m-2.3-2.5A13 13 0 0 0 2 10s3 5.5 8 5.5a8 8 0 0 0 3.3-.7"
181
- stroke="currentColor"
182
- strokeWidth="1.5"
183
- strokeLinecap="round"
184
- />
185
- </svg>
186
- );
187
- }
188
-
189
- export interface PasswordInputProps extends Omit<InputProps, "type" | "suffix"> {
190
- /**
191
- * 비밀번호 표시 토글 버튼을 숨긴다. 비밀번호를 절대 노출하면 안 되는 화면(결제 등)에서 사용.
192
- *
193
- * @default false
194
- */
195
- hideToggle?: boolean;
196
- }
197
-
198
- /**
199
- * 비밀번호 입력. 기본으로 표시 토글 버튼이 suffix에 부착되며 `hideToggle`로 숨길 수 있다.
200
- * 토글은 `aria-pressed`로 상태가 노출되고 Tab 흐름에서 제외(`tabIndex=-1`)된다.
201
- */
202
- export const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
203
- ({ hideToggle, ...props }, ref) => {
204
- const [visible, setVisible] = React.useState(false);
205
-
206
- const toggle = hideToggle ? undefined : (
207
- <button
208
- type="button"
209
- className={styles.toggle}
210
- onClick={() => setVisible((v) => !v)}
211
- aria-label={visible ? "비밀번호 숨기기" : "비밀번호 표시"}
212
- aria-pressed={visible}
213
- tabIndex={-1}
214
- >
215
- {visible ? <EyeOffIcon /> : <EyeIcon />}
216
- </button>
217
- );
218
-
219
- return (
220
- <Input
221
- ref={ref}
222
- type={visible ? "text" : "password"}
223
- suffix={toggle}
224
- {...props}
225
- />
226
- );
227
- },
228
- );
229
- PasswordInput.displayName = "PasswordInput";
230
-
231
161
  /* ───────── NumberInput ─────────
232
162
  * 정수 입력 + 천 단위 콤마(옵션). value/onValueChange는 number | undefined.
233
163
  */
@@ -146,59 +146,6 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
146
146
  );
147
147
  Input.displayName = "Input";
148
148
 
149
- /* ───────── PasswordInput ───────── */
150
-
151
- const passwordToggleClasses =
152
- "inline-flex items-center justify-center w-8 h-8 p-0 bg-transparent border-none rounded-[calc(var(--radius)-2px)] text-foreground-muted cursor-pointer transition-[color,background-color] duration-[var(--duration-fast)] hover:text-foreground hover:bg-background-muted focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-ring focus-visible:outline-offset-2";
153
-
154
- function EyeIcon() {
155
- return (
156
- <svg viewBox="0 0 20 20" width="16" height="16" fill="none" aria-hidden>
157
- <path d="M2 10s3-5.5 8-5.5S18 10 18 10s-3 5.5-8 5.5S2 10 2 10Z" stroke="currentColor" strokeWidth="1.5" />
158
- <circle cx="10" cy="10" r="2.25" stroke="currentColor" strokeWidth="1.5" />
159
- </svg>
160
- );
161
- }
162
-
163
- function EyeOffIcon() {
164
- return (
165
- <svg viewBox="0 0 20 20" width="16" height="16" fill="none" aria-hidden>
166
- <path
167
- d="M3 3l14 14M8 5a8 8 0 0 1 2-.3c5 0 8 5.3 8 5.3a13 13 0 0 1-2.3 2.9M12 12a2.5 2.5 0 0 1-3.4-3.4m-2.3-2.5A13 13 0 0 0 2 10s3 5.5 8 5.5a8 8 0 0 0 3.3-.7"
168
- stroke="currentColor"
169
- strokeWidth="1.5"
170
- strokeLinecap="round"
171
- />
172
- </svg>
173
- );
174
- }
175
-
176
- export interface PasswordInputProps extends Omit<InputProps, "type" | "suffix"> {
177
- hideToggle?: boolean;
178
- }
179
-
180
- export const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
181
- ({ hideToggle, ...props }, ref) => {
182
- const [visible, setVisible] = React.useState(false);
183
-
184
- const toggle = hideToggle ? undefined : (
185
- <button
186
- type="button"
187
- className={passwordToggleClasses}
188
- onClick={() => setVisible((v) => !v)}
189
- aria-label={visible ? "비밀번호 숨기기" : "비밀번호 표시"}
190
- aria-pressed={visible}
191
- tabIndex={-1}
192
- >
193
- {visible ? <EyeOffIcon /> : <EyeIcon />}
194
- </button>
195
- );
196
-
197
- return <Input ref={ref} type={visible ? "text" : "password"} suffix={toggle} {...props} />;
198
- },
199
- );
200
- PasswordInput.displayName = "PasswordInput";
201
-
202
149
  /* ───────── NumberInput / PhoneInput / BusinessNumberInput
203
150
  * 이 세 컴포넌트는 Input 을 wrap 만 — 자체 className 사용 안 함.
204
151
  * plain 변종과 동일한 로직을 그대로 노출. */
@@ -165,76 +165,6 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
165
165
  );
166
166
  Input.displayName = "Input";
167
167
 
168
- /* ───────── PasswordInput ───────── */
169
-
170
- function EyeIcon() {
171
- return (
172
- <svg viewBox="0 0 20 20" width="16" height="16" fill="none" aria-hidden>
173
- <path
174
- d="M2 10s3-5.5 8-5.5S18 10 18 10s-3 5.5-8 5.5S2 10 2 10Z"
175
- stroke="currentColor"
176
- strokeWidth="1.5"
177
- />
178
- <circle cx="10" cy="10" r="2.25" stroke="currentColor" strokeWidth="1.5" />
179
- </svg>
180
- );
181
- }
182
-
183
- function EyeOffIcon() {
184
- return (
185
- <svg viewBox="0 0 20 20" width="16" height="16" fill="none" aria-hidden>
186
- <path
187
- d="M3 3l14 14M8 5a8 8 0 0 1 2-.3c5 0 8 5.3 8 5.3a13 13 0 0 1-2.3 2.9M12 12a2.5 2.5 0 0 1-3.4-3.4m-2.3-2.5A13 13 0 0 0 2 10s3 5.5 8 5.5a8 8 0 0 0 3.3-.7"
188
- stroke="currentColor"
189
- strokeWidth="1.5"
190
- strokeLinecap="round"
191
- />
192
- </svg>
193
- );
194
- }
195
-
196
- export interface PasswordInputProps extends Omit<InputProps, "type" | "suffix"> {
197
- /**
198
- * 비밀번호 표시 토글 버튼을 숨긴다. 비밀번호를 절대 노출하면 안 되는 화면(결제 등)에서 사용.
199
- *
200
- * @default false
201
- */
202
- hideToggle?: boolean;
203
- }
204
-
205
- /**
206
- * 비밀번호 입력. 기본으로 표시 토글 버튼이 suffix에 부착되며 `hideToggle`로 숨길 수 있다.
207
- * 토글은 `aria-pressed`로 상태가 노출되고 Tab 흐름에서 제외(`tabIndex=-1`)된다.
208
- */
209
- export const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
210
- ({ hideToggle, ...props }, ref) => {
211
- const [visible, setVisible] = React.useState(false);
212
-
213
- const toggle = hideToggle ? undefined : (
214
- <button
215
- type="button"
216
- className="sh-ui-input__toggle"
217
- onClick={() => setVisible((v) => !v)}
218
- aria-label={visible ? "비밀번호 숨기기" : "비밀번호 표시"}
219
- aria-pressed={visible}
220
- tabIndex={-1}
221
- >
222
- {visible ? <EyeOffIcon /> : <EyeIcon />}
223
- </button>
224
- );
225
-
226
- return (
227
- <Input
228
- ref={ref}
229
- type={visible ? "text" : "password"}
230
- suffix={toggle}
231
- {...props}
232
- />
233
- );
234
- },
235
- );
236
- PasswordInput.displayName = "PasswordInput";
237
-
238
168
  /* ───────── NumberInput ─────────
239
169
  * 정수 입력 + 천 단위 콤마(옵션). value/onValueChange는 number | undefined.
240
170
  */
@@ -10,7 +10,6 @@ import {
10
10
  group,
11
11
  input,
12
12
  inputWrap,
13
- toggle,
14
13
  withPrefix,
15
14
  withSuffix,
16
15
  } from "./styles.css";
@@ -140,68 +139,6 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
140
139
  );
141
140
  Input.displayName = "Input";
142
141
 
143
- /* ───────── PasswordInput ───────── */
144
-
145
- function EyeIcon() {
146
- return (
147
- <svg viewBox="0 0 20 20" width="16" height="16" fill="none" aria-hidden>
148
- <path
149
- d="M2 10s3-5.5 8-5.5S18 10 18 10s-3 5.5-8 5.5S2 10 2 10Z"
150
- stroke="currentColor"
151
- strokeWidth="1.5"
152
- />
153
- <circle cx="10" cy="10" r="2.25" stroke="currentColor" strokeWidth="1.5" />
154
- </svg>
155
- );
156
- }
157
-
158
- function EyeOffIcon() {
159
- return (
160
- <svg viewBox="0 0 20 20" width="16" height="16" fill="none" aria-hidden>
161
- <path
162
- d="M3 3l14 14M8 5a8 8 0 0 1 2-.3c5 0 8 5.3 8 5.3a13 13 0 0 1-2.3 2.9M12 12a2.5 2.5 0 0 1-3.4-3.4m-2.3-2.5A13 13 0 0 0 2 10s3 5.5 8 5.5a8 8 0 0 0 3.3-.7"
163
- stroke="currentColor"
164
- strokeWidth="1.5"
165
- strokeLinecap="round"
166
- />
167
- </svg>
168
- );
169
- }
170
-
171
- export interface PasswordInputProps extends Omit<InputProps, "type" | "suffix"> {
172
- /** 비밀번호 표시 토글 버튼을 숨긴다. @default false */
173
- hideToggle?: boolean;
174
- }
175
-
176
- export const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
177
- ({ hideToggle, ...props }, ref) => {
178
- const [visible, setVisible] = React.useState(false);
179
-
180
- const toggleBtn = hideToggle ? undefined : (
181
- <button
182
- type="button"
183
- className={toggle}
184
- onClick={() => setVisible((v) => !v)}
185
- aria-label={visible ? "비밀번호 숨기기" : "비밀번호 표시"}
186
- aria-pressed={visible}
187
- tabIndex={-1}
188
- >
189
- {visible ? <EyeOffIcon /> : <EyeIcon />}
190
- </button>
191
- );
192
-
193
- return (
194
- <Input
195
- ref={ref}
196
- type={visible ? "text" : "password"}
197
- suffix={toggleBtn}
198
- {...props}
199
- />
200
- );
201
- },
202
- );
203
- PasswordInput.displayName = "PasswordInput";
204
-
205
142
  /* ───────── NumberInput ───────── */
206
143
 
207
144
  export interface NumberInputProps
@@ -3,7 +3,7 @@
3
3
  "summaries": {
4
4
  "button": "기본 버튼 — variant(primary/secondary/ghost/danger/link) + size(sm/md/lg).",
5
5
  "card": "카드 컨테이너 — compound (Card.Header / Card.Body / Card.Footer / Card.Title / Card.Description).",
6
- "input": "단일 행 텍스트 입력 — hasError 지원. 같은 모듈에 NumberInput / PasswordInput / PhoneInput / BusinessNumberInput 변형 포함.",
6
+ "input": "단일 행 텍스트 입력 — hasError 지원. 같은 모듈에 NumberInput / PhoneInput / BusinessNumberInput 변형 포함. 비밀번호 토글은 InputGroup + InputAdornment 레시피로 조립.",
7
7
  "numeric-input": "슬라이더 동반·토큰 편집기용 컴팩트 숫자 입력 — onChange 즉시 min/max clamp, focus select-all, 단위(px/ms/%/° 등) suffix. 일반 폼 입력은 NumberInput 권장.",
8
8
  "textarea": "여러 행 텍스트 입력 — rows, autoResize.",
9
9
  "label": "폼 레이블 — htmlFor로 입력과 연결.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-ui-cli",
3
- "version": "0.59.8",
3
+ "version": "0.61.0",
4
4
  "description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -27,10 +27,10 @@
27
27
  "dependencies": {
28
28
  "@inquirer/prompts": "^7.0.0",
29
29
  "@modelcontextprotocol/sdk": "^1.0.0",
30
- "fs-extra": "^11.2.0",
31
30
  "zod": "^4.3.6"
32
31
  },
33
32
  "devDependencies": {
33
+ "fs-extra": "^11.2.0",
34
34
  "vitest": "^3.2.4"
35
35
  },
36
36
  "publishConfig": {
@@ -43,9 +43,9 @@ export const flatArch = {
43
43
  },
44
44
 
45
45
  // Scoped alias — 카테고리별로 명시. FSD 의 catch-all `@/*` 와 의도적으로 다름.
46
+ // Next.js 의 `app/` 라우트 디렉토리는 import alias 가 거의 쓰이지 않아 제외.
46
47
  tsconfigPaths: {
47
48
  '@/lib/*': ['./lib/*'],
48
49
  '@/components/*': ['./components/*'],
49
- '@/app/*': ['./app/*'],
50
50
  },
51
51
  };