sh-ui-cli 0.83.0 → 0.84.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.
- package/data/changelog/versions.json +13 -0
- package/data/registry/react/components/sidebar/index.module.tsx +60 -0
- package/data/registry/react/components/sidebar/index.tailwind.tsx +60 -0
- package/data/registry/react/components/sidebar/index.tsx +86 -0
- package/data/registry/react/components/sidebar/styles.css +51 -0
- package/data/registry/react/components/sidebar/styles.module.css +51 -0
- package/data/registry/react/tokens-used.json +3 -1
- package/package.json +1 -1
|
@@ -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.84.0",
|
|
7
|
+
"date": "2026-05-13",
|
|
8
|
+
"title": "Sidebar — Brand 컴파운드 primitive (브랜드 카드 · 워크스페이스 스위처 · 유저 프로필)",
|
|
9
|
+
"type": "minor",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"**`SidebarBrand` 컴파운드 primitive 신설 — 브랜드 카드 / 워크스페이스 스위처 / 유저 프로필 의 90% 를 커버** — `<SidebarHeader>` / `<SidebarFooter>` 안에서 매번 박던 패턴 (icon + title + subtitle + trailing chevron) 을 단일 합성으로. Card/Dialog 와 동일한 separate-exports 컨벤션.",
|
|
12
|
+
"**Exports (6개)**: `SidebarBrand` (행 wrapper, 내부 `data-when-collapsed=\"center\"` 자동 적용) · `SidebarBrandIcon` (항상 visible, collapsed 시 sole 요소) · `SidebarBrandText` (`data-when-collapsed=\"hide\"`) · `SidebarBrandTitle` (단일 줄 본문, truncate 내장) · `SidebarBrandSubtitle` (보조 줄, foreground-muted) · `SidebarBrandAction` (`data-when-collapsed=\"hide\"`, ml-auto trailing icon).",
|
|
13
|
+
"**v0.83.0 의 `data-when-collapsed` 컨벤션 내장** — 사용자가 attribute 박지 않아도 collapsible=\"icon\" 모드 전환 시 자동 적응. 인터랙티브 케이스는 `<DropdownMenuTrigger render={<button data-when-collapsed=\"strip-chrome\" />}>` 로 wrap.",
|
|
14
|
+
"**3 변종 모두 지원** — plain `styles.css` / module `styles.module.css` 에 룰 직접, tailwind 변종은 utility 클래스로 동등 스타일. 듀얼카피 (apps/docs) 동기화."
|
|
15
|
+
],
|
|
16
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.84.0"
|
|
17
|
+
},
|
|
5
18
|
{
|
|
6
19
|
"version": "0.83.0",
|
|
7
20
|
"date": "2026-05-13",
|
|
@@ -511,6 +511,66 @@ export function SidebarFooter({ className, ...props }: React.HTMLAttributes<HTML
|
|
|
511
511
|
);
|
|
512
512
|
}
|
|
513
513
|
|
|
514
|
+
/* ───────────── Brand (compound primitive, v0.84.0+) ─────────────
|
|
515
|
+
* data-when-collapsed 컨벤션 내장. plain 변종 주석 참고. */
|
|
516
|
+
|
|
517
|
+
export function SidebarBrand({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
518
|
+
return (
|
|
519
|
+
<div
|
|
520
|
+
data-when-collapsed="center"
|
|
521
|
+
className={cn(styles.sidebar__brand, className)}
|
|
522
|
+
{...props}
|
|
523
|
+
/>
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export function SidebarBrandIcon({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
528
|
+
return (
|
|
529
|
+
<div
|
|
530
|
+
className={cn(styles.sidebar__brand_icon, className)}
|
|
531
|
+
{...props}
|
|
532
|
+
/>
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export function SidebarBrandText({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
537
|
+
return (
|
|
538
|
+
<div
|
|
539
|
+
data-when-collapsed="hide"
|
|
540
|
+
className={cn(styles.sidebar__brand_text, className)}
|
|
541
|
+
{...props}
|
|
542
|
+
/>
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export function SidebarBrandTitle({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
547
|
+
return (
|
|
548
|
+
<div
|
|
549
|
+
className={cn(styles.sidebar__brand_title, className)}
|
|
550
|
+
{...props}
|
|
551
|
+
/>
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export function SidebarBrandSubtitle({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
556
|
+
return (
|
|
557
|
+
<div
|
|
558
|
+
className={cn(styles.sidebar__brand_subtitle, className)}
|
|
559
|
+
{...props}
|
|
560
|
+
/>
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export function SidebarBrandAction({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
565
|
+
return (
|
|
566
|
+
<div
|
|
567
|
+
data-when-collapsed="hide"
|
|
568
|
+
className={cn(styles.sidebar__brand_action, className)}
|
|
569
|
+
{...props}
|
|
570
|
+
/>
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
514
574
|
/** Sidebar의 스크롤 영역. 메뉴/그룹 목록을 둔다. */
|
|
515
575
|
export function SidebarContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
516
576
|
return (
|
|
@@ -352,6 +352,66 @@ export function SidebarFooter({ className, ...props }: React.HTMLAttributes<HTML
|
|
|
352
352
|
return <div className={cn("flex flex-col gap-[var(--space-2)] p-[var(--space-2)] overflow-hidden", className)} {...props} />;
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
+
/* ───────────── Brand (compound primitive, v0.84.0+) ─────────────
|
|
356
|
+
* data-when-collapsed 컨벤션 내장. plain 변종 주석 참고. */
|
|
357
|
+
|
|
358
|
+
export function SidebarBrand({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
359
|
+
return (
|
|
360
|
+
<div
|
|
361
|
+
data-when-collapsed="center"
|
|
362
|
+
className={cn("flex flex-row items-center gap-[var(--space-2)] px-[var(--space-2)] py-[var(--space-2)] rounded-[var(--radius)] min-w-0", className)}
|
|
363
|
+
{...props}
|
|
364
|
+
/>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function SidebarBrandIcon({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
369
|
+
return (
|
|
370
|
+
<div
|
|
371
|
+
className={cn("flex items-center justify-center shrink-0 size-8 rounded-[calc(var(--radius)-2px)]", className)}
|
|
372
|
+
{...props}
|
|
373
|
+
/>
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function SidebarBrandText({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
378
|
+
return (
|
|
379
|
+
<div
|
|
380
|
+
data-when-collapsed="hide"
|
|
381
|
+
className={cn("flex flex-col flex-1 min-w-0 gap-0", className)}
|
|
382
|
+
{...props}
|
|
383
|
+
/>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function SidebarBrandTitle({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
388
|
+
return (
|
|
389
|
+
<div
|
|
390
|
+
className={cn("text-[length:var(--text-sm)] font-[var(--weight-medium)] leading-tight truncate text-[color:var(--sidebar-fg)]", className)}
|
|
391
|
+
{...props}
|
|
392
|
+
/>
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export function SidebarBrandSubtitle({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
397
|
+
return (
|
|
398
|
+
<div
|
|
399
|
+
className={cn("text-[length:var(--text-xs)] leading-tight truncate text-[color:var(--foreground-muted)]", className)}
|
|
400
|
+
{...props}
|
|
401
|
+
/>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function SidebarBrandAction({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
406
|
+
return (
|
|
407
|
+
<div
|
|
408
|
+
data-when-collapsed="hide"
|
|
409
|
+
className={cn("ml-auto shrink-0 flex items-center justify-center", className)}
|
|
410
|
+
{...props}
|
|
411
|
+
/>
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
355
415
|
export function SidebarContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
356
416
|
return <div className={cn("flex flex-col flex-1 min-h-0 overflow-y-auto gap-0", className)} {...props} />;
|
|
357
417
|
}
|
|
@@ -521,6 +521,92 @@ export function SidebarFooter({ className, ...props }: React.HTMLAttributes<HTML
|
|
|
521
521
|
);
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
+
/* ───────────── Brand (compound primitive, v0.84.0+) ─────────────
|
|
525
|
+
* SidebarHeader / SidebarFooter 의 흔한 패턴 (브랜드 카드 · 워크스페이스 스위처 ·
|
|
526
|
+
* 유저 프로필) 의 90% 를 커버. data-when-collapsed 컨벤션을 내장해 사용자가
|
|
527
|
+
* attribute 박지 않아도 collapsed/icon 모드 자동 적응.
|
|
528
|
+
*
|
|
529
|
+
* 정적 예시:
|
|
530
|
+
* <SidebarBrand>
|
|
531
|
+
* <SidebarBrandIcon><Logo /></SidebarBrandIcon>
|
|
532
|
+
* <SidebarBrandText>
|
|
533
|
+
* <SidebarBrandTitle>Atlas</SidebarBrandTitle>
|
|
534
|
+
* <SidebarBrandSubtitle>workspace</SidebarBrandSubtitle>
|
|
535
|
+
* </SidebarBrandText>
|
|
536
|
+
* </SidebarBrand>
|
|
537
|
+
*
|
|
538
|
+
* 인터랙티브 (워크스페이스 스위처):
|
|
539
|
+
* <DropdownMenu>
|
|
540
|
+
* <DropdownMenuTrigger render={<button data-when-collapsed='strip-chrome' />}>
|
|
541
|
+
* <SidebarBrand>...</SidebarBrand>
|
|
542
|
+
* </DropdownMenuTrigger>
|
|
543
|
+
* ...
|
|
544
|
+
* </DropdownMenu>
|
|
545
|
+
*/
|
|
546
|
+
|
|
547
|
+
/** 브랜드 행 wrapper. collapsed 일 땐 가운데 정렬 + 좌우 padding 0 (data-when-collapsed=center 내장). */
|
|
548
|
+
export function SidebarBrand({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
549
|
+
return (
|
|
550
|
+
<div
|
|
551
|
+
data-when-collapsed="center"
|
|
552
|
+
className={cn("sh-ui-sidebar__brand", className)}
|
|
553
|
+
{...props}
|
|
554
|
+
/>
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/** 항상 visible 한 아이콘 슬롯. collapsed 일 땐 SidebarBrand 의 유일한 요소. */
|
|
559
|
+
export function SidebarBrandIcon({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
560
|
+
return (
|
|
561
|
+
<div
|
|
562
|
+
className={cn("sh-ui-sidebar__brand-icon", className)}
|
|
563
|
+
{...props}
|
|
564
|
+
/>
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/** 텍스트 컨테이너. collapsed 일 땐 hidden (data-when-collapsed=hide 내장). */
|
|
569
|
+
export function SidebarBrandText({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
570
|
+
return (
|
|
571
|
+
<div
|
|
572
|
+
data-when-collapsed="hide"
|
|
573
|
+
className={cn("sh-ui-sidebar__brand-text", className)}
|
|
574
|
+
{...props}
|
|
575
|
+
/>
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** 단일 줄 본문 (편의) — sh-ui-sidebar__brand-title 클래스만 박는다. */
|
|
580
|
+
export function SidebarBrandTitle({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
581
|
+
return (
|
|
582
|
+
<div
|
|
583
|
+
className={cn("sh-ui-sidebar__brand-title", className)}
|
|
584
|
+
{...props}
|
|
585
|
+
/>
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/** 보조 줄 — sh-ui-sidebar__brand-subtitle. text-foreground-muted + text-xs. */
|
|
590
|
+
export function SidebarBrandSubtitle({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
591
|
+
return (
|
|
592
|
+
<div
|
|
593
|
+
className={cn("sh-ui-sidebar__brand-subtitle", className)}
|
|
594
|
+
{...props}
|
|
595
|
+
/>
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Trailing icon (드롭다운 chevron 등). collapsed 일 땐 hidden. */
|
|
600
|
+
export function SidebarBrandAction({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
601
|
+
return (
|
|
602
|
+
<div
|
|
603
|
+
data-when-collapsed="hide"
|
|
604
|
+
className={cn("sh-ui-sidebar__brand-action", className)}
|
|
605
|
+
{...props}
|
|
606
|
+
/>
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
524
610
|
/** Sidebar의 스크롤 영역. 메뉴/그룹 목록을 둔다. */
|
|
525
611
|
export function SidebarContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
526
612
|
return (
|
|
@@ -318,6 +318,57 @@
|
|
|
318
318
|
background-color: transparent;
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
/* ───────────── Brand (compound primitive, v0.84.0+) ───────────── */
|
|
322
|
+
.sh-ui-sidebar__brand {
|
|
323
|
+
display: flex;
|
|
324
|
+
flex-direction: row;
|
|
325
|
+
align-items: center;
|
|
326
|
+
gap: var(--space-2);
|
|
327
|
+
padding: var(--space-2);
|
|
328
|
+
border-radius: var(--radius);
|
|
329
|
+
min-width: 0;
|
|
330
|
+
}
|
|
331
|
+
.sh-ui-sidebar__brand-icon {
|
|
332
|
+
display: flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
justify-content: center;
|
|
335
|
+
flex-shrink: 0;
|
|
336
|
+
width: 2rem;
|
|
337
|
+
height: 2rem;
|
|
338
|
+
border-radius: calc(var(--radius) - 2px);
|
|
339
|
+
}
|
|
340
|
+
.sh-ui-sidebar__brand-text {
|
|
341
|
+
display: flex;
|
|
342
|
+
flex-direction: column;
|
|
343
|
+
flex: 1 1 0%;
|
|
344
|
+
min-width: 0;
|
|
345
|
+
gap: 0;
|
|
346
|
+
}
|
|
347
|
+
.sh-ui-sidebar__brand-title {
|
|
348
|
+
font-size: var(--text-sm);
|
|
349
|
+
font-weight: var(--weight-medium);
|
|
350
|
+
line-height: 1.25;
|
|
351
|
+
color: var(--sidebar-fg);
|
|
352
|
+
white-space: nowrap;
|
|
353
|
+
overflow: hidden;
|
|
354
|
+
text-overflow: ellipsis;
|
|
355
|
+
}
|
|
356
|
+
.sh-ui-sidebar__brand-subtitle {
|
|
357
|
+
font-size: var(--text-xs);
|
|
358
|
+
line-height: 1.25;
|
|
359
|
+
color: var(--foreground-muted);
|
|
360
|
+
white-space: nowrap;
|
|
361
|
+
overflow: hidden;
|
|
362
|
+
text-overflow: ellipsis;
|
|
363
|
+
}
|
|
364
|
+
.sh-ui-sidebar__brand-action {
|
|
365
|
+
margin-left: auto;
|
|
366
|
+
flex-shrink: 0;
|
|
367
|
+
display: flex;
|
|
368
|
+
align-items: center;
|
|
369
|
+
justify-content: center;
|
|
370
|
+
}
|
|
371
|
+
|
|
321
372
|
.sh-ui-sidebar__content {
|
|
322
373
|
display: flex;
|
|
323
374
|
flex-direction: column;
|
|
@@ -301,6 +301,57 @@
|
|
|
301
301
|
background-color: transparent;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
/* ───────────── Brand (compound primitive, v0.84.0+) ───────────── */
|
|
305
|
+
.sidebar__brand {
|
|
306
|
+
display: flex;
|
|
307
|
+
flex-direction: row;
|
|
308
|
+
align-items: center;
|
|
309
|
+
gap: var(--space-2);
|
|
310
|
+
padding: var(--space-2);
|
|
311
|
+
border-radius: var(--radius);
|
|
312
|
+
min-width: 0;
|
|
313
|
+
}
|
|
314
|
+
.sidebar__brand_icon {
|
|
315
|
+
display: flex;
|
|
316
|
+
align-items: center;
|
|
317
|
+
justify-content: center;
|
|
318
|
+
flex-shrink: 0;
|
|
319
|
+
width: 2rem;
|
|
320
|
+
height: 2rem;
|
|
321
|
+
border-radius: calc(var(--radius) - 2px);
|
|
322
|
+
}
|
|
323
|
+
.sidebar__brand_text {
|
|
324
|
+
display: flex;
|
|
325
|
+
flex-direction: column;
|
|
326
|
+
flex: 1 1 0%;
|
|
327
|
+
min-width: 0;
|
|
328
|
+
gap: 0;
|
|
329
|
+
}
|
|
330
|
+
.sidebar__brand_title {
|
|
331
|
+
font-size: var(--text-sm);
|
|
332
|
+
font-weight: var(--weight-medium);
|
|
333
|
+
line-height: 1.25;
|
|
334
|
+
color: var(--sidebar-fg);
|
|
335
|
+
white-space: nowrap;
|
|
336
|
+
overflow: hidden;
|
|
337
|
+
text-overflow: ellipsis;
|
|
338
|
+
}
|
|
339
|
+
.sidebar__brand_subtitle {
|
|
340
|
+
font-size: var(--text-xs);
|
|
341
|
+
line-height: 1.25;
|
|
342
|
+
color: var(--foreground-muted);
|
|
343
|
+
white-space: nowrap;
|
|
344
|
+
overflow: hidden;
|
|
345
|
+
text-overflow: ellipsis;
|
|
346
|
+
}
|
|
347
|
+
.sidebar__brand_action {
|
|
348
|
+
margin-left: auto;
|
|
349
|
+
flex-shrink: 0;
|
|
350
|
+
display: flex;
|
|
351
|
+
align-items: center;
|
|
352
|
+
justify-content: center;
|
|
353
|
+
}
|
|
354
|
+
|
|
304
355
|
.sidebar__content {
|
|
305
356
|
display: flex;
|
|
306
357
|
flex-direction: column;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$description": "컴포넌트별 토큰 의존성 (var(--*) 추출). build-registry-tokens.mjs 가 자동 생성.",
|
|
3
|
-
"$generated": "2026-05-
|
|
3
|
+
"$generated": "2026-05-13T03:22:30.348Z",
|
|
4
4
|
"components": {
|
|
5
5
|
"button": {
|
|
6
6
|
"plain": [
|
|
@@ -678,6 +678,7 @@
|
|
|
678
678
|
"--duration-fast",
|
|
679
679
|
"--duration-slow",
|
|
680
680
|
"--foreground",
|
|
681
|
+
"--foreground-muted",
|
|
681
682
|
"--opacity-disabled",
|
|
682
683
|
"--radius",
|
|
683
684
|
"--shadow-xl",
|
|
@@ -688,6 +689,7 @@
|
|
|
688
689
|
"--text-lg",
|
|
689
690
|
"--text-sm",
|
|
690
691
|
"--text-xs",
|
|
692
|
+
"--weight-medium",
|
|
691
693
|
"--z-modal",
|
|
692
694
|
"--z-overlay"
|
|
693
695
|
],
|