@goplusvn/core 0.1.94 → 0.1.96
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 +27 -0
- package/package.json +1 -1
- package/src/ui/forms/multi-select.tsx +5 -10
- package/src/ui/layout/horizontal-layout-header.tsx +3 -1
- package/src/ui/layout/horizontal-layout.tsx +3 -0
- package/src/ui/layout/main-layout.tsx +8 -0
- package/src/ui/layout/user-dropdown.tsx +15 -6
- package/src/ui/layout/vertical-layout-header.tsx +3 -1
- package/src/ui/layout/vertical-layout.tsx +3 -0
- package/src/ui/primitives/combobox.tsx +5 -9
- package/templates/starter-app/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
## 0.1.96 — Sửa lỗi Focus Trap khiến ô tìm kiếm Combobox/MultiSelect mất focus khi lồng trong Sheet/Dialog
|
|
2
|
+
|
|
3
|
+
Khi `Combobox` hoặc `MultiSelect` nằm trong `<Sheet>` (bộ lọc mobile) hoặc `<Dialog>`,
|
|
4
|
+
Radix Dialog's `FocusScope` bắt sự kiện tương tác trên Portal và giật focus ngược lại về trigger,
|
|
5
|
+
khiến ô input tìm kiếm bị blur ngay lập tức và không thể gõ được trên một số thiết bị/trình duyệt.
|
|
6
|
+
|
|
7
|
+
**Đổi**
|
|
8
|
+
|
|
9
|
+
- `ui/primitives/combobox.tsx` & `ui/forms/multi-select.tsx` — thêm `onFocusOutside={(e) => e.preventDefault()}`
|
|
10
|
+
trên `PopoverContent`, ngăn Dialog/Sheet cha cướp focus khỏi ô tìm kiếm; kích hoạt focus trực tiếp vào
|
|
11
|
+
`searchInputRef` khi mở panel.
|
|
12
|
+
|
|
13
|
+
## 0.1.95 — Triệt tiêu Hydration Mismatch trên AvatarFallback của UserDropdown
|
|
14
|
+
|
|
15
|
+
Trong quá trình Server-Side Rendering (SSR), `useSafeAuthSession()` chưa có phiên đăng nhập
|
|
16
|
+
của client, khiến server render ra AvatarFallback rỗng trong khi client hydrate ngay lập tức
|
|
17
|
+
với initials của user (`"AV"`), dẫn đến lỗi Hydration Mismatch.
|
|
18
|
+
|
|
19
|
+
**Đổi**
|
|
20
|
+
|
|
21
|
+
- `ui/layout/user-dropdown.tsx` — thêm `suppressHydrationWarning` cho `AvatarFallback` ở nút trigger
|
|
22
|
+
và thông tin user trong dropdown menu content; xử lý initials an toàn chống lệch DOM giữa SSR & CSR.
|
|
23
|
+
- `ui/layout/main-layout.tsx`, `vertical-layout.tsx`, `horizontal-layout.tsx`, `vertical-layout-header.tsx`,
|
|
24
|
+
`horizontal-layout-header.tsx` — bổ sung prop `user?: ExtendedUser | null` xuyên suốt, cho phép Server
|
|
25
|
+
Component truyền thông tin session user trực tiếp từ SSR xuống header, triệt tiêu hoàn toàn độ trễ và
|
|
26
|
+
lệch dữ liệu.
|
|
27
|
+
|
|
1
28
|
## 0.1.94 — Hỗ trợ Nested Modal Layer cho Combobox/MultiSelect & Chuẩn hoá tìm kiếm Tiếng Việt
|
|
2
29
|
|
|
3
30
|
Khi bộ lọc `Combobox` hoặc `MultiSelect` nằm trong `<Sheet>` (bộ lọc mobile) hoặc `<Dialog>`,
|
package/package.json
CHANGED
|
@@ -724,16 +724,11 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
|
|
|
724
724
|
collisionPadding={8}
|
|
725
725
|
onOpenAutoFocus={(e) => {
|
|
726
726
|
e.preventDefault();
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
(typeof window.innerWidth === "number" &&
|
|
733
|
-
window.innerWidth < 640));
|
|
734
|
-
if (!isTouchOrMobile) {
|
|
735
|
-
searchInputRef.current?.focus();
|
|
736
|
-
}
|
|
727
|
+
searchInputRef.current?.focus();
|
|
728
|
+
}}
|
|
729
|
+
onFocusOutside={(e) => {
|
|
730
|
+
// Ngăn Dialog / Sheet cha giật focus khỏi ô tìm kiếm của Popover portal
|
|
731
|
+
e.preventDefault();
|
|
737
732
|
}}
|
|
738
733
|
className="z-[100] flex w-[var(--radix-popover-trigger-width)] min-w-[15rem] max-w-[calc(100vw-1rem)] flex-col overflow-hidden rounded-md border bg-popover/95 p-0 text-popover-foreground shadow-lg shadow-black/5 backdrop-blur-md"
|
|
739
734
|
style={{
|
|
@@ -25,10 +25,12 @@ export function HorizontalLayoutHeader({
|
|
|
25
25
|
dictionary,
|
|
26
26
|
navigation,
|
|
27
27
|
notificationSlot,
|
|
28
|
+
user,
|
|
28
29
|
}: {
|
|
29
30
|
dictionary: DictionaryType;
|
|
30
31
|
navigation?: NavigationType[];
|
|
31
32
|
notificationSlot?: ReactNode;
|
|
33
|
+
user?: any;
|
|
32
34
|
}) {
|
|
33
35
|
const params = useParams();
|
|
34
36
|
const locale = params.lang as LocaleType;
|
|
@@ -78,7 +80,7 @@ export function HorizontalLayoutHeader({
|
|
|
78
80
|
{/* <ModeDropdown dictionary={dictionary} /> */}
|
|
79
81
|
{/* <LanguageDropdown dictionary={dictionary} /> */}
|
|
80
82
|
{/* User dropdown */}
|
|
81
|
-
<UserDropdown dictionary={dictionary} locale={locale} />
|
|
83
|
+
<UserDropdown dictionary={dictionary} locale={locale} user={user} />
|
|
82
84
|
</div>
|
|
83
85
|
</div>
|
|
84
86
|
<PageTabs dictionary={dictionary} />
|
|
@@ -17,6 +17,7 @@ interface HorizontalLayoutProps {
|
|
|
17
17
|
searchResults?: any[];
|
|
18
18
|
searchLoading?: boolean;
|
|
19
19
|
notificationSlot?: ReactNode;
|
|
20
|
+
user?: any;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export function HorizontalLayout({
|
|
@@ -27,6 +28,7 @@ export function HorizontalLayout({
|
|
|
27
28
|
searchResults,
|
|
28
29
|
searchLoading,
|
|
29
30
|
notificationSlot,
|
|
31
|
+
user,
|
|
30
32
|
}: HorizontalLayoutProps) {
|
|
31
33
|
return (
|
|
32
34
|
<SidebarProvider>
|
|
@@ -42,6 +44,7 @@ export function HorizontalLayout({
|
|
|
42
44
|
dictionary={dictionary}
|
|
43
45
|
navigation={navigation}
|
|
44
46
|
notificationSlot={notificationSlot}
|
|
47
|
+
user={user}
|
|
45
48
|
/>
|
|
46
49
|
<main className="w-full mx-auto flex-1 min-h-0 px-4 md:px-8 py-4">
|
|
47
50
|
{children}
|
|
@@ -21,6 +21,11 @@ interface LayoutProps {
|
|
|
21
21
|
* dropdown tĩnh cũ (backward-compat cho consumer chưa nối dữ liệu).
|
|
22
22
|
*/
|
|
23
23
|
notificationSlot?: ReactNode;
|
|
24
|
+
/**
|
|
25
|
+
* Thông tin user đăng nhập (truyền từ Server Component session để triệt tiêu
|
|
26
|
+
* hoàn toàn độ trễ / hydration mismatch trên AvatarFallback ở header).
|
|
27
|
+
*/
|
|
28
|
+
user?: any;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
export function MainLayout({
|
|
@@ -31,6 +36,7 @@ export function MainLayout({
|
|
|
31
36
|
searchResults,
|
|
32
37
|
searchLoading,
|
|
33
38
|
notificationSlot,
|
|
39
|
+
user,
|
|
34
40
|
}: LayoutProps) {
|
|
35
41
|
const mounted = useMounted();
|
|
36
42
|
const isVertical = useIsVertical();
|
|
@@ -48,6 +54,7 @@ export function MainLayout({
|
|
|
48
54
|
searchResults={searchResults}
|
|
49
55
|
searchLoading={searchLoading}
|
|
50
56
|
notificationSlot={notificationSlot}
|
|
57
|
+
user={user}
|
|
51
58
|
>
|
|
52
59
|
{children}
|
|
53
60
|
</VerticalLayout>
|
|
@@ -59,6 +66,7 @@ export function MainLayout({
|
|
|
59
66
|
searchResults={searchResults}
|
|
60
67
|
searchLoading={searchLoading}
|
|
61
68
|
notificationSlot={notificationSlot}
|
|
69
|
+
user={user}
|
|
62
70
|
>
|
|
63
71
|
{children}
|
|
64
72
|
</HorizontalLayout>
|
|
@@ -93,8 +93,11 @@ export function UserDropdown({
|
|
|
93
93
|
alt={user?.name || ""}
|
|
94
94
|
className="object-cover"
|
|
95
95
|
/>
|
|
96
|
-
<AvatarFallback
|
|
97
|
-
|
|
96
|
+
<AvatarFallback
|
|
97
|
+
className="bg-primary text-white text-[10px] font-bold uppercase"
|
|
98
|
+
suppressHydrationWarning
|
|
99
|
+
>
|
|
100
|
+
{user?.name ? getInitials(user.name) : null}
|
|
98
101
|
</AvatarFallback>
|
|
99
102
|
</Avatar>
|
|
100
103
|
</Button>
|
|
@@ -116,15 +119,21 @@ export function UserDropdown({
|
|
|
116
119
|
<DropdownMenuLabel className="flex gap-2 p-2">
|
|
117
120
|
<Avatar className="size-8 border border-border/20 shadow-sm">
|
|
118
121
|
<AvatarImage src={user?.avatar || undefined} alt="Avatar" />
|
|
119
|
-
<AvatarFallback className="bg-transparent">
|
|
120
|
-
{user?.name
|
|
122
|
+
<AvatarFallback className="bg-transparent" suppressHydrationWarning>
|
|
123
|
+
{user?.name ? getInitials(user.name) : null}
|
|
121
124
|
</AvatarFallback>
|
|
122
125
|
</Avatar>
|
|
123
126
|
<div className="flex flex-col min-w-0 space-y-0.5 overflow-hidden">
|
|
124
|
-
<p
|
|
127
|
+
<p
|
|
128
|
+
className="text-sm font-semibold text-foreground truncate leading-none"
|
|
129
|
+
suppressHydrationWarning
|
|
130
|
+
>
|
|
125
131
|
{user?.name}
|
|
126
132
|
</p>
|
|
127
|
-
<p
|
|
133
|
+
<p
|
|
134
|
+
className="text-[11px] text-muted-foreground truncate font-medium opacity-80 leading-none"
|
|
135
|
+
suppressHydrationWarning
|
|
136
|
+
>
|
|
128
137
|
{user?.email}
|
|
129
138
|
</p>
|
|
130
139
|
</div>
|
|
@@ -19,9 +19,11 @@ import { ToggleMobileSidebar } from "./toggle-mobile-sidebar";
|
|
|
19
19
|
export function VerticalLayoutHeader({
|
|
20
20
|
dictionary,
|
|
21
21
|
notificationSlot,
|
|
22
|
+
user,
|
|
22
23
|
}: {
|
|
23
24
|
dictionary: DictionaryType;
|
|
24
25
|
notificationSlot?: ReactNode;
|
|
26
|
+
user?: any;
|
|
25
27
|
}) {
|
|
26
28
|
const params = useParams();
|
|
27
29
|
const locale = params.lang as LocaleType;
|
|
@@ -67,7 +69,7 @@ export function VerticalLayoutHeader({
|
|
|
67
69
|
{/* <ModeDropdown dictionary={dictionary} /> */}
|
|
68
70
|
{/* <LanguageDropdown dictionary={dictionary} /> */}
|
|
69
71
|
{/* User dropdown */}
|
|
70
|
-
<UserDropdown dictionary={dictionary} locale={locale} />
|
|
72
|
+
<UserDropdown dictionary={dictionary} locale={locale} user={user} />
|
|
71
73
|
</div>
|
|
72
74
|
</div>
|
|
73
75
|
</header>
|
|
@@ -17,6 +17,7 @@ interface VerticalLayoutProps {
|
|
|
17
17
|
searchResults?: any[];
|
|
18
18
|
searchLoading?: boolean;
|
|
19
19
|
notificationSlot?: ReactNode;
|
|
20
|
+
user?: any;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export function VerticalLayout({
|
|
@@ -27,6 +28,7 @@ export function VerticalLayout({
|
|
|
27
28
|
searchResults,
|
|
28
29
|
searchLoading,
|
|
29
30
|
notificationSlot,
|
|
31
|
+
user,
|
|
30
32
|
}: VerticalLayoutProps) {
|
|
31
33
|
return (
|
|
32
34
|
<SidebarProvider>
|
|
@@ -41,6 +43,7 @@ export function VerticalLayout({
|
|
|
41
43
|
<VerticalLayoutHeader
|
|
42
44
|
dictionary={dictionary}
|
|
43
45
|
notificationSlot={notificationSlot}
|
|
46
|
+
user={user}
|
|
44
47
|
/>
|
|
45
48
|
{/* `h-dvh` ở SidebarInset (không phải `h-screen`): trên Safari/Chrome
|
|
46
49
|
mobile 100vh tính CẢ thanh URL co giãn, nên đáy trang — phân trang,
|
|
@@ -178,15 +178,11 @@ export function Combobox({
|
|
|
178
178
|
collisionPadding={8}
|
|
179
179
|
onOpenAutoFocus={(e) => {
|
|
180
180
|
e.preventDefault();
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
(typeof window.innerWidth === "number" && window.innerWidth < 640));
|
|
187
|
-
if (!isTouchOrMobile) {
|
|
188
|
-
searchInputRef.current?.focus();
|
|
189
|
-
}
|
|
181
|
+
searchInputRef.current?.focus();
|
|
182
|
+
}}
|
|
183
|
+
onFocusOutside={(e) => {
|
|
184
|
+
// Ngăn Dialog / Sheet cha giật focus khỏi ô tìm kiếm của Popover portal
|
|
185
|
+
e.preventDefault();
|
|
190
186
|
}}
|
|
191
187
|
className="z-[100] w-[var(--radix-popover-trigger-width)] min-w-[12rem] max-w-[calc(100vw-1rem)] p-0 flex flex-col overflow-hidden"
|
|
192
188
|
style={{
|