indas-ui 0.1.7 → 0.1.8
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.
|
@@ -64,7 +64,11 @@ const modalSizes = {
|
|
|
64
64
|
* @param breakpoint - Pixel width threshold (default 768)
|
|
65
65
|
*/
|
|
66
66
|
function useIsMobile(breakpoint = 768) {
|
|
67
|
-
|
|
67
|
+
return useMobileQuery(breakpoint) ?? false;
|
|
68
|
+
}
|
|
69
|
+
/** As useIsMobile, but null until the width has been read (the first render, and on the server). */
|
|
70
|
+
function useMobileQuery(breakpoint) {
|
|
71
|
+
const [isMobile, setIsMobile] = React.useState(null);
|
|
68
72
|
React.useEffect(() => {
|
|
69
73
|
const mq = window.matchMedia(`(max-width: ${breakpoint}px)`);
|
|
70
74
|
setIsMobile(mq.matches);
|
|
@@ -241,7 +245,22 @@ ModalOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
|
241
245
|
*/
|
|
242
246
|
const ModalContent = React.forwardRef(({ className, children, hideCloseButton, disableOutsideClick, size, style, 'aria-describedby': ariaDescribedBy, ...props }, ref) => {
|
|
243
247
|
const stableRef = useStableRef(ref);
|
|
244
|
-
|
|
248
|
+
// The page and the dialog are different trees, so switching between them remounts everything
|
|
249
|
+
// inside: a form half filled in was wiped when a tablet turned, or a window was dragged, across
|
|
250
|
+
// the breakpoint. So the layout follows the screen only while closed; open, it keeps the one it
|
|
251
|
+
// opened with, and catches up once closed. The content exists only while open, so its ref says.
|
|
252
|
+
const live = useMobileQuery(768);
|
|
253
|
+
const [open, setOpen] = React.useState(false);
|
|
254
|
+
const [held, setHeld] = React.useState(null);
|
|
255
|
+
// The first reading always counts: before it the width is unknown, and a modal open at page load
|
|
256
|
+
// on a phone must still become the page.
|
|
257
|
+
if (live !== null && held !== live && (held === null || !open))
|
|
258
|
+
setHeld(live);
|
|
259
|
+
const isMobile = held ?? false;
|
|
260
|
+
const contentRef = React.useCallback((node) => {
|
|
261
|
+
stableRef(node);
|
|
262
|
+
setOpen(node !== null);
|
|
263
|
+
}, [stableRef]);
|
|
245
264
|
// Nested modals must paint above their parent. Radix portals every modal to
|
|
246
265
|
// <body>, so equal z-index makes stacking fall back to DOM order and the
|
|
247
266
|
// child renders behind / overlapping the parent.
|
|
@@ -256,12 +275,12 @@ const ModalContent = React.forwardRef(({ className, children, hideCloseButton, d
|
|
|
256
275
|
}, [hasOpenChild]);
|
|
257
276
|
// On mobile: render as a full-screen page
|
|
258
277
|
if (isMobile) {
|
|
259
|
-
return ((0, jsx_runtime_1.jsx)(DialogPrimitive.Portal, { children: (0, jsx_runtime_1.jsx)(DialogPrimitive.Content, { ref:
|
|
278
|
+
return ((0, jsx_runtime_1.jsx)(DialogPrimitive.Portal, { children: (0, jsx_runtime_1.jsx)(DialogPrimitive.Content, { ref: contentRef, "aria-describedby": ariaDescribedBy, style: { zIndex, ...style }, className: (0, utils_1.cn)("fixed inset-0 flex flex-col", "bg-[rgb(var(--bg-surface))]", className),
|
|
260
279
|
// Always block outside click on mobile — there's nothing "outside"
|
|
261
280
|
onInteractOutside: (e) => e.preventDefault(), onEscapeKeyDown: handleEscapeKeyDown, ...props, children: (0, jsx_runtime_1.jsx)(ModalMobileContext.Provider, { value: { isMobilePage: true, onClose: undefined }, children: children }) }) }));
|
|
262
281
|
}
|
|
263
282
|
// Desktop: unchanged Radix dialog
|
|
264
|
-
return ((0, jsx_runtime_1.jsxs)(ModalPortal, { children: [(0, jsx_runtime_1.jsx)(ModalOverlay, { style: { zIndex: zIndex - 1 } }), (0, jsx_runtime_1.jsxs)(DialogPrimitive.Content, { ref:
|
|
283
|
+
return ((0, jsx_runtime_1.jsxs)(ModalPortal, { children: [(0, jsx_runtime_1.jsx)(ModalOverlay, { style: { zIndex: zIndex - 1 } }), (0, jsx_runtime_1.jsxs)(DialogPrimitive.Content, { ref: contentRef, "aria-describedby": ariaDescribedBy, style: { zIndex, ...style }, className: (0, utils_1.cn)("fixed left-[50%] top-[50%] grid translate-x-[-50%] translate-y-[-50%]", "border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-surface))] shadow-lg", "rounded-lg sm:rounded-lg", size ? modalSizes[size] : "max-w-lg w-full mx-4 h-auto max-h-[85vh]",
|
|
265
284
|
// Gap + padding only for standard modals — fullscreen/master/p-0 modals
|
|
266
285
|
// run their own flush header/body/footer layout, so the gap shows as bands.
|
|
267
286
|
size !== 'master' && size !== 'fullscreen' && !className?.includes('p-0') && "gap-3 sm:gap-4 p-4 sm:p-6", className), onInteractOutside: (e) => {
|
|
@@ -25,7 +25,11 @@ const modalSizes = {
|
|
|
25
25
|
* @param breakpoint - Pixel width threshold (default 768)
|
|
26
26
|
*/
|
|
27
27
|
export function useIsMobile(breakpoint = 768) {
|
|
28
|
-
|
|
28
|
+
return useMobileQuery(breakpoint) ?? false;
|
|
29
|
+
}
|
|
30
|
+
/** As useIsMobile, but null until the width has been read (the first render, and on the server). */
|
|
31
|
+
function useMobileQuery(breakpoint) {
|
|
32
|
+
const [isMobile, setIsMobile] = React.useState(null);
|
|
29
33
|
React.useEffect(() => {
|
|
30
34
|
const mq = window.matchMedia(`(max-width: ${breakpoint}px)`);
|
|
31
35
|
setIsMobile(mq.matches);
|
|
@@ -198,7 +202,22 @@ ModalOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
|
198
202
|
*/
|
|
199
203
|
const ModalContent = React.forwardRef(({ className, children, hideCloseButton, disableOutsideClick, size, style, 'aria-describedby': ariaDescribedBy, ...props }, ref) => {
|
|
200
204
|
const stableRef = useStableRef(ref);
|
|
201
|
-
|
|
205
|
+
// The page and the dialog are different trees, so switching between them remounts everything
|
|
206
|
+
// inside: a form half filled in was wiped when a tablet turned, or a window was dragged, across
|
|
207
|
+
// the breakpoint. So the layout follows the screen only while closed; open, it keeps the one it
|
|
208
|
+
// opened with, and catches up once closed. The content exists only while open, so its ref says.
|
|
209
|
+
const live = useMobileQuery(768);
|
|
210
|
+
const [open, setOpen] = React.useState(false);
|
|
211
|
+
const [held, setHeld] = React.useState(null);
|
|
212
|
+
// The first reading always counts: before it the width is unknown, and a modal open at page load
|
|
213
|
+
// on a phone must still become the page.
|
|
214
|
+
if (live !== null && held !== live && (held === null || !open))
|
|
215
|
+
setHeld(live);
|
|
216
|
+
const isMobile = held ?? false;
|
|
217
|
+
const contentRef = React.useCallback((node) => {
|
|
218
|
+
stableRef(node);
|
|
219
|
+
setOpen(node !== null);
|
|
220
|
+
}, [stableRef]);
|
|
202
221
|
// Nested modals must paint above their parent. Radix portals every modal to
|
|
203
222
|
// <body>, so equal z-index makes stacking fall back to DOM order and the
|
|
204
223
|
// child renders behind / overlapping the parent.
|
|
@@ -213,12 +232,12 @@ const ModalContent = React.forwardRef(({ className, children, hideCloseButton, d
|
|
|
213
232
|
}, [hasOpenChild]);
|
|
214
233
|
// On mobile: render as a full-screen page
|
|
215
234
|
if (isMobile) {
|
|
216
|
-
return (_jsx(DialogPrimitive.Portal, { children: _jsx(DialogPrimitive.Content, { ref:
|
|
235
|
+
return (_jsx(DialogPrimitive.Portal, { children: _jsx(DialogPrimitive.Content, { ref: contentRef, "aria-describedby": ariaDescribedBy, style: { zIndex, ...style }, className: cn("fixed inset-0 flex flex-col", "bg-[rgb(var(--bg-surface))]", className),
|
|
217
236
|
// Always block outside click on mobile — there's nothing "outside"
|
|
218
237
|
onInteractOutside: (e) => e.preventDefault(), onEscapeKeyDown: handleEscapeKeyDown, ...props, children: _jsx(ModalMobileContext.Provider, { value: { isMobilePage: true, onClose: undefined }, children: children }) }) }));
|
|
219
238
|
}
|
|
220
239
|
// Desktop: unchanged Radix dialog
|
|
221
|
-
return (_jsxs(ModalPortal, { children: [_jsx(ModalOverlay, { style: { zIndex: zIndex - 1 } }), _jsxs(DialogPrimitive.Content, { ref:
|
|
240
|
+
return (_jsxs(ModalPortal, { children: [_jsx(ModalOverlay, { style: { zIndex: zIndex - 1 } }), _jsxs(DialogPrimitive.Content, { ref: contentRef, "aria-describedby": ariaDescribedBy, style: { zIndex, ...style }, className: cn("fixed left-[50%] top-[50%] grid translate-x-[-50%] translate-y-[-50%]", "border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-surface))] shadow-lg", "rounded-lg sm:rounded-lg", size ? modalSizes[size] : "max-w-lg w-full mx-4 h-auto max-h-[85vh]",
|
|
222
241
|
// Gap + padding only for standard modals — fullscreen/master/p-0 modals
|
|
223
242
|
// run their own flush header/body/footer layout, so the gap shows as bands.
|
|
224
243
|
size !== 'master' && size !== 'fullscreen' && !className?.includes('p-0') && "gap-3 sm:gap-4 p-4 sm:p-6", className), onInteractOutside: (e) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "indas-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Indas UI — React component library for business apps: Tailwind design tokens, Radix primitives, Lucide icons, DataGrid, dashboards and charts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Indas Analytics Pvt Ltd",
|