@goplusvn/core 0.1.11 → 0.1.13

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.
@@ -21,6 +21,8 @@ export * from "./sticky-layout";
21
21
  export * from "./prefetch-link";
22
22
  export * from "./dynamic-icon";
23
23
  export * from "./input-number";
24
+ export * from "./body-lock-guard";
25
+ export * from "./body-lock";
24
26
 
25
27
  // Feedback Client Components
26
28
  export * from "../feedback/sheet";
@@ -218,10 +218,16 @@ const Sidebar = React.forwardRef<
218
218
  }, [hoverExpandEnabled, setHoverOpen]);
219
219
 
220
220
  if (collapsible === "none") {
221
+ // A non-collapsing sidebar is permanently EXPANDED, so it must use the
222
+ // expanded surface (bg-background / text-foreground), NOT the deep
223
+ // `bg-sidebar` colour. On a coloured sidebar (e.g. the navy default) the
224
+ // old `bg-sidebar text-sidebar-foreground` left the group labels + active
225
+ // item — which are `text-primary` — at ~1.9:1 contrast (unreadable). This
226
+ // matches the expanded state of the collapsible sidebar (labels-on-light).
221
227
  return (
222
228
  <div
223
229
  className={cn(
224
- "flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
230
+ "flex h-full w-(--sidebar-width) flex-col border-r bg-background text-foreground",
225
231
  className,
226
232
  )}
227
233
  ref={ref}
@@ -294,7 +300,7 @@ const Sidebar = React.forwardRef<
294
300
  : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
295
301
  // Adjustments for collapsible=icon
296
302
  variant === "floating" || variant === "inset"
297
- ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_8px)] group-data-[collapsible=icon]:px-2.5"
303
+ ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_8px)] group-data-[collapsible=icon]:px-1"
298
304
  : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[collapsible=icon]:border-r group-data-[collapsible=icon]:px-0",
299
305
  // Hover expand - override icon width to full width on hover
300
306
  "group-data-[hover-expanded=true]:!w-(--sidebar-width) group-data-[hover-expanded=true]:shadow-xl group-data-[hover-expanded=true]:z-[100]",
@@ -310,7 +316,12 @@ const Sidebar = React.forwardRef<
310
316
  "group-data-[state=collapsed]:bg-sidebar group-data-[state=collapsed]:border-border/50",
311
317
  "group-data-[state=expanded]:bg-background group-data-[state=expanded]:border-border/50",
312
318
  "group-data-[hover-expanded=true]:!bg-background group-data-[hover-expanded=true]:!border-border/50",
313
- "group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow",
319
+ // Floating & inset: the sidebar is a soft floating panel. Round
320
+ // ALL corners (rounded-xl) so the top-right corner where it meets
321
+ // the header reads as a soft curve, not a hard 90° junction. Inset
322
+ // previously had no radius (sharp navy block) — now matches.
323
+ "group-data-[variant=floating]:rounded-xl group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow",
324
+ "group-data-[variant=inset]:rounded-xl group-data-[variant=inset]:border group-data-[variant=inset]:border-sidebar-border group-data-[variant=inset]:shadow",
314
325
  )}
315
326
  >
316
327
  {children}
@@ -386,7 +397,17 @@ const SidebarInset = React.forwardRef<
386
397
  ref={ref}
387
398
  className={cn(
388
399
  "relative flex min-h-svh flex-1 flex-col bg-muted/40 transition-[margin] duration-200 ease-linear",
389
- "peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
400
+ // Inset: content is a card floating with a uniform m-2 gap on ALL sides
401
+ // (no ml-0 override) so there's a small gap between the sidebar and the
402
+ // content/header instead of them touching.
403
+ "peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
404
+ // Floating: the content is full-bleed (no card), and its LEFT edge is
405
+ // the side that meets the floating sidebar. Give it a small left gap
406
+ // (ml-2) so it doesn't touch the sidebar, and round both left corners
407
+ // (top-left clips the sticky header, bottom-left clips the footer via
408
+ // overflow-hidden) so the content hugs the floating sidebar with soft
409
+ // curves instead of hard 90° corners. Right edge stays flush.
410
+ "md:peer-data-[variant=floating]:ml-2 md:peer-data-[variant=floating]:rounded-l-xl",
390
411
  className,
391
412
  )}
392
413
  {...props}
@@ -2,28 +2,21 @@
2
2
 
3
3
  import * as React from "react";
4
4
 
5
+ import { releaseStuckBodyLock } from "./body-lock";
6
+
5
7
  /**
6
- * Lưới an toàn cho "lỗi conflict event kinh điển": Radix (Dialog/AlertDialog/Sheet
7
- * + Select/Combobox/Popover lồng bên trong) đôi khi để sót `pointer-events: none`
8
- * / scroll-lock trên <body> sau khi đóng, khiến TOÀN trang không click được.
8
+ * Gọi trong *Content của mỗi primitive overlay (Dialog/Sheet/AlertDialog…). Content chỉ
9
+ * mount khi overlay mở; khi nó unmount (đã đóng) ta gỡ khoá body NẾU còn sót nhưng chỉ
10
+ * khi không còn modal nào đang mở (xem {@link releaseStuckBodyLock}).
9
11
  *
10
- * Gọi hook này trong *Content của mỗi primitive overlay. Content chỉ mount khi
11
- * overlay mở; khi unmount (đã đóng) ta gỡ khoá — nhưng CHỈ khi không còn lớp
12
- * dialog/popover nào đang mở, để không phá modal khác đang chồng lên.
12
+ * Đây lớp gỡ khoá TỨC THÌ tại thời điểm đóng; {@link BodyLockGuard} là lưới an toàn
13
+ * toàn cục bao quát các trường hợp còn lại.
13
14
  */
14
15
  export function useReleaseStuckBodyLock() {
15
16
  React.useEffect(() => {
16
17
  return () => {
17
- setTimeout(() => {
18
- const hasOpenLayer = document.querySelector(
19
- '[role="dialog"][data-state="open"], [role="alertdialog"][data-state="open"], [data-radix-popper-content-wrapper]',
20
- );
21
- if (!hasOpenLayer && document.body.style.pointerEvents === "none") {
22
- document.body.style.pointerEvents = "";
23
- document.body.style.overflow = "";
24
- document.body.removeAttribute("data-scroll-locked");
25
- }
26
- }, 0);
18
+ // Đợi một nhịp để Radix chạy xong phần dọn dẹp đồng bộ của nó trước.
19
+ setTimeout(releaseStuckBodyLock, 0);
27
20
  };
28
21
  }, []);
29
22
  }