sh-ui-cli 0.81.0 → 0.82.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 +25 -0
- package/data/registry/react/components/code-editor/index.module.tsx +0 -2
- package/data/registry/react/components/code-editor/index.tailwind.tsx +1 -2
- package/data/registry/react/components/code-editor/index.tsx +0 -2
- package/data/registry/react/components/header/index.tailwind.tsx +1 -1
- package/data/registry/react/components/sidebar/index.module.tsx +17 -8
- package/data/registry/react/components/sidebar/index.tailwind.tsx +21 -6
- package/data/registry/react/components/sidebar/index.tsx +27 -8
- package/data/registry/react/components/sidebar/styles.css +15 -0
- package/data/registry/react/components/sidebar/styles.module.css +12 -0
- package/package.json +1 -1
- package/src/create/generator.js +12 -0
|
@@ -2,6 +2,31 @@
|
|
|
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.1",
|
|
7
|
+
"date": "2026-05-13",
|
|
8
|
+
"title": "Sidebar / Header tailwind 변종의 결합 data 셀렉터 → Tailwind v4 호환 체이닝 수정",
|
|
9
|
+
"type": "patch",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"**Sidebar tailwind 변종의 결합 data 셀렉터가 Tailwind v4 에서 미emit 되던 결함 수정** — `data-[state=collapsed][data-collapsible=offcanvas]:w-0` 류 형식이 Tailwind v4 파서에서 해석 안 되어 `<SidebarTrigger>` 클릭으로 `data-state` 가 토글돼도 width 가 그대로 유지되어 시각적으로 안 닫히던 결함. v4 호환 syntax (variant 체이닝) `data-[state=collapsed]:data-[collapsible=offcanvas]:w-0` 로 일괄 변환. offcanvas (w-0/border-r-0/border-l-0/overflow-hidden) + icon collapsible (w-[var(--sidebar-width-icon)]) 5개 selector 모두 적용.",
|
|
12
|
+
"**Header tailwind 변종의 동일 안티패턴 수정** — `data-[sticky-hide][data-hidden]:-translate-y-full` → `data-[sticky-hide]:data-[hidden]:-translate-y-full`. sticky-hide 모드의 스크롤-숨김 transform 이 적용 안 되던 결함 해소.",
|
|
13
|
+
"plain CSS / module CSS 변종은 영향 없음 — 처음부터 CSS attribute selector(`.sh-ui-sidebar[data-state=\"collapsed\"][data-collapsible=\"offcanvas\"]`) 라 정상 동작."
|
|
14
|
+
],
|
|
15
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.82.1"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"version": "0.82.0",
|
|
19
|
+
"date": "2026-05-13",
|
|
20
|
+
"title": "atlas P2 잔여 — Sidebar topbar 정렬 + 메뉴 사이즈 디폴트 다운 + DX 정리",
|
|
21
|
+
"type": "minor",
|
|
22
|
+
"highlights": [
|
|
23
|
+
"**`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 과 픽셀 정렬.",
|
|
24
|
+
"**`SidebarMenuButton` / `SidebarMenuSubButton` / `SidebarCollapsibleTrigger` 의 size 디폴트 `md` → `sm` (D3)** — 일반 사이드바 패턴(28~32px) 톤에 맞춤. v0.81.0 의 `--control-md` 40→36px 다운과 결합돼 사이드바가 데스크탑 정보 밀집 화면에서 후하게 느껴지던 회귀 차단. atlas 실측에서 size 명시 24건 전부 `sm` 이었던 신호 반영.",
|
|
25
|
+
"**`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` 한 줄도 같이.",
|
|
26
|
+
"**`CodeEditor` 의 `language` 타입 `\"text\"` / `\"plaintext\"` 중복 단일화 (B3)** — 의미상 동일한 두 값이 공존해 사용자가 어느 쪽을 쓸지 모호하던 결함. `\"plaintext\"` 제거, `\"text\"` 만 유지. `language=\"plaintext\"` 사용처는 `\"text\"` 로 교체."
|
|
27
|
+
],
|
|
28
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.82.0"
|
|
29
|
+
},
|
|
5
30
|
{
|
|
6
31
|
"version": "0.81.0",
|
|
7
32
|
"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" | "
|
|
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
|
}
|
|
@@ -150,7 +150,7 @@ export const Header = React.forwardRef<HTMLElement, HeaderProps>(function Header
|
|
|
150
150
|
<header
|
|
151
151
|
ref={setRefs}
|
|
152
152
|
className={cn(
|
|
153
|
-
"relative flex items-center gap-[var(--space-4)] h-[var(--control-md)] px-[var(--space-3)] border-b border-border transition-[transform,background-color] duration-[var(--duration-base)] [--sh-ui-header-hover-bg:var(--background-muted)] [--sh-ui-header-blur-opacity:85%] [--sh-ui-header-blur-radius:16px] motion-reduce:transition-none max-md:gap-[var(--space-2)] data-[sticky-hide]
|
|
153
|
+
"relative flex items-center gap-[var(--space-4)] h-[var(--control-md)] px-[var(--space-3)] border-b border-border transition-[transform,background-color] duration-[var(--duration-base)] [--sh-ui-header-hover-bg:var(--background-muted)] [--sh-ui-header-blur-opacity:85%] [--sh-ui-header-blur-radius:16px] motion-reduce:transition-none max-md:gap-[var(--space-2)] data-[sticky-hide]:data-[hidden]:-translate-y-full",
|
|
154
154
|
variantClasses[variant],
|
|
155
155
|
className,
|
|
156
156
|
)}
|
|
@@ -482,10 +482,19 @@ export function SidebarInset({ className, ...props }: React.HTMLAttributes<HTMLE
|
|
|
482
482
|
|
|
483
483
|
/* ───────────── Header / Footer / Content / Separator ───────────── */
|
|
484
484
|
|
|
485
|
-
|
|
486
|
-
|
|
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 "
|
|
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 = "
|
|
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 "
|
|
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 = "
|
|
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 "
|
|
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 = "
|
|
864
|
+
size = "sm",
|
|
856
865
|
children,
|
|
857
866
|
onClick,
|
|
858
867
|
...props
|
|
@@ -149,7 +149,7 @@ export interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
const sidebarRoot =
|
|
152
|
-
"flex flex-col w-[var(--sidebar-width)] shrink-0 bg-[var(--sidebar-bg)] text-[var(--sidebar-fg)] border-r border-[var(--sidebar-border)] transition-[width] duration-[var(--duration-slow)] relative z-[5] data-[side=right]:border-r-0 data-[side=right]:border-l data-[side=right]:order-1 data-[state=collapsed]
|
|
152
|
+
"flex flex-col w-[var(--sidebar-width)] shrink-0 bg-[var(--sidebar-bg)] text-[var(--sidebar-fg)] border-r border-[var(--sidebar-border)] transition-[width] duration-[var(--duration-slow)] relative z-[5] data-[side=right]:border-r-0 data-[side=right]:border-l data-[side=right]:order-1 data-[state=collapsed]:data-[collapsible=offcanvas]:w-0 data-[state=collapsed]:data-[collapsible=offcanvas]:border-r-0 data-[state=collapsed]:data-[collapsible=offcanvas]:border-l-0 data-[state=collapsed]:data-[collapsible=offcanvas]:overflow-hidden data-[state=collapsed]:data-[collapsible=icon]:w-[var(--sidebar-width-icon)] data-[variant=floating]:border-none data-[variant=floating]:p-[var(--space-2)] data-[variant=floating]:bg-transparent data-[variant=inset]:bg-transparent data-[variant=inset]:border-none motion-reduce:transition-none";
|
|
153
153
|
|
|
154
154
|
export function Sidebar({ side = "left", variant = "sidebar", collapsible = "offcanvas", className, children, ...props }: SidebarProps) {
|
|
155
155
|
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
|
|
@@ -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
|
|
309
|
-
|
|
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 = "
|
|
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 = "
|
|
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 = "
|
|
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
|
-
|
|
486
|
-
|
|
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 "
|
|
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 = "
|
|
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 "
|
|
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 = "
|
|
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 "
|
|
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 = "
|
|
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
package/src/create/generator.js
CHANGED
|
@@ -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
|
/**
|