gov-layout 1.3.1 → 1.3.4

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
@@ -222,6 +222,7 @@ const menuItems: MenuItem[] = [
222
222
  - ✅ Default ตั้งค่าระบบ + ช่วยเหลือ (override ได้)
223
223
  - ✅ โปรไฟล์ + ออกจากระบบ ล่างสุดเสมอ
224
224
  - ✅ `dividerAfter` เส้นคั่นระหว่างกลุ่ม
225
+ - ✅ ใช้ Standard Avatar Placeholder กรณีที่ไม่มีรูปโปรไฟล์หรือโหลดรูปไม่สำเร็จ (v1.3.2+) 🆕
225
226
 
226
227
  ---
227
228
 
package/dist/index.js CHANGED
@@ -625,6 +625,25 @@ function ProfileIcon() {
625
625
  }
626
626
  );
627
627
  }
628
+ function AvatarPlaceholder({ size = 40 }) {
629
+ return /* @__PURE__ */ jsxRuntime.jsx(
630
+ "div",
631
+ {
632
+ style: {
633
+ width: `${size}px`,
634
+ height: `${size}px`,
635
+ borderRadius: "50%",
636
+ backgroundColor: "#f1f5f9",
637
+ display: "flex",
638
+ alignItems: "center",
639
+ justifyContent: "center",
640
+ flexShrink: 0,
641
+ boxShadow: "0 2px 8px rgba(0,0,0,0.05)"
642
+ },
643
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size * 0.6, height: size * 0.6, viewBox: "0 0 24 24", fill: "#94a3b8", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" }) })
644
+ }
645
+ );
646
+ }
628
647
  function SidebarUserProfile({
629
648
  user,
630
649
  roleLabel,
@@ -633,6 +652,10 @@ function SidebarUserProfile({
633
652
  onProfile
634
653
  }) {
635
654
  const isDark = useDarkMode();
655
+ const [imgError, setImgError] = react.useState(false);
656
+ react.useEffect(() => {
657
+ setImgError(false);
658
+ }, [user?.pictureUrl]);
636
659
  if (!user) return null;
637
660
  const getFullName = () => {
638
661
  if (user.firstName && user.lastName) {
@@ -640,9 +663,6 @@ function SidebarUserProfile({
640
663
  }
641
664
  return user.firstName || user.lastName || "\u0E1C\u0E39\u0E49\u0E43\u0E0A\u0E49";
642
665
  };
643
- const getInitial = () => {
644
- return user.firstName?.charAt(0) || user.lastName?.charAt(0) || "?";
645
- };
646
666
  if (collapsed) {
647
667
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
648
668
  padding: "12px 8px",
@@ -658,36 +678,21 @@ function SidebarUserProfile({
658
678
  onClick: onProfile,
659
679
  title: onProfile ? "\u0E14\u0E39\u0E42\u0E1B\u0E23\u0E44\u0E1F\u0E25\u0E4C" : getFullName(),
660
680
  style: { cursor: onProfile ? "pointer" : "default" },
661
- children: user.pictureUrl ? /* @__PURE__ */ jsxRuntime.jsx(
681
+ children: user.pictureUrl && !imgError ? /* @__PURE__ */ jsxRuntime.jsx(
662
682
  "img",
663
683
  {
664
684
  src: user.pictureUrl,
665
685
  alt: getFullName(),
686
+ onError: () => setImgError(true),
666
687
  style: {
667
688
  width: "36px",
668
689
  height: "36px",
669
690
  borderRadius: "50%",
670
- objectFit: "cover"
691
+ objectFit: "cover",
692
+ flexShrink: 0
671
693
  }
672
694
  }
673
- ) : /* @__PURE__ */ jsxRuntime.jsx(
674
- "div",
675
- {
676
- style: {
677
- width: "36px",
678
- height: "36px",
679
- borderRadius: "50%",
680
- background: "var(--color-alias-color-brand-primary, #1e7d55)",
681
- display: "flex",
682
- alignItems: "center",
683
- justifyContent: "center",
684
- color: "#fff",
685
- fontWeight: 700,
686
- fontSize: "14px"
687
- },
688
- children: getInitial()
689
- }
690
- )
695
+ ) : /* @__PURE__ */ jsxRuntime.jsx(AvatarPlaceholder, { size: 36 })
691
696
  }
692
697
  ),
693
698
  onProfile && /* @__PURE__ */ jsxRuntime.jsx(
@@ -777,11 +782,12 @@ function SidebarUserProfile({
777
782
  transition: "opacity 0.15s ease"
778
783
  },
779
784
  children: [
780
- user.pictureUrl ? /* @__PURE__ */ jsxRuntime.jsx(
785
+ user.pictureUrl && !imgError ? /* @__PURE__ */ jsxRuntime.jsx(
781
786
  "img",
782
787
  {
783
788
  src: user.pictureUrl,
784
789
  alt: getFullName(),
790
+ onError: () => setImgError(true),
785
791
  style: {
786
792
  width: "40px",
787
793
  height: "40px",
@@ -790,25 +796,7 @@ function SidebarUserProfile({
790
796
  flexShrink: 0
791
797
  }
792
798
  }
793
- ) : /* @__PURE__ */ jsxRuntime.jsx(
794
- "div",
795
- {
796
- style: {
797
- width: "40px",
798
- height: "40px",
799
- borderRadius: "50%",
800
- background: "var(--color-alias-color-brand-primary, #1e7d55)",
801
- display: "flex",
802
- alignItems: "center",
803
- justifyContent: "center",
804
- color: "#fff",
805
- fontWeight: 700,
806
- fontSize: "16px",
807
- flexShrink: 0
808
- },
809
- children: getInitial()
810
- }
811
- ),
799
+ ) : /* @__PURE__ */ jsxRuntime.jsx(AvatarPlaceholder, { size: 40 }),
812
800
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
813
801
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
814
802
  fontWeight: 600,
@@ -1100,6 +1088,25 @@ function HamburgerIcon() {
1100
1088
  function MessageOutlinedIcon(props) {
1101
1089
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { focusable: "false", "aria-hidden": "true", viewBox: "0 0 24 24", fill: "currentColor", ...props, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 4h16v12H5.17L4 17.17zm0-2c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm2 10h12v2H6zm0-3h12v2H6zm0-3h12v2H6z" }) });
1102
1090
  }
1091
+ function AvatarPlaceholder2({ size = 44 }) {
1092
+ return /* @__PURE__ */ jsxRuntime.jsx(
1093
+ "div",
1094
+ {
1095
+ style: {
1096
+ width: `${size}px`,
1097
+ height: `${size}px`,
1098
+ borderRadius: "50%",
1099
+ backgroundColor: "#f1f5f9",
1100
+ display: "flex",
1101
+ alignItems: "center",
1102
+ justifyContent: "center",
1103
+ flexShrink: 0,
1104
+ boxShadow: "0 2px 8px rgba(0,0,0,0.05)"
1105
+ },
1106
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size * 0.6, height: size * 0.6, viewBox: "0 0 24 24", fill: "#94a3b8", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" }) })
1107
+ }
1108
+ );
1109
+ }
1103
1110
  function getNotifBg(type) {
1104
1111
  const map = {
1105
1112
  success: "#dcfce7",
@@ -1131,11 +1138,14 @@ function UserHeader({
1131
1138
  className
1132
1139
  }) {
1133
1140
  const displayName = `${user?.firstName || ""} ${user?.lastName || ""}`.trim() || "\u0E1C\u0E39\u0E49\u0E43\u0E0A\u0E49";
1134
- const firstChar = user?.firstName?.charAt(0) || "\u0E1C";
1135
1141
  const [isNotifOpen, setIsNotifOpen] = react.useState(false);
1136
1142
  const [activeFilter, setActiveFilter] = react.useState("all");
1137
1143
  const notifRef = react.useRef(null);
1138
1144
  const isDark = useDarkMode();
1145
+ const [imgError, setImgError] = react.useState(false);
1146
+ react.useEffect(() => {
1147
+ setImgError(false);
1148
+ }, [user?.pictureUrl]);
1139
1149
  react.useEffect(() => {
1140
1150
  const handleClickOutside = (e) => {
1141
1151
  if (notifRef.current && !notifRef.current.contains(e.target)) {
@@ -1198,42 +1208,22 @@ function UserHeader({
1198
1208
  overflow: "hidden"
1199
1209
  },
1200
1210
  children: [
1201
- user?.pictureUrl ? /* @__PURE__ */ jsxRuntime.jsx(
1211
+ user?.pictureUrl && !imgError ? /* @__PURE__ */ jsxRuntime.jsx(
1202
1212
  "img",
1203
1213
  {
1204
1214
  src: user.pictureUrl,
1205
1215
  alt: displayName,
1216
+ onError: () => setImgError(true),
1206
1217
  style: {
1207
1218
  width: "44px",
1208
1219
  height: "44px",
1209
1220
  borderRadius: "50%",
1210
1221
  objectFit: "cover",
1211
- border: "2px solid rgba(255,255,255,0.7)"
1212
- }
1213
- }
1214
- ) : /* @__PURE__ */ jsxRuntime.jsx(
1215
- "div",
1216
- {
1217
- style: {
1218
- width: "44px",
1219
- height: "44px",
1220
- borderRadius: "50%",
1221
- background: "rgba(255,255,255,0.2)",
1222
1222
  border: "2px solid rgba(255,255,255,0.7)",
1223
- display: "flex",
1224
- alignItems: "center",
1225
- justifyContent: "center",
1226
- boxShadow: "0 1px 3px rgba(0,0,0,0.1)"
1227
- },
1228
- children: /* @__PURE__ */ jsxRuntime.jsx(
1229
- "span",
1230
- {
1231
- style: { color: "#fff", fontSize: "18px", fontWeight: 600 },
1232
- children: firstChar
1233
- }
1234
- )
1223
+ flexShrink: 0
1224
+ }
1235
1225
  }
1236
- ),
1226
+ ) : /* @__PURE__ */ jsxRuntime.jsx(AvatarPlaceholder2, { size: 44 }),
1237
1227
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
1238
1228
  /* @__PURE__ */ jsxRuntime.jsxs(
1239
1229
  "p",
@@ -1625,6 +1615,25 @@ function ProfileIcon2() {
1625
1615
  /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "7", r: "4" })
1626
1616
  ] });
1627
1617
  }
1618
+ function AvatarPlaceholder3({ size = 56 }) {
1619
+ return /* @__PURE__ */ jsxRuntime.jsx(
1620
+ "div",
1621
+ {
1622
+ style: {
1623
+ width: `${size}px`,
1624
+ height: `${size}px`,
1625
+ borderRadius: "50%",
1626
+ backgroundColor: "#f1f5f9",
1627
+ display: "flex",
1628
+ alignItems: "center",
1629
+ justifyContent: "center",
1630
+ flexShrink: 0,
1631
+ boxShadow: "0 2px 8px rgba(0,0,0,0.05)"
1632
+ },
1633
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size * 0.6, height: size * 0.6, viewBox: "0 0 24 24", fill: "#94a3b8", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" }) })
1634
+ }
1635
+ );
1636
+ }
1628
1637
  function UserSidebar({
1629
1638
  user,
1630
1639
  roleLabel,
@@ -1637,6 +1646,10 @@ function UserSidebar({
1637
1646
  onProfile
1638
1647
  }) {
1639
1648
  const isDark = useDarkMode();
1649
+ const [imgError, setImgError] = react.useState(false);
1650
+ react.useEffect(() => {
1651
+ setImgError(false);
1652
+ }, [user?.pictureUrl]);
1640
1653
  react.useEffect(() => {
1641
1654
  if (isOpen) {
1642
1655
  document.body.style.overflow = "hidden";
@@ -1652,10 +1665,6 @@ function UserSidebar({
1652
1665
  if (user.firstName && user.lastName) return `${user.firstName} ${user.lastName}`;
1653
1666
  return user.firstName || user.lastName || "\u0E1C\u0E39\u0E49\u0E43\u0E0A\u0E49";
1654
1667
  };
1655
- const getInitial = () => {
1656
- if (!user) return "?";
1657
- return user.firstName?.charAt(0) || user.lastName?.charAt(0) || "?";
1658
- };
1659
1668
  const handleMenuClick = (path) => {
1660
1669
  onNavigate(path);
1661
1670
  onClose();
@@ -1715,38 +1724,22 @@ function UserSidebar({
1715
1724
  padding: "8px"
1716
1725
  },
1717
1726
  children: [
1718
- user.pictureUrl ? /* @__PURE__ */ jsxRuntime.jsx(
1727
+ user.pictureUrl && !imgError ? /* @__PURE__ */ jsxRuntime.jsx(
1719
1728
  "img",
1720
1729
  {
1721
1730
  src: user.pictureUrl,
1722
1731
  alt: getFullName(),
1732
+ onError: () => setImgError(true),
1723
1733
  style: {
1724
1734
  width: "56px",
1725
1735
  height: "56px",
1726
1736
  borderRadius: "50%",
1727
1737
  objectFit: "cover",
1738
+ flexShrink: 0,
1728
1739
  boxShadow: "0 2px 8px rgba(0,0,0,0.1)"
1729
1740
  }
1730
1741
  }
1731
- ) : /* @__PURE__ */ jsxRuntime.jsx(
1732
- "div",
1733
- {
1734
- style: {
1735
- width: "56px",
1736
- height: "56px",
1737
- borderRadius: "50%",
1738
- background: "linear-gradient(135deg, var(--color-alias-color-brand-secondary, #80d897), var(--color-alias-color-brand-primary, #1e7d55))",
1739
- display: "flex",
1740
- alignItems: "center",
1741
- justifyContent: "center",
1742
- color: "#fff",
1743
- fontWeight: 700,
1744
- fontSize: "20px",
1745
- boxShadow: "0 2px 8px rgba(0,0,0,0.1)"
1746
- },
1747
- children: getInitial()
1748
- }
1749
- ),
1742
+ ) : /* @__PURE__ */ jsxRuntime.jsx(AvatarPlaceholder3, { size: 56 }),
1750
1743
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
1751
1744
  /* @__PURE__ */ jsxRuntime.jsx(
1752
1745
  "p",