sh-ui-cli 0.81.0 → 0.82.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.
@@ -2,6 +2,19 @@
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.82.0",
7
+ "date": "2026-05-13",
8
+ "title": "atlas P2 잔여 — Sidebar topbar 정렬 + 메뉴 사이즈 디폴트 다운 + DX 정리",
9
+ "type": "minor",
10
+ "highlights": [
11
+ "**`SidebarHeader` 에 `align=\"topbar\"` + `divider` prop 신설 (B8)** — admin/dashboard 의 좌 Sidebar 헤더와 우 Topbar 구분선이 4~5px 어긋나던 정렬 hack (`!h-14 !p-0 !flex-row` + SidebarSeparator 제거 + `border-b` 직접 박는 류) 영구 해결. `align=\"topbar\"` 가 56px(`3.5rem`) 가로 행 + 가로 패딩만 적용, `divider` 가 `border-bottom: 1px solid var(--sidebar-border)` 박는다. 두 옵션 조합 시 우 Topbar 의 border-bottom 과 픽셀 정렬.",
12
+ "**`SidebarMenuButton` / `SidebarMenuSubButton` / `SidebarCollapsibleTrigger` 의 size 디폴트 `md` → `sm` (D3)** — 일반 사이드바 패턴(28~32px) 톤에 맞춤. v0.81.0 의 `--control-md` 40→36px 다운과 결합돼 사이드바가 데스크탑 정보 밀집 화면에서 후하게 느껴지던 회귀 차단. atlas 실측에서 size 명시 24건 전부 `sm` 이었던 신호 반영.",
13
+ "**`create_project` 직후 출력에 베이스 컴포넌트 추가 힌트 1줄 (B6)** — 기존엔 `cd / pnpm install / pnpm dev` 3줄만 출력. monorepo 는 `cd .../packages/ui/ui-core` + `npx sh-ui-cli add button card input dialog`, standalone 은 `npx sh-ui-cli add ...` 만. MCP 사용자에게는 `sh_ui_add_component` 한 줄도 같이.",
14
+ "**`CodeEditor` 의 `language` 타입 `\"text\"` / `\"plaintext\"` 중복 단일화 (B3)** — 의미상 동일한 두 값이 공존해 사용자가 어느 쪽을 쓸지 모호하던 결함. `\"plaintext\"` 제거, `\"text\"` 만 유지. `language=\"plaintext\"` 사용처는 `\"text\"` 로 교체."
15
+ ],
16
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.82.0"
17
+ },
5
18
  {
6
19
  "version": "0.81.0",
7
20
  "date": "2026-05-13",
@@ -15,7 +15,6 @@ import styles from "./styles.module.css";
15
15
  import { cn } from "@SH_UI_UTILS@";
16
16
  export type CodeEditorLanguage =
17
17
  | "text"
18
- | "plaintext"
19
18
  | "javascript"
20
19
  | "typescript"
21
20
  | "jsx"
@@ -85,7 +84,6 @@ function languageExtension(language: CodeEditorLanguage): Extension {
85
84
  case "yaml":
86
85
  return yaml();
87
86
  case "text":
88
- case "plaintext":
89
87
  default:
90
88
  return [];
91
89
  }
@@ -13,7 +13,7 @@ import { yaml } from "@codemirror/lang-yaml";
13
13
 
14
14
  import { cn } from "@SH_UI_UTILS@";
15
15
  export type CodeEditorLanguage =
16
- | "text" | "plaintext" | "javascript" | "typescript" | "jsx" | "tsx"
16
+ | "text" | "javascript" | "typescript" | "jsx" | "tsx"
17
17
  | "json" | "css" | "html" | "markdown" | "yaml";
18
18
 
19
19
  export interface CodeEditorProps {
@@ -45,7 +45,6 @@ function languageExtension(language: CodeEditorLanguage): Extension {
45
45
  case "markdown": return markdown();
46
46
  case "yaml": return yaml();
47
47
  case "text":
48
- case "plaintext":
49
48
  default: return [];
50
49
  }
51
50
  }
@@ -15,7 +15,6 @@ import "./styles.css";
15
15
  import { cn } from "@SH_UI_UTILS@";
16
16
  export type CodeEditorLanguage =
17
17
  | "text"
18
- | "plaintext"
19
18
  | "javascript"
20
19
  | "typescript"
21
20
  | "jsx"
@@ -85,7 +84,6 @@ function languageExtension(language: CodeEditorLanguage): Extension {
85
84
  case "yaml":
86
85
  return yaml();
87
86
  case "text":
88
- case "plaintext":
89
87
  default:
90
88
  return [];
91
89
  }
@@ -482,10 +482,19 @@ export function SidebarInset({ className, ...props }: React.HTMLAttributes<HTMLE
482
482
 
483
483
  /* ───────────── Header / Footer / Content / Separator ───────────── */
484
484
 
485
- /** Sidebar 상단 영역. 보통 로고/검색을 둔다. */
486
- export function SidebarHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
485
+ export interface SidebarHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
486
+ /** @default "stack" */
487
+ align?: "stack" | "topbar";
488
+ /** @default false */
489
+ divider?: boolean;
490
+ }
491
+
492
+ /** Sidebar 상단 영역. 로고/검색/topbar-align 모드 지원. */
493
+ export function SidebarHeader({ className, align = "stack", divider = false, ...props }: SidebarHeaderProps) {
487
494
  return (
488
495
  <div
496
+ data-align={align}
497
+ data-divider={divider ? "true" : undefined}
489
498
  className={cn(styles.sidebar__header, className)}
490
499
  {...props}
491
500
  />
@@ -587,7 +596,7 @@ export interface SidebarMenuButtonProps extends React.ButtonHTMLAttributes<HTMLB
587
596
  * - `md` — 일반 (기본)
588
597
  * - `lg` — 강조 메뉴
589
598
  *
590
- * @default "md"
599
+ * @default "sm"
591
600
  */
592
601
  size?: "sm" | "md" | "lg";
593
602
  /**
@@ -617,7 +626,7 @@ export interface SidebarMenuButtonProps extends React.ButtonHTMLAttributes<HTMLB
617
626
  */
618
627
  export const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(
619
628
  function SidebarMenuButton(
620
- { className, isActive, size = "md", render, sectionId, panelId, onClick, children, ...props },
629
+ { className, isActive, size = "sm", render, sectionId, panelId, onClick, children, ...props },
621
630
  ref
622
631
  ) {
623
632
  const tocActive = useTOCActiveId();
@@ -701,7 +710,7 @@ export interface SidebarMenuSubButtonProps extends React.AnchorHTMLAttributes<HT
701
710
  isActive?: boolean;
702
711
  /**
703
712
  * 크기.
704
- * @default "md"
713
+ * @default "sm"
705
714
  */
706
715
  size?: "sm" | "md";
707
716
  /**
@@ -715,7 +724,7 @@ export interface SidebarMenuSubButtonProps extends React.AnchorHTMLAttributes<HT
715
724
  /** 서브 메뉴 항목 내부의 링크. `render` prop 으로 다른 엘리먼트 슬롯 가능. `sectionId`로 TOC 활성 연동. */
716
725
  export const SidebarMenuSubButton = React.forwardRef<HTMLAnchorElement, SidebarMenuSubButtonProps>(
717
726
  function SidebarMenuSubButton(
718
- { className, isActive, size = "md", render, sectionId, children, ...props },
727
+ { className, isActive, size = "sm", render, sectionId, children, ...props },
719
728
  ref
720
729
  ) {
721
730
  const tocActive = useTOCActiveId();
@@ -844,7 +853,7 @@ export function SidebarCollapsible({
844
853
  export interface SidebarCollapsibleTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
845
854
  /**
846
855
  * 크기. 부모 메뉴와 시각 위계를 맞춘다.
847
- * @default "md"
856
+ * @default "sm"
848
857
  */
849
858
  size?: "sm" | "md" | "lg";
850
859
  }
@@ -852,7 +861,7 @@ export interface SidebarCollapsibleTriggerProps extends React.ButtonHTMLAttribut
852
861
  /** Collapsible을 토글하는 메뉴 버튼. flyout 모드면 Popover Trigger로 자동 위임된다. */
853
862
  export function SidebarCollapsibleTrigger({
854
863
  className,
855
- size = "md",
864
+ size = "sm",
856
865
  children,
857
866
  onClick,
858
867
  ...props
@@ -305,8 +305,23 @@ export function SidebarInset({ className, ...props }: React.HTMLAttributes<HTMLE
305
305
  return <main className={cn("flex-1 min-w-0 bg-background flex flex-col", className)} {...props} />;
306
306
  }
307
307
 
308
- export function SidebarHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
309
- return <div className={cn("flex flex-col gap-[var(--space-2)] p-[var(--space-2)] overflow-hidden", className)} {...props} />;
308
+ export interface SidebarHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
309
+ /** @default "stack" */
310
+ align?: "stack" | "topbar";
311
+ /** @default false */
312
+ divider?: boolean;
313
+ }
314
+
315
+ export function SidebarHeader({ className, align = "stack", divider = false, ...props }: SidebarHeaderProps) {
316
+ const base = align === "topbar"
317
+ ? "flex flex-row items-center gap-[var(--space-2)] h-14 px-[var(--space-3)] overflow-hidden"
318
+ : "flex flex-col gap-[var(--space-2)] p-[var(--space-2)] overflow-hidden";
319
+ return (
320
+ <div
321
+ className={cn(base, divider && "border-b border-[var(--sidebar-border)]", className)}
322
+ {...props}
323
+ />
324
+ );
310
325
  }
311
326
 
312
327
  export function SidebarFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
@@ -370,7 +385,7 @@ const menuButtonSize = {
370
385
  };
371
386
 
372
387
  export const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(
373
- function SidebarMenuButton({ className, isActive, size = "md", render, sectionId, panelId, onClick, children, ...props }, ref) {
388
+ function SidebarMenuButton({ className, isActive, size = "sm", render, sectionId, panelId, onClick, children, ...props }, ref) {
374
389
  const tocActive = useTOCActiveId();
375
390
  const ctx = React.useContext(SidebarContext);
376
391
  const panelActive = panelId != null && ctx?.activePanel === panelId;
@@ -436,7 +451,7 @@ const menuSubButtonBase =
436
451
  "flex items-center gap-[var(--space-2)] h-7 px-[var(--space-2)] rounded-[calc(var(--radius)-2px)] text-[0.8125rem] text-[var(--sidebar-fg)] no-underline transition-[background-color,color] duration-[var(--duration-fast)] min-w-0 [&>span]:flex-1 [&>span]:min-w-0 [&>span]:overflow-hidden [&>span]:[text-overflow:ellipsis] [&>span]:whitespace-nowrap hover:bg-[var(--sidebar-accent)] hover:text-[var(--sidebar-accent-fg)] data-[active]:bg-primary data-[active]:text-primary-foreground data-[active]:font-semibold data-[active]:hover:bg-primary-hover motion-reduce:transition-none";
437
452
 
438
453
  export const SidebarMenuSubButton = React.forwardRef<HTMLAnchorElement, SidebarMenuSubButtonProps>(
439
- function SidebarMenuSubButton({ className, isActive, size = "md", render, sectionId, children, ...props }, ref) {
454
+ function SidebarMenuSubButton({ className, isActive, size = "sm", render, sectionId, children, ...props }, ref) {
440
455
  const tocActive = useTOCActiveId();
441
456
  const resolvedIsActive = isActive ?? (sectionId != null ? tocActive === sectionId : undefined);
442
457
  const cls = cn(menuSubButtonBase, size === "sm" && "text-[length:var(--text-xs)]", className);
@@ -510,7 +525,7 @@ export interface SidebarCollapsibleTriggerProps extends React.ButtonHTMLAttribut
510
525
  size?: "sm" | "md" | "lg";
511
526
  }
512
527
 
513
- export function SidebarCollapsibleTrigger({ className, size = "md", children, onClick, ...props }: SidebarCollapsibleTriggerProps) {
528
+ export function SidebarCollapsibleTrigger({ className, size = "sm", children, onClick, ...props }: SidebarCollapsibleTriggerProps) {
514
529
  const { open, toggle, flyoutMode, flyoutOpen } = useCollapsible();
515
530
  const cls = cn(menuButtonBase, menuButtonSize[size], className);
516
531
  const isOpen = flyoutMode ? flyoutOpen : open;
@@ -482,10 +482,29 @@ export function SidebarInset({ className, ...props }: React.HTMLAttributes<HTMLE
482
482
 
483
483
  /* ───────────── Header / Footer / Content / Separator ───────────── */
484
484
 
485
- /** Sidebar 상단 영역. 보통 로고/검색을 둔다. */
486
- export function SidebarHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
485
+ export interface SidebarHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
486
+ /**
487
+ * 헤더 레이아웃 모드.
488
+ * - `stack` (기본) — 세로 쌓기. 로고 + 검색 등을 위아래로.
489
+ * - `topbar` — 우측 Topbar 와 같은 56px 가로 행. admin/dashboard 에서 좌 Sidebar 헤더와
490
+ * 우 Topbar 의 구분선을 한 줄로 정렬할 때 사용.
491
+ * @default "stack"
492
+ */
493
+ align?: "stack" | "topbar";
494
+ /**
495
+ * 헤더 하단에 1px 구분선. 별도 `SidebarSeparator` 없이 헤더-콘텐츠 경계를 그릴 때.
496
+ * `align="topbar"` 와 조합하면 우측 Topbar 의 border-bottom 과 정확히 정렬된다.
497
+ * @default false
498
+ */
499
+ divider?: boolean;
500
+ }
501
+
502
+ /** Sidebar 상단 영역. 로고/검색/topbar-align 모드 지원. */
503
+ export function SidebarHeader({ className, align = "stack", divider = false, ...props }: SidebarHeaderProps) {
487
504
  return (
488
505
  <div
506
+ data-align={align}
507
+ data-divider={divider ? "true" : undefined}
489
508
  className={cn("sh-ui-sidebar__header", className)}
490
509
  {...props}
491
510
  />
@@ -587,7 +606,7 @@ export interface SidebarMenuButtonProps extends React.ButtonHTMLAttributes<HTMLB
587
606
  * - `md` — 일반 (기본)
588
607
  * - `lg` — 강조 메뉴
589
608
  *
590
- * @default "md"
609
+ * @default "sm"
591
610
  */
592
611
  size?: "sm" | "md" | "lg";
593
612
  /**
@@ -618,7 +637,7 @@ export interface SidebarMenuButtonProps extends React.ButtonHTMLAttributes<HTMLB
618
637
  */
619
638
  export const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(
620
639
  function SidebarMenuButton(
621
- { className, isActive, size = "md", render, sectionId, panelId, onClick, children, ...props },
640
+ { className, isActive, size = "sm", render, sectionId, panelId, onClick, children, ...props },
622
641
  ref
623
642
  ) {
624
643
  const tocActive = useTOCActiveId();
@@ -712,7 +731,7 @@ export interface SidebarMenuSubButtonProps extends React.AnchorHTMLAttributes<HT
712
731
  isActive?: boolean;
713
732
  /**
714
733
  * 크기.
715
- * @default "md"
734
+ * @default "sm"
716
735
  */
717
736
  size?: "sm" | "md";
718
737
  /**
@@ -729,7 +748,7 @@ export interface SidebarMenuSubButtonProps extends React.AnchorHTMLAttributes<HT
729
748
  /** 서브 메뉴 항목 내부의 링크. `render` prop 으로 다른 엘리먼트 슬롯 가능. `sectionId`로 TOC 활성 연동. */
730
749
  export const SidebarMenuSubButton = React.forwardRef<HTMLAnchorElement, SidebarMenuSubButtonProps>(
731
750
  function SidebarMenuSubButton(
732
- { className, isActive, size = "md", render, sectionId, children, ...props },
751
+ { className, isActive, size = "sm", render, sectionId, children, ...props },
733
752
  ref
734
753
  ) {
735
754
  const tocActive = useTOCActiveId();
@@ -866,7 +885,7 @@ export function SidebarCollapsible({
866
885
  export interface SidebarCollapsibleTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
867
886
  /**
868
887
  * 크기. 부모 메뉴와 시각 위계를 맞춘다.
869
- * @default "md"
888
+ * @default "sm"
870
889
  */
871
890
  size?: "sm" | "md" | "lg";
872
891
  }
@@ -874,7 +893,7 @@ export interface SidebarCollapsibleTriggerProps extends React.ButtonHTMLAttribut
874
893
  /** Collapsible을 토글하는 메뉴 버튼. flyout 모드면 Popover Trigger로 자동 위임된다. */
875
894
  export function SidebarCollapsibleTrigger({
876
895
  className,
877
- size = "md",
896
+ size = "sm",
878
897
  children,
879
898
  onClick,
880
899
  ...props
@@ -273,6 +273,21 @@
273
273
  overflow: hidden;
274
274
  }
275
275
 
276
+ /* align=topbar — Topbar 와 동일한 56px 행. 좌측 Sidebar 헤더 구분선이 우측 Topbar
277
+ * 구분선과 한 줄로 만난다. admin/dashboard 의 가장 흔한 정렬 패턴. */
278
+ .sh-ui-sidebar__header[data-align="topbar"] {
279
+ flex-direction: row;
280
+ align-items: center;
281
+ gap: var(--space-2);
282
+ height: 3.5rem;
283
+ padding: 0 var(--space-3);
284
+ }
285
+
286
+ /* divider — 헤더 하단 1px 라인. topbar 모드와 조합 시 우측 Topbar 의 border-bottom 과 픽셀 정렬. */
287
+ .sh-ui-sidebar__header[data-divider="true"] {
288
+ border-bottom: 1px solid var(--sidebar-border);
289
+ }
290
+
276
291
  .sh-ui-sidebar__content {
277
292
  display: flex;
278
293
  flex-direction: column;
@@ -273,6 +273,18 @@
273
273
  overflow: hidden;
274
274
  }
275
275
 
276
+ .sidebar__header[data-align="topbar"] {
277
+ flex-direction: row;
278
+ align-items: center;
279
+ gap: var(--space-2);
280
+ height: 3.5rem;
281
+ padding: 0 var(--space-3);
282
+ }
283
+
284
+ .sidebar__header[data-divider="true"] {
285
+ border-bottom: 1px solid var(--sidebar-border);
286
+ }
287
+
276
288
  .sidebar__content {
277
289
  display: flex;
278
290
  flex-direction: column;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-ui-cli",
3
- "version": "0.81.0",
3
+ "version": "0.82.0",
4
4
  "description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -356,6 +356,18 @@ export async function createProject(options = {}) {
356
356
  console.log(`\n cd ${projectName}`);
357
357
  console.log(' pnpm install');
358
358
  console.log(' pnpm dev\n');
359
+
360
+ // v0.82.0+ — 다음 액션이 막막한 첫-사용 경험 보강. baseline 컴포넌트를 어디서/어떻게
361
+ // 추가할지 1줄 가이드. monorepo 는 ui-core 가 SoT, standalone 은 cwd 자체.
362
+ if (projectType === 'monorepo') {
363
+ console.log('다음 단계 — 베이스 컴포넌트 추가 (예시):');
364
+ console.log(` cd ${projectName}/packages/ui/ui-core`);
365
+ console.log(' npx sh-ui-cli add button card input dialog\n');
366
+ console.log('또는 MCP: sh_ui_add_component({ names: ["button", "card", "input", "dialog"] })\n');
367
+ } else {
368
+ console.log('다음 단계 — 베이스 컴포넌트 추가 (예시):');
369
+ console.log(' npx sh-ui-cli add button card input dialog\n');
370
+ }
359
371
  }
360
372
 
361
373
  /**