gov-layout 1.1.9 → 1.1.10
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 +251 -113
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,28 +1,49 @@
|
|
|
1
1
|
# gov-layout
|
|
2
2
|
|
|
3
|
-
Government Layout Components
|
|
3
|
+
Government Layout Components สำหรับเว็บแอปพลิเคชันภาครัฐ
|
|
4
4
|
|
|
5
5
|
> ใช้คู่กับ `gov-token-css` เพื่อให้สีตรงตาม Design System
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📥 Installation
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
12
|
npm install gov-layout gov-token-css
|
|
11
13
|
```
|
|
12
14
|
|
|
15
|
+
```css
|
|
16
|
+
/* ใน globals.css */
|
|
17
|
+
@import "gov-token-css";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 📦 Components ทั้งหมด
|
|
23
|
+
|
|
24
|
+
| Component | ใช้สำหรับ | import |
|
|
25
|
+
|-----------|----------|--------|
|
|
26
|
+
| `StaffSidebar` | Sidebar เจ้าหน้าที่ | `import { StaffSidebar } from 'gov-layout'` |
|
|
27
|
+
| `UserHeader` | Header ผู้ใช้ทั่วไป | `import { UserHeader } from 'gov-layout'` |
|
|
28
|
+
| `UserSidebar` | Sidebar ผู้ใช้ (slide-in) | `import { UserSidebar } from 'gov-layout'` |
|
|
29
|
+
| `SettingsPanel` | หน้าตั้งค่า (font + theme) | `import { SettingsPanel } from 'gov-layout'` |
|
|
30
|
+
| `SettingsProvider` | Context wrapper | `import { SettingsProvider } from 'gov-layout'` |
|
|
31
|
+
| `useSettings` | Hook อ่าน/เปลี่ยนค่า | `import { useSettings } from 'gov-layout'` |
|
|
32
|
+
|
|
13
33
|
---
|
|
14
34
|
|
|
15
|
-
##
|
|
35
|
+
## 1. StaffSidebar (เจ้าหน้าที่)
|
|
16
36
|
|
|
17
|
-
|
|
37
|
+
Sidebar ฝั่งซ้ายแบบ fixed — รองรับพับ/กาง (collapsible)
|
|
18
38
|
|
|
19
|
-
|
|
39
|
+
### ตัวอย่างใช้งาน
|
|
20
40
|
|
|
21
41
|
```tsx
|
|
22
42
|
import { StaffSidebar } from 'gov-layout';
|
|
23
43
|
import type { MenuItem } from 'gov-layout';
|
|
24
44
|
|
|
25
45
|
const menuItems: MenuItem[] = [
|
|
46
|
+
// === Dropdown (มี children) ===
|
|
26
47
|
{
|
|
27
48
|
id: 'services',
|
|
28
49
|
title: 'งานบริการ',
|
|
@@ -30,19 +51,13 @@ const menuItems: MenuItem[] = [
|
|
|
30
51
|
children: [
|
|
31
52
|
{ id: 'water', title: 'ประปา', path: '/services/water' },
|
|
32
53
|
{ id: 'tax', title: 'ภาษี', path: '/services/tax' },
|
|
54
|
+
{ id: 'civil', title: 'ทะเบียน', path: '/services/civil' },
|
|
33
55
|
],
|
|
56
|
+
dividerAfter: true, // เส้นคั่นหลังเมนูนี้
|
|
34
57
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
icon: <HelpIcon />,
|
|
39
|
-
path: '/help',
|
|
40
|
-
dividerAfter: true,
|
|
41
|
-
},
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
const bottomMenu: MenuItem[] = [
|
|
45
|
-
{ id: 'settings', title: 'ตั้งค่าระบบ', icon: <SettingsIcon />, path: '/settings' },
|
|
58
|
+
// === เมนูเดี่ยว (ไม่มี children) ===
|
|
59
|
+
{ id: 'users', title: 'จัดการผู้ใช้', icon: <UserIcon />, path: '/users' },
|
|
60
|
+
{ id: 'reports', title: 'รายงาน', icon: <ChartIcon />, path: '/reports' },
|
|
46
61
|
];
|
|
47
62
|
|
|
48
63
|
<StaffSidebar
|
|
@@ -50,39 +65,96 @@ const bottomMenu: MenuItem[] = [
|
|
|
50
65
|
orgName="เทศบาลตำบลหลักเมือง"
|
|
51
66
|
orgSubtitle="จังหวัดราชบุรี"
|
|
52
67
|
menuItems={menuItems}
|
|
53
|
-
|
|
54
|
-
user={{ firstName: 'Kaimuk', lastName: 'Jakprim' }}
|
|
68
|
+
user={{ firstName: 'สมชาย', lastName: 'ใจดี' }}
|
|
55
69
|
roleLabel="เจ้าหน้าที่"
|
|
56
70
|
currentPath="/services/water"
|
|
57
71
|
onNavigate={(path) => router.push(path)}
|
|
58
72
|
onLogout={() => signOut()}
|
|
59
|
-
collapsible
|
|
73
|
+
collapsible
|
|
60
74
|
/>
|
|
61
75
|
```
|
|
62
76
|
|
|
63
|
-
|
|
64
|
-
-
|
|
77
|
+
> **💡 ข้อมูลองค์กร (`orgLogo`, `orgName`, `orgSubtitle`) มาจากไหนก็ได้:**
|
|
78
|
+
> - ดึงจาก **ตัวกลาง SSO** หลังล็อกอิน (แนะนำ) เช่น `useSSOAuth().organization`
|
|
79
|
+
> - ดึงจาก **API** เช่น `fetchOrgInfo()`
|
|
80
|
+
> - **Fix ค่า** ตรงๆ ก็ได้ ถ้าใช้ระบบเดียว
|
|
81
|
+
>
|
|
82
|
+
> Library ไม่ผูกกับแหล่งข้อมูลใดๆ — แค่รับ props แล้วแสดงผล
|
|
83
|
+
|
|
84
|
+
### Default Bottom Menu
|
|
85
|
+
|
|
86
|
+
ไม่ต้องตั้งค่าเอง — มี **ตั้งค่าระบบ** + **ช่วยเหลือ** อยู่ด้านล่างอัตโนมัติ
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
งานบริการ ▽
|
|
90
|
+
ประปา / ภาษี / ทะเบียน
|
|
91
|
+
──────────────
|
|
92
|
+
จัดการผู้ใช้
|
|
93
|
+
รายงาน
|
|
94
|
+
ตั้งค่าระบบ ← default (ไม่ต้องส่ง)
|
|
95
|
+
ช่วยเหลือ ← default (ไม่ต้องส่ง)
|
|
96
|
+
↕ spacer
|
|
97
|
+
โปรไฟล์ + ออกจากระบบ
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
ถ้าอยากเปลี่ยน bottom menu เอง:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
<StaffSidebar
|
|
104
|
+
bottomMenuItems={[
|
|
105
|
+
{ id: 'settings', title: 'ตั้งค่า', icon: <GearIcon />, path: '/settings' },
|
|
106
|
+
]}
|
|
107
|
+
...
|
|
108
|
+
/>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
ถ้าไม่อยากมี bottom menu:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
<StaffSidebar bottomMenuItems={[]} ... />
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Props ทั้งหมด
|
|
118
|
+
|
|
119
|
+
| Prop | Type | Default | คำอธิบาย |
|
|
120
|
+
|------|------|---------|----------|
|
|
121
|
+
| `orgLogo` | `string?` | - | URL รูปตราองค์กร |
|
|
122
|
+
| `orgName` | `string` | **required** | ชื่อองค์กร |
|
|
123
|
+
| `orgSubtitle` | `string?` | - | ชื่อรอง เช่น จังหวัด |
|
|
124
|
+
| `menuItems` | `MenuItem[]` | **required** | เมนูหลัก |
|
|
125
|
+
| `bottomMenuItems` | `MenuItem[]?` | ตั้งค่าระบบ + ช่วยเหลือ | เมนูด้านล่าง |
|
|
126
|
+
| `user` | `User \| null` | **required** | ข้อมูลผู้ใช้ |
|
|
127
|
+
| `roleLabel` | `string` | **required** | ป้ายตำแหน่ง เช่น "เจ้าหน้าที่" |
|
|
128
|
+
| `onNavigate` | `(path) => void` | **required** | callback เมื่อคลิกเมนู |
|
|
129
|
+
| `onLogout` | `() => void` | **required** | callback ออกจากระบบ |
|
|
130
|
+
| `currentPath` | `string?` | - | path ปัจจุบัน (highlight active) |
|
|
131
|
+
| `width` | `string?` | `'280px'` | ความกว้าง sidebar |
|
|
132
|
+
| `collapsible` | `boolean?` | `false` | เปิดโหมดพับ/กาง |
|
|
133
|
+
| `isOpen` | `boolean?` | - | controlled open/close |
|
|
134
|
+
| `onToggle` | `() => void?` | - | callback เมื่อกดพับ/กาง |
|
|
135
|
+
|
|
136
|
+
### Features
|
|
137
|
+
|
|
138
|
+
- ✅ Dropdown submenu (พับ/กางอัตโนมัติ)
|
|
65
139
|
- ✅ Auto-expand เมื่อ child active
|
|
66
140
|
- ✅ Active item highlight
|
|
67
|
-
- ✅
|
|
68
|
-
- ✅
|
|
69
|
-
- ✅
|
|
70
|
-
|
|
71
|
-
|
|
141
|
+
- ✅ Collapsible — พับเป็น icon-only 64px, กางเป็น 280px
|
|
142
|
+
- ✅ Tooltip เมื่อพับ
|
|
143
|
+
- ✅ Default ตั้งค่าระบบ + ช่วยเหลือ (override ได้)
|
|
144
|
+
- ✅ โปรไฟล์ + ออกจากระบบ ล่างสุดเสมอ
|
|
145
|
+
- ✅ `dividerAfter` เส้นคั่นระหว่างกลุ่ม
|
|
72
146
|
|
|
73
147
|
---
|
|
74
148
|
|
|
75
|
-
|
|
149
|
+
## 2. UserHeader (ผู้ใช้ทั่วไป)
|
|
76
150
|
|
|
77
|
-
Header ด้านบนพร้อม notification bell
|
|
151
|
+
Header ด้านบนพร้อม notification bell
|
|
78
152
|
|
|
79
153
|
```tsx
|
|
80
154
|
import { UserHeader } from 'gov-layout';
|
|
81
155
|
|
|
82
|
-
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
|
83
|
-
|
|
84
156
|
<UserHeader
|
|
85
|
-
user={{ firstName: '
|
|
157
|
+
user={{ firstName: 'สมหญิง', pictureUrl: '/avatar.jpg' }}
|
|
86
158
|
notifications={[
|
|
87
159
|
{
|
|
88
160
|
id: 1,
|
|
@@ -93,22 +165,24 @@ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
|
|
93
165
|
isRead: false,
|
|
94
166
|
},
|
|
95
167
|
]}
|
|
96
|
-
onToggleSidebar={() =>
|
|
168
|
+
onToggleSidebar={() => setOpen(true)}
|
|
97
169
|
onMarkAllRead={() => markAllRead()}
|
|
98
170
|
onViewAll={() => router.push('/notifications')}
|
|
99
171
|
/>
|
|
100
172
|
```
|
|
101
173
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
- ✅ Notification
|
|
105
|
-
- ✅
|
|
174
|
+
### Features
|
|
175
|
+
|
|
176
|
+
- ✅ Notification bell พร้อม badge (99+ เมื่อเกิน)
|
|
177
|
+
- ✅ ไม่มีแจ้งเตือน → ไม่แสดง badge
|
|
178
|
+
- ✅ Notification dropdown แบบ scroll
|
|
179
|
+
- ✅ ปุ่มเปิด sidebar (☰)
|
|
106
180
|
|
|
107
181
|
---
|
|
108
182
|
|
|
109
|
-
|
|
183
|
+
## 3. UserSidebar (ผู้ใช้ทั่วไป)
|
|
110
184
|
|
|
111
|
-
Sidebar ฝั่งขวาแบบ slide-in
|
|
185
|
+
Sidebar ฝั่งขวาแบบ slide-in + overlay
|
|
112
186
|
|
|
113
187
|
```tsx
|
|
114
188
|
import { UserSidebar } from 'gov-layout';
|
|
@@ -116,39 +190,39 @@ import { UserSidebar } from 'gov-layout';
|
|
|
116
190
|
<UserSidebar
|
|
117
191
|
isOpen={isSidebarOpen}
|
|
118
192
|
onClose={() => setIsSidebarOpen(false)}
|
|
119
|
-
user={{ firstName: '
|
|
120
|
-
roleLabel="ผู้สูงอายุ"
|
|
193
|
+
user={{ firstName: 'สมหญิง', lastName: 'ใจดี', pictureUrl: '/avatar.jpg' }}
|
|
194
|
+
roleLabel="ผู้สูงอายุ"
|
|
121
195
|
menuItems={[
|
|
122
196
|
{ id: 'profile', title: 'ข้อมูลส่วนตัว', icon: <UserIcon />, path: '/profile' },
|
|
123
197
|
{ id: 'services', title: 'บริการหลัก', icon: <HomeIcon />, path: '/services' },
|
|
124
|
-
{ id: 'settings', title: 'ตั้งค่าระบบ', icon: <
|
|
198
|
+
{ id: 'settings', title: 'ตั้งค่าระบบ', icon: <GearIcon />, path: '/settings' },
|
|
125
199
|
]}
|
|
126
200
|
onNavigate={(path) => router.push(path)}
|
|
127
201
|
onLogout={() => signOut()}
|
|
128
202
|
/>
|
|
129
203
|
```
|
|
130
204
|
|
|
131
|
-
> **หมายเหตุ:** `roleLabel`
|
|
205
|
+
> **หมายเหตุ:** `roleLabel` แต่ละระบบส่งค่าเองได้ เช่น "ผู้สูงอายุ", "ผู้ใช้ปกติ", "อาสาสมัคร"
|
|
132
206
|
|
|
133
207
|
---
|
|
134
208
|
|
|
135
|
-
|
|
209
|
+
## 4. SettingsPanel (ตั้งค่าระบบ) 🆕
|
|
136
210
|
|
|
137
|
-
|
|
211
|
+
ปรับขนาดตัวอักษร (5 ระดับ) + โหมดสว่าง/มืด — ค่าจำใน localStorage
|
|
138
212
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
**1) ครอบ `SettingsProvider` ที่ root layout:**
|
|
213
|
+
### ขั้นตอนที่ 1: ครอบ SettingsProvider
|
|
142
214
|
|
|
143
215
|
```tsx
|
|
144
|
-
// app/providers.tsx
|
|
216
|
+
// app/providers.tsx ← ต้องเป็น 'use client'
|
|
145
217
|
'use client';
|
|
146
218
|
import { SettingsProvider } from 'gov-layout';
|
|
147
219
|
|
|
148
220
|
export default function Providers({ children }: { children: React.ReactNode }) {
|
|
149
221
|
return <SettingsProvider>{children}</SettingsProvider>;
|
|
150
222
|
}
|
|
223
|
+
```
|
|
151
224
|
|
|
225
|
+
```tsx
|
|
152
226
|
// app/layout.tsx
|
|
153
227
|
import Providers from './providers';
|
|
154
228
|
|
|
@@ -163,97 +237,87 @@ export default function RootLayout({ children }) {
|
|
|
163
237
|
}
|
|
164
238
|
```
|
|
165
239
|
|
|
166
|
-
|
|
240
|
+
### ขั้นตอนที่ 2: วาง SettingsPanel
|
|
167
241
|
|
|
168
242
|
```tsx
|
|
169
243
|
import { SettingsPanel } from 'gov-layout';
|
|
170
244
|
|
|
171
|
-
// ผู้ใช้ทั่วไป
|
|
245
|
+
// ผู้ใช้ทั่วไป → ทั้ง font + theme
|
|
172
246
|
<SettingsPanel />
|
|
173
247
|
|
|
174
|
-
// เจ้าหน้าที่
|
|
248
|
+
// เจ้าหน้าที่ → แค่ปรับขนาดฟอนต์
|
|
175
249
|
<SettingsPanel showTheme={false} />
|
|
176
250
|
```
|
|
177
251
|
|
|
178
|
-
|
|
252
|
+
| Prop | Type | Default | คำอธิบาย |
|
|
253
|
+
|------|------|---------|----------|
|
|
254
|
+
| `showTheme` | `boolean?` | `true` | แสดงตัวเลือกโหมดสว่าง/มืด |
|
|
255
|
+
| `className` | `string?` | - | className เพิ่มเติม |
|
|
256
|
+
|
|
257
|
+
### ขั้นตอนที่ 3: ใช้ useSettings() hook (ถ้าต้องการ)
|
|
179
258
|
|
|
180
259
|
```tsx
|
|
181
260
|
import { useSettings } from 'gov-layout';
|
|
182
261
|
|
|
183
262
|
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 ที่ต้องเพิ่มในโปรเจกต์
|
|
263
|
+
const { theme, toggleTheme, fontSize, setFontSize, fontSizeOption } = useSettings();
|
|
206
264
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
265
|
+
return (
|
|
266
|
+
<div>
|
|
267
|
+
<p>ธีม: {theme}</p>
|
|
268
|
+
<p>ฟอนต์: {fontSize} (×{fontSizeOption.scale})</p>
|
|
269
|
+
<button onClick={toggleTheme}>สลับธีม</button>
|
|
270
|
+
<button onClick={() => setFontSize('large')}>ฟอนต์ใหญ่</button>
|
|
271
|
+
</div>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
214
274
|
```
|
|
215
275
|
|
|
216
|
-
|
|
276
|
+
### ขนาดตัวอักษร (5 ระดับ)
|
|
217
277
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
SidebarMenu, // เมนู dropdown
|
|
226
|
-
SidebarUserProfile // avatar + logout
|
|
227
|
-
} from 'gov-layout';
|
|
228
|
-
```
|
|
278
|
+
| ค่า | Label | Scale | ผลลัพธ์ |
|
|
279
|
+
|-----|-------|-------|---------|
|
|
280
|
+
| `xsmall` | เล็กมาก | ×0.8 | ย่อทุกอย่าง 80% |
|
|
281
|
+
| `small` | เล็ก | ×0.9 | ย่อเล็กน้อย |
|
|
282
|
+
| `medium` | กลาง | ×1.0 | ค่าเริ่มต้น |
|
|
283
|
+
| `large` | ใหญ่ | ×1.2 | ขยาย 120% |
|
|
284
|
+
| `xlarge` | ใหญ่มาก | ×1.4 | ขยาย 140% |
|
|
229
285
|
|
|
230
|
-
|
|
286
|
+
### หลักการทำงาน
|
|
231
287
|
|
|
232
|
-
|
|
288
|
+
- **Theme** — เพิ่ม/ลบ class `dark` บน `<html>` ➜ ใช้ CSS `html.dark` จัดสี
|
|
289
|
+
- **Font size** — ปรับ `body.style.zoom` + CSS variables ตาม scale
|
|
290
|
+
- **Persistence** — เก็บใน localStorage (`app-theme`, `app-font-size`)
|
|
233
291
|
|
|
234
|
-
|
|
292
|
+
### Dark mode CSS ที่ต้องเพิ่ม
|
|
235
293
|
|
|
236
294
|
```css
|
|
237
|
-
/*
|
|
238
|
-
|
|
295
|
+
/* globals.css — เพิ่มสำหรับ dark mode */
|
|
296
|
+
html.dark body { background-color: #0f172a; color: #f1f5f9; }
|
|
297
|
+
html.dark aside { background-color: #1e293b !important; }
|
|
298
|
+
html.dark header { background-color: #1e293b !important; }
|
|
299
|
+
html.dark h1,
|
|
300
|
+
html.dark h2,
|
|
301
|
+
html.dark h3 { color: #f1f5f9 !important; }
|
|
302
|
+
html.dark p { color: #94a3b8 !important; }
|
|
239
303
|
```
|
|
240
304
|
|
|
241
|
-
ถ้าไม่ได้ import `gov-token-css` จะใช้สี fallback อัตโนมัติ
|
|
242
|
-
|
|
243
305
|
---
|
|
244
306
|
|
|
245
307
|
## 📐 Types
|
|
246
308
|
|
|
247
309
|
```ts
|
|
310
|
+
// เมนู
|
|
248
311
|
interface MenuItem {
|
|
249
312
|
id: string;
|
|
250
313
|
title: string;
|
|
251
|
-
path?: string;
|
|
252
|
-
icon?: React.ReactNode;
|
|
253
|
-
children?: MenuItem[];
|
|
254
|
-
dividerAfter?: boolean;
|
|
314
|
+
path?: string; // path สำหรับ navigate
|
|
315
|
+
icon?: React.ReactNode; // icon component
|
|
316
|
+
children?: MenuItem[]; // submenu → แสดงเป็น dropdown
|
|
317
|
+
dividerAfter?: boolean; // เส้นคั่นด้านล่าง
|
|
255
318
|
}
|
|
256
319
|
|
|
320
|
+
// ผู้ใช้
|
|
257
321
|
interface User {
|
|
258
322
|
id?: string | number;
|
|
259
323
|
firstName?: string;
|
|
@@ -262,6 +326,7 @@ interface User {
|
|
|
262
326
|
role?: string;
|
|
263
327
|
}
|
|
264
328
|
|
|
329
|
+
// การแจ้งเตือน
|
|
265
330
|
interface NotificationItem {
|
|
266
331
|
id: string | number;
|
|
267
332
|
title: string;
|
|
@@ -271,36 +336,109 @@ interface NotificationItem {
|
|
|
271
336
|
isRead: boolean;
|
|
272
337
|
}
|
|
273
338
|
|
|
339
|
+
// ตั้งค่า
|
|
274
340
|
type Theme = 'light' | 'dark';
|
|
275
341
|
type FontSizeKey = 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
|
|
276
342
|
```
|
|
277
343
|
|
|
278
344
|
---
|
|
279
345
|
|
|
280
|
-
## 📁 Layout
|
|
346
|
+
## 📁 Layout Examples
|
|
347
|
+
|
|
348
|
+
### Staff Layout (เจ้าหน้าที่)
|
|
281
349
|
|
|
282
350
|
```tsx
|
|
283
|
-
|
|
351
|
+
'use client';
|
|
352
|
+
import { StaffSidebar, SettingsPanel } from 'gov-layout';
|
|
353
|
+
|
|
284
354
|
export default function AdminLayout({ children }) {
|
|
355
|
+
const [currentPath, setCurrentPath] = useState('/');
|
|
356
|
+
|
|
285
357
|
return (
|
|
286
358
|
<div style={{ display: 'flex' }}>
|
|
287
|
-
<StaffSidebar
|
|
288
|
-
|
|
289
|
-
|
|
359
|
+
<StaffSidebar
|
|
360
|
+
orgLogo="/logo.png"
|
|
361
|
+
orgName="เทศบาลตำบลหลักเมือง"
|
|
362
|
+
orgSubtitle="จังหวัดราชบุรี"
|
|
363
|
+
menuItems={menuItems}
|
|
364
|
+
user={user}
|
|
365
|
+
roleLabel="เจ้าหน้าที่"
|
|
366
|
+
currentPath={currentPath}
|
|
367
|
+
onNavigate={(path) => setCurrentPath(path)}
|
|
368
|
+
onLogout={() => signOut()}
|
|
369
|
+
collapsible
|
|
370
|
+
/>
|
|
371
|
+
<main style={{ marginLeft: 280, flex: 1, padding: 32 }}>
|
|
372
|
+
{currentPath === '/settings' ? (
|
|
373
|
+
<SettingsPanel showTheme={false} />
|
|
374
|
+
) : (
|
|
375
|
+
children
|
|
376
|
+
)}
|
|
290
377
|
</main>
|
|
291
378
|
</div>
|
|
292
379
|
);
|
|
293
380
|
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### User Layout (ผู้ใช้ทั่วไป)
|
|
384
|
+
|
|
385
|
+
```tsx
|
|
386
|
+
'use client';
|
|
387
|
+
import { UserHeader, UserSidebar, SettingsPanel } from 'gov-layout';
|
|
294
388
|
|
|
295
|
-
// User Layout (ผู้ใช้ทั่วไป)
|
|
296
389
|
export default function UserLayout({ children }) {
|
|
297
390
|
const [open, setOpen] = useState(false);
|
|
391
|
+
const [currentPath, setCurrentPath] = useState('/');
|
|
392
|
+
|
|
298
393
|
return (
|
|
299
394
|
<>
|
|
300
|
-
<UserHeader
|
|
301
|
-
|
|
302
|
-
|
|
395
|
+
<UserHeader
|
|
396
|
+
user={user}
|
|
397
|
+
notifications={notifications}
|
|
398
|
+
onToggleSidebar={() => setOpen(true)}
|
|
399
|
+
/>
|
|
400
|
+
<UserSidebar
|
|
401
|
+
isOpen={open}
|
|
402
|
+
onClose={() => setOpen(false)}
|
|
403
|
+
user={user}
|
|
404
|
+
roleLabel="ผู้ใช้ทั่วไป"
|
|
405
|
+
menuItems={menuItems}
|
|
406
|
+
onNavigate={(path) => setCurrentPath(path)}
|
|
407
|
+
onLogout={() => signOut()}
|
|
408
|
+
/>
|
|
409
|
+
<main style={{ padding: 32 }}>
|
|
410
|
+
{currentPath === '/settings' ? (
|
|
411
|
+
<SettingsPanel showTheme={true} />
|
|
412
|
+
) : (
|
|
413
|
+
children
|
|
414
|
+
)}
|
|
415
|
+
</main>
|
|
303
416
|
</>
|
|
304
417
|
);
|
|
305
418
|
}
|
|
306
419
|
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## 🔧 Sub-Components
|
|
424
|
+
|
|
425
|
+
ใช้แยกกันได้ถ้าต้องการ customize เฉพาะส่วน
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
import {
|
|
429
|
+
SidebarHeader, // logo + ชื่อองค์กร
|
|
430
|
+
SidebarMenu, // เมนู dropdown
|
|
431
|
+
SidebarUserProfile, // avatar + logout
|
|
432
|
+
ThemeSettings, // UI เลือก theme อย่างเดียว
|
|
433
|
+
FontSizeSettings, // UI เลือก font size อย่างเดียว
|
|
434
|
+
} from 'gov-layout';
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## 🎨 Styling
|
|
440
|
+
|
|
441
|
+
- Components ใช้ **inline styles** + CSS variables จาก `gov-token-css`
|
|
442
|
+
- ถ้าไม่ได้ import `gov-token-css` จะใช้สี **fallback** อัตโนมัติ
|
|
443
|
+
- Dark mode ต้องเพิ่ม CSS เอง (ดู section Dark mode CSS ด้านบน)
|
|
444
|
+
- Font size ใช้ `body.style.zoom` → scale ทุกอย่างรวมถึง inline `px`
|