@goplusvn/core 0.1.12 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,66 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.13 — Customizer: make radius, density & inset/floating actually apply
4
+
5
+ The Customizer exposed controls that stored a value but changed nothing on
6
+ screen. Wired them up at the source so every app using core gets them working:
7
+
8
+ - **Radius ("Bo góc") now applies.** `SettingsProvider` drives the real
9
+ `--radius` token from `settings.radius`; Tailwind's `@theme inline` derives
10
+ `rounded-sm/md/lg/xl` from it, so the whole UI re-rounds live (0 → square,
11
+ 1rem → fully rounded). Previously nothing read `settings.radius`.
12
+ - **Density ("Mật độ") now applies.** `SettingsProvider` sets
13
+ `data-density="compact"` on `<html>`; `base.css` tightens the global
14
+ `--spacing` scale (0.25rem → 0.215rem) so every spacing utility packs in
15
+ compact mode. Previously `useDensity()` was read nowhere.
16
+ - **Inset / floating sidebar no longer renders a harsh frame.** Core's default
17
+ sidebar is deep navy, and shadcn's inset/floating paints the page frame with
18
+ that sidebar colour — reading as a broken-looking block. The frame is now the
19
+ soft `--muted` neutral, so the content floats as a card on a calm page
20
+ (adapts to dark mode; the rounded-xl + shadow core already applies stay).
21
+ - **Inset / floating collapsed rail no longer looks squeezed.** In icon mode the
22
+ floating/inset content box is `--sidebar-width-icon + 8px` (56px) but had
23
+ `px-2.5` padding, leaving the pill only 36px — narrower than the default 48px
24
+ rail. Reduced to `px-1` so the pill is a comfortable 48px with a 4px floating
25
+ gutter.
26
+ - **Inset / floating sidebar corners are softened.** The floating panel was
27
+ `rounded-lg` and the inset panel had NO radius (a sharp navy block whose
28
+ top-right corner met the header as a hard 90°). Both now use `rounded-xl`
29
+ (matching the inset content card) so the sidebar reads as a soft floating
30
+ panel — the header/sidebar junction is a gentle curve, not a rough corner.
31
+ The floating content area also rounds both left corners
32
+ (`rounded-l-xl`, clipping the sticky header + footer via overflow-hidden) so
33
+ its whole left edge hugs the floating sidebar with soft curves; the right
34
+ edge stays flush to the viewport (inset already rounds via its `rounded-xl`
35
+ card). Finally, the header's own BOTTOM-left corner is rounded in both
36
+ variants (base.css: `[data-variant=floating|inset] ~ main > header`), so its
37
+ divider curves into the left edge instead of ending in a hard inner corner.
38
+ And the content no longer touches the sidebar: inset drops its `ml-0` (so the
39
+ uniform `m-2` leaves a left gap) and floating gains `ml-2`, giving a small
40
+ gap between the sidebar and the content/header.
41
+ - **"Ẩn ngoài" (`collapsible="offcanvas"`) can finally hide the sidebar.**
42
+ `AppSidebar` force-remapped `offcanvas → icon`, so the option collapsed to an
43
+ icon rail instead of hiding — identical to "Biểu tượng" and impossible to fully
44
+ hide the sidebar. The choice is now honoured as-is: offcanvas slides the
45
+ sidebar off-screen (reopen via the header toggle), icon → rail.
46
+ - **Dropped the "none" (Tắt) collapsible option from the Customizer.** Locking
47
+ the sidebar expanded made the header collapse-toggle a no-op — a
48
+ conflicting/dead control. The picker now offers only offcanvas + icon, both of
49
+ which the toggle actually drives. (`none` remains a valid type for any code
50
+ that sets it directly; it's just no longer offered in the UI.)
51
+ - **Dropped the "Bố cục" (layout: horizontal/vertical) control from the
52
+ Customizer.** The horizontal layout swaps the whole nav for a top menubar,
53
+ which turns every sidebar option ("Kiểu thanh bên", "Thu gọn thanh bên") into
54
+ a no-op — the same dead-control problem. The customizer now locks to the
55
+ vertical sidebar the apps are designed around (`defaultSettings.layout` stays
56
+ `vertical`; `HorizontalLayout` is untouched for code that sets it directly).
57
+ - **Non-collapsing sidebar (`collapsible="none"`) is now readable.** It rendered
58
+ `bg-sidebar text-sidebar-foreground` (the deep navy), but the group labels +
59
+ active item are `text-primary`, giving ~1.9:1 contrast (unreadable) on that
60
+ navy. A non-collapsing sidebar is permanently *expanded*, so it now uses the
61
+ expanded surface (`bg-background` / `text-foreground`), matching the rest of
62
+ the design (labels-on-light) — contrast jumps to ~9:1.
63
+
3
64
  ## 0.1.11 — print: force light colours on the print surface
4
65
 
5
66
  - **Fix faded print output in dark mode.** Print pages render under the app's
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@goplusvn/core",
3
3
  "description": "GoPlusVN Platform Kit - ERP kernel: layout, RBAC, CRUD, multi-tenant, system pages",
4
- "version": "0.1.12",
4
+ "version": "0.1.13",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "registry": "https://registry.npmjs.org",
@@ -76,6 +76,25 @@ export function SettingsProvider({
76
76
  setSettings(defaultSettings);
77
77
  }, [deleteStoredSettings]);
78
78
 
79
+ // Apply the appearance choices the Customizer exposes but that nothing else
80
+ // wires up. Without this, the "Radius" and "Density" controls store a value
81
+ // yet change nothing on screen (dead controls in every app using core).
82
+ // - radius → drive the real `--radius` token. Tailwind's @theme inline
83
+ // derives rounded-sm/md/lg/xl from it (see styles/base.css), so the whole
84
+ // UI re-rounds live. The Customizer stores rem (0, 0.3, 0.5, 0.75, 1).
85
+ // - density → expose as a `data-density` attribute; base.css tightens the
86
+ // global `--spacing` scale in compact mode ([data-density="compact"]).
87
+ // Client-only (useEffect) so there's no SSR/hydration mismatch.
88
+ useEffect(() => {
89
+ const root = document.documentElement;
90
+ root.style.setProperty("--radius", `${settings.radius ?? 0.5}rem`);
91
+ if (settings.density === "compact") {
92
+ root.setAttribute("data-density", "compact");
93
+ } else {
94
+ root.removeAttribute("data-density");
95
+ }
96
+ }, [settings.radius, settings.density]);
97
+
79
98
  return (
80
99
  <SettingsContext.Provider
81
100
  value={{ settings, updateSettings, resetSettings }}
@@ -582,4 +582,45 @@ aside.EmojiPickerReact {
582
582
  .dark .custom-scrollbar::-webkit-scrollbar-thumb:hover {
583
583
  background: #64748b;
584
584
  /* slate-500 */
585
+ }
586
+
587
+ /* ============================================================================
588
+ * Customizer: Density ("Comfortable" vs "Compact")
589
+ * SettingsProvider sets data-density="compact" on <html> from settings.density.
590
+ * Tailwind v4 derives EVERY spacing utility (p-/px-/gap-/space-/h-/w-/size-…)
591
+ * from the single `--spacing` token (default 0.25rem). Tightening it here packs
592
+ * the whole UI from one lever — tables, forms, cards, the sidebar — instead of
593
+ * per-component edits. 0.215rem ≈ 14% tighter: denser, still comfortably hit.
594
+ * ========================================================================== */
595
+ [data-density="compact"] {
596
+ --spacing: 0.215rem;
597
+ }
598
+
599
+ /* ============================================================================
600
+ * Customizer: Sidebar variant "inset" / "floating" — frame colour.
601
+ * Core's default sidebar is a deep navy (--sidebar-background), and the shadcn
602
+ * inset/floating treatment paints the page frame with that same sidebar colour
603
+ * (has-[[data-variant=inset]]:bg-sidebar). On the navy default that reads as a
604
+ * harsh, broken-looking block. Repaint the frame with the soft neutral so the
605
+ * content reads as a CARD FLOATING ON A CALM PAGE (the rounded-xl + shadow core
606
+ * already applies do the floating). Token-based → adapts to dark mode.
607
+ * Un-layered → wins over the Tailwind `bg-sidebar` utility (in @layer utilities)
608
+ * regardless of specificity.
609
+ * ========================================================================== */
610
+ .group\/sidebar-wrapper:has([data-variant="inset"]),
611
+ .group\/sidebar-wrapper:has([data-variant="floating"]) {
612
+ background-color: hsl(var(--muted));
613
+ }
614
+
615
+ /* ============================================================================
616
+ * Customizer: floating / inset — round the header's BOTTOM-left corner.
617
+ * The content panel already rounds its left corners (rounded-l-xl clips the
618
+ * sticky header's top-left) and the sidebar rounds its corners, but the
619
+ * header's divider still met the left edge as a hard inner corner. The layout
620
+ * header is the first child of the SidebarInset <main>, a sibling after the
621
+ * sidebar peer (which carries data-variant). 0.75rem = rounded-xl, matching.
622
+ * ========================================================================== */
623
+ [data-variant="floating"] ~ main > header,
624
+ [data-variant="inset"] ~ main > header {
625
+ border-bottom-left-radius: 0.75rem;
585
626
  }
@@ -5,8 +5,6 @@ import { useParams, usePathname, useRouter } from "next/navigation";
5
5
  import {
6
6
  AlignLeft,
7
7
  AlignRight,
8
- AlignStartHorizontal,
9
- AlignStartVertical,
10
8
  MoonStar,
11
9
  RotateCcw,
12
10
  Sun,
@@ -48,11 +46,10 @@ interface CustomizerProps {
48
46
  }
49
47
 
50
48
  const sidebarVariants: SidebarVariantType[] = ["sidebar", "floating", "inset"];
51
- const sidebarCollapsibleOptions: SidebarCollapsibleType[] = [
52
- "offcanvas",
53
- "icon",
54
- "none",
55
- ];
49
+ // "none" (lock the sidebar expanded) is intentionally NOT offered: it makes the
50
+ // header collapse-toggle a no-op, which reads as a broken/conflicting control.
51
+ // Both remaining options work with that toggle — offcanvas hides, icon → rail.
52
+ const sidebarCollapsibleOptions: SidebarCollapsibleType[] = ["offcanvas", "icon"];
56
53
  const densityOptions: DensityType[] = ["comfortable", "compact"];
57
54
 
58
55
  // Localized labels — the customizer follows the active URL locale (params.lang)
@@ -246,41 +243,14 @@ export function Customizer({ trigger, triggerClassName }: CustomizerProps) {
246
243
  <SunMoon className="shrink-0 h-4 w-4" />
247
244
  </Button>
248
245
  </div>
249
- <div className="space-y-1.5">
250
- <span className="text-sm">{t("layout")}</span>
251
- <div className="grid grid-cols-2 gap-2">
252
- <Button
253
- variant={
254
- settings.layout === "horizontal"
255
- ? "secondary"
256
- : "outline"
257
- }
258
- onClick={() =>
259
- updateSettings({
260
- ...settings,
261
- layout: "horizontal",
262
- })
263
- }
264
- >
265
- <AlignStartHorizontal className="shrink-0 h-4 w-4 me-2" />
266
- {t("horizontal")}
267
- </Button>
268
- <Button
269
- variant={
270
- settings.layout === "vertical" ? "secondary" : "outline"
271
- }
272
- onClick={() =>
273
- updateSettings({
274
- ...settings,
275
- layout: "vertical",
276
- })
277
- }
278
- >
279
- <AlignStartVertical className="shrink-0 h-4 w-4 me-2" />
280
- {t("vertical")}
281
- </Button>
282
- </div>
283
- </div>
246
+ {/* "Bố cục" (layout: horizontal/vertical) removed from the
247
+ picker. The horizontal layout swaps the whole nav for a top
248
+ menubar, which makes every sidebar option below ("Kiểu thanh
249
+ bên", "Thu gọn thanh bên") a no-op — dead/conflicting
250
+ controls. The apps here are designed around the vertical
251
+ sidebar, so the customizer locks to it (defaultSettings.layout
252
+ stays "vertical"; the HorizontalLayout code is untouched for
253
+ anything that sets layout directly). */}
284
254
 
285
255
  <div className="space-y-1.5">
286
256
  <span className="text-sm">{t("sidebarVariant")}</span>
@@ -185,11 +185,13 @@ export function AppSidebar({
185
185
  }
186
186
  };
187
187
 
188
- // Enforce icon mode for Carbon style consistency if not set explicitly to offcanvas by some override (though we prefer icon)
189
- const collapsibleMode =
190
- settings.sidebarCollapsible === "offcanvas"
191
- ? "icon"
192
- : settings.sidebarCollapsible || "icon";
188
+ // Honour the Customizer's "Thu gọn thanh bên" choice as-is:
189
+ // offcanvas collapses fully off-screen (hidden), reopened via header toggle
190
+ // icon → collapses to an icon rail
191
+ // none never collapses (always expanded)
192
+ // Previously offcanvas was force-remapped to icon, so "Ẩn ngoài" could never
193
+ // actually hide the sidebar (it behaved identically to "Biểu tượng").
194
+ const collapsibleMode = settings.sidebarCollapsible || "icon";
193
195
 
194
196
  return (
195
197
  <SidebarWrapper
@@ -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}