@snapdragonsnursery/react-components 1.0.1 → 1.0.4

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": "@snapdragonsnursery/react-components",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -19,6 +19,7 @@
19
19
  },
20
20
  "homepage": "https://github.com/Snapdragons-Nursery/react-components#readme",
21
21
  "dependencies": {
22
+ "@headlessui/react": "^2.2.4",
22
23
  "react": "^18.3.1",
23
24
  "react-dom": "^18.3.1"
24
25
  },
@@ -7,34 +7,19 @@ import {
7
7
  clearUserContext,
8
8
  } from "./telemetry";
9
9
  import ThemeToggle from "./ThemeToggle";
10
+ import { Menu } from "@headlessui/react";
10
11
 
11
12
  const isMobile = window.innerWidth < 640;
12
13
 
13
14
  function AuthButtons({ theme, setTheme, authState, setAuthState }) {
14
15
  const [isLoggingIn, setIsLoggingIn] = useState(false);
15
16
  const [profilePicture, setProfilePicture] = useState(null);
16
- const [showProfileMenu, setShowProfileMenu] = useState(false);
17
- const menuRef = useRef();
18
17
  const isLocalhost = window.location.hostname === "localhost";
19
18
 
20
19
  // Use MSAL hooks - parent app should ensure MSAL is initialized
21
20
  const { instance, accounts } = useMsal();
22
21
  const isAuthenticated = useIsAuthenticated();
23
22
 
24
- // Close profile menu when clicking outside
25
- useEffect(() => {
26
- const handleClickOutside = (event) => {
27
- if (menuRef.current && !menuRef.current.contains(event.target)) {
28
- setShowProfileMenu(false);
29
- }
30
- };
31
-
32
- document.addEventListener("mousedown", handleClickOutside);
33
- return () => {
34
- document.removeEventListener("mousedown", handleClickOutside);
35
- };
36
- }, []);
37
-
38
23
  // Update auth state when MSAL state changes
39
24
  useEffect(() => {
40
25
  if (instance) {
@@ -250,70 +235,65 @@ function AuthButtons({ theme, setTheme, authState, setAuthState }) {
250
235
  }
251
236
 
252
237
  return (
253
- <div className="relative">
254
- {/* Profile picture as trigger */}
255
- {profilePicture ? (
256
- <img
257
- src={profilePicture}
258
- alt={`${displayName}'s profile picture`}
259
- className="w-8 h-8 rounded-full object-cover border-2 border-blue-500 cursor-pointer"
260
- onClick={() => setShowProfileMenu((v) => !v)}
261
- />
262
- ) : (
263
- <div
264
- className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center cursor-pointer border-2 border-blue-500"
265
- onClick={() => setShowProfileMenu((v) => !v)}
266
- >
267
- <span className="text-white text-sm font-medium">
268
- {displayName
269
- .split(" ")
270
- .map((n) => n[0])
271
- .join("")
272
- .toUpperCase()
273
- .slice(0, 2)}
274
- </span>
238
+ <Menu as="div" className="relative inline-block text-left">
239
+ <div>
240
+ <Menu.Button className="focus:outline-none">
241
+ {profilePicture ? (
242
+ <img
243
+ src={profilePicture}
244
+ alt={`${displayName}'s profile picture`}
245
+ className="w-8 h-8 rounded-full object-cover border-2 border-blue-500 cursor-pointer"
246
+ />
247
+ ) : (
248
+ <div className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center cursor-pointer border-2 border-blue-500">
249
+ <span className="text-white text-sm font-medium">
250
+ {displayName
251
+ .split(" ")
252
+ .map((n) => n[0])
253
+ .join("")
254
+ .toUpperCase()
255
+ .slice(0, 2)}
256
+ </span>
257
+ </div>
258
+ )}
259
+ </Menu.Button>
260
+ </div>
261
+ <Menu.Items className="absolute right-0 mt-2 min-w-[220px] max-w-xs w-auto bg-white dark:bg-gray-800 rounded-lg shadow-lg z-50 py-2 border border-gray-200 dark:border-gray-700 focus:outline-none">
262
+ <div className="px-4 py-2 text-xs text-gray-500 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700">
263
+ Signed in as <span className="font-semibold">{displayName}</span>
275
264
  </div>
276
- )}
277
- {showProfileMenu && (
278
- <div
279
- ref={menuRef}
280
- className="absolute right-0 mt-2 min-w-[220px] max-w-xs w-auto bg-white dark:bg-gray-800 rounded-lg shadow-lg z-50 py-2 border border-gray-200 dark:border-gray-700"
281
- >
282
- <div className="px-4 py-2 text-xs text-gray-500 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700">
283
- Signed in as <span className="font-semibold">{displayName}</span>
284
- </div>
285
- {/* Theme toggle inside dropdown */}
286
- <div className="px-4 py-2">
287
- <ThemeToggle theme={theme} setTheme={setTheme} />
288
- </div>
289
- <button
290
- className="flex items-center w-full px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
291
- onClick={() => {
292
- setShowProfileMenu(false);
293
- handleLogout();
294
- }}
295
- >
296
- <span className="mr-2">
297
- {/* Logout icon */}
298
- <svg
299
- className="w-5 h-5"
300
- fill="none"
301
- stroke="currentColor"
302
- viewBox="0 0 24 24"
303
- >
304
- <path
305
- strokeLinecap="round"
306
- strokeLinejoin="round"
307
- strokeWidth={2}
308
- d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
309
- />
310
- </svg>
311
- </span>
312
- Logout
313
- </button>
265
+ <div className="px-4 py-2">
266
+ <ThemeToggle theme={theme} setTheme={setTheme} />
314
267
  </div>
315
- )}
316
- </div>
268
+ <Menu.Item>
269
+ {({ active }) => (
270
+ <button
271
+ className={`flex items-center w-full px-4 py-2 text-sm transition-colors ${
272
+ active ? "bg-gray-100 dark:bg-gray-700" : ""
273
+ }`}
274
+ onClick={handleLogout}
275
+ >
276
+ <span className="mr-2">
277
+ <svg
278
+ className="w-5 h-5"
279
+ fill="none"
280
+ stroke="currentColor"
281
+ viewBox="0 0 24 24"
282
+ >
283
+ <path
284
+ strokeLinecap="round"
285
+ strokeLinejoin="round"
286
+ strokeWidth={2}
287
+ d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
288
+ />
289
+ </svg>
290
+ </span>
291
+ Logout
292
+ </button>
293
+ )}
294
+ </Menu.Item>
295
+ </Menu.Items>
296
+ </Menu>
317
297
  );
318
298
  }
319
299
 
@@ -59,17 +59,18 @@ function ThemeToggle({ theme, setTheme }) {
59
59
  };
60
60
 
61
61
  return (
62
- <div className="flex justify-center items-center bg-gray-200 dark:bg-gray-700 rounded-full p-1.5 w-full">
62
+ <div className="flex justify-center items-center bg-gray-200 dark:bg-gray-700 rounded-full p-2 w-full gap-2">
63
63
  {options.map((opt) => (
64
64
  <button
65
65
  key={opt.value}
66
66
  onClick={() => handleThemeChange(opt.value)}
67
- className={`flex-1 flex items-center justify-center h-10 rounded-full transition-all duration-200 focus:outline-none
67
+ className={`flex-1 flex items-center justify-center h-12 min-w-[44px] rounded-full transition-all duration-200 focus:outline-none mx-1
68
68
  ${
69
69
  theme === opt.value
70
70
  ? "bg-white dark:bg-gray-900 shadow text-blue-600 dark:text-yellow-400"
71
71
  : "bg-transparent text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600"
72
72
  }`}
73
+ style={{ padding: "0 12px" }}
73
74
  title={opt.label + " mode"}
74
75
  aria-pressed={theme === opt.value}
75
76
  >
@@ -0,0 +1,37 @@
1
+ import React, { useState } from "react";
2
+ import ThemeToggle from "./ThemeToggle";
3
+
4
+ export default function ThemeToggleTest() {
5
+ const [theme, setTheme] = useState("system");
6
+
7
+ return (
8
+ <div
9
+ style={{
10
+ minHeight: "100vh",
11
+ background:
12
+ theme === "dark"
13
+ ? "#1a202c"
14
+ : theme === "light"
15
+ ? "#f9fafb"
16
+ : "#e5e7eb",
17
+ display: "flex",
18
+ alignItems: "center",
19
+ justifyContent: "center",
20
+ transition: "background 0.3s",
21
+ }}
22
+ >
23
+ <div style={{ width: 300 }}>
24
+ <ThemeToggle theme={theme} setTheme={setTheme} />
25
+ <div
26
+ style={{
27
+ marginTop: 24,
28
+ textAlign: "center",
29
+ color: theme === "dark" ? "#fff" : "#222",
30
+ }}
31
+ >
32
+ <strong>Current theme:</strong> {theme}
33
+ </div>
34
+ </div>
35
+ </div>
36
+ );
37
+ }
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { default as AuthButtons } from "./AuthButtons";
2
2
  export { default as ThemeToggle } from "./ThemeToggle";
3
3
  export { default as ChildSearchModal } from "./ChildSearchModal";
4
+ export { default as ThemeToggleTest } from "./ThemeToggleTest";
4
5
  export { configureTelemetry } from "./telemetry";