@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.0",
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
- /** ชื่อแสดงของแต่ละ segment (`{ "po-tracking": "PO Tracking" }`) — ไม่พบ = ตัวแรกพิมพ์ใหญ่ แทน - ด้วยช่องว่าง */
17
- labels?: Record<string, string>;
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, labels?: Record<string, 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>