sh-ui-cli 0.117.0 → 0.118.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,18 @@
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.118.0",
7
+ "date": "2026-06-18",
8
+ "title": "Table 컴포넌트 — TanStack 데이터 테이블 (정렬·선택·페이지)",
9
+ "type": "minor",
10
+ "highlights": [
11
+ "신규 Table — presentational 프리미티브(Table/TableHeader/TableBody/TableRow/TableHead/TableCell/TableFooter/TableCaption). 네이티브 <table> + sh-ui 토큰, 의존성 0",
12
+ "TanStack Table v8 통합 예제 — 정렬·행 선택·페이지네이션이 동작하는 데이터 테이블(docs). 컬럼/셀 렌더는 100% 자유",
13
+ "shadcn 모델(headless 엔진 + presentational 레이어). 풀 기능(필터·고정·그룹화·DnD)은 후속 phase"
14
+ ],
15
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.118.0"
16
+ },
5
17
  {
6
18
  "version": "0.117.0",
7
19
  "date": "2026-06-18",
@@ -0,0 +1,69 @@
1
+ import * as React from "react";
2
+ import styles from "./styles.module.css";
3
+ import { cn } from "@SH_UI_UTILS@";
4
+
5
+ export const Table = React.forwardRef<
6
+ HTMLTableElement,
7
+ React.TableHTMLAttributes<HTMLTableElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div className={styles["table__wrapper"]}>
10
+ <table ref={ref} className={cn(styles.table, className)} {...props} />
11
+ </div>
12
+ ));
13
+ Table.displayName = "Table";
14
+
15
+ export const TableHeader = React.forwardRef<
16
+ HTMLTableSectionElement,
17
+ React.HTMLAttributes<HTMLTableSectionElement>
18
+ >(({ className, ...props }, ref) => (
19
+ <thead ref={ref} className={cn(styles.table__header, className)} {...props} />
20
+ ));
21
+ TableHeader.displayName = "TableHeader";
22
+
23
+ export const TableBody = React.forwardRef<
24
+ HTMLTableSectionElement,
25
+ React.HTMLAttributes<HTMLTableSectionElement>
26
+ >(({ className, ...props }, ref) => (
27
+ <tbody ref={ref} className={cn(styles.table__body, className)} {...props} />
28
+ ));
29
+ TableBody.displayName = "TableBody";
30
+
31
+ export const TableFooter = React.forwardRef<
32
+ HTMLTableSectionElement,
33
+ React.HTMLAttributes<HTMLTableSectionElement>
34
+ >(({ className, ...props }, ref) => (
35
+ <tfoot ref={ref} className={cn(styles.table__footer, className)} {...props} />
36
+ ));
37
+ TableFooter.displayName = "TableFooter";
38
+
39
+ export const TableRow = React.forwardRef<
40
+ HTMLTableRowElement,
41
+ React.HTMLAttributes<HTMLTableRowElement>
42
+ >(({ className, ...props }, ref) => (
43
+ <tr ref={ref} className={cn(styles.table__row, className)} {...props} />
44
+ ));
45
+ TableRow.displayName = "TableRow";
46
+
47
+ export const TableHead = React.forwardRef<
48
+ HTMLTableCellElement,
49
+ React.ThHTMLAttributes<HTMLTableCellElement>
50
+ >(({ className, scope = "col", ...props }, ref) => (
51
+ <th ref={ref} scope={scope} className={cn(styles.table__head, className)} {...props} />
52
+ ));
53
+ TableHead.displayName = "TableHead";
54
+
55
+ export const TableCell = React.forwardRef<
56
+ HTMLTableCellElement,
57
+ React.TdHTMLAttributes<HTMLTableCellElement>
58
+ >(({ className, ...props }, ref) => (
59
+ <td ref={ref} className={cn(styles.table__cell, className)} {...props} />
60
+ ));
61
+ TableCell.displayName = "TableCell";
62
+
63
+ export const TableCaption = React.forwardRef<
64
+ HTMLTableCaptionElement,
65
+ React.HTMLAttributes<HTMLTableCaptionElement>
66
+ >(({ className, ...props }, ref) => (
67
+ <caption ref={ref} className={cn(styles.table__caption, className)} {...props} />
68
+ ));
69
+ TableCaption.displayName = "TableCaption";
@@ -0,0 +1,68 @@
1
+ import * as React from "react";
2
+ import { cn } from "@SH_UI_UTILS@";
3
+
4
+ export const Table = React.forwardRef<
5
+ HTMLTableElement,
6
+ React.TableHTMLAttributes<HTMLTableElement>
7
+ >(({ className, ...props }, ref) => (
8
+ <div className="w-full overflow-x-auto">
9
+ <table ref={ref} className={cn("w-full border-collapse text-[length:var(--text-sm)] text-foreground", className)} {...props} />
10
+ </div>
11
+ ));
12
+ Table.displayName = "Table";
13
+
14
+ export const TableHeader = React.forwardRef<
15
+ HTMLTableSectionElement,
16
+ React.HTMLAttributes<HTMLTableSectionElement>
17
+ >(({ className, ...props }, ref) => (
18
+ <thead ref={ref} className={cn(className)} {...props} />
19
+ ));
20
+ TableHeader.displayName = "TableHeader";
21
+
22
+ export const TableBody = React.forwardRef<
23
+ HTMLTableSectionElement,
24
+ React.HTMLAttributes<HTMLTableSectionElement>
25
+ >(({ className, ...props }, ref) => (
26
+ <tbody ref={ref} className={cn(className)} {...props} />
27
+ ));
28
+ TableBody.displayName = "TableBody";
29
+
30
+ export const TableFooter = React.forwardRef<
31
+ HTMLTableSectionElement,
32
+ React.HTMLAttributes<HTMLTableSectionElement>
33
+ >(({ className, ...props }, ref) => (
34
+ <tfoot ref={ref} className={cn("border-t border-border font-medium", className)} {...props} />
35
+ ));
36
+ TableFooter.displayName = "TableFooter";
37
+
38
+ export const TableRow = React.forwardRef<
39
+ HTMLTableRowElement,
40
+ React.HTMLAttributes<HTMLTableRowElement>
41
+ >(({ className, ...props }, ref) => (
42
+ <tr ref={ref} className={cn("border-b border-border transition-[background-color] duration-[var(--duration-fast)] hover:bg-background-muted data-[state=selected]:bg-background-muted motion-reduce:transition-none", className)} {...props} />
43
+ ));
44
+ TableRow.displayName = "TableRow";
45
+
46
+ export const TableHead = React.forwardRef<
47
+ HTMLTableCellElement,
48
+ React.ThHTMLAttributes<HTMLTableCellElement>
49
+ >(({ className, scope = "col", ...props }, ref) => (
50
+ <th ref={ref} scope={scope} className={cn("h-[var(--control-md)] px-[var(--space-3)] text-start font-medium text-foreground-muted align-middle whitespace-nowrap", className)} {...props} />
51
+ ));
52
+ TableHead.displayName = "TableHead";
53
+
54
+ export const TableCell = React.forwardRef<
55
+ HTMLTableCellElement,
56
+ React.TdHTMLAttributes<HTMLTableCellElement>
57
+ >(({ className, ...props }, ref) => (
58
+ <td ref={ref} className={cn("p-[var(--space-3)] align-middle", className)} {...props} />
59
+ ));
60
+ TableCell.displayName = "TableCell";
61
+
62
+ export const TableCaption = React.forwardRef<
63
+ HTMLTableCaptionElement,
64
+ React.HTMLAttributes<HTMLTableCaptionElement>
65
+ >(({ className, ...props }, ref) => (
66
+ <caption ref={ref} className={cn("mt-[var(--space-3)] text-foreground-muted text-[length:var(--text-xs)] text-start", className)} {...props} />
67
+ ));
68
+ TableCaption.displayName = "TableCaption";
@@ -0,0 +1,69 @@
1
+ import * as React from "react";
2
+ import "./styles.css";
3
+ import { cn } from "@SH_UI_UTILS@";
4
+
5
+ export const Table = React.forwardRef<
6
+ HTMLTableElement,
7
+ React.TableHTMLAttributes<HTMLTableElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div className="sh-ui-table__wrapper">
10
+ <table ref={ref} className={cn("sh-ui-table", className)} {...props} />
11
+ </div>
12
+ ));
13
+ Table.displayName = "Table";
14
+
15
+ export const TableHeader = React.forwardRef<
16
+ HTMLTableSectionElement,
17
+ React.HTMLAttributes<HTMLTableSectionElement>
18
+ >(({ className, ...props }, ref) => (
19
+ <thead ref={ref} className={cn("sh-ui-table__header", className)} {...props} />
20
+ ));
21
+ TableHeader.displayName = "TableHeader";
22
+
23
+ export const TableBody = React.forwardRef<
24
+ HTMLTableSectionElement,
25
+ React.HTMLAttributes<HTMLTableSectionElement>
26
+ >(({ className, ...props }, ref) => (
27
+ <tbody ref={ref} className={cn("sh-ui-table__body", className)} {...props} />
28
+ ));
29
+ TableBody.displayName = "TableBody";
30
+
31
+ export const TableFooter = React.forwardRef<
32
+ HTMLTableSectionElement,
33
+ React.HTMLAttributes<HTMLTableSectionElement>
34
+ >(({ className, ...props }, ref) => (
35
+ <tfoot ref={ref} className={cn("sh-ui-table__footer", className)} {...props} />
36
+ ));
37
+ TableFooter.displayName = "TableFooter";
38
+
39
+ export const TableRow = React.forwardRef<
40
+ HTMLTableRowElement,
41
+ React.HTMLAttributes<HTMLTableRowElement>
42
+ >(({ className, ...props }, ref) => (
43
+ <tr ref={ref} className={cn("sh-ui-table__row", className)} {...props} />
44
+ ));
45
+ TableRow.displayName = "TableRow";
46
+
47
+ export const TableHead = React.forwardRef<
48
+ HTMLTableCellElement,
49
+ React.ThHTMLAttributes<HTMLTableCellElement>
50
+ >(({ className, scope = "col", ...props }, ref) => (
51
+ <th ref={ref} scope={scope} className={cn("sh-ui-table__head", className)} {...props} />
52
+ ));
53
+ TableHead.displayName = "TableHead";
54
+
55
+ export const TableCell = React.forwardRef<
56
+ HTMLTableCellElement,
57
+ React.TdHTMLAttributes<HTMLTableCellElement>
58
+ >(({ className, ...props }, ref) => (
59
+ <td ref={ref} className={cn("sh-ui-table__cell", className)} {...props} />
60
+ ));
61
+ TableCell.displayName = "TableCell";
62
+
63
+ export const TableCaption = React.forwardRef<
64
+ HTMLTableCaptionElement,
65
+ React.HTMLAttributes<HTMLTableCaptionElement>
66
+ >(({ className, ...props }, ref) => (
67
+ <caption ref={ref} className={cn("sh-ui-table__caption", className)} {...props} />
68
+ ));
69
+ TableCaption.displayName = "TableCaption";
@@ -0,0 +1,57 @@
1
+ .sh-ui-table__wrapper {
2
+ width: 100%;
3
+ overflow-x: auto;
4
+ }
5
+
6
+ .sh-ui-table {
7
+ width: 100%;
8
+ border-collapse: collapse;
9
+ font-size: var(--text-sm);
10
+ color: var(--foreground);
11
+ }
12
+
13
+ .sh-ui-table__head {
14
+ height: var(--control-md);
15
+ padding: 0 var(--space-3);
16
+ text-align: start;
17
+ font-weight: var(--weight-medium);
18
+ color: var(--foreground-muted);
19
+ vertical-align: middle;
20
+ white-space: nowrap;
21
+ }
22
+
23
+ .sh-ui-table__cell {
24
+ padding: var(--space-3);
25
+ vertical-align: middle;
26
+ }
27
+
28
+ .sh-ui-table__row {
29
+ border-bottom: 1px solid var(--border);
30
+ transition: background-color var(--duration-fast) var(--ease-standard);
31
+ }
32
+
33
+ .sh-ui-table__body .sh-ui-table__row:hover {
34
+ background: var(--background-muted);
35
+ }
36
+
37
+ .sh-ui-table__row[data-state="selected"] {
38
+ background: var(--background-muted);
39
+ }
40
+
41
+ .sh-ui-table__footer {
42
+ border-top: 1px solid var(--border);
43
+ font-weight: var(--weight-medium);
44
+ }
45
+
46
+ .sh-ui-table__caption {
47
+ margin-top: var(--space-3);
48
+ color: var(--foreground-muted);
49
+ font-size: var(--text-xs);
50
+ text-align: start;
51
+ }
52
+
53
+ @media (prefers-reduced-motion: reduce) {
54
+ .sh-ui-table__row {
55
+ transition: none;
56
+ }
57
+ }
@@ -0,0 +1,57 @@
1
+ .table__wrapper {
2
+ width: 100%;
3
+ overflow-x: auto;
4
+ }
5
+
6
+ .table {
7
+ width: 100%;
8
+ border-collapse: collapse;
9
+ font-size: var(--text-sm);
10
+ color: var(--foreground);
11
+ }
12
+
13
+ .table__head {
14
+ height: var(--control-md);
15
+ padding: 0 var(--space-3);
16
+ text-align: start;
17
+ font-weight: var(--weight-medium);
18
+ color: var(--foreground-muted);
19
+ vertical-align: middle;
20
+ white-space: nowrap;
21
+ }
22
+
23
+ .table__cell {
24
+ padding: var(--space-3);
25
+ vertical-align: middle;
26
+ }
27
+
28
+ .table__row {
29
+ border-bottom: 1px solid var(--border);
30
+ transition: background-color var(--duration-fast) var(--ease-standard);
31
+ }
32
+
33
+ .table__body .table__row:hover {
34
+ background: var(--background-muted);
35
+ }
36
+
37
+ .table__row[data-state="selected"] {
38
+ background: var(--background-muted);
39
+ }
40
+
41
+ .table__footer {
42
+ border-top: 1px solid var(--border);
43
+ font-weight: var(--weight-medium);
44
+ }
45
+
46
+ .table__caption {
47
+ margin-top: var(--space-3);
48
+ color: var(--foreground-muted);
49
+ font-size: var(--text-xs);
50
+ text-align: start;
51
+ }
52
+
53
+ @media (prefers-reduced-motion: reduce) {
54
+ .table__row {
55
+ transition: none;
56
+ }
57
+ }
@@ -0,0 +1,59 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import * as React from "react";
4
+ import {
5
+ Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption,
6
+ } from "./index";
7
+
8
+ function Sample(props: { selected?: boolean }) {
9
+ return (
10
+ <Table>
11
+ <TableCaption>사용자 목록</TableCaption>
12
+ <TableHeader>
13
+ <TableRow>
14
+ <TableHead>이름</TableHead>
15
+ </TableRow>
16
+ </TableHeader>
17
+ <TableBody>
18
+ <TableRow data-state={props.selected ? "selected" : undefined}>
19
+ <TableCell>Kim</TableCell>
20
+ </TableRow>
21
+ </TableBody>
22
+ <TableFooter>
23
+ <TableRow><TableCell>합계 1</TableCell></TableRow>
24
+ </TableFooter>
25
+ </Table>
26
+ );
27
+ }
28
+
29
+ describe("Table primitives", () => {
30
+ it("네이티브 table 구조를 렌더", () => {
31
+ const { container } = render(<Sample />);
32
+ expect(container.querySelector("table")).toBeTruthy();
33
+ expect(container.querySelector("thead")).toBeTruthy();
34
+ expect(container.querySelector("tbody")).toBeTruthy();
35
+ expect(container.querySelector("tfoot")).toBeTruthy();
36
+ expect(container.querySelector("caption")).toBeTruthy();
37
+ });
38
+
39
+ it("TableHead 는 th[scope=col]", () => {
40
+ const { container } = render(<Sample />);
41
+ const th = container.querySelector("th");
42
+ expect(th?.getAttribute("scope")).toBe("col");
43
+ });
44
+
45
+ it("columnheader role 로 헤더 셀 접근", () => {
46
+ render(<Sample />);
47
+ expect(screen.getByRole("columnheader", { name: "이름" })).toBeTruthy();
48
+ });
49
+
50
+ it("data-state=selected 가 행에 반영", () => {
51
+ const { container } = render(<Sample selected />);
52
+ expect(container.querySelector('tbody tr[data-state="selected"]')).toBeTruthy();
53
+ });
54
+
55
+ it("className 이 병합된다", () => {
56
+ const { container } = render(<Table className="custom"><TableBody><TableRow><TableCell>x</TableCell></TableRow></TableBody></Table>);
57
+ expect(container.querySelector("table.custom")).toBeTruthy();
58
+ });
59
+ });
@@ -1704,6 +1704,39 @@
1704
1704
  "dependencies": [],
1705
1705
  "registryDependencies": ["utils"]
1706
1706
  },
1707
+ "table": {
1708
+ "name": "table",
1709
+ "type": "component",
1710
+ "files": [
1711
+ {
1712
+ "src": "components/table/index.tsx",
1713
+ "dest": "{components}/table/index.tsx",
1714
+ "frameworks": ["plain"]
1715
+ },
1716
+ {
1717
+ "src": "components/table/styles.css",
1718
+ "dest": "{components}/table/styles.css",
1719
+ "frameworks": ["plain"]
1720
+ },
1721
+ {
1722
+ "src": "components/table/index.tailwind.tsx",
1723
+ "dest": "{components}/table/index.tsx",
1724
+ "frameworks": ["tailwind"]
1725
+ },
1726
+ {
1727
+ "src": "components/table/index.module.tsx",
1728
+ "dest": "{components}/table/index.tsx",
1729
+ "frameworks": ["css-modules"]
1730
+ },
1731
+ {
1732
+ "src": "components/table/styles.module.css",
1733
+ "dest": "{components}/table/styles.module.css",
1734
+ "frameworks": ["css-modules"]
1735
+ }
1736
+ ],
1737
+ "dependencies": [],
1738
+ "registryDependencies": ["utils"]
1739
+ },
1707
1740
  "carousel": {
1708
1741
  "name": "carousel",
1709
1742
  "type": "component",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$description": "컴포넌트별 토큰 의존성 (var(--*) 추출). build-registry-tokens.mjs 가 자동 생성.",
3
- "$generated": "2026-06-18T06:02:23.309Z",
3
+ "$generated": "2026-06-19T00:56:42.605Z",
4
4
  "components": {
5
5
  "button": {
6
6
  "plain": [
@@ -1970,6 +1970,42 @@
1970
1970
  ],
1971
1971
  "vanilla-extract": []
1972
1972
  },
1973
+ "table": {
1974
+ "plain": [
1975
+ "--background-muted",
1976
+ "--border",
1977
+ "--control-md",
1978
+ "--duration-fast",
1979
+ "--ease-standard",
1980
+ "--foreground",
1981
+ "--foreground-muted",
1982
+ "--space-3",
1983
+ "--text-sm",
1984
+ "--text-xs",
1985
+ "--weight-medium"
1986
+ ],
1987
+ "tailwind": [
1988
+ "--control-md",
1989
+ "--duration-fast",
1990
+ "--space-3",
1991
+ "--text-sm",
1992
+ "--text-xs"
1993
+ ],
1994
+ "css-modules": [
1995
+ "--background-muted",
1996
+ "--border",
1997
+ "--control-md",
1998
+ "--duration-fast",
1999
+ "--ease-standard",
2000
+ "--foreground",
2001
+ "--foreground-muted",
2002
+ "--space-3",
2003
+ "--text-sm",
2004
+ "--text-xs",
2005
+ "--weight-medium"
2006
+ ],
2007
+ "vanilla-extract": []
2008
+ },
1973
2009
  "carousel": {
1974
2010
  "plain": [
1975
2011
  "--background",
@@ -37,6 +37,7 @@
37
37
  "progress": "ShUiProgress — 선형 진행률, indeterminate.",
38
38
  "spinner": "ShUiSpinner — 원형 로딩.",
39
39
  "separator": "ShUiSeparator — 구분선 (horizontal/vertical).",
40
- "skeleton": "ShUiSkeleton — 로딩 플레이스홀더."
40
+ "skeleton": "ShUiSkeleton — 로딩 플레이스홀더.",
41
+ "tree": "ShUiTree — 계층 데이터 트리. 확장/축소 + 단일 선택(제어·비제어). ShUiTreeNode(id/label/children/icon/disabled) 재귀. 키보드(화살표·Home/End·Enter/Space)·Semantics. ShUiTreeSize sm/md."
41
42
  }
42
43
  }
@@ -58,6 +58,7 @@
58
58
  "calendar": "내부 캘린더 위젯 (DatePicker 가 사용). single / multiple / range 모드. CalendarMessages 로 a11y 텍스트 override. 일반적으로 직접 사용보다 DatePicker / DateRangePicker 권장.",
59
59
  "scroll-area": "커스텀 스크롤 컨테이너 — composite export ScrollArea (Base UI). 내부에서 viewport + scrollbar + thumb + corner 를 자동 구성하며 OS-native 스크롤바를 대체한다. orientation: \"vertical\" | \"horizontal\" | \"both\" (기본 vertical). 외부 height/width 가 정해진 컨테이너 안에서 사용. viewportClassName 으로 viewport 의 패딩/레이아웃 분리 적용. 스크롤바는 hover/scrolling 시 fade in, prefers-reduced-motion 존중.",
60
60
  "tree": "계층 데이터 트리 뷰 — Tree 단일 export + TreeNode 타입(`@/components/ui/tree/types`). nodes(TreeNode[]: id/label/children/icon/disabled) 를 평탄화해 role=tree 로 렌더. 확장: expandedIds/onExpandedChange(controlled) 또는 defaultExpandedIds(uncontrolled). 선택: selectedId/onSelect(controlled) 또는 defaultSelectedId(uncontrolled). 키보드 네비(화살표·Home/End·Enter·typeahead), renderLabel 커스텀 렌더러, size(sm/md). Base UI 비의존 자체 구현.",
61
+ "table": "데이터 테이블 프리미티브 — Table / TableHeader / TableBody / TableFooter / TableRow / TableHead / TableCell / TableCaption 8종 presentational (네이티브 <table> + sh-ui 토큰, 의존성 0). 정렬·행선택·페이지네이션은 TanStack Table v8 로 조립 — docs 의 data-table 예제 참고. shadcn 모델(headless 엔진 + presentational 레이어).",
61
62
  "sheet": "화면 가장자리에서 슬라이드 인 하는 side drawer — separate exports: Sheet / SheetTrigger / SheetClose / SheetContent / SheetTitle / SheetDescription / SheetHeader / SheetFooter / SheetCloseX (Base UI Drawer 래핑, 포커스 트랩). 글로벌 알림함 · 작업 큐 · 보조 패널 같은 사이드바 무관 모달 시트용. 사이드바 인근 detail 패널은 SidebarPanel, 중앙 강제 모달은 Dialog 권장. SheetContent 의 side: \"right\" | \"left\" | \"top\" | \"bottom\" (기본 right) 으로 진입 방향 지정. SheetTrigger·SheetClose 는 자체 button — 다른 엘리먼트 슬롯은 `render` prop. ESC/바깥 클릭/포커스 복귀 자동, prefers-reduced-motion 시 transform 트랜지션 제거."
62
63
  }
63
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-ui-cli",
3
- "version": "0.117.0",
3
+ "version": "0.118.0",
4
4
  "description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
5
5
  "license": "MIT",
6
6
  "repository": {