@djangocfg/ui-core 2.1.281 → 2.1.282
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/README.md +1 -0
- package/package.json +4 -4
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useBodyScrollLock.ts +114 -0
- package/src/styles/utilities.css +21 -0
package/README.md
CHANGED
|
@@ -76,6 +76,7 @@ Default **`TooltipContent`** styling uses semantic **popover** tokens (`bg-popov
|
|
|
76
76
|
| `useIsPhone()` | `< 640px` — phones only |
|
|
77
77
|
| `useIsMobile()` | `< 768px` — phones + small tablets |
|
|
78
78
|
| `useIsTabletOrBelow()` | `< 1024px` — phones + tablets |
|
|
79
|
+
| `useBodyScrollLock(locked)` | Lock body scroll while `locked=true`; counter-based (multi-consumer safe), iOS-safe via `position: fixed` fallback |
|
|
79
80
|
| `useCopy` | Copy to clipboard |
|
|
80
81
|
| `useCountdown` | Countdown timer |
|
|
81
82
|
| `useDebounce` | Debounce values |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.282",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"playground": "playground dev"
|
|
87
87
|
},
|
|
88
88
|
"peerDependencies": {
|
|
89
|
-
"@djangocfg/i18n": "^2.1.
|
|
89
|
+
"@djangocfg/i18n": "^2.1.282",
|
|
90
90
|
"consola": "^3.4.2",
|
|
91
91
|
"lucide-react": "^0.545.0",
|
|
92
92
|
"moment": "^2.30.1",
|
|
@@ -148,9 +148,9 @@
|
|
|
148
148
|
"vaul": "1.1.2"
|
|
149
149
|
},
|
|
150
150
|
"devDependencies": {
|
|
151
|
-
"@djangocfg/i18n": "^2.1.
|
|
151
|
+
"@djangocfg/i18n": "^2.1.282",
|
|
152
152
|
"@djangocfg/playground": "workspace:*",
|
|
153
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
153
|
+
"@djangocfg/typescript-config": "^2.1.282",
|
|
154
154
|
"@types/node": "^24.7.2",
|
|
155
155
|
"@types/react": "^19.1.0",
|
|
156
156
|
"@types/react-dom": "^19.1.0",
|
package/src/hooks/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { useDebounce } from './useDebounce';
|
|
|
12
12
|
export { useDebugTools } from './useDebugTools';
|
|
13
13
|
export { useEventListener, events } from './useEventsBus';
|
|
14
14
|
export { useIsMobile, useIsPhone, useIsTabletOrBelow, BREAKPOINTS } from './useMobile';
|
|
15
|
+
export { useBodyScrollLock } from './useBodyScrollLock';
|
|
15
16
|
export type { Breakpoint } from './useMobile';
|
|
16
17
|
export { useMediaQuery, BREAKPOINTS as MEDIA_BREAKPOINTS } from './useMediaQuery';
|
|
17
18
|
export { useCopy } from './useCopy';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
import { useDeviceDetect } from './useDeviceDetect';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* useBodyScrollLock — locks body scroll while `locked` is true.
|
|
9
|
+
*
|
|
10
|
+
* Safe to use from multiple components concurrently: a module-scope counter
|
|
11
|
+
* tracks active locks and only releases the lock when the last caller unmounts
|
|
12
|
+
* (or passes `locked=false`).
|
|
13
|
+
*
|
|
14
|
+
* Strategy:
|
|
15
|
+
* - iOS Safari: pin body in place with `position: fixed; top: -scrollY` and
|
|
16
|
+
* restore scroll on release. `overflow: hidden` alone is ignored on iOS.
|
|
17
|
+
* - Other browsers: `body.overflow = 'hidden'` plus a `paddingRight`
|
|
18
|
+
* compensator for the disappearing scrollbar so layout doesn't shift.
|
|
19
|
+
* We intentionally do NOT touch `html.overflow` — that removes the
|
|
20
|
+
* scrolling container for `position: sticky` descendants and breaks
|
|
21
|
+
* sticky navbars while the lock is active.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* useBodyScrollLock(isMobile && drawerOpen);
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
let lockCount = 0;
|
|
28
|
+
interface SavedState {
|
|
29
|
+
bodyOverflow: string;
|
|
30
|
+
bodyPaddingRight: string;
|
|
31
|
+
bodyPosition: string;
|
|
32
|
+
bodyTop: string;
|
|
33
|
+
bodyWidth: string;
|
|
34
|
+
scrollY: number;
|
|
35
|
+
iosMode: boolean;
|
|
36
|
+
}
|
|
37
|
+
let saved: SavedState | null = null;
|
|
38
|
+
|
|
39
|
+
function getScrollbarWidth(): number {
|
|
40
|
+
if (typeof window === 'undefined') return 0;
|
|
41
|
+
return window.innerWidth - document.documentElement.clientWidth;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function applyLock(iosMode: boolean) {
|
|
45
|
+
const body = document.body;
|
|
46
|
+
const scrollY = window.scrollY;
|
|
47
|
+
|
|
48
|
+
saved = {
|
|
49
|
+
bodyOverflow: body.style.overflow,
|
|
50
|
+
bodyPaddingRight: body.style.paddingRight,
|
|
51
|
+
bodyPosition: body.style.position,
|
|
52
|
+
bodyTop: body.style.top,
|
|
53
|
+
bodyWidth: body.style.width,
|
|
54
|
+
scrollY,
|
|
55
|
+
iosMode,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (iosMode) {
|
|
59
|
+
// iOS Safari ignores `overflow: hidden` on body — pin body in place and
|
|
60
|
+
// remember scroll position so we can restore it on release.
|
|
61
|
+
body.style.position = 'fixed';
|
|
62
|
+
body.style.top = `-${scrollY}px`;
|
|
63
|
+
body.style.width = '100%';
|
|
64
|
+
body.style.overflow = 'hidden';
|
|
65
|
+
} else {
|
|
66
|
+
// Avoid touching `html.overflow`: it breaks `position: sticky` ancestors
|
|
67
|
+
// (they lose their scrolling container and fall back to static flow).
|
|
68
|
+
const scrollbarWidth = getScrollbarWidth();
|
|
69
|
+
if (scrollbarWidth > 0) {
|
|
70
|
+
const current = parseFloat(window.getComputedStyle(body).paddingRight) || 0;
|
|
71
|
+
body.style.paddingRight = `${current + scrollbarWidth}px`;
|
|
72
|
+
}
|
|
73
|
+
body.style.overflow = 'hidden';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function releaseLock() {
|
|
78
|
+
if (!saved) return;
|
|
79
|
+
const body = document.body;
|
|
80
|
+
const { iosMode, scrollY } = saved;
|
|
81
|
+
|
|
82
|
+
body.style.overflow = saved.bodyOverflow;
|
|
83
|
+
body.style.paddingRight = saved.bodyPaddingRight;
|
|
84
|
+
body.style.position = saved.bodyPosition;
|
|
85
|
+
body.style.top = saved.bodyTop;
|
|
86
|
+
body.style.width = saved.bodyWidth;
|
|
87
|
+
|
|
88
|
+
if (iosMode) {
|
|
89
|
+
window.scrollTo(0, scrollY);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
saved = null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function useBodyScrollLock(locked: boolean): void {
|
|
96
|
+
const device = useDeviceDetect();
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (!locked) return;
|
|
100
|
+
if (typeof document === 'undefined') return;
|
|
101
|
+
|
|
102
|
+
if (lockCount === 0) {
|
|
103
|
+
applyLock(device.isIOS);
|
|
104
|
+
}
|
|
105
|
+
lockCount += 1;
|
|
106
|
+
|
|
107
|
+
return () => {
|
|
108
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
109
|
+
if (lockCount === 0) {
|
|
110
|
+
releaseLock();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}, [locked, device.isIOS]);
|
|
114
|
+
}
|
package/src/styles/utilities.css
CHANGED
|
@@ -4,6 +4,27 @@
|
|
|
4
4
|
* Compatible with Tailwind CSS v4
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Display font utility — for marketing headlines and large numeric stats.
|
|
9
|
+
*
|
|
10
|
+
* The font itself is NOT shipped here. Apps pick their own display font and
|
|
11
|
+
* expose it as `--font-display` on <html> (via `next/font`, `@font-face`, or
|
|
12
|
+
* equivalent). If the variable is missing, falls back to the system UI stack.
|
|
13
|
+
*
|
|
14
|
+
* @example (next/font in an app layout)
|
|
15
|
+
* const display = Plus_Jakarta_Sans({ weight: ['700', '800'], variable: '--font-display' });
|
|
16
|
+
* <html className={display.variable}>
|
|
17
|
+
*
|
|
18
|
+
* @example (usage)
|
|
19
|
+
* <h1 className="font-display text-7xl font-black tracking-tight">Ship fast.</h1>
|
|
20
|
+
*/
|
|
21
|
+
.font-display {
|
|
22
|
+
font-family: var(--font-display), system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
23
|
+
font-feature-settings: 'ss01', 'ss03';
|
|
24
|
+
letter-spacing: -0.035em;
|
|
25
|
+
padding-bottom: 0.08em;
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
/**
|
|
8
29
|
* Screen Reader Only
|
|
9
30
|
* Hides content visually but keeps it accessible to screen readers
|