@suphark/ui 0.9.0 → 0.10.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,13 @@
2
2
 
3
3
  รูปแบบตาม [Keep a Changelog](https://keepachangelog.com/) · semver: patch = หน้าตา/บั๊ก, minor = component/prop ใหม่, major = เปลี่ยน/ลบ props
4
4
 
5
+ ## [0.10.0] — 2026-09-21
6
+
7
+ ### Added
8
+
9
+ - **`SectionNav`** (`@suphark/ui/next`, สเปกจาก Pi Auth — ชิ้นสุดท้ายที่แอปยังประกอบเอง) — หัวหมวด + แถบแท็บย่อยใต้หัวข้อสำหรับ layout ของหมวดที่มีหลายหน้า (directory/access/security/integrations): `title`/`description`/`actions` ส่งต่อ `PageHeader`, `tabs[{ href? | slug?, label, exact?, badge? }]` (href หรือ `${base}/${slug}`), แต่ละแท็บเป็น `NavLink` (`-mb-px border-b-2` ไฮไลต์ `border-primary` ตาม pathname, `aria-current=page`), badge ต่อท้าย label, `navLabel` (ค่าเริ่มต้น `UiText.sectionNav.tabs` — "แท็บย่อย"/"Section tabs"), ไม่มี tabs = ไม่มี nav; ไม่มี state ใช้จาก server component ได้ (เฉพาะแถบ `<nav>` เป็น client component เล็ก ๆ เพื่ออ่าน UiText); types `SectionNavProps`, `SectionNavTab`; `UiText.sectionNav` (optional)
10
+ - showcase: หน้า `section-nav` — 97 หน้า; tests 3 กรณี (รวม 126)
11
+
5
12
  ## [0.9.0] — 2026-09-21
6
13
 
7
14
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suphark/ui",
3
- "version": "0.9.0",
3
+ "version": "0.10.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 เอง",
@@ -0,0 +1,17 @@
1
+ "use client";
2
+
3
+ import { useUiText } from "@suphark/ui/components/shared/ui-text";
4
+ import type { ReactNode } from "react";
5
+
6
+ /** แถบแท็บของ SectionNav — แยกเป็น client เพื่ออ่าน aria-label ค่าเริ่มต้นจาก UiText (context) */
7
+ export function SectionNavBar({ label, children }: { label?: string; children: ReactNode }) {
8
+ const text = useUiText();
9
+ return (
10
+ <nav
11
+ aria-label={label ?? text.sectionNav?.tabs ?? "แท็บย่อย"}
12
+ className="flex flex-wrap gap-1 border-b text-sm"
13
+ >
14
+ {children}
15
+ </nav>
16
+ );
17
+ }
@@ -0,0 +1,79 @@
1
+ import { NavLink } from "@suphark/ui/components/app-shell/nav-link";
2
+ import { PageHeader } from "@suphark/ui/components/shared/page-header";
3
+ import { cn } from "@suphark/ui/lib/utils";
4
+ import type { ReactNode } from "react";
5
+ import { SectionNavBar } from "./section-nav-bar";
6
+
7
+ export interface SectionNavTab {
8
+ /** ลิงก์เต็ม — หรือส่ง `slug` แล้วให้ `base` ของ SectionNav ประกอบเป็น `${base}/${slug}` */
9
+ href?: string;
10
+ slug?: string;
11
+ label: ReactNode;
12
+ /** active เฉพาะ path ตรงเป๊ะ (ค่าเริ่มต้น prefix match) */
13
+ exact?: boolean;
14
+ /** ต่อท้าย label เช่น จำนวน */
15
+ badge?: ReactNode;
16
+ }
17
+
18
+ export interface SectionNavProps {
19
+ title: ReactNode;
20
+ description?: ReactNode;
21
+ actions?: ReactNode;
22
+ /** prefix ของ href เมื่อแท็บให้ `slug` (เช่น "/directory") */
23
+ base?: string;
24
+ tabs?: ReadonlyArray<SectionNavTab>;
25
+ /** aria-label ของแถบแท็บ (ค่าเริ่มต้น `UiText.sectionNav.tabs` — "แท็บย่อย") */
26
+ navLabel?: string;
27
+ className?: string;
28
+ }
29
+
30
+ function tabHref(tab: SectionNavTab, base?: string): string {
31
+ if (tab.href) return tab.href;
32
+ const b = (base ?? "").replace(/\/+$/, "");
33
+ return `${b}/${(tab.slug ?? "").replace(/^\/+/, "")}`;
34
+ }
35
+
36
+ /**
37
+ * หัวหมวด + แถบแท็บย่อยใต้หัวข้อ สำหรับ layout ของหมวดที่มีหลายหน้า (directory/access/security/integrations …)
38
+ * = `PageHeader` + `NavLink` แต่ละแท็บ (ไฮไลต์ตาม pathname) — ไม่มี state ใช้จาก server component ได้
39
+ *
40
+ * <SectionNav title="ไดเรกทอรี" base="/directory" tabs={[{ slug: "users", label: "ผู้ใช้" }, { slug: "groups", label: "กลุ่ม", badge: 3 }]} />
41
+ */
42
+ export function SectionNav({
43
+ title,
44
+ description,
45
+ actions,
46
+ base,
47
+ tabs,
48
+ navLabel,
49
+ className,
50
+ }: SectionNavProps) {
51
+ return (
52
+ <div data-slot="section-nav" className={cn("flex flex-col gap-3", className)}>
53
+ <PageHeader title={title} description={description} actions={actions} />
54
+ {tabs && tabs.length > 0 && (
55
+ <SectionNavBar label={navLabel}>
56
+ {tabs.map((tab) => {
57
+ const href = tabHref(tab, base);
58
+ return (
59
+ <NavLink
60
+ key={href}
61
+ href={href}
62
+ exact={tab.exact}
63
+ className="-mb-px inline-flex items-center gap-1.5 border-transparent border-b-2 px-3 py-2 text-muted-foreground transition-colors hover:text-foreground"
64
+ activeClassName="border-primary font-medium text-foreground"
65
+ >
66
+ {tab.label}
67
+ {tab.badge != null && (
68
+ <span className="rounded-full bg-muted px-1.5 text-muted-foreground text-xs tabular-nums">
69
+ {tab.badge}
70
+ </span>
71
+ )}
72
+ </NavLink>
73
+ );
74
+ })}
75
+ </SectionNavBar>
76
+ )}
77
+ </div>
78
+ );
79
+ }
@@ -47,6 +47,8 @@ export interface UiText {
47
47
  signOut?: string;
48
48
  /** ThemeMenuItems: หัวกลุ่ม + ตัวเลือก (optional) */
49
49
  theme?: Partial<{ label: string; light: string; dark: string; system: string }>;
50
+ /** SectionNav: aria-label ของแถบแท็บย่อย (optional — "แท็บย่อย") */
51
+ sectionNav?: Partial<{ tabs: string }>;
50
52
  }
51
53
 
52
54
  export const UI_TEXT_TH: UiText = {
@@ -79,6 +81,7 @@ export const UI_TEXT_TH: UiText = {
79
81
  userMenu: "เมนูผู้ใช้",
80
82
  signOut: "ออกจากระบบ",
81
83
  theme: { label: "ธีม", light: "สว่าง", dark: "มืด", system: "ตามระบบ" },
84
+ sectionNav: { tabs: "แท็บย่อย" },
82
85
  };
83
86
 
84
87
  export const UI_TEXT_EN: UiText = {
@@ -111,6 +114,7 @@ export const UI_TEXT_EN: UiText = {
111
114
  userMenu: "User menu",
112
115
  signOut: "Sign out",
113
116
  theme: { label: "Theme", light: "Light", dark: "Dark", system: "System" },
117
+ sectionNav: { tabs: "Section tabs" },
114
118
  };
115
119
 
116
120
  const UiTextContext = createContext<UiText>(UI_TEXT_TH);
package/src/next.ts CHANGED
@@ -15,6 +15,11 @@ export {
15
15
  PageBreadcrumb,
16
16
  type PageBreadcrumbItem,
17
17
  } from "./components/shared/navigation/page-breadcrumb";
18
+ export {
19
+ SectionNav,
20
+ type SectionNavProps,
21
+ type SectionNavTab,
22
+ } from "./components/shared/navigation/section-nav";
18
23
  export { Stepper, type StepperStep, type StepStatus } from "./components/shared/navigation/stepper";
19
24
  export { type RowAction, type RowActionItem, RowActions } from "./components/shared/row-actions";
20
25
  export { RowMenu, type RowMenuDialog, type RowMenuItem } from "./components/shared/row-menu";