@djangocfg/ui-core 2.1.528 → 2.1.531

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-core",
3
- "version": "2.1.528",
3
+ "version": "2.1.531",
4
4
  "description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
5
5
  "keywords": [
6
6
  "ui-components",
@@ -128,7 +128,7 @@
128
128
  "check:contrast": "node scripts/check-preset-contrast.mjs"
129
129
  },
130
130
  "peerDependencies": {
131
- "@djangocfg/i18n": "^2.1.528",
131
+ "@djangocfg/i18n": "^2.1.531",
132
132
  "consola": "^3.4.2",
133
133
  "lucide-react": "^0.545.0",
134
134
  "moment": "^2.30.1",
@@ -206,8 +206,8 @@
206
206
  "@chenglou/pretext": "^0.0.8"
207
207
  },
208
208
  "devDependencies": {
209
- "@djangocfg/i18n": "^2.1.528",
210
- "@djangocfg/typescript-config": "^2.1.528",
209
+ "@djangocfg/i18n": "^2.1.531",
210
+ "@djangocfg/typescript-config": "^2.1.531",
211
211
  "@types/node": "^24.13.3",
212
212
  "@types/react": "19.2.15",
213
213
  "@types/react-dom": "19.2.3",
@@ -80,7 +80,11 @@ const DialogContent = React.forwardRef<
80
80
  ? "left-0 top-0 grid grid-rows-[minmax(0,1fr)] h-[100dvh] w-screen max-w-none gap-0 border-0 p-0 shadow-none data-[state=closed]:zoom-out-[0.98] data-[state=open]:zoom-in-[0.98]"
81
81
  // Rounded on ALL sizes — the radius used to be `sm:`-gated, which left
82
82
  // phone dialogs with hard square corners. Centered, soft-cornered card.
83
- : "left-1/2 top-1/2 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 rounded-[var(--radius-dialog)] border p-6 shadow-lg data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
83
+ // Width by MEASUREMENT, not breakpoint: `min()` keeps the card off
84
+ // both edges at any viewport. A `sm:` gate reads the window, so a
85
+ // narrow desktop pane lost its margins and rendered as a sheet with
86
+ // its rounding cropped.
87
+ : "left-1/2 top-1/2 grid w-[min(32rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-[var(--radius-dialog)] border p-6 shadow-lg data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
84
88
  className
85
89
  )}
86
90
  {...props}
@@ -105,7 +109,9 @@ const DialogHeader = ({
105
109
  }: React.HTMLAttributes<HTMLDivElement>) => (
106
110
  <div
107
111
  className={cn(
108
- "flex flex-col space-y-1.5 text-center sm:text-left",
112
+ // Start-aligned at every width: the `sm:` gate centred the title in a
113
+ // narrow pane only, so one dialog read as two different components.
114
+ "flex flex-col space-y-1.5 text-start",
109
115
  className
110
116
  )}
111
117
  {...props}
@@ -119,7 +125,10 @@ const DialogFooter = ({
119
125
  }: React.HTMLAttributes<HTMLDivElement>) => (
120
126
  <div
121
127
  className={cn(
122
- "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
128
+ // One row, buttons sized to their labels. The `sm:`-gated stack read the
129
+ // window, so a narrow pane got two full-bleed buttons — Cancel as loud as
130
+ // Confirm. `flex-wrap` handles a long label by measurement instead.
131
+ "flex flex-wrap items-center justify-end gap-2",
123
132
  className
124
133
  )}
125
134
  {...props}
@@ -97,7 +97,11 @@ const verticalSizePx: Record<DrawerSize, number> = {
97
97
  };
98
98
 
99
99
  const directionStyles = {
100
- bottom: "inset-x-0 bottom-0 mt-24 rounded-t-lg border-t",
100
+ // `max-h-[85dvh]` belongs to the panel, not to a caller: with `height:auto`
101
+ // a tall panel would otherwise run off the top of a phone. A caller's own
102
+ // `max-h` cannot do this job — as a flex column the panel FILLS it, so the
103
+ // cap becomes a fixed height around short content.
104
+ bottom: "inset-x-0 bottom-0 mt-24 max-h-[85dvh] rounded-t-lg border-t",
101
105
  top: "inset-x-0 top-0 mb-24 rounded-b-lg border-b",
102
106
  right: "inset-y-0 right-0 h-full border-l",
103
107
  left: "inset-y-0 left-0 h-full border-r",
@@ -297,7 +301,25 @@ const DrawerContent = React.forwardRef<
297
301
  />
298
302
  )}
299
303
  {isVertical && <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />}
300
- {children}
304
+ {/*
305
+ Content padding lives HERE, not on each caller. `DialogContent` has
306
+ its own; this did not, so every surface that renders as a Dialog on
307
+ desktop and a Drawer on a phone lost its inset at the breakpoint —
308
+ section headings and values sat flush against the panel edge and read
309
+ as clipped. A default each caller must remember is a default nobody
310
+ applies.
311
+
312
+ The bottom inset is larger than the sides on purpose: it is what tells
313
+ a reader whether the list has ended or continues under the edge, and
314
+ on a phone it also clears the home indicator.
315
+
316
+ It owns the SCROLL but never the height: `flex-1` here filled the
317
+ panel and left the last row hundreds of pixels above the edge. Only
318
+ the panel's own `max-h` decides how tall the drawer gets.
319
+ */}
320
+ <div className="min-h-0 overflow-y-auto overscroll-contain px-5 pb-[max(1.25rem,env(safe-area-inset-bottom))]">
321
+ {children}
322
+ </div>
301
323
  </DrawerPrimitive.Content>
302
324
  </DrawerPortal>
303
325
  );
@@ -10,6 +10,7 @@
10
10
  import * as React from 'react';
11
11
 
12
12
  import { useIsMobile } from '../../../hooks';
13
+ import { cn } from '../../../lib/utils';
13
14
 
14
15
  import {
15
16
  Dialog,
@@ -74,20 +75,58 @@ ResponsiveSheet.displayName = "ResponsiveSheet"
74
75
  // Content
75
76
  // ─────────────────────────────────────────────────────────────────────────────
76
77
 
78
+ /** Reading width of the panel. `md` suits a form or a detail card. */
79
+ type ResponsiveSheetWidth = "sm" | "md" | "lg" | "xl";
80
+
81
+ const WIDTH: Record<ResponsiveSheetWidth, string> = {
82
+ sm: "sm:max-w-sm",
83
+ md: "sm:max-w-md",
84
+ lg: "sm:max-w-lg",
85
+ xl: "sm:max-w-xl",
86
+ };
87
+
77
88
  interface ResponsiveSheetContentProps {
78
89
  children: React.ReactNode;
79
90
  className?: string;
91
+ /** Panel width on a wide viewport. A phone always takes the full width. */
92
+ width?: ResponsiveSheetWidth;
80
93
  }
81
94
 
82
- function ResponsiveSheetContent({ children, className }: ResponsiveSheetContentProps) {
95
+ /**
96
+ * The panel.
97
+ *
98
+ * It arrives sized, inset and centred — a caller passes CONTENT, not a layout.
99
+ * Every default here exists because a caller had to supply it by hand and the
100
+ * two callers we had disagreed: the same `w-[calc(100%-1rem)]` twice, one of
101
+ * them with a `flex flex-col` that made a short card stand full height.
102
+ *
103
+ * The phone case is where this matters. `DialogContent` carries its own
104
+ * padding and centring; `DrawerContent` does not, so anything that renders as
105
+ * both lost its layout at the breakpoint and nowhere else.
106
+ */
107
+ function ResponsiveSheetContent({
108
+ children,
109
+ className,
110
+ width = "md",
111
+ }: ResponsiveSheetContentProps) {
83
112
  const { isMobile } = React.useContext(ResponsiveSheetContext);
84
113
 
85
114
  if (isMobile) {
86
- // Preserve legacy content-hugging behavior; new Drawer default is `md`.
87
- return <DrawerContent className={className} height="auto">{children}</DrawerContent>;
115
+ // `height="auto"` hugs the content; the drawer's own `max-h` is the
116
+ // ceiling. A cap passed from here would become a fixed height instead,
117
+ // because the panel is a flex column and fills what it is given.
118
+ return (
119
+ <DrawerContent className={className} height="auto">
120
+ {children}
121
+ </DrawerContent>
122
+ );
88
123
  }
89
124
 
90
- return <DialogContent className={className}>{children}</DialogContent>;
125
+ return (
126
+ <DialogContent className={cn("w-[calc(100%-1rem)]", WIDTH[width], className)}>
127
+ {children}
128
+ </DialogContent>
129
+ );
91
130
  }
92
131
  ResponsiveSheetContent.displayName = "ResponsiveSheetContent"
93
132