gov-layout 1.1.6 → 1.1.7

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # gov-layout
2
2
 
3
- Government Layout Components — Staff Sidebar + User Header/Sidebar
3
+ Government Layout Components — Staff Sidebar + User Header/Sidebar + Settings Panel
4
4
 
5
5
  > ใช้คู่กับ `gov-token-css` เพื่อให้สีตรงตาม Design System
6
6
 
@@ -16,7 +16,7 @@ npm install gov-layout gov-token-css
16
16
 
17
17
  ### 1. StaffSidebar (เจ้าหน้าที่)
18
18
 
19
- Sidebar ฝั่งซ้ายแบบ fixed สำหรับหน้า admin
19
+ Sidebar ฝั่งซ้ายแบบ fixed สำหรับหน้า admin — รองรับพับ/กาง (collapsible)
20
20
 
21
21
  ```tsx
22
22
  import { StaffSidebar } from 'gov-layout';
@@ -56,7 +56,7 @@ const bottomMenu: MenuItem[] = [
56
56
  currentPath="/services/water"
57
57
  onNavigate={(path) => router.push(path)}
58
58
  onLogout={() => signOut()}
59
- width="280px"
59
+ collapsible // ← เปิดโหมดพับ/กาง
60
60
  />
61
61
  ```
62
62
 
@@ -66,12 +66,15 @@ const bottomMenu: MenuItem[] = [
66
66
  - ✅ Active item highlight
67
67
  - ✅ Bottom menu (ตั้งค่าระบบ)
68
68
  - ✅ User profile + logout ข้างล่าง
69
+ - ✅ **Collapsible** — พับเป็น icon-only 64px, กางเป็น 280px
70
+ - ปุ่ม toggle อยู่ขอบขวาบน (10%) ของ sidebar
71
+ - Tooltip แสดงชื่อเมนูเมื่อพับ
69
72
 
70
73
  ---
71
74
 
72
75
  ### 2. UserHeader (ผู้ใช้ทั่วไป)
73
76
 
74
- Header ด้านบนพร้อม notification bell
77
+ Header ด้านบนพร้อม notification bell — badge แสดง 99+ เมื่อเกิน 99
75
78
 
76
79
  ```tsx
77
80
  import { UserHeader } from 'gov-layout';
@@ -96,6 +99,11 @@ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
96
99
  />
97
100
  ```
98
101
 
102
+ **Features:**
103
+ - ✅ Notification bell พร้อม badge (แสดง 99+ เมื่อเกิน 99)
104
+ - ✅ Notification dropdown แบบ scroll ได้
105
+ - ✅ ปุ่มเปิด sidebar
106
+
99
107
  ---
100
108
 
101
109
  ### 3. UserSidebar (ผู้ใช้ทั่วไป)
@@ -109,7 +117,7 @@ import { UserSidebar } from 'gov-layout';
109
117
  isOpen={isSidebarOpen}
110
118
  onClose={() => setIsSidebarOpen(false)}
111
119
  user={{ firstName: 'Kaimuk', lastName: 'Jakprim', pictureUrl: '/avatar.jpg' }}
112
- roleLabel="ผู้สูงอายุ"
120
+ roleLabel="ผู้สูงอายุ" // ← กำหนดตาม role ของแต่ละระบบ
113
121
  menuItems={[
114
122
  { id: 'profile', title: 'ข้อมูลส่วนตัว', icon: <UserIcon />, path: '/profile' },
115
123
  { id: 'services', title: 'บริการหลัก', icon: <HomeIcon />, path: '/services' },
@@ -120,6 +128,91 @@ import { UserSidebar } from 'gov-layout';
120
128
  />
121
129
  ```
122
130
 
131
+ > **หมายเหตุ:** `roleLabel` ไม่ได้ fix ไว้ แต่ละระบบส่งค่าเองได้ เช่น "ผู้สูงอายุ", "ผู้ใช้ปกติ", "อาสาสมัคร"
132
+
133
+ ---
134
+
135
+ ### 4. SettingsPanel (ตั้งค่าระบบ) 🆕
136
+
137
+ หน้าตั้งค่าพร้อมปรับขนาดตัวอักษร (5 ระดับ) + โหมดสว่าง/มืด
138
+
139
+ #### ขั้นตอนการใช้งาน
140
+
141
+ **1) ครอบ `SettingsProvider` ที่ root layout:**
142
+
143
+ ```tsx
144
+ // app/providers.tsx — ต้องเป็น 'use client'
145
+ 'use client';
146
+ import { SettingsProvider } from 'gov-layout';
147
+
148
+ export default function Providers({ children }: { children: React.ReactNode }) {
149
+ return <SettingsProvider>{children}</SettingsProvider>;
150
+ }
151
+
152
+ // app/layout.tsx
153
+ import Providers from './providers';
154
+
155
+ export default function RootLayout({ children }) {
156
+ return (
157
+ <html lang="th">
158
+ <body>
159
+ <Providers>{children}</Providers>
160
+ </body>
161
+ </html>
162
+ );
163
+ }
164
+ ```
165
+
166
+ **2) วาง `SettingsPanel` ในหน้าตั้งค่า:**
167
+
168
+ ```tsx
169
+ import { SettingsPanel } from 'gov-layout';
170
+
171
+ // ผู้ใช้ทั่วไป — ได้ทั้ง font + theme
172
+ <SettingsPanel />
173
+
174
+ // เจ้าหน้าที่ — แค่ปรับขนาดฟอนต์
175
+ <SettingsPanel showTheme={false} />
176
+ ```
177
+
178
+ **3) ใช้ `useSettings()` hook ถ้าอยากอ่าน/เปลี่ยนค่าเอง:**
179
+
180
+ ```tsx
181
+ import { useSettings } from 'gov-layout';
182
+
183
+ function MyComponent() {
184
+ const { theme, toggleTheme, fontSize, setFontSize } = useSettings();
185
+ return <p>ธีม: {theme} | ฟอนต์: {fontSize}</p>;
186
+ }
187
+ ```
188
+
189
+ #### ขนาดตัวอักษร (5 ระดับ)
190
+
191
+ | ค่า | Label | Scale |
192
+ |-----|-------|-------|
193
+ | `xsmall` | เล็กมาก | ×0.8 |
194
+ | `small` | เล็ก | ×0.9 |
195
+ | `medium` | กลาง (default) | ×1.0 |
196
+ | `large` | ใหญ่ | ×1.2 |
197
+ | `xlarge` | ใหญ่มาก | ×1.4 |
198
+
199
+ #### หลักการทำงาน
200
+
201
+ - **Theme** — เพิ่ม/ลบ class `dark` บน `<html>` → ใช้ CSS `html.dark` selector จัดสี
202
+ - **Font size** — ปรับ `body.style.zoom`, `root.style.fontSize`, design token CSS variables ตาม scale
203
+ - ค่าเก็บใน **localStorage** (`app-theme`, `app-font-size`) จำค่าได้เมื่อ reload
204
+
205
+ #### Dark mode CSS ที่ต้องเพิ่มในโปรเจกต์
206
+
207
+ ```css
208
+ /* globals.css */
209
+ html.dark body { background-color: #0f172a; color: #f1f5f9; }
210
+ html.dark aside { background-color: #1e293b !important; }
211
+ html.dark header { background-color: #1e293b !important; }
212
+ html.dark h1, html.dark h2, html.dark h3 { color: #f1f5f9 !important; }
213
+ html.dark p { color: #94a3b8 !important; }
214
+ ```
215
+
123
216
  ---
124
217
 
125
218
  ## 🔧 Sub-Components
@@ -177,6 +270,9 @@ interface NotificationItem {
177
270
  type: 'info' | 'success' | 'warning' | 'error' | 'reminder';
178
271
  isRead: boolean;
179
272
  }
273
+
274
+ type Theme = 'light' | 'dark';
275
+ type FontSizeKey = 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
180
276
  ```
181
277
 
182
278
  ---
@@ -188,7 +284,7 @@ interface NotificationItem {
188
284
  export default function AdminLayout({ children }) {
189
285
  return (
190
286
  <div style={{ display: 'flex' }}>
191
- <StaffSidebar ... />
287
+ <StaffSidebar collapsible ... />
192
288
  <main style={{ marginLeft: '280px', flex: 1 }}>
193
289
  {children}
194
290
  </main>
package/dist/index.js CHANGED
@@ -469,6 +469,23 @@ function ToggleIcon({ isOpen }) {
469
469
  }
470
470
  );
471
471
  }
472
+ function HelpCircleIcon() {
473
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
474
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
475
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }),
476
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
477
+ ] });
478
+ }
479
+ function GearIcon() {
480
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
481
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "3" }),
482
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" })
483
+ ] });
484
+ }
485
+ var DEFAULT_BOTTOM_MENU = [
486
+ { id: "help", title: "\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D", icon: /* @__PURE__ */ jsxRuntime.jsx(HelpCircleIcon, {}), path: "/help" },
487
+ { id: "settings", title: "\u0E15\u0E31\u0E49\u0E07\u0E04\u0E48\u0E32\u0E23\u0E30\u0E1A\u0E1A", icon: /* @__PURE__ */ jsxRuntime.jsx(GearIcon, {}), path: "/settings" }
488
+ ];
472
489
  function StaffSidebar({
473
490
  orgLogo,
474
491
  orgName,
@@ -491,6 +508,7 @@ function StaffSidebar({
491
508
  const collapsed = collapsible && !sidebarOpen;
492
509
  const collapsedWidth = "64px";
493
510
  const currentWidth = collapsed ? collapsedWidth : width;
511
+ const resolvedBottomMenu = bottomMenuItems !== void 0 ? bottomMenuItems : DEFAULT_BOTTOM_MENU;
494
512
  const handleToggle = () => {
495
513
  if (onToggle) {
496
514
  onToggle();
@@ -536,10 +554,10 @@ function StaffSidebar({
536
554
  collapsed
537
555
  }
538
556
  ),
539
- bottomMenuItems && bottomMenuItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
557
+ resolvedBottomMenu && resolvedBottomMenu.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
540
558
  SidebarMenu,
541
559
  {
542
- menuItems: bottomMenuItems,
560
+ menuItems: resolvedBottomMenu,
543
561
  onItemClick: onNavigate,
544
562
  currentPath,
545
563
  collapsed