@djangocfg/ui-nextjs 2.1.9 → 2.1.14
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 +5 -4
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useDeviceDetect.ts +270 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-nextjs",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.14",
|
|
4
4
|
"description": "Next.js UI component library with Radix UI primitives, Tailwind CSS styling, charts, and form components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"check": "tsc --noEmit"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@djangocfg/api": "^2.1.
|
|
62
|
-
"@djangocfg/ui-core": "^2.1.
|
|
61
|
+
"@djangocfg/api": "^2.1.14",
|
|
62
|
+
"@djangocfg/ui-core": "^2.1.14",
|
|
63
63
|
"@types/react": "^19.1.0",
|
|
64
64
|
"@types/react-dom": "^19.1.0",
|
|
65
65
|
"consola": "^3.4.2",
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"next-themes": "^0.4.6",
|
|
93
93
|
"prism-react-renderer": "^2.4.1",
|
|
94
94
|
"react-chartjs-2": "^5.3.0",
|
|
95
|
+
"react-device-detect": "^2.2.3",
|
|
95
96
|
"react-hotkeys-hook": "^5.2.1",
|
|
96
97
|
"react-json-tree": "^0.20.0",
|
|
97
98
|
"react-lottie-player": "^2.1.0",
|
|
@@ -103,7 +104,7 @@
|
|
|
103
104
|
"vidstack": "next"
|
|
104
105
|
},
|
|
105
106
|
"devDependencies": {
|
|
106
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
107
|
+
"@djangocfg/typescript-config": "^2.1.14",
|
|
107
108
|
"@types/node": "^24.7.2",
|
|
108
109
|
"eslint": "^9.37.0",
|
|
109
110
|
"tailwindcss-animate": "1.0.7",
|
package/src/hooks/index.ts
CHANGED
|
@@ -22,3 +22,7 @@ export { useCfgRouter } from './useCfgRouter';
|
|
|
22
22
|
// Keyboard shortcuts
|
|
23
23
|
export { useHotkey, useHotkeysContext, HotkeysProvider, isHotkeyPressed } from './useHotkey';
|
|
24
24
|
export type { UseHotkeyOptions, HotkeyCallback, Keys } from './useHotkey';
|
|
25
|
+
|
|
26
|
+
// Device detection
|
|
27
|
+
export { useDeviceDetect } from './useDeviceDetect';
|
|
28
|
+
export type { DeviceDetectResult } from './useDeviceDetect';
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState, useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
// Safe defaults for SSR
|
|
6
|
+
const defaultSelectors = {
|
|
7
|
+
isMobile: false,
|
|
8
|
+
isTablet: false,
|
|
9
|
+
isDesktop: false,
|
|
10
|
+
isBrowser: false,
|
|
11
|
+
isMobileOnly: false,
|
|
12
|
+
isSmartTV: false,
|
|
13
|
+
isConsole: false,
|
|
14
|
+
isWearable: false,
|
|
15
|
+
isEmbedded: false,
|
|
16
|
+
isAndroid: false,
|
|
17
|
+
isIOS: false,
|
|
18
|
+
isWindows: false,
|
|
19
|
+
isMacOs: false,
|
|
20
|
+
isWinPhone: false,
|
|
21
|
+
isChrome: false,
|
|
22
|
+
isFirefox: false,
|
|
23
|
+
isSafari: false,
|
|
24
|
+
isOpera: false,
|
|
25
|
+
isIE: false,
|
|
26
|
+
isEdge: false,
|
|
27
|
+
isEdgeChromium: false,
|
|
28
|
+
isLegacyEdge: false,
|
|
29
|
+
isChromium: false,
|
|
30
|
+
isMobileSafari: false,
|
|
31
|
+
isYandex: false,
|
|
32
|
+
isMIUI: false,
|
|
33
|
+
isSamsungBrowser: false,
|
|
34
|
+
isElectron: false,
|
|
35
|
+
osVersion: 'unknown',
|
|
36
|
+
osName: 'unknown',
|
|
37
|
+
fullBrowserVersion: 'unknown',
|
|
38
|
+
browserVersion: 'unknown',
|
|
39
|
+
browserName: 'unknown',
|
|
40
|
+
mobileVendor: 'unknown',
|
|
41
|
+
mobileModel: 'unknown',
|
|
42
|
+
engineName: 'unknown',
|
|
43
|
+
engineVersion: 'unknown',
|
|
44
|
+
getUA: '',
|
|
45
|
+
deviceType: 'unknown',
|
|
46
|
+
isIOS13: false,
|
|
47
|
+
isIPad13: false,
|
|
48
|
+
isIPhone13: false,
|
|
49
|
+
isIPod13: false,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const defaultDeviceData = {
|
|
53
|
+
deviceType: 'unknown',
|
|
54
|
+
osName: 'unknown',
|
|
55
|
+
osVersion: 'unknown',
|
|
56
|
+
browserName: 'unknown',
|
|
57
|
+
browserVersion: 'unknown',
|
|
58
|
+
fullBrowserVersion: 'unknown',
|
|
59
|
+
mobileVendor: 'unknown',
|
|
60
|
+
mobileModel: 'unknown',
|
|
61
|
+
engineName: 'unknown',
|
|
62
|
+
engineVersion: 'unknown',
|
|
63
|
+
getUA: '',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const defaultOrientation = {
|
|
67
|
+
isPortrait: false,
|
|
68
|
+
isLandscape: false,
|
|
69
|
+
orientation: 'portrait' as 'portrait' | 'landscape',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Device detection hook wrapper for react-device-detect
|
|
74
|
+
*
|
|
75
|
+
* Provides a convenient interface to access device information including:
|
|
76
|
+
* - Device type (mobile, tablet, desktop, etc.)
|
|
77
|
+
* - Browser information (name, version, etc.)
|
|
78
|
+
* - OS information (name, version, etc.)
|
|
79
|
+
* - Orientation (portrait/landscape)
|
|
80
|
+
*
|
|
81
|
+
* @param userAgent - Optional user agent string (useful for SSR)
|
|
82
|
+
* @returns Device detection object with all available information
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```tsx
|
|
86
|
+
* const device = useDeviceDetect();
|
|
87
|
+
*
|
|
88
|
+
* if (device.isMobile) {
|
|
89
|
+
* return <MobileView />;
|
|
90
|
+
* }
|
|
91
|
+
*
|
|
92
|
+
* return <DesktopView />;
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export function useDeviceDetect(userAgent?: string) {
|
|
96
|
+
const [deviceInfo, setDeviceInfo] = useState<{
|
|
97
|
+
selectors: typeof defaultSelectors;
|
|
98
|
+
deviceData: typeof defaultDeviceData;
|
|
99
|
+
orientation: {
|
|
100
|
+
isPortrait: boolean;
|
|
101
|
+
isLandscape: boolean;
|
|
102
|
+
orientation: 'portrait' | 'landscape';
|
|
103
|
+
};
|
|
104
|
+
}>({
|
|
105
|
+
selectors: defaultSelectors,
|
|
106
|
+
deviceData: defaultDeviceData,
|
|
107
|
+
orientation: defaultOrientation,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
// Only run on client side
|
|
112
|
+
if (typeof window === 'undefined') return;
|
|
113
|
+
|
|
114
|
+
// Dynamic import to avoid SSR issues
|
|
115
|
+
import('react-device-detect').then((deviceDetect) => {
|
|
116
|
+
// Get user agent string
|
|
117
|
+
const ua = userAgent || (typeof window !== 'undefined' ? window.navigator.userAgent : '');
|
|
118
|
+
|
|
119
|
+
if (!ua) {
|
|
120
|
+
console.warn('No user agent available');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Parse user agent using library's parseUserAgent function
|
|
125
|
+
const parsed = deviceDetect.parseUserAgent(ua);
|
|
126
|
+
|
|
127
|
+
if (!parsed) {
|
|
128
|
+
console.warn('Failed to parse user agent');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Build selectors using library's buildSelectorsObject
|
|
133
|
+
// We need to import buildSelectorsObject, but it's not exported
|
|
134
|
+
// So we'll use getSelectorsByUserAgent which is exported
|
|
135
|
+
const selectors = deviceDetect.getSelectorsByUserAgent(ua) || defaultSelectors;
|
|
136
|
+
|
|
137
|
+
// Extract device data from parsed result
|
|
138
|
+
const deviceData = {
|
|
139
|
+
deviceType: parsed.device?.type || 'unknown',
|
|
140
|
+
osName: parsed.os?.name || 'unknown',
|
|
141
|
+
osVersion: parsed.os?.version || 'unknown',
|
|
142
|
+
browserName: parsed.browser?.name || 'unknown',
|
|
143
|
+
browserVersion: parsed.browser?.version || 'unknown',
|
|
144
|
+
fullBrowserVersion: parsed.browser?.version || 'unknown',
|
|
145
|
+
mobileVendor: parsed.device?.vendor || 'unknown',
|
|
146
|
+
mobileModel: parsed.device?.model || 'unknown',
|
|
147
|
+
engineName: parsed.engine?.name || 'unknown',
|
|
148
|
+
engineVersion: parsed.engine?.version || 'unknown',
|
|
149
|
+
getUA: parsed.ua || ua,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Get orientation - use library's hook if available, otherwise calculate
|
|
153
|
+
let orientation = defaultOrientation;
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
// Try to use the hook, but we can't call hooks conditionally
|
|
157
|
+
// So we'll calculate orientation manually
|
|
158
|
+
if (typeof window !== 'undefined') {
|
|
159
|
+
const isPortrait = window.innerHeight > window.innerWidth;
|
|
160
|
+
orientation = {
|
|
161
|
+
isPortrait,
|
|
162
|
+
isLandscape: !isPortrait,
|
|
163
|
+
orientation: (isPortrait ? 'portrait' : 'landscape') as 'portrait' | 'landscape',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.warn('Failed to get orientation:', error);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
setDeviceInfo({ selectors, deviceData, orientation });
|
|
171
|
+
}).catch((error) => {
|
|
172
|
+
console.warn('Failed to load device detection:', error);
|
|
173
|
+
});
|
|
174
|
+
}, [userAgent]);
|
|
175
|
+
|
|
176
|
+
// Update orientation on window resize
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if (typeof window === 'undefined') return;
|
|
179
|
+
|
|
180
|
+
const handleResize = () => {
|
|
181
|
+
const isPortrait = window.innerHeight > window.innerWidth;
|
|
182
|
+
setDeviceInfo((prev) => ({
|
|
183
|
+
...prev,
|
|
184
|
+
orientation: {
|
|
185
|
+
isPortrait,
|
|
186
|
+
isLandscape: !isPortrait,
|
|
187
|
+
orientation: (isPortrait ? 'portrait' : 'landscape') as 'portrait' | 'landscape',
|
|
188
|
+
},
|
|
189
|
+
}));
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
window.addEventListener('resize', handleResize);
|
|
193
|
+
window.addEventListener('orientationchange', handleResize);
|
|
194
|
+
|
|
195
|
+
return () => {
|
|
196
|
+
window.removeEventListener('resize', handleResize);
|
|
197
|
+
window.removeEventListener('orientationchange', handleResize);
|
|
198
|
+
};
|
|
199
|
+
}, []);
|
|
200
|
+
|
|
201
|
+
const { selectors, deviceData, orientation } = deviceInfo;
|
|
202
|
+
|
|
203
|
+
return useMemo(() => {
|
|
204
|
+
return {
|
|
205
|
+
// Device type selectors
|
|
206
|
+
isMobile: selectors.isMobile ?? false,
|
|
207
|
+
isTablet: selectors.isTablet ?? false,
|
|
208
|
+
isDesktop: selectors.isDesktop ?? false,
|
|
209
|
+
isBrowser: selectors.isBrowser ?? false,
|
|
210
|
+
isMobileOnly: selectors.isMobileOnly ?? false,
|
|
211
|
+
isSmartTV: selectors.isSmartTV ?? false,
|
|
212
|
+
isConsole: selectors.isConsole ?? false,
|
|
213
|
+
isWearable: selectors.isWearable ?? false,
|
|
214
|
+
isEmbedded: selectors.isEmbedded ?? false,
|
|
215
|
+
|
|
216
|
+
// OS selectors
|
|
217
|
+
isAndroid: selectors.isAndroid ?? false,
|
|
218
|
+
isIOS: selectors.isIOS ?? false,
|
|
219
|
+
isWindows: selectors.isWindows ?? false,
|
|
220
|
+
isMacOs: selectors.isMacOs ?? false,
|
|
221
|
+
isWinPhone: selectors.isWinPhone ?? false,
|
|
222
|
+
|
|
223
|
+
// Browser selectors
|
|
224
|
+
isChrome: selectors.isChrome ?? false,
|
|
225
|
+
isFirefox: selectors.isFirefox ?? false,
|
|
226
|
+
isSafari: selectors.isSafari ?? false,
|
|
227
|
+
isOpera: selectors.isOpera ?? false,
|
|
228
|
+
isIE: selectors.isIE ?? false,
|
|
229
|
+
isEdge: selectors.isEdge ?? false,
|
|
230
|
+
isEdgeChromium: selectors.isEdgeChromium ?? false,
|
|
231
|
+
isLegacyEdge: selectors.isLegacyEdge ?? false,
|
|
232
|
+
isChromium: selectors.isChromium ?? false,
|
|
233
|
+
isMobileSafari: selectors.isMobileSafari ?? false,
|
|
234
|
+
isYandex: selectors.isYandex ?? false,
|
|
235
|
+
isMIUI: selectors.isMIUI ?? false,
|
|
236
|
+
isSamsungBrowser: selectors.isSamsungBrowser ?? false,
|
|
237
|
+
isElectron: selectors.isElectron ?? false,
|
|
238
|
+
|
|
239
|
+
// iOS version selectors
|
|
240
|
+
isIOS13: selectors.isIOS13 ?? false,
|
|
241
|
+
isIPad13: selectors.isIPad13 ?? false,
|
|
242
|
+
isIPhone13: selectors.isIPhone13 ?? false,
|
|
243
|
+
isIPod13: selectors.isIPod13 ?? false,
|
|
244
|
+
|
|
245
|
+
// Device information
|
|
246
|
+
deviceType: deviceData.deviceType ?? 'unknown',
|
|
247
|
+
osName: deviceData.osName ?? 'unknown',
|
|
248
|
+
osVersion: deviceData.osVersion ?? 'unknown',
|
|
249
|
+
browserName: deviceData.browserName ?? 'unknown',
|
|
250
|
+
browserVersion: deviceData.browserVersion ?? 'unknown',
|
|
251
|
+
fullBrowserVersion: deviceData.fullBrowserVersion ?? 'unknown',
|
|
252
|
+
mobileVendor: deviceData.mobileVendor ?? 'unknown',
|
|
253
|
+
mobileModel: deviceData.mobileModel ?? 'unknown',
|
|
254
|
+
engineName: deviceData.engineName ?? 'unknown',
|
|
255
|
+
engineVersion: deviceData.engineVersion ?? 'unknown',
|
|
256
|
+
getUA: deviceData.getUA ?? '',
|
|
257
|
+
|
|
258
|
+
// Orientation
|
|
259
|
+
isPortrait: orientation.isPortrait,
|
|
260
|
+
isLandscape: orientation.isLandscape,
|
|
261
|
+
orientation: orientation.orientation,
|
|
262
|
+
|
|
263
|
+
// Raw data (for advanced usage)
|
|
264
|
+
selectors,
|
|
265
|
+
deviceData,
|
|
266
|
+
};
|
|
267
|
+
}, [selectors, deviceData, orientation]);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export type DeviceDetectResult = ReturnType<typeof useDeviceDetect>;
|