@suphark/ui 0.3.0 → 0.3.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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
รูปแบบตาม [Keep a Changelog](https://keepachangelog.com/) · semver: patch = หน้าตา/บั๊ก, minor = component/prop ใหม่, major = เปลี่ยน/ลบ props
|
|
4
4
|
|
|
5
|
+
## [0.3.1] — 2026-09-18
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- `AppShell`: slot `sidebarHeader` ซ่อนอัตโนมัติเมื่อยุบเป็นไอคอน (เดิมล้นทับเนื้อหา — รายงานจาก Pi Auth) + prop ใหม่ `sidebarHeaderCollapsed` สำหรับไอคอนแทนตอนยุบ
|
|
10
|
+
- `HeaderBreadcrumbs`: `labels` รับฟังก์ชัน `(segment, href) => ReactNode` ได้ (ใส่ `t()` ของ next-intl) และ map รับ ReactNode — ฟังก์ชันต้องมาจาก client component (ส่งจาก server layout จะได้ 500 "Functions cannot be passed directly to Client Components"; showcase สาธิตด้วย `breadcrumbs: <ShellBreadcrumbs />`)
|
|
11
|
+
- README: อธิบายว่า `ThemeMenuItems` มี group + หัวในตัว ไม่ต้องห่อเอง
|
|
12
|
+
|
|
5
13
|
## [0.3.0] — 2026-09-18
|
|
6
14
|
|
|
7
15
|
### Added
|
package/README.md
CHANGED
|
@@ -113,6 +113,9 @@ export default async function Layout({ children }) {
|
|
|
113
113
|
|
|
114
114
|
- `header={false}` ไม่มี header · `collapsible="offcanvas"` ซ่อนทั้งแผง (supplychain) · `contentMaxWidth="max-w-5xl"` จำกัดความกว้าง (Pi Auth)
|
|
115
115
|
- `pin={{ pinnedHrefs, onTogglePin }}` เปิดปุ่มปักหมุดเมนู (แอปเก็บ state เอง) · `header.center` วาง pinned menus
|
|
116
|
+
- `sidebarHeader` ซ่อนอัตโนมัติเมื่อยุบเป็นไอคอน — ใส่ `sidebarHeaderCollapsed` ถ้าต้องการไอคอนแทน (เช่นปุ่ม ⌘K)
|
|
117
|
+
- `header.breadcrumbs.labels` รับฟังก์ชัน `(segment, href) => ReactNode` ได้ (ใส่ `t()` ของ next-intl) — **แต่ฟังก์ชันส่งจาก server layout ไม่ได้** (React error "Functions cannot be passed directly to Client Components") ให้ทำ client component ที่เรียก `<HeaderBreadcrumbs labels={…} />` แล้วส่งเป็น `breadcrumbs: <MyBreadcrumbs />` (ดู `apps/showcase/src/app/shell/breadcrumbs.tsx`)
|
|
118
|
+
- `<UserMenu>` children คือรายการเมนู: `ThemeMenuItems` มี `DropdownMenuGroup` + หัว "ธีม" ในตัวแล้ว (ปรับข้อความด้วย `label`/`labels`) ไม่ต้องห่อเพิ่ม; รายการอื่นใส่ `DropdownMenuItem`/`DropdownMenuGroup` เอง
|
|
116
119
|
- ตัวอย่างรันได้ที่ `apps/showcase/src/app/shell`
|
|
117
120
|
|
|
118
121
|
### สีแบรนด์ต่อ tenant/แอป (`--brand`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suphark/ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Design system กลางของ Suphark apps: shadcn/ui (base-nova, Base UI, Tailwind v4) + shared components + token/ฟอนต์ Noto Sans Thai Looped — ส่งเป็น source ให้แอป Next transpile เอง",
|
|
@@ -43,8 +43,10 @@ export interface AppShellProps {
|
|
|
43
43
|
searchPlaceholder?: string;
|
|
44
44
|
/** ปุ่ม pin เมนูขึ้น header (แอปเก็บ state เอง) */
|
|
45
45
|
pin?: AppNavPinProps;
|
|
46
|
-
/** ส่วนเพิ่มใน sidebar ใต้โลโก้ เช่น TenantSwitcher / CommandPalette */
|
|
46
|
+
/** ส่วนเพิ่มใน sidebar ใต้โลโก้ เช่น TenantSwitcher / CommandPalette — ซ่อนอัตโนมัติเมื่อยุบเป็นไอคอน */
|
|
47
47
|
sidebarHeader?: ReactNode;
|
|
48
|
+
/** สิ่งที่แสดงแทน `sidebarHeader` ตอนยุบเป็นไอคอน (เช่นปุ่มไอคอน ⌘K) — ไม่ส่ง = ไม่แสดงอะไร */
|
|
49
|
+
sidebarHeaderCollapsed?: ReactNode;
|
|
48
50
|
/** ท้าย sidebar — ปกติคือ `<UserMenu variant="sidebar" />` */
|
|
49
51
|
sidebarFooter?: ReactNode;
|
|
50
52
|
/**
|
|
@@ -96,6 +98,7 @@ export function AppShell({
|
|
|
96
98
|
searchPlaceholder = "ค้นหาเมนู…",
|
|
97
99
|
pin,
|
|
98
100
|
sidebarHeader,
|
|
101
|
+
sidebarHeaderCollapsed,
|
|
99
102
|
sidebarFooter,
|
|
100
103
|
header = true,
|
|
101
104
|
brandColor,
|
|
@@ -133,6 +136,7 @@ export function AppShell({
|
|
|
133
136
|
searchPlaceholder={searchPlaceholder}
|
|
134
137
|
pin={pin}
|
|
135
138
|
sidebarHeader={sidebarHeader}
|
|
139
|
+
sidebarHeaderCollapsed={sidebarHeaderCollapsed}
|
|
136
140
|
sidebarFooter={sidebarFooter}
|
|
137
141
|
collapsible={collapsible}
|
|
138
142
|
/>
|
|
@@ -195,6 +199,7 @@ function ShellSidebar({
|
|
|
195
199
|
searchPlaceholder,
|
|
196
200
|
pin,
|
|
197
201
|
sidebarHeader,
|
|
202
|
+
sidebarHeaderCollapsed,
|
|
198
203
|
sidebarFooter,
|
|
199
204
|
collapsible,
|
|
200
205
|
}: Pick<
|
|
@@ -205,6 +210,7 @@ function ShellSidebar({
|
|
|
205
210
|
| "searchPlaceholder"
|
|
206
211
|
| "pin"
|
|
207
212
|
| "sidebarHeader"
|
|
213
|
+
| "sidebarHeaderCollapsed"
|
|
208
214
|
| "sidebarFooter"
|
|
209
215
|
| "collapsible"
|
|
210
216
|
>) {
|
|
@@ -249,7 +255,12 @@ function ShellSidebar({
|
|
|
249
255
|
</InputGroup>
|
|
250
256
|
</div>
|
|
251
257
|
)}
|
|
252
|
-
{sidebarHeader
|
|
258
|
+
{sidebarHeader && (
|
|
259
|
+
<div className="group-data-[collapsible=icon]:hidden">{sidebarHeader}</div>
|
|
260
|
+
)}
|
|
261
|
+
{sidebarHeaderCollapsed && (
|
|
262
|
+
<div className="hidden group-data-[collapsible=icon]:block">{sidebarHeaderCollapsed}</div>
|
|
263
|
+
)}
|
|
253
264
|
</SidebarHeader>
|
|
254
265
|
<SidebarContent>
|
|
255
266
|
<AppNav nav={visibleNav} pin={pin} forceOpen={searching} />
|
|
@@ -10,11 +10,18 @@ import {
|
|
|
10
10
|
} from "@suphark/ui/components/ui/breadcrumb";
|
|
11
11
|
import Link from "next/link";
|
|
12
12
|
import { usePathname } from "next/navigation";
|
|
13
|
-
import { Fragment } from "react";
|
|
13
|
+
import { Fragment, type ReactNode } from "react";
|
|
14
14
|
|
|
15
15
|
export interface HeaderBreadcrumbsProps {
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* ชื่อแสดงของแต่ละ segment: map (`{ "po-tracking": "PO Tracking" }`) หรือฟังก์ชัน
|
|
18
|
+
* `(segment, href) => ReactNode | undefined` (เช่น t() ของ next-intl)
|
|
19
|
+
* คืน undefined/ไม่พบ = ตัวแรกพิมพ์ใหญ่ แทน - ด้วยช่องว่าง
|
|
20
|
+
*
|
|
21
|
+
* ระวัง: ฟังก์ชันส่งข้าม server→client boundary ไม่ได้ ("Functions cannot be passed directly to Client Components")
|
|
22
|
+
* ถ้า layout เป็น server component ให้ใช้ map หรือส่ง `breadcrumbs: <MyBreadcrumbs />` ที่เป็น client component เรียก HeaderBreadcrumbs เอง
|
|
23
|
+
*/
|
|
24
|
+
labels?: Record<string, ReactNode> | ((segment: string, href: string) => ReactNode | undefined);
|
|
18
25
|
homeHref?: string;
|
|
19
26
|
homeLabel?: string;
|
|
20
27
|
/** ตัด segment นำหน้าออก เช่น `["console"]` เมื่อทุกหน้าอยู่ใต้ /console */
|
|
@@ -25,9 +32,9 @@ export interface HeaderBreadcrumbsProps {
|
|
|
25
32
|
|
|
26
33
|
const ID_LIKE = /^(\d+|[0-9a-f]{8}-[0-9a-f-]{27}|c[a-z0-9]{20,}|[0-9a-z]{16,})$/i;
|
|
27
34
|
|
|
28
|
-
function labelOf(segment: string,
|
|
29
|
-
const known = labels?.[segment];
|
|
30
|
-
if (known) return known;
|
|
35
|
+
function labelOf(segment: string, href: string, labels?: HeaderBreadcrumbsProps["labels"]) {
|
|
36
|
+
const known = typeof labels === "function" ? labels(segment, href) : labels?.[segment];
|
|
37
|
+
if (known !== undefined && known !== null && known !== "") return known;
|
|
31
38
|
const decoded = decodeURIComponent(segment);
|
|
32
39
|
return decoded.charAt(0).toUpperCase() + decoded.slice(1).replace(/-/g, " ");
|
|
33
40
|
}
|
|
@@ -56,7 +63,7 @@ export function HeaderBreadcrumbs({
|
|
|
56
63
|
{shown.length > 0 && <BreadcrumbSeparator className="hidden md:block" />}
|
|
57
64
|
{shown.map(({ segment, href }, index) => {
|
|
58
65
|
const isLast = index === shown.length - 1;
|
|
59
|
-
const label = labelOf(segment, labels);
|
|
66
|
+
const label = labelOf(segment, href, labels);
|
|
60
67
|
return (
|
|
61
68
|
<Fragment key={href}>
|
|
62
69
|
<BreadcrumbItem>
|