Conversation
gfazioli
commented
Jan 25, 2026
- feat(Window): add viewport and mixed unit constraints support
- 🚀 feat(Window): allow string units for dragBounds and add demos
- feat(Window): add container boundary constraints for non‑portal windows
- Extend min/max width/height props to accept px, vw, vh, and % values - Introduce convertToPixels helper with comprehensive tests - Add demos and stories for viewport‑based and mixed unit constraints - Update documentation to showcase new constraint options
- Extend WindowBounds to accept string values (px, vw, vh, %) - Convert string bounds to pixels in use-mantine-window - Add viewport, percentage, and mixed unit drag bounds demos and docs
- Reset default position to (0,0) and add default size in demos - Update docs to state windows cannot be dragged or resized outside container - Hook now clamps width/height to parent size and limits resize to container bounds - Adjust drag bounds and resize logic to respect container dimensions and withinPortal flag
There was a problem hiding this comment.
Pull request overview
This PR adds support for viewport units (vw, vh), percentages, and mixed unit types for window sizing and drag boundary constraints in the Mantine Window component.
Changes:
- Added
convertToPixelsutility function to handle multiple CSS unit types (px, vw, vh, %) - Enhanced
dragBounds,minWidth,maxWidth,minHeight, andmaxHeightto accept string units in addition to numbers - Improved container boundary constraints for non-portal windows during resize operations
- Added comprehensive documentation and demo examples for new features
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| package/src/lib/convert-to-pixels.ts | New utility function to convert CSS values (px, vw, vh, %) to pixels |
| package/src/lib/convert-to-pixels.test.ts | Comprehensive test suite for the conversion utility |
| package/src/hooks/use-mantine-window.ts | Updated clamping and bounds logic to use convertToPixels; added container constraints for non-portal resize |
| package/src/Window.tsx | Updated type definitions to accept string units for size and boundary constraints |
| package/src/Window.story.tsx | Added five new story examples demonstrating viewport and mixed unit features |
| docs/docs.mdx | Added documentation sections for viewport-based and percentage-based constraints |
| docs/demos/index.ts | Exported new demo components |
| docs/demos/Window.demo.withinPortal.tsx | Updated positioning and documentation for container boundary behavior |
| docs/demos/Window.demo.viewportDragBounds.tsx | New demo showing viewport-based drag boundaries |
| docs/demos/Window.demo.viewportConstraints.tsx | New demo showing viewport-based size constraints |
| docs/demos/Window.demo.percentageDragBounds.tsx | New demo showing percentage-based drag boundaries |
| docs/demos/Window.demo.mixedDragBounds.tsx | New demo showing mixed unit drag boundaries |
| docs/demos/Window.demo.mixedConstraints.tsx | New demo showing mixed unit size constraints |
Comments suppressed due to low confidence (1)
package/src/hooks/use-mantine-window.ts:431
- Container boundary constraints are inconsistently applied across resize directions. The topLeft, top, left, and bottomLeft cases don't have explicit container boundary checks after clamping, while topRight, right, bottomRight, bottom, and bottomLeft do.
For the topLeft case specifically, when the window position changes (newX and newY are adjusted), there's no verification that the new position doesn't push the window outside the container bounds. This could allow the window to extend beyond the container when resizing from these corners.
Consider adding container boundary validation for all resize directions to ensure consistent behavior, or add a comment explaining why certain directions don't need these checks.
switch (resizeDirection.current) {
case 'topLeft':
newWidth = clampWidth(resizeStart.current.width - deltaX);
newHeight = clampHeight(resizeStart.current.height - deltaY);
newX = resizeStart.current.posX + (resizeStart.current.width - newWidth);
newY = resizeStart.current.posY + (resizeStart.current.height - newHeight);
break;
case 'top':
newHeight = clampHeight(resizeStart.current.height - deltaY);
newY = resizeStart.current.posY + (resizeStart.current.height - newHeight);
break;
case 'topRight':
newWidth = clampWidth(resizeStart.current.width + deltaX);
// Limit width based on position and container
if (!withinPortal && newX + newWidth > containerMaxWidth) {
newWidth = containerMaxWidth - newX;
}
newHeight = clampHeight(resizeStart.current.height - deltaY);
newY = resizeStart.current.posY + (resizeStart.current.height - newHeight);
break;
case 'right':
newWidth = clampWidth(resizeStart.current.width + deltaX);
// Limit width based on position and container
if (!withinPortal && newX + newWidth > containerMaxWidth) {
newWidth = containerMaxWidth - newX;
}
break;
case 'bottomRight':
newWidth = clampWidth(resizeStart.current.width + deltaX);
// Limit width based on position and container
if (!withinPortal && newX + newWidth > containerMaxWidth) {
newWidth = containerMaxWidth - newX;
}
newHeight = clampHeight(resizeStart.current.height + deltaY);
// Limit height based on position and container
if (!withinPortal && newY + newHeight > containerMaxHeight) {
newHeight = containerMaxHeight - newY;
}
break;
case 'bottom':
newHeight = clampHeight(resizeStart.current.height + deltaY);
// Limit height based on position and container
if (!withinPortal && newY + newHeight > containerMaxHeight) {
newHeight = containerMaxHeight - newY;
}
break;
case 'bottomLeft':
newWidth = clampWidth(resizeStart.current.width - deltaX);
newHeight = clampHeight(resizeStart.current.height + deltaY);
// Limit height based on position and container
if (!withinPortal && newY + newHeight > containerMaxHeight) {
newHeight = containerMaxHeight - newY;
}
newX = resizeStart.current.posX + (resizeStart.current.width - newWidth);
break;
case 'left':
newWidth = clampWidth(resizeStart.current.width - deltaX);
newX = resizeStart.current.posX + (resizeStart.current.width - newWidth);
break;
…formed inputs - Add tests for negative values, NaN, Infinity, malformed strings, zero, large, and decimal inputs - Guard against non‑finite numbers in pixel, viewport, and percentage conversions - Ensure function returns undefined for invalid or non‑finite values, improving robustness
- Ensures callback updates when windowRef changes, preventing stale references.
…ort and container size - Use Mantine's useViewportSize and useResizeObserver to track container and viewport dimensions. - Memoize constraint conversions to avoid repeated calculations during drag/resize. - Simplify drag and resize bounds using memoized values. - Improve performance and correctness when window is in portal or not.
…ainer mode - Add bounds checks for containerMaxWidth/Height during resize - Ensure windows stay within container boundaries - Improves user experience by keeping windows visible and draggable only within the container.
…n useMantineWindow - Return undefined for percentage units when window is undefined (SSR compatibility). - Add tests verifying SSR behavior of convertToPixels. - Use local state for container dimensions to avoid flicker on first render. - Update constraints and bounds logic to use the new container state.
… and size - Update Window props to accept string units for width, height, x, y - Convert units to pixels on mount with SSR‑safe defaults - Use useMounted, useClickOutside, and useMergedRef for z‑index handling - Add data-mantine-window attribute for easier querying - Add demos, stories, and tests for viewport, percentage, and mixed units - Update docs to showcase new responsive examples
- Extract state handling to `useWindowState` - Extract constraints conversion to `useWindowConstraints` - Extract dimension tracking to `useWindowDimensions` - Extract drag handling to `useWindowDrag` - Extract resize handling to `useWindowResize` - Simplify `useMantineWindow` to orchestrate these hooks and improve readability.
- Eliminates unused icons and UI components - Reduces bundle size and improves build performance - Simplifies Window component logic
- Verify clampWidth respects min/max, container limits, and edge cases. - Verify clampHeight handles min/max, container limits, and edge cases. - Test applyDragBounds with explicit bounds, partial bounds, negative and decimal values. - Ensure viewport and container constraints work when dragBounds absent. - Confirm behavior for oversized windows, zero dimensions, and Infinity values.
- Introduce Window.demo.percentagePosition and Window.demo.percentageSize - Update docs to explain viewport vs. percentage references and best practices - Refactor existing demos to use useDisclosure and remove withinPortal - Add eslint-disable comment to console logs in callbacks demo
- Replace table with bullet points to clarify viewport, percentage, and pixel references - Explain how `withinPortal` affects percentage units - Improve readability and consistency with other documentation sections
- Percentages now return undefined when no reference size is provided, preventing misleading pixel values. - Updated tests to assert undefined for '50%' and '100%' without a reference. - Maintains consistent behavior across unit conversions.
…ocalStorage key - Add viewport fallback when container width/height are 0 to avoid NaN calculations. - Use clamp functions during resize to keep dimensions within constraints. - Ensure localStorage keys are always set correctly, fixing incorrect key usage.
- Replace Box wrapper with Button and useDisclosure for opening windows - Remove withinPortal to keep windows inline - Simplify useWindowResize calculations using adjusted values - Update localStorage keys to use volatile suffix when persistState is false
- Remove containerRef and windowRef from useEffect dependency array - Prevents unnecessary re‑runs when refs change - Improves performance and avoids stale closures in the hook
- Update Window prop docs to include viewport units and percentages - Extend convertToPixels to support '%' and return undefined when reference missing - Add detailed examples and supported units list