From 7df42ddf5827b1eb2427739c071733029c1e451e Mon Sep 17 00:00:00 2001 From: Cryptoryda <113293883+cryptoryda@users.noreply.github.com> Date: Tue, 19 Nov 2024 17:26:32 +0000 Subject: [PATCH 1/4] password toolkit ui matches with main three lines --- .../InputSecurePassword.tsx | 200 +++++++++++++----- 1 file changed, 146 insertions(+), 54 deletions(-) diff --git a/packages/app/src/systems/Core/components/InputSecurePassword/InputSecurePassword.tsx b/packages/app/src/systems/Core/components/InputSecurePassword/InputSecurePassword.tsx index afd5ac874d..185cbe2677 100644 --- a/packages/app/src/systems/Core/components/InputSecurePassword/InputSecurePassword.tsx +++ b/packages/app/src/systems/Core/components/InputSecurePassword/InputSecurePassword.tsx @@ -1,16 +1,10 @@ -import type { ThemeUtilsCSS } from '@fuel-ui/css'; -import { cssObj } from '@fuel-ui/css'; -import type { InputPasswordProps } from '@fuel-ui/react'; -import { - Box, - Icon, - InputPassword, - PasswordStrength, - usePasswordStrength, -} from '@fuel-ui/react'; -import unsafeList from '@fuel-ui/react/unsafe-passwords'; -import { useEffect, useState } from 'react'; -import type { ControllerRenderProps, FieldValues } from 'react-hook-form'; +import type { ThemeUtilsCSS } from "@fuel-ui/css"; +import { cssObj } from "@fuel-ui/css"; +import type { InputPasswordProps } from "@fuel-ui/react"; +import { Box, Icon, InputPassword } from "@fuel-ui/react"; +import { useEffect, useState } from "react"; +import React from "react"; +import type { ControllerRenderProps, FieldValues } from "react-hook-form"; export type InputSecurePasswordProps = { onChangeStrength?: (strength: string) => void; @@ -25,51 +19,128 @@ export type InputSecurePasswordProps = { css?: ThemeUtilsCSS; }; +const calculatePasswordStrength = (password: string): string => { + let score = 0; + + if (password.length >= 8) score += 1; + if (/[a-z]/.test(password) && /[A-Z]/.test(password)) score += 1; + if (/\d/.test(password)) score += 1; + if (/[@$!%*?&#^_-]/.test(password)) score += 1; + + if (score <= 1) return "weak"; + if (score === 2) return "average"; + if (score >= 3) return "strong"; + + return "weak"; +}; + export function InputSecurePassword({ inputProps, field, onChangeStrength, onChange, onBlur, - placeholder = 'Type your password', - ariaLabel = 'Your Password', + placeholder = "Type your password", + ariaLabel = "Your Password", css, }: InputSecurePasswordProps) { const [passwordTooltipOpened, setPasswordTooltipOpened] = useState(false); - const password = field.value || ''; - const { strength } = usePasswordStrength({ - minLength: 8, - password, - unsafeList, - }); + const [strength, setStrength] = useState("weak"); + const password = field.value || ""; - // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { - onChangeStrength?.(strength); - }, [strength]); + const currentStrength = calculatePasswordStrength(password); + setStrength(currentStrength); + onChangeStrength?.(currentStrength); + }, [password, onChangeStrength]); + + const getStrengthColor = (level: string, index: number) => { + if (level === "weak") { + return index === 0 ? "red" : "gray"; + } + if (level === "average") { + return index === 0 || index === 1 ? "yellow" : "gray"; + } + if (level === "strong") { + return "green"; + } + return "gray"; // Default to gray for any unexpected case + }; return ( - - + {[...Array(3)].map((_, index) => ( + + ))} + + + {/* Tooltip Wrapper */} + setPasswordTooltipOpened(true)} onMouseLeave={() => setPasswordTooltipOpened(false)} - aria-label="Password strength" > - - - - + + {passwordTooltipOpened && ( + + + + {strength.charAt(0).toUpperCase() + strength.slice(1)} + + + + {[...Array(3)].map((_, index) => ( + + ))} + + + +

A secure password should have:

+
    +
  • {password.length >= 8 ? "✓" : "✗"} Min. 8 characters
  • +
  • + {/[a-z]/.test(password) && /[A-Z]/.test(password) + ? "✓" + : "✗"}{" "} + Upper & lower case letters +
  • +
  • {/\d/.test(password) ? "✓" : "✗"} Numbers
  • +
  • + {/[@$!%*?&#^_-]/.test(password) ? "✓" : "✗"} Special + characters +
  • +
+
+
+ )} +
+ + Date: Sat, 23 Nov 2024 11:08:26 +0000 Subject: [PATCH 2/4] Feat(siderbar text highlighting with click) in TableOfContent component --- .../docs/src/components/TableOfContent.tsx | 94 +++++++++++++------ 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/packages/docs/src/components/TableOfContent.tsx b/packages/docs/src/components/TableOfContent.tsx index fd3fcf7eb3..a8e57583cc 100644 --- a/packages/docs/src/components/TableOfContent.tsx +++ b/packages/docs/src/components/TableOfContent.tsx @@ -1,10 +1,30 @@ -import { cssObj } from '@fuel-ui/css'; -import { Box, Heading, Link, List, Text } from '@fuel-ui/react'; -import { useDocContext } from '~/src/hooks/useDocContext'; +import { useEffect, useState } from "react"; +import { cssObj } from "@fuel-ui/css"; +import { Box, Heading, Link, List, Text } from "@fuel-ui/react"; +import { useDocContext } from "~/src/hooks/useDocContext"; export function TableOfContent() { const { doc } = useDocContext(); const { headings } = doc; + + const [currentHash, setCurrentHash] = useState(""); + + useEffect(() => { + if (typeof window !== "undefined") { + setCurrentHash(window.location.hash); + + const handleHashChange = () => { + setCurrentHash(window.location.hash); + }; + + window.addEventListener("hashchange", handleHashChange); + + return () => { + window.removeEventListener("hashchange", handleHashChange); + }; + } + }, []); + return ( @@ -12,12 +32,30 @@ export function TableOfContent() { {headings.map((heading) => ( - {heading.title} + + {heading.title} + {heading.children && ( - {heading.children.map((heading) => ( - - {heading.title} + {heading.children.map((subHeading) => ( + + + {subHeading.title} + ))} @@ -30,7 +68,7 @@ export function TableOfContent() { isExternal href="https://github.com/fuellabs/fuels-wallet/issues/new/choose" > - Questions? Give us a feedback + Questions? Give us feedback Edit this page @@ -41,52 +79,52 @@ export function TableOfContent() { ); } -const LIST_ITEM = '.fuel_List > .fuel_ListItem'; +const LIST_ITEM = ".fuel_List > .fuel_ListItem"; const styles = { queries: cssObj({ - display: 'none', + display: "none", - '@xl': { - display: 'block', + "@xl": { + display: "block", }, }), root: cssObj({ - position: 'sticky', + position: "sticky", top: 0, - py: '$8', - pr: '$8', + py: "$8", + pr: "$8", h6: { mt: 0, }, [LIST_ITEM]: { - pb: '$2', + pb: "$2", a: { - fontWeight: '$normal', - color: '$intentsBase11', + fontWeight: "$normal", + color: "$intentsBase11", }, }, [`${LIST_ITEM} > ${LIST_ITEM}:nth-child(1)`]: { - pt: '$2', + pt: "$2", }, [`${LIST_ITEM} > ${LIST_ITEM}`]: { a: { - fontWeight: '$normal', - color: '$intentsBase9', + fontWeight: "$normal", + color: "$intentsBase9", }, }, }), feedback: cssObj({ - display: 'flex', - flexDirection: 'column', - pt: '$3', - borderTop: '1px solid $border', - fontSize: '$sm', + display: "flex", + flexDirection: "column", + pt: "$3", + borderTop: "1px solid $border", + fontSize: "$sm", - 'a, a:visited': { - color: '$intentsBase10', + "a, a:visited": { + color: "$intentsBase10", }, }), }; From fcf968a526c837fd4f83f2abdac0a6203317ef63 Mon Sep 17 00:00:00 2001 From: Cryptoryda <113293883+cryptoryda@users.noreply.github.com> Date: Sat, 23 Nov 2024 11:12:05 +0000 Subject: [PATCH 3/4] allowing scroll to update Headings text highlighting --- .../docs/src/components/TableOfContent.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/docs/src/components/TableOfContent.tsx b/packages/docs/src/components/TableOfContent.tsx index a8e57583cc..329d5c91c9 100644 --- a/packages/docs/src/components/TableOfContent.tsx +++ b/packages/docs/src/components/TableOfContent.tsx @@ -25,6 +25,33 @@ export function TableOfContent() { } }, []); + useEffect(() => { + if (typeof window === "undefined") return; + + const observerOptions = { + root: null, + rootMargin: "0px", + threshold: 0.6, + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setCurrentHash(`#${entry.target.id}`); + } + }); + }, observerOptions); + + const sections = headings.map((heading) => + document.getElementById(heading.id) + ); + sections.forEach((section) => section && observer.observe(section)); + + return () => { + sections.forEach((section) => section && observer.unobserve(section)); + }; + }, [headings]); + return ( From bd07b34b3869afb5a10002bf251ca8a8a7e2d797 Mon Sep 17 00:00:00 2001 From: Cryptoryda <113293883+cryptoryda@users.noreply.github.com> Date: Sun, 8 Dec 2024 15:31:04 +0000 Subject: [PATCH 4/4] scroll highlight fix --- .../docs/src/components/TableOfContent.tsx | 66 ++++++++++++++----- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/packages/docs/src/components/TableOfContent.tsx b/packages/docs/src/components/TableOfContent.tsx index 329d5c91c9..25621e0bea 100644 --- a/packages/docs/src/components/TableOfContent.tsx +++ b/packages/docs/src/components/TableOfContent.tsx @@ -8,49 +8,77 @@ export function TableOfContent() { const { headings } = doc; const [currentHash, setCurrentHash] = useState(""); + const [userClicked, setUserClicked] = useState(false); + // Update the current hash based on window.location.hash on mount useEffect(() => { if (typeof window !== "undefined") { setCurrentHash(window.location.hash); - - const handleHashChange = () => { - setCurrentHash(window.location.hash); - }; - - window.addEventListener("hashchange", handleHashChange); - - return () => { - window.removeEventListener("hashchange", handleHashChange); - }; } }, []); + // Track hash changes when a user clicks on a link + const handleClick = (hash: string) => { + setUserClicked(true); // Indicate that the change is user-initiated + setCurrentHash(hash); // Update the current hash + setTimeout(() => setUserClicked(false), 1000); // Reset after a short delay + }; + + // Scroll observer for automatic highlighting during scrolling useEffect(() => { if (typeof window === "undefined") return; const observerOptions = { - root: null, + root: null, // Use the viewport rootMargin: "0px", - threshold: 0.6, + threshold: Array.from({ length: 11 }, (_, i) => i / 10), // Threshold from 0.0 to 1.0 }; + const sectionVisibility = new Map(); + const observer = new IntersectionObserver((entries) => { + if (userClicked) return; // Skip if the user clicked a link recently + entries.forEach((entry) => { - if (entry.isIntersecting) { - setCurrentHash(`#${entry.target.id}`); + sectionVisibility.set(entry.target.id, entry.intersectionRatio); + }); + + // Find the section with the highest visibility ratio + let mostVisibleId = ""; + let maxVisibility = 0; + sectionVisibility.forEach((visibility, id) => { + if (visibility > maxVisibility) { + mostVisibleId = id; + maxVisibility = visibility; } }); + + if (mostVisibleId) { + setCurrentHash(`#${mostVisibleId}`); + } }, observerOptions); - const sections = headings.map((heading) => + // Flatten main headings and subheadings into a single list + const allSections: any = []; + headings.forEach((heading) => { + allSections.push(heading); + if (heading.children) { + allSections.push(...heading.children); + } + }); + + const sections = allSections.map((heading: any) => document.getElementById(heading.id) ); - sections.forEach((section) => section && observer.observe(section)); + + sections.forEach((section: any) => section && observer.observe(section)); return () => { - sections.forEach((section) => section && observer.unobserve(section)); + sections.forEach( + (section: any) => section && observer.unobserve(section) + ); }; - }, [headings]); + }, [headings, userClicked]); return ( @@ -61,6 +89,7 @@ export function TableOfContent() { handleClick(`#${heading.id}`)} // Handle click events style={ currentHash === `#${heading.id}` ? { color: "white", fontWeight: "bold" } @@ -75,6 +104,7 @@ export function TableOfContent() { handleClick(`#${subHeading.id}`)} // Handle click events style={ currentHash === `#${subHeading.id}` ? { color: "white", fontWeight: "bold" }