gov-layout 1.3.7 → 1.3.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 +84 -6
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +313 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +314 -36
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -5
- package/src/styles.css +0 -49
package/README.md
CHANGED
|
@@ -15,11 +15,8 @@ npm install gov-layout gov-token-css
|
|
|
15
15
|
```css
|
|
16
16
|
/* ใน globals.css */
|
|
17
17
|
@import "gov-token-css";
|
|
18
|
-
@import "gov-layout/styles.css";
|
|
19
18
|
```
|
|
20
19
|
|
|
21
|
-
> ใช้ `gov-layout/styles.css` เพื่อให้ class สีเช่น `text-text-primary` ทำงาน แม้ไม่ได้ import component จาก `gov-layout`
|
|
22
|
-
|
|
23
20
|
---
|
|
24
21
|
|
|
25
22
|
## 📦 Components ทั้งหมด
|
|
@@ -111,7 +108,7 @@ const menuItems: MenuItem[] = [
|
|
|
111
108
|
|
|
112
109
|
## 1. StaffSidebar (เจ้าหน้าที่)
|
|
113
110
|
|
|
114
|
-
Sidebar ฝั่งซ้ายแบบ fixed — รองรับพับ/กาง (collapsible)
|
|
111
|
+
Sidebar ฝั่งซ้ายแบบ fixed — รองรับพับ/กาง (collapsible) + **รองรับมือถือแบบ Drawer** 🆕
|
|
115
112
|
|
|
116
113
|
### ตัวอย่างใช้งาน
|
|
117
114
|
|
|
@@ -214,6 +211,70 @@ const menuItems: MenuItem[] = [
|
|
|
214
211
|
| `collapsible` | `boolean?` | `false` | เปิดโหมดพับ/กาง |
|
|
215
212
|
| `isOpen` | `boolean?` | - | controlled open/close |
|
|
216
213
|
| `onToggle` | `() => void?` | - | callback เมื่อกดพับ/กาง |
|
|
214
|
+
| `mobileBreakpoint` | `number?` | `768` | breakpoint (px) สำหรับสลับเป็นโหมดมือถือ 🆕 |
|
|
215
|
+
|
|
216
|
+
### 📱 Mobile Responsive (v1.3.5+) 🆕
|
|
217
|
+
|
|
218
|
+
เมื่อหน้าจอ **แคบกว่า `mobileBreakpoint`** (default: 768px) sidebar จะสลับเป็นโหมดมือถืออัตโนมัติ:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
┌─────────────────────────────┐
|
|
222
|
+
│ 🟢 ชื่อองค์กร [☰] │ ← Mobile Header (sticky top)
|
|
223
|
+
├─────────────────────────────┤
|
|
224
|
+
│ │
|
|
225
|
+
│ เนื้อหาหลัก │
|
|
226
|
+
│ │
|
|
227
|
+
└─────────────────────────────┘
|
|
228
|
+
|
|
229
|
+
กดปุ่ม ☰ →
|
|
230
|
+
|
|
231
|
+
┌──────────────────┬──────────┐
|
|
232
|
+
│ 🟢 ชื่อองค์กร [✕]│▒▒▒▒▒▒▒▒▒│ ← Drawer + Overlay
|
|
233
|
+
│──────────────────│▒▒▒▒▒▒▒▒▒│
|
|
234
|
+
│ ☐ แดชบอร์ด │▒▒▒▒▒▒▒▒▒│
|
|
235
|
+
│ ☐ ตรวจสอบคำร้อง │▒▒▒▒▒▒▒▒▒│
|
|
236
|
+
│ ☐ รายงาน │▒▒▒▒▒▒▒▒▒│
|
|
237
|
+
│ ... │▒▒▒▒▒▒▒▒▒│
|
|
238
|
+
│──────────────────│▒▒▒▒▒▒▒▒▒│
|
|
239
|
+
│ 👤 ผู้ใช้ [🚪] │▒▒▒▒▒▒▒▒▒│
|
|
240
|
+
└──────────────────┴──────────┘
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**ฟีเจอร์โหมดมือถือ:**
|
|
244
|
+
|
|
245
|
+
- ✅ **Mobile Header** — แถบด้านบนสีเขียวแสดงโลโก้ + ชื่อองค์กร + ปุ่ม ☰
|
|
246
|
+
- ✅ **Drawer slide-in** — เลื่อนเข้าจากซ้ายพร้อม animation
|
|
247
|
+
- ✅ **Overlay backdrop** — พื้นหลังมืดเมื่อเปิด drawer (กดปิดได้)
|
|
248
|
+
- ✅ **ปุ่มปิด (✕)** — อยู่ใน header ของ drawer
|
|
249
|
+
- ✅ **ปิดอัตโนมัติ** — เมื่อกดเมนูแล้ว drawer ปิดเอง
|
|
250
|
+
- ✅ **ESC key** — กด Escape ปิด drawer
|
|
251
|
+
- ✅ **Lock scroll** — ล็อก body scroll เมื่อ drawer เปิด
|
|
252
|
+
- ✅ **ไม่ต้องเขียนโค้ดเพิ่ม** — ใช้ `<StaffSidebar>` ตัวเดียว ตรวจจับอัตโนมัติ
|
|
253
|
+
|
|
254
|
+
**ไม่ต้องแก้โค้ดเดิม** — แค่อัพเดตเวอร์ชัน ระบบจะตรวจจับขนาดหน้าจอให้อัตโนมัติ:
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
// ใช้ StaffSidebar เหมือนเดิม — mobile responsive ทำงานอัตโนมัติ
|
|
258
|
+
<StaffSidebar
|
|
259
|
+
orgName="เทศบาลตำบลหลักเมือง"
|
|
260
|
+
orgSubtitle="จังหวัดราชบุรี"
|
|
261
|
+
menuItems={menuItems}
|
|
262
|
+
user={user}
|
|
263
|
+
roleLabel="เจ้าหน้าที่"
|
|
264
|
+
currentPath={currentPath}
|
|
265
|
+
onNavigate={(path) => router.push(path)}
|
|
266
|
+
onLogout={() => signOut()}
|
|
267
|
+
/>
|
|
268
|
+
|
|
269
|
+
// หรือปรับ breakpoint เอง
|
|
270
|
+
<StaffSidebar mobileBreakpoint={1024} ... />
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
> 💡 **Layout `marginLeft`:** บนมือถือ sidebar ไม่กินพื้นที่ → ใส่ `marginLeft: 0` ให้ main content
|
|
274
|
+
>
|
|
275
|
+
> ```tsx
|
|
276
|
+
> <main style={{ marginLeft: isMobile ? 0 : 280 }}>{children}</main>
|
|
277
|
+
> ```
|
|
217
278
|
|
|
218
279
|
### Features
|
|
219
280
|
|
|
@@ -225,7 +286,8 @@ const menuItems: MenuItem[] = [
|
|
|
225
286
|
- ✅ Default ตั้งค่าระบบ + ช่วยเหลือ (override ได้)
|
|
226
287
|
- ✅ โปรไฟล์ + ออกจากระบบ ล่างสุดเสมอ
|
|
227
288
|
- ✅ `dividerAfter` เส้นคั่นระหว่างกลุ่ม
|
|
228
|
-
- ✅ ใช้ Standard Avatar Placeholder กรณีที่ไม่มีรูปโปรไฟล์หรือโหลดรูปไม่สำเร็จ (v1.3.2+)
|
|
289
|
+
- ✅ ใช้ Standard Avatar Placeholder กรณีที่ไม่มีรูปโปรไฟล์หรือโหลดรูปไม่สำเร็จ (v1.3.2+)
|
|
290
|
+
- ✅ **Mobile Responsive Drawer** — สลับเป็น slide-in drawer บนมือถืออัตโนมัติ (v1.3.5+) 🆕
|
|
229
291
|
|
|
230
292
|
---
|
|
231
293
|
|
|
@@ -489,10 +551,25 @@ type FontSizeKey = 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
|
|
|
489
551
|
|
|
490
552
|
```tsx
|
|
491
553
|
'use client';
|
|
554
|
+
import { useState, useEffect } from 'react';
|
|
492
555
|
import { StaffSidebar, SettingsPanel } from 'gov-layout';
|
|
493
556
|
|
|
557
|
+
// Hook ตรวจจับมือถือ (ใช้ breakpoint เดียวกับ StaffSidebar)
|
|
558
|
+
function useIsMobile(breakpoint = 768) {
|
|
559
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
560
|
+
useEffect(() => {
|
|
561
|
+
const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
|
|
562
|
+
setIsMobile(mql.matches);
|
|
563
|
+
const handler = (e: MediaQueryListEvent) => setIsMobile(e.matches);
|
|
564
|
+
mql.addEventListener('change', handler);
|
|
565
|
+
return () => mql.removeEventListener('change', handler);
|
|
566
|
+
}, [breakpoint]);
|
|
567
|
+
return isMobile;
|
|
568
|
+
}
|
|
569
|
+
|
|
494
570
|
export default function AdminLayout({ children }) {
|
|
495
571
|
const [currentPath, setCurrentPath] = useState('/');
|
|
572
|
+
const isMobile = useIsMobile();
|
|
496
573
|
|
|
497
574
|
return (
|
|
498
575
|
<div style={{ display: 'flex' }}>
|
|
@@ -509,7 +586,8 @@ export default function AdminLayout({ children }) {
|
|
|
509
586
|
onProfile={() => setCurrentPath('/profile')}
|
|
510
587
|
collapsible
|
|
511
588
|
/>
|
|
512
|
-
|
|
589
|
+
{/* มือถือ: marginLeft=0 เพราะ sidebar เป็น drawer */}
|
|
590
|
+
<main style={{ marginLeft: isMobile ? 0 : 280, flex: 1, padding: 32 }}>
|
|
513
591
|
{currentPath === '/settings' ? (
|
|
514
592
|
<SettingsPanel showTheme={false} />
|
|
515
593
|
) : (
|
package/dist/index.d.mts
CHANGED
|
@@ -53,8 +53,9 @@ interface StaffSidebarProps {
|
|
|
53
53
|
onExpandRequest?: () => void;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
declare function StaffSidebar({ orgLogo, orgName, orgSubtitle, menuItems, bottomMenuItems, user, roleLabel, onNavigate, onLogout, onProfile, currentPath, width, className, collapsible, isOpen: controlledIsOpen, onToggle, onExpandRequest, topOffset, }: StaffSidebarProps & {
|
|
56
|
+
declare function StaffSidebar({ orgLogo, orgName, orgSubtitle, menuItems, bottomMenuItems, user, roleLabel, onNavigate, onLogout, onProfile, currentPath, width, className, collapsible, isOpen: controlledIsOpen, onToggle, onExpandRequest, topOffset, mobileBreakpoint, }: StaffSidebarProps & {
|
|
57
57
|
topOffset?: number | string;
|
|
58
|
+
mobileBreakpoint?: number;
|
|
58
59
|
}): react_jsx_runtime.JSX.Element;
|
|
59
60
|
|
|
60
61
|
interface SidebarHeaderProps {
|
package/dist/index.d.ts
CHANGED
|
@@ -53,8 +53,9 @@ interface StaffSidebarProps {
|
|
|
53
53
|
onExpandRequest?: () => void;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
declare function StaffSidebar({ orgLogo, orgName, orgSubtitle, menuItems, bottomMenuItems, user, roleLabel, onNavigate, onLogout, onProfile, currentPath, width, className, collapsible, isOpen: controlledIsOpen, onToggle, onExpandRequest, topOffset, }: StaffSidebarProps & {
|
|
56
|
+
declare function StaffSidebar({ orgLogo, orgName, orgSubtitle, menuItems, bottomMenuItems, user, roleLabel, onNavigate, onLogout, onProfile, currentPath, width, className, collapsible, isOpen: controlledIsOpen, onToggle, onExpandRequest, topOffset, mobileBreakpoint, }: StaffSidebarProps & {
|
|
57
57
|
topOffset?: number | string;
|
|
58
|
+
mobileBreakpoint?: number;
|
|
58
59
|
}): react_jsx_runtime.JSX.Element;
|
|
59
60
|
|
|
60
61
|
interface SidebarHeaderProps {
|
package/dist/index.js
CHANGED
|
@@ -3,30 +3,7 @@
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
function styleInject(css, { insertAt } = {}) {
|
|
8
|
-
if (typeof document === "undefined") return;
|
|
9
|
-
const head = document.head || document.getElementsByTagName("head")[0];
|
|
10
|
-
const style = document.createElement("style");
|
|
11
|
-
style.type = "text/css";
|
|
12
|
-
if (insertAt === "top") {
|
|
13
|
-
if (head.firstChild) {
|
|
14
|
-
head.insertBefore(style, head.firstChild);
|
|
15
|
-
} else {
|
|
16
|
-
head.appendChild(style);
|
|
17
|
-
}
|
|
18
|
-
} else {
|
|
19
|
-
head.appendChild(style);
|
|
20
|
-
}
|
|
21
|
-
if (style.styleSheet) {
|
|
22
|
-
style.styleSheet.cssText = css;
|
|
23
|
-
} else {
|
|
24
|
-
style.appendChild(document.createTextNode(css));
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// src/styles.css
|
|
29
|
-
styleInject(".text-primary,\n.text-text-primary {\n color: var(--color-alias-text-colors-primary, #060d26);\n}\n.text-secondary,\n.text-text-secondary {\n color: var(--color-alias-text-colors-secondary, #1e7d55);\n}\n.text-tertiary,\n.text-text-tertiary {\n color: var(--color-alias-text-colors-tertiary, #475272);\n}\n.text-placeholder,\n.text-text-placeholder {\n color: var(--color-alias-text-colors-placeholder, #707993);\n}\n.text-critical,\n.text-text-critical {\n color: var(--color-alias-text-colors-critical, #f21515);\n}\nhtml.dark .text-primary,\nhtml.dark .text-text-primary {\n color: var(--color-alias-text-colors-primary, #060d26) !important;\n}\nhtml.dark .text-secondary,\nhtml.dark .text-text-secondary {\n color: var(--color-alias-text-colors-secondary, #1e7d55) !important;\n}\nhtml.dark .text-tertiary,\nhtml.dark .text-text-tertiary {\n color: var(--color-alias-text-colors-tertiary, #475272) !important;\n}\nhtml.dark .text-placeholder,\nhtml.dark .text-text-placeholder {\n color: var(--color-alias-text-colors-placeholder, #707993) !important;\n}\nhtml.dark .text-critical,\nhtml.dark .text-text-critical {\n color: var(--color-alias-text-colors-critical, #f21515) !important;\n}\n");
|
|
6
|
+
// src/sidebar/StaffSidebar.tsx
|
|
30
7
|
function useDarkMode() {
|
|
31
8
|
const [isDark, setIsDark] = react.useState(false);
|
|
32
9
|
react.useEffect(() => {
|
|
@@ -138,10 +115,7 @@ function SearchIcon({ size = 20, className, style } = {}) {
|
|
|
138
115
|
] });
|
|
139
116
|
}
|
|
140
117
|
function BellIcon({ size = 20, className, style } = {}) {
|
|
141
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
142
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" }),
|
|
143
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M13.73 21a2 2 0 0 1-3.46 0" })
|
|
144
|
-
] });
|
|
118
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...defaultProps(size), viewBox: "0 0 22 24", stroke: "none", fill: "none", className, style, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21.2321 13.4463L19.3618 6.71712C18.8136 4.74574 17.6222 3.01418 15.9769 1.79767C14.3316 0.581165 12.3269 -0.0504751 10.2814 0.00315377C8.23593 0.0567826 6.26708 0.792603 4.6878 2.09365C3.10852 3.3947 2.00945 5.18632 1.5653 7.18371L0.11728 13.6954C-0.0426654 14.4149 -0.0389674 15.1611 0.128101 15.879C0.29517 16.5969 0.621344 17.2681 1.08254 17.8431C1.54374 18.418 2.12818 18.8821 2.79272 19.2009C3.45727 19.5198 4.18494 19.6853 4.92202 19.6853H6.01861C6.24454 20.7979 6.84817 21.7982 7.72723 22.5167C8.60629 23.2352 9.70671 23.6277 10.842 23.6277C11.9774 23.6277 13.0778 23.2352 13.9569 22.5167C14.8359 21.7982 15.4396 20.7979 15.6655 19.6853H16.4904C17.2492 19.6853 17.9977 19.5099 18.6774 19.1728C19.3572 18.8357 19.9498 18.346 20.409 17.742C20.8682 17.1379 21.1815 16.4359 21.3245 15.6907C21.4674 14.9455 21.4352 14.1774 21.2321 13.4463ZM10.842 21.654C10.2334 21.6515 9.64049 21.461 9.1443 21.1086C8.6481 20.7562 8.27291 20.2591 8.07005 19.6853H13.614C13.4112 20.2591 13.036 20.7562 12.5398 21.1086C12.0436 21.461 11.4506 21.6515 10.842 21.654ZM18.8411 16.55C18.5668 16.9139 18.2114 17.2088 17.8032 17.4113C17.3949 17.6138 16.9451 17.7183 16.4894 17.7165H4.92202C4.47982 17.7164 4.04328 17.6171 3.64463 17.4257C3.24598 17.2344 2.89539 16.9559 2.61874 16.611C2.34208 16.266 2.14643 15.8633 2.04622 15.4326C1.94602 15.0019 1.94381 14.5542 2.03977 14.1226L3.4868 7.60994C3.83561 6.04105 4.69886 4.63379 5.93932 3.61186C7.17978 2.58992 8.72625 2.01197 10.3329 1.96988C11.9395 1.92779 13.5142 2.42398 14.8064 3.37956C16.0987 4.33515 17.0344 5.69528 17.4649 7.24376L19.3352 13.9729C19.4588 14.4114 19.4785 14.8725 19.3927 15.3199C19.3069 15.7672 19.1181 16.1884 18.8411 16.55Z", fill: "currentColor" }) });
|
|
145
119
|
}
|
|
146
120
|
function FolderIcon({ size = 20, className, style } = {}) {
|
|
147
121
|
return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...defaultProps(size), className, style, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" }) });
|
|
@@ -903,6 +877,18 @@ function SidebarUserProfile({
|
|
|
903
877
|
}
|
|
904
878
|
);
|
|
905
879
|
}
|
|
880
|
+
function useIsMobile(breakpoint = 768) {
|
|
881
|
+
const [isMobile, setIsMobile] = react.useState(false);
|
|
882
|
+
react.useEffect(() => {
|
|
883
|
+
if (typeof window === "undefined") return;
|
|
884
|
+
const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
|
|
885
|
+
setIsMobile(mql.matches);
|
|
886
|
+
const handler = (e) => setIsMobile(e.matches);
|
|
887
|
+
mql.addEventListener("change", handler);
|
|
888
|
+
return () => mql.removeEventListener("change", handler);
|
|
889
|
+
}, [breakpoint]);
|
|
890
|
+
return isMobile;
|
|
891
|
+
}
|
|
906
892
|
function ToggleIcon({ isOpen }) {
|
|
907
893
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
908
894
|
"svg",
|
|
@@ -923,6 +909,21 @@ function ToggleIcon({ isOpen }) {
|
|
|
923
909
|
}
|
|
924
910
|
);
|
|
925
911
|
}
|
|
912
|
+
function HamburgerIcon() {
|
|
913
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "24", height: "18", viewBox: "0 0 27 18", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
914
|
+
"path",
|
|
915
|
+
{
|
|
916
|
+
d: "M1.1875 17.8426C0.851042 17.8426 0.569077 17.7287 0.341604 17.501C0.113868 17.2735 0 16.9914 0 16.6547C0 16.3182 0.113868 16.0363 0.341604 15.8088C0.569077 15.5816 0.851042 15.468 1.1875 15.468H25.7292C26.0656 15.468 26.3476 15.5817 26.5751 15.8092C26.8028 16.0369 26.9167 16.3192 26.9167 16.6559C26.9167 16.9923 26.8028 17.2743 26.5751 17.5018C26.3476 17.729 26.0656 17.8426 25.7292 17.8426H1.1875ZM1.1875 10.1088C0.851042 10.1088 0.569077 9.99492 0.341604 9.76719C0.113868 9.53945 0 9.25735 0 8.9209C0 8.58417 0.113868 8.30221 0.341604 8.075C0.569077 7.84753 0.851042 7.73379 1.1875 7.73379H25.7292C26.0656 7.73379 26.3476 7.84766 26.5751 8.0754C26.8028 8.30313 26.9167 8.58523 26.9167 8.92169C26.9167 9.25841 26.8028 9.54037 26.5751 9.76758C26.3476 9.99506 26.0656 10.1088 25.7292 10.1088H1.1875ZM1.1875 2.3746C0.851042 2.3746 0.569077 2.26087 0.341604 2.0334C0.113868 1.80566 0 1.52343 0 1.18671C0 0.850249 0.113868 0.568284 0.341604 0.340812C0.569077 0.113604 0.851042 0 1.1875 0H25.7292C26.0656 0 26.3476 0.113868 26.5751 0.341604C26.8028 0.569077 26.9167 0.851174 26.9167 1.1879C26.9167 1.52435 26.8028 1.80632 26.5751 2.03379C26.3476 2.261 26.0656 2.3746 25.7292 2.3746H1.1875Z",
|
|
917
|
+
fill: "currentColor"
|
|
918
|
+
}
|
|
919
|
+
) });
|
|
920
|
+
}
|
|
921
|
+
function CloseIcon() {
|
|
922
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
923
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
924
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
925
|
+
] });
|
|
926
|
+
}
|
|
926
927
|
function HelpCircleIcon2() {
|
|
927
928
|
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: [
|
|
928
929
|
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
@@ -958,10 +959,13 @@ function StaffSidebar({
|
|
|
958
959
|
isOpen: controlledIsOpen,
|
|
959
960
|
onToggle,
|
|
960
961
|
onExpandRequest,
|
|
961
|
-
topOffset = 0
|
|
962
|
+
topOffset = 0,
|
|
963
|
+
mobileBreakpoint = 768
|
|
962
964
|
}) {
|
|
963
965
|
const isDark = useDarkMode();
|
|
966
|
+
const isMobile = useIsMobile(mobileBreakpoint);
|
|
964
967
|
const [internalOpen, setInternalOpen] = react.useState(true);
|
|
968
|
+
const [mobileDrawerOpen, setMobileDrawerOpen] = react.useState(false);
|
|
965
969
|
const sidebarOpen = controlledIsOpen !== void 0 ? controlledIsOpen : internalOpen;
|
|
966
970
|
const collapsed = collapsible && !sidebarOpen;
|
|
967
971
|
const collapsedWidth = "64px";
|
|
@@ -985,6 +989,283 @@ function StaffSidebar({
|
|
|
985
989
|
}
|
|
986
990
|
}
|
|
987
991
|
};
|
|
992
|
+
const openDrawer = react.useCallback(() => setMobileDrawerOpen(true), []);
|
|
993
|
+
const closeDrawer = react.useCallback(() => setMobileDrawerOpen(false), []);
|
|
994
|
+
const handleMobileNavigate = react.useCallback((path) => {
|
|
995
|
+
onNavigate(path);
|
|
996
|
+
if (isMobile) closeDrawer();
|
|
997
|
+
}, [onNavigate, isMobile, closeDrawer]);
|
|
998
|
+
react.useEffect(() => {
|
|
999
|
+
if (isMobile && mobileDrawerOpen) {
|
|
1000
|
+
const originalOverflow = document.body.style.overflow;
|
|
1001
|
+
document.body.style.overflow = "hidden";
|
|
1002
|
+
return () => {
|
|
1003
|
+
document.body.style.overflow = originalOverflow;
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
}, [isMobile, mobileDrawerOpen]);
|
|
1007
|
+
react.useEffect(() => {
|
|
1008
|
+
if (!isMobile || !mobileDrawerOpen) return;
|
|
1009
|
+
const handler = (e) => {
|
|
1010
|
+
if (e.key === "Escape") closeDrawer();
|
|
1011
|
+
};
|
|
1012
|
+
window.addEventListener("keydown", handler);
|
|
1013
|
+
return () => window.removeEventListener("keydown", handler);
|
|
1014
|
+
}, [isMobile, mobileDrawerOpen, closeDrawer]);
|
|
1015
|
+
if (isMobile) {
|
|
1016
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1017
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1018
|
+
"header",
|
|
1019
|
+
{
|
|
1020
|
+
style: {
|
|
1021
|
+
position: "sticky",
|
|
1022
|
+
top: 0,
|
|
1023
|
+
left: 0,
|
|
1024
|
+
right: 0,
|
|
1025
|
+
height: "60px",
|
|
1026
|
+
background: "linear-gradient(to bottom left, #3AAE7D, #346D55)",
|
|
1027
|
+
display: "flex",
|
|
1028
|
+
alignItems: "center",
|
|
1029
|
+
justifyContent: "space-between",
|
|
1030
|
+
padding: "0 16px",
|
|
1031
|
+
zIndex: 49,
|
|
1032
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.15)"
|
|
1033
|
+
},
|
|
1034
|
+
children: [
|
|
1035
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px", flex: 1, minWidth: 0 }, children: [
|
|
1036
|
+
orgLogo ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1037
|
+
"img",
|
|
1038
|
+
{
|
|
1039
|
+
src: orgLogo,
|
|
1040
|
+
alt: orgName,
|
|
1041
|
+
style: {
|
|
1042
|
+
width: "40px",
|
|
1043
|
+
height: "40px",
|
|
1044
|
+
borderRadius: "50%",
|
|
1045
|
+
objectFit: "cover",
|
|
1046
|
+
flexShrink: 0,
|
|
1047
|
+
border: "2px solid rgba(255,255,255,0.3)"
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
|
|
1051
|
+
width: "40px",
|
|
1052
|
+
height: "40px",
|
|
1053
|
+
borderRadius: "50%",
|
|
1054
|
+
background: "rgba(255,255,255,0.2)",
|
|
1055
|
+
display: "flex",
|
|
1056
|
+
alignItems: "center",
|
|
1057
|
+
justifyContent: "center",
|
|
1058
|
+
color: "#fff",
|
|
1059
|
+
fontWeight: 700,
|
|
1060
|
+
fontSize: "16px",
|
|
1061
|
+
flexShrink: 0
|
|
1062
|
+
}, children: orgName.slice(0, 2) }),
|
|
1063
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0 }, children: [
|
|
1064
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: {
|
|
1065
|
+
color: "#fff",
|
|
1066
|
+
fontWeight: 700,
|
|
1067
|
+
fontSize: "15px",
|
|
1068
|
+
lineHeight: "20px",
|
|
1069
|
+
margin: 0,
|
|
1070
|
+
overflow: "hidden",
|
|
1071
|
+
textOverflow: "ellipsis",
|
|
1072
|
+
whiteSpace: "nowrap"
|
|
1073
|
+
}, children: orgName }),
|
|
1074
|
+
orgSubtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
|
|
1075
|
+
color: "rgba(255,255,255,0.75)",
|
|
1076
|
+
fontSize: "12px",
|
|
1077
|
+
lineHeight: "16px",
|
|
1078
|
+
margin: 0,
|
|
1079
|
+
overflow: "hidden",
|
|
1080
|
+
textOverflow: "ellipsis",
|
|
1081
|
+
whiteSpace: "nowrap"
|
|
1082
|
+
}, children: orgSubtitle })
|
|
1083
|
+
] })
|
|
1084
|
+
] }),
|
|
1085
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1086
|
+
"button",
|
|
1087
|
+
{
|
|
1088
|
+
onClick: openDrawer,
|
|
1089
|
+
"aria-label": "\u0E40\u0E1B\u0E34\u0E14\u0E40\u0E21\u0E19\u0E39",
|
|
1090
|
+
style: {
|
|
1091
|
+
width: "44px",
|
|
1092
|
+
height: "44px",
|
|
1093
|
+
borderRadius: "10px",
|
|
1094
|
+
border: "none",
|
|
1095
|
+
background: "rgba(255,255,255,0.12)",
|
|
1096
|
+
cursor: "pointer",
|
|
1097
|
+
display: "flex",
|
|
1098
|
+
alignItems: "center",
|
|
1099
|
+
justifyContent: "center",
|
|
1100
|
+
color: "#ffffff",
|
|
1101
|
+
flexShrink: 0,
|
|
1102
|
+
transition: "background-color 0.15s ease",
|
|
1103
|
+
WebkitTapHighlightColor: "transparent"
|
|
1104
|
+
},
|
|
1105
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(HamburgerIcon, {})
|
|
1106
|
+
}
|
|
1107
|
+
)
|
|
1108
|
+
]
|
|
1109
|
+
}
|
|
1110
|
+
),
|
|
1111
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1112
|
+
"div",
|
|
1113
|
+
{
|
|
1114
|
+
onClick: closeDrawer,
|
|
1115
|
+
style: {
|
|
1116
|
+
position: "fixed",
|
|
1117
|
+
inset: 0,
|
|
1118
|
+
background: "rgba(0,0,0,0.45)",
|
|
1119
|
+
zIndex: 99,
|
|
1120
|
+
opacity: mobileDrawerOpen ? 1 : 0,
|
|
1121
|
+
pointerEvents: mobileDrawerOpen ? "auto" : "none",
|
|
1122
|
+
transition: "opacity 0.3s ease",
|
|
1123
|
+
WebkitTapHighlightColor: "transparent"
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
),
|
|
1127
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1128
|
+
"aside",
|
|
1129
|
+
{
|
|
1130
|
+
className,
|
|
1131
|
+
style: {
|
|
1132
|
+
position: "fixed",
|
|
1133
|
+
top: 0,
|
|
1134
|
+
left: 0,
|
|
1135
|
+
bottom: 0,
|
|
1136
|
+
width: "min(85vw, 320px)",
|
|
1137
|
+
background: isDark ? "#1e293b" : "#fff",
|
|
1138
|
+
zIndex: 100,
|
|
1139
|
+
display: "flex",
|
|
1140
|
+
flexDirection: "column",
|
|
1141
|
+
overflow: "hidden",
|
|
1142
|
+
boxShadow: mobileDrawerOpen ? "4px 0 24px rgba(0,0,0,0.2)" : "none",
|
|
1143
|
+
transform: mobileDrawerOpen ? "translateX(0)" : "translateX(-100%)",
|
|
1144
|
+
transition: "transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
1145
|
+
color: isDark ? "#ffffff" : void 0
|
|
1146
|
+
},
|
|
1147
|
+
children: [
|
|
1148
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
|
|
1149
|
+
display: "flex",
|
|
1150
|
+
alignItems: "center",
|
|
1151
|
+
justifyContent: "space-between",
|
|
1152
|
+
padding: "16px 16px 16px 20px",
|
|
1153
|
+
background: "linear-gradient(to bottom left, #3AAE7D, #346D55)",
|
|
1154
|
+
flexShrink: 0
|
|
1155
|
+
}, children: [
|
|
1156
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px", flex: 1, minWidth: 0 }, children: [
|
|
1157
|
+
orgLogo ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1158
|
+
"img",
|
|
1159
|
+
{
|
|
1160
|
+
src: orgLogo,
|
|
1161
|
+
alt: orgName,
|
|
1162
|
+
style: {
|
|
1163
|
+
width: "44px",
|
|
1164
|
+
height: "44px",
|
|
1165
|
+
borderRadius: "50%",
|
|
1166
|
+
objectFit: "cover",
|
|
1167
|
+
flexShrink: 0,
|
|
1168
|
+
border: "2px solid rgba(255,255,255,0.3)"
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
|
|
1172
|
+
width: "44px",
|
|
1173
|
+
height: "44px",
|
|
1174
|
+
borderRadius: "50%",
|
|
1175
|
+
background: "rgba(255,255,255,0.2)",
|
|
1176
|
+
display: "flex",
|
|
1177
|
+
alignItems: "center",
|
|
1178
|
+
justifyContent: "center",
|
|
1179
|
+
color: "#fff",
|
|
1180
|
+
fontWeight: 700,
|
|
1181
|
+
fontSize: "16px",
|
|
1182
|
+
flexShrink: 0
|
|
1183
|
+
}, children: orgName.slice(0, 2) }),
|
|
1184
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0 }, children: [
|
|
1185
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: {
|
|
1186
|
+
color: "#fff",
|
|
1187
|
+
fontWeight: 700,
|
|
1188
|
+
fontSize: "15px",
|
|
1189
|
+
lineHeight: "20px",
|
|
1190
|
+
margin: 0,
|
|
1191
|
+
overflow: "hidden",
|
|
1192
|
+
textOverflow: "ellipsis",
|
|
1193
|
+
whiteSpace: "nowrap"
|
|
1194
|
+
}, children: orgName }),
|
|
1195
|
+
orgSubtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
|
|
1196
|
+
color: "rgba(255,255,255,0.75)",
|
|
1197
|
+
fontSize: "12px",
|
|
1198
|
+
lineHeight: "16px",
|
|
1199
|
+
margin: 0
|
|
1200
|
+
}, children: orgSubtitle })
|
|
1201
|
+
] })
|
|
1202
|
+
] }),
|
|
1203
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1204
|
+
"button",
|
|
1205
|
+
{
|
|
1206
|
+
onClick: closeDrawer,
|
|
1207
|
+
"aria-label": "\u0E1B\u0E34\u0E14\u0E40\u0E21\u0E19\u0E39",
|
|
1208
|
+
style: {
|
|
1209
|
+
width: "40px",
|
|
1210
|
+
height: "40px",
|
|
1211
|
+
borderRadius: "10px",
|
|
1212
|
+
border: "none",
|
|
1213
|
+
background: "rgba(255,255,255,0.15)",
|
|
1214
|
+
cursor: "pointer",
|
|
1215
|
+
display: "flex",
|
|
1216
|
+
alignItems: "center",
|
|
1217
|
+
justifyContent: "center",
|
|
1218
|
+
color: "#ffffff",
|
|
1219
|
+
flexShrink: 0,
|
|
1220
|
+
transition: "background-color 0.15s ease",
|
|
1221
|
+
WebkitTapHighlightColor: "transparent"
|
|
1222
|
+
},
|
|
1223
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
|
|
1224
|
+
}
|
|
1225
|
+
)
|
|
1226
|
+
] }),
|
|
1227
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, overflowY: "auto", minHeight: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1228
|
+
SidebarMenu,
|
|
1229
|
+
{
|
|
1230
|
+
menuItems,
|
|
1231
|
+
onItemClick: handleMobileNavigate,
|
|
1232
|
+
currentPath,
|
|
1233
|
+
collapsed: false
|
|
1234
|
+
}
|
|
1235
|
+
) }),
|
|
1236
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flexShrink: 0 }, children: [
|
|
1237
|
+
resolvedBottomMenu && resolvedBottomMenu.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1238
|
+
/* @__PURE__ */ jsxRuntime.jsx("hr", { style: {
|
|
1239
|
+
border: "none",
|
|
1240
|
+
borderTop: `1px solid ${isDark ? "#374151" : "var(--color-border-colors-neutral, #c8cedd)"}`,
|
|
1241
|
+
margin: "4px 12px"
|
|
1242
|
+
} }),
|
|
1243
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1244
|
+
SidebarMenu,
|
|
1245
|
+
{
|
|
1246
|
+
menuItems: resolvedBottomMenu,
|
|
1247
|
+
onItemClick: handleMobileNavigate,
|
|
1248
|
+
currentPath,
|
|
1249
|
+
collapsed: false
|
|
1250
|
+
}
|
|
1251
|
+
)
|
|
1252
|
+
] }),
|
|
1253
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1254
|
+
SidebarUserProfile,
|
|
1255
|
+
{
|
|
1256
|
+
user,
|
|
1257
|
+
roleLabel,
|
|
1258
|
+
onLogout,
|
|
1259
|
+
onProfile,
|
|
1260
|
+
collapsed: false
|
|
1261
|
+
}
|
|
1262
|
+
)
|
|
1263
|
+
] })
|
|
1264
|
+
]
|
|
1265
|
+
}
|
|
1266
|
+
)
|
|
1267
|
+
] });
|
|
1268
|
+
}
|
|
988
1269
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
989
1270
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
990
1271
|
"aside",
|
|
@@ -1100,7 +1381,7 @@ function BellIcon2() {
|
|
|
1100
1381
|
}
|
|
1101
1382
|
) });
|
|
1102
1383
|
}
|
|
1103
|
-
function
|
|
1384
|
+
function HamburgerIcon2() {
|
|
1104
1385
|
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "24", height: "18", viewBox: "0 0 27 18", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1105
1386
|
"path",
|
|
1106
1387
|
{
|
|
@@ -1348,11 +1629,8 @@ function UserHeader({
|
|
|
1348
1629
|
isNotifOpen && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1349
1630
|
"div",
|
|
1350
1631
|
{
|
|
1632
|
+
className: "absolute top-full mt-2 w-[calc(100vw-32px)] max-w-[360px] left-1/2 -translate-x-1/2 md:left-auto md:right-0 md:translate-x-0",
|
|
1351
1633
|
style: {
|
|
1352
|
-
position: "absolute",
|
|
1353
|
-
right: 0,
|
|
1354
|
-
marginTop: "8px",
|
|
1355
|
-
width: "360px",
|
|
1356
1634
|
background: isDark ? "#1e293b" : "#fff",
|
|
1357
1635
|
borderRadius: "12px",
|
|
1358
1636
|
boxShadow: isDark ? "0 10px 40px rgba(0,0,0,0.4)" : "0 10px 40px rgba(0,0,0,0.12)",
|
|
@@ -1616,7 +1894,7 @@ function UserHeader({
|
|
|
1616
1894
|
onMouseLeave: (e) => {
|
|
1617
1895
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
1618
1896
|
},
|
|
1619
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1897
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(HamburgerIcon2, {})
|
|
1620
1898
|
}
|
|
1621
1899
|
)
|
|
1622
1900
|
] })
|