@djangocfg/ui-nextjs 2.1.9 → 2.1.10

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-nextjs",
3
- "version": "2.1.9",
3
+ "version": "2.1.10",
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.9",
62
- "@djangocfg/ui-core": "^2.1.9",
61
+ "@djangocfg/api": "^2.1.10",
62
+ "@djangocfg/ui-core": "^2.1.10",
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.9",
107
+ "@djangocfg/typescript-config": "^2.1.10",
107
108
  "@types/node": "^24.7.2",
108
109
  "eslint": "^9.37.0",
109
110
  "tailwindcss-animate": "1.0.7",
@@ -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>;