@suphark/ui 0.11.2 → 0.12.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/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.12.0] — 2026-09-23
6
+
7
+ ### Added
8
+
9
+ - **`OrganizationSwitcher` prop `itemAction(org)`** (ขอจาก Pi Auth) — ปุ่มท้ายแถวของแต่ละองค์กรในเมนู เช่น ⚙ "ตั้งค่าองค์กร" แบบ Vercel/Clerk: แอปส่ง element ที่กดได้มาเอง (พร้อม aria-label/title) component ห่อให้และ **หยุด click/keydown ไม่ให้สลับองค์กร**; คืน `null` = แถวนั้นไม่มีปุ่ม; มี `itemAction` แล้วองค์กรเดียวก็ยังมีเมนู (เดิมยุบเป็นป้ายเฉย ๆ เมื่อไม่มี `footer`) — เดิมแอปต้องวาง "ตั้งค่าองค์กร" เป็นรายการแยกใน `footer`
10
+ - **`OrganizationSwitcher` prop `selectableActive`** (ขอจาก Pi Auth) — แถวที่ active กดได้ด้วย เรียก `onSwitch` เหมือนแถวอื่น (ค่าเริ่มต้น false = คงพฤติกรรมเดิม กดแล้วไม่ทำอะไร); สำหรับเมนูที่ "เลือก" = ไปหน้าของสิ่งนั้นไม่ใช่แค่สลับ เช่น crumb บัญชีของ Pi Auth ที่กดชื่อบัญชีแล้วเข้าคอนโซลบัญชีนั้น — ตัดรายการ "คอนโซลบัญชี" ท้ายเมนูที่ซ้ำช่องทางออกได้
11
+ - showcase: หน้า `organization-switcher` ตัวแรกใส่ ⚙ ท้ายแถว; tests 2 กรณี (รวม 130)
12
+
5
13
  ## [0.11.2] — 2026-09-22
6
14
 
7
15
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suphark/ui",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
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 เอง",
@@ -40,6 +40,16 @@ export interface OrganizationSwitcherProps {
40
40
  hint?: (org: OrganizationOption) => ReactNode;
41
41
  /** slot ท้ายเมนู เช่น "สร้างองค์กร…", "จัดการองค์กร" */
42
42
  footer?: ReactNode;
43
+ /**
44
+ * ปุ่มท้ายแถวของแต่ละองค์กร เช่น ⚙ "ตั้งค่าองค์กร" (แบบ Vercel/Clerk) — คืน `null` เพื่อไม่แสดงในแถวนั้น
45
+ * คลิกแล้ว **ไม่สลับองค์กร** (component หยุด event ให้); ส่ง element ที่กดได้เองและใส่ aria-label/title ด้วย
46
+ */
47
+ itemAction?: (org: OrganizationOption) => ReactNode;
48
+ /**
49
+ * แถวที่ active กดได้ด้วย (เรียก `onSwitch` เหมือนแถวอื่น) — ค่าเริ่มต้น false = กดแล้วไม่ทำอะไร
50
+ * ใช้เมื่อ "เลือก" = ไปหน้าของสิ่งนั้น ไม่ใช่แค่สลับ (เช่น crumb บัญชี: กดชื่อบัญชี = เข้าคอนโซลบัญชีนั้น)
51
+ */
52
+ selectableActive?: boolean;
43
53
  /** `header` = HeaderCrumb ใน header (layout top); `sidebar` = ปุ่มเต็มกว้างใน `sidebarHeader` ของ AppShell */
44
54
  placement?: "header" | "sidebar";
45
55
  className?: string;
@@ -64,6 +74,8 @@ export function OrganizationSwitcher({
64
74
  heading,
65
75
  hint = (o) => (o.slug ? `/${o.slug}` : undefined),
66
76
  footer,
77
+ itemAction,
78
+ selectableActive = false,
67
79
  placement = "header",
68
80
  className,
69
81
  }: OrganizationSwitcherProps) {
@@ -72,7 +84,7 @@ export function OrganizationSwitcher({
72
84
  const [pending, start] = useTransition();
73
85
  const active = organizations.find((o) => o.id === activeId);
74
86
  const label = active ? active.name : (fallbackLabel ?? text.switcher?.choose ?? "เลือกองค์กร");
75
- const single = organizations.length <= 1 && !footer;
87
+ const single = organizations.length <= 1 && !footer && !itemAction;
76
88
 
77
89
  const trigger =
78
90
  placement === "sidebar" ? (
@@ -102,7 +114,7 @@ export function OrganizationSwitcher({
102
114
  if (single) return trigger;
103
115
 
104
116
  function pick(id: string) {
105
- if (id === activeId) return;
117
+ if (id === activeId && !selectableActive) return;
106
118
  start(async () => {
107
119
  await onSwitch(id);
108
120
  if (refresh) router.refresh();
@@ -119,6 +131,7 @@ export function OrganizationSwitcher({
119
131
  </DropdownMenuLabel>
120
132
  {organizations.map((o) => {
121
133
  const isActive = o.id === activeId;
134
+ const action = itemAction?.(o);
122
135
  return (
123
136
  <DropdownMenuItem
124
137
  key={o.id}
@@ -134,6 +147,17 @@ export function OrganizationSwitcher({
134
147
  )}
135
148
  </span>
136
149
  {isActive && <Check className="size-4 shrink-0" aria-hidden="true" />}
150
+ {action && (
151
+ // ปุ่มท้ายแถว (⚙) — กดแล้วต้องไม่สลับองค์กร จึงหยุด event ไม่ให้ถึง onClick ของแถว
152
+ // biome-ignore lint/a11y/noStaticElementInteractions: ตัวห่อไม่ใช่ปุ่ม — แอปส่ง element ที่กดได้มาเอง
153
+ <span
154
+ className="-my-1 -mr-1 flex shrink-0 items-center"
155
+ onClick={(e) => e.stopPropagation()}
156
+ onKeyDown={(e) => e.stopPropagation()}
157
+ >
158
+ {action}
159
+ </span>
160
+ )}
137
161
  </DropdownMenuItem>
138
162
  );
139
163
  })}