Skip to content

Commit 09a95ce

Browse files
authored
Merge pull request #65 from AndrewHUNGNguyen/main
Frontend changes to add question icon for your-handle
2 parents f8bfc3f + 195310f commit 09a95ce

File tree

3 files changed

+119
-107
lines changed

3 files changed

+119
-107
lines changed

app/components/HelpInfoButton.tsx

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"use client"
2+
import { useState, useRef, useEffect, type ReactNode } from "react"
3+
import styled from "styled-components"
4+
import { QuestionIcon } from "./icons"
5+
6+
type HelpInfoButtonProps = {
7+
children: ReactNode
8+
minWidth?: string
9+
maxWidth?: string
10+
}
11+
12+
export const HelpInfoButton = ({ children, minWidth, maxWidth }: HelpInfoButtonProps) => {
13+
const [isOpen, setIsOpen] = useState(false)
14+
const wrapperRef = useRef<HTMLDivElement | null>(null)
15+
16+
const handleToggle = () => {
17+
setIsOpen(!isOpen)
18+
}
19+
20+
useEffect(() => {
21+
if (!isOpen) return
22+
23+
const handleClickOutside = (event: MouseEvent) => {
24+
if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) {
25+
setIsOpen(false)
26+
}
27+
}
28+
29+
document.addEventListener("mousedown", handleClickOutside)
30+
return () => {
31+
document.removeEventListener("mousedown", handleClickOutside)
32+
}
33+
}, [isOpen])
34+
35+
return (
36+
<HelpIconWrapper ref={wrapperRef} onClick={handleToggle}>
37+
<QuestionIcon />
38+
{isOpen && (
39+
<Tooltip $minWidth={minWidth} $maxWidth={maxWidth}>
40+
{children}
41+
</Tooltip>
42+
)}
43+
</HelpIconWrapper>
44+
)
45+
}
46+
47+
const HelpIconWrapper = styled.div`
48+
position: relative;
49+
cursor: pointer;
50+
display: inline-flex;
51+
align-items: center;
52+
justify-content: center;
53+
color: rgba(255, 255, 255, 0.6);
54+
transition: color 0.2s ease;
55+
flex-shrink: 0;
56+
57+
&:hover {
58+
color: rgba(255, 255, 255, 0.9);
59+
}
60+
`
61+
62+
const Tooltip = styled.div<{ $minWidth?: string; $maxWidth?: string }>`
63+
position: absolute;
64+
bottom: calc(100% + 8px);
65+
right: 0;
66+
background: rgba(0, 0, 0, 0.95);
67+
color: rgba(255, 255, 255, 0.95);
68+
padding: 0.75rem 1rem;
69+
border-radius: 8px;
70+
font-size: 0.875rem;
71+
line-height: 1.4;
72+
white-space: normal;
73+
min-width: ${(props) => props.$minWidth || "200px"};
74+
max-width: ${(props) => props.$maxWidth || "250px"};
75+
width: max-content;
76+
z-index: 1000;
77+
box-shadow:
78+
0 4px 12px rgba(0, 0, 0, 0.4),
79+
0 0 0 1px rgba(255, 255, 255, 0.1);
80+
pointer-events: auto;
81+
82+
&::after {
83+
content: "";
84+
position: absolute;
85+
top: 100%;
86+
right: 1rem;
87+
transform: translateX(50%);
88+
border: 6px solid transparent;
89+
border-top-color: rgba(0, 0, 0, 0.95);
90+
}
91+
`

app/components/Nametag.tsx

Lines changed: 4 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import styled from "styled-components"
33
import { useState, useRef, useEffect } from "react"
44
import { TextInput } from "./TextInput"
55
import { Button } from "./Button"
6-
import { EditIcon, CameraIcon, QuestionIcon } from "./icons"
6+
import { EditIcon, CameraIcon } from "./icons"
7+
import { HelpInfoButton } from "./HelpInfoButton"
78

89
type NametagData = {
910
fullName: string
@@ -38,10 +39,8 @@ export const Nametag = ({
3839
const [isEditing, setIsEditing] = useState(initialEditing || forcedEditMode)
3940
const [formData, setFormData] = useState<NametagData>(data)
4041
const [rotation, setRotation] = useState({ x: 0, y: 0 })
41-
const [openTooltip, setOpenTooltip] = useState<string | null>(null)
4242
const fileInputRef = useRef<HTMLInputElement>(null)
4343
const containerRef = useRef<HTMLDivElement>(null)
44-
const tooltipRefs = useRef<{ [key: string]: HTMLDivElement | null }>({})
4544

4645
// In forced edit mode, always keep editing enabled (unless readOnly)
4746
// In readOnly mode, never allow editing
@@ -79,29 +78,6 @@ export const Nametag = ({
7978
// eslint-disable-next-line react-hooks/exhaustive-deps
8079
}, [data, isEditing])
8180

82-
// Close tooltip when clicking outside
83-
useEffect(() => {
84-
const handleClickOutside = (event: MouseEvent) => {
85-
if (openTooltip) {
86-
const tooltipElement = tooltipRefs.current[openTooltip]
87-
if (tooltipElement && !tooltipElement.contains(event.target as Node)) {
88-
setOpenTooltip(null)
89-
}
90-
}
91-
}
92-
93-
if (openTooltip) {
94-
document.addEventListener("mousedown", handleClickOutside)
95-
return () => {
96-
document.removeEventListener("mousedown", handleClickOutside)
97-
}
98-
}
99-
}, [openTooltip])
100-
101-
const toggleTooltip = (fieldName: string) => {
102-
setOpenTooltip(openTooltip === fieldName ? null : fieldName)
103-
}
104-
10581
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
10682
if (!containerRef.current) return
10783

@@ -304,18 +280,7 @@ export const Nametag = ({
304280
required
305281
/>
306282
</NametagInputWrapper>
307-
<HelpIconWrapper onClick={() => toggleTooltip("title")}>
308-
<QuestionIcon />
309-
{openTooltip === "title" && (
310-
<Tooltip
311-
ref={(el) => {
312-
tooltipRefs.current["title"] = el
313-
}}
314-
>
315-
Your job title or role.
316-
</Tooltip>
317-
)}
318-
</HelpIconWrapper>
283+
<HelpInfoButton>Your job title or role.</HelpInfoButton>
319284
</InputWithHelpContainer>
320285
</NametagInputGroup>
321286

@@ -337,18 +302,7 @@ export const Nametag = ({
337302
required
338303
/>
339304
</NametagInputWrapper>
340-
<HelpIconWrapper onClick={() => toggleTooltip("affiliation")}>
341-
<QuestionIcon />
342-
{openTooltip === "affiliation" && (
343-
<Tooltip
344-
ref={(el) => {
345-
tooltipRefs.current["affiliation"] = el
346-
}}
347-
>
348-
Your company, organization, or school name.
349-
</Tooltip>
350-
)}
351-
</HelpIconWrapper>
305+
<HelpInfoButton>Your company, organization, or school name.</HelpInfoButton>
352306
</InputWithHelpContainer>
353307
</NametagInputGroup>
354308
</NametagRight>
@@ -672,52 +626,6 @@ const NametagInputWrapper = styled.div<{ $fontSize?: string; $fontWeight?: strin
672626
}
673627
`
674628

675-
const HelpIconWrapper = styled.div`
676-
position: relative;
677-
cursor: pointer;
678-
display: inline-flex;
679-
align-items: center;
680-
justify-content: center;
681-
color: rgba(255, 255, 255, 0.6);
682-
transition: color 0.2s ease;
683-
flex-shrink: 0;
684-
685-
&:hover {
686-
color: rgba(255, 255, 255, 0.9);
687-
}
688-
`
689-
690-
const Tooltip = styled.div`
691-
position: absolute;
692-
bottom: calc(100% + 8px);
693-
right: 0;
694-
background: rgba(0, 0, 0, 0.95);
695-
color: rgba(255, 255, 255, 0.95);
696-
padding: 0.75rem 1rem;
697-
border-radius: 8px;
698-
font-size: 0.875rem;
699-
line-height: 1.4;
700-
white-space: normal;
701-
min-width: 200px;
702-
max-width: 250px;
703-
width: max-content;
704-
z-index: 1000;
705-
box-shadow:
706-
0 4px 12px rgba(0, 0, 0, 0.4),
707-
0 0 0 1px rgba(255, 255, 255, 0.1);
708-
pointer-events: auto;
709-
710-
&::after {
711-
content: "";
712-
position: absolute;
713-
top: 100%;
714-
right: 1rem;
715-
transform: translateX(50%);
716-
border: 6px solid transparent;
717-
border-top-color: rgba(0, 0, 0, 0.95);
718-
}
719-
`
720-
721629
const NametagDisplayText = styled.div<{ $fontSize?: string; $fontWeight?: string }>`
722630
font-size: ${(props) => props.$fontSize || "1rem"};
723631
font-weight: ${(props) => props.$fontWeight || "normal"};

app/setup/page.tsx

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { PotionBackground } from "../components/PotionBackground"
88
import { Nametag } from "../components/Nametag"
99
import { TextInput } from "../components/TextInput"
1010
import { Button } from "../components/Button"
11+
import { HelpInfoButton } from "../components/HelpInfoButton"
1112

1213
type NametagData = {
1314
fullName: string
@@ -298,17 +299,22 @@ export default function Setup() {
298299
<Section>
299300
<SectionTitle>Choose a handle</SectionTitle>
300301
<HandleInputWrapper>
301-
<TextInput
302-
variant="secondary"
303-
size="default"
304-
value={handle}
305-
onChange={(e) => setHandle(e.target.value.toLowerCase())}
306-
placeholder="your-handle"
307-
required
308-
pattern="(?:[a-z0-9_]|-){3,30}"
309-
minLength={3}
310-
maxLength={30}
311-
/>
302+
<HandleInputRow>
303+
<TextInput
304+
variant="secondary"
305+
size="default"
306+
value={handle}
307+
onChange={(e) => setHandle(e.target.value.toLowerCase())}
308+
placeholder="your-handle"
309+
required
310+
pattern="(?:[a-z0-9_]|-){3,30}"
311+
minLength={3}
312+
maxLength={30}
313+
/>
314+
<HelpInfoButton minWidth="220px" maxWidth="260px">
315+
Your unique DEVx username, used for your nametag or public profile.
316+
</HelpInfoButton>
317+
</HandleInputRow>
312318
{handle && (
313319
<HandleStatus>
314320
{checkingHandle ? (
@@ -439,6 +445,13 @@ const HandleInputWrapper = styled.div`
439445
width: 100%;
440446
`
441447

448+
const HandleInputRow = styled.div`
449+
display: flex;
450+
align-items: center;
451+
gap: 0.5rem;
452+
width: 100%;
453+
`
454+
442455
const HandleStatus = styled.div`
443456
display: flex;
444457
align-items: center;

0 commit comments

Comments
 (0)