Skip to content

Commit c8c9aeb

Browse files
author
Andrew Nguyen
committed
Visual Affordance Changes conflict res
1 parent 820237b commit c8c9aeb

4 files changed

Lines changed: 172 additions & 43 deletions

File tree

app/components/Nametag.tsx

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ type NametagProps = {
2323
forcedEditMode?: boolean
2424
onDataChange?: (data: NametagData) => void
2525
readOnly?: boolean
26+
validationErrors?: {
27+
profilePhoto?: boolean
28+
fullName?: boolean
29+
}
30+
showRequiredAsterisks?: boolean
2631
}
2732

2833
export const Nametag = ({
@@ -34,7 +39,9 @@ export const Nametag = ({
3439
initialEditing = false,
3540
forcedEditMode = false,
3641
onDataChange,
37-
readOnly = false
42+
readOnly = false,
43+
validationErrors = {},
44+
showRequiredAsterisks = false
3845
}: NametagProps) => {
3946
const [isEditing, setIsEditing] = useState(initialEditing || forcedEditMode)
4047
const [formData, setFormData] = useState<NametagData>(data)
@@ -137,6 +144,10 @@ export const Nametag = ({
137144
setIsEditing(false)
138145
}
139146

147+
const handlePhotoFrameClick = () => {
148+
if (!readOnly) fileInputRef.current?.click()
149+
}
150+
140151
// Read-only mode (skip if forced edit mode)
141152
if (!effectiveEditing) {
142153
return (
@@ -212,7 +223,10 @@ export const Nametag = ({
212223
</SaveButtonWrapper>
213224
)}
214225
<NametagLeft>
215-
<PhotoFrame onClick={readOnly ? undefined : () => fileInputRef.current?.click()}>
226+
<PhotoFrame
227+
onClick={readOnly ? undefined : handlePhotoFrameClick}
228+
$error={validationErrors.profilePhoto}
229+
>
216230
{uploading ? (
217231
<PlaceholderAvatar>Loading...</PlaceholderAvatar>
218232
) : formData.profilePhoto ? (
@@ -228,6 +242,12 @@ export const Nametag = ({
228242
</PhotoOverlay>
229243
)}
230244
</PhotoFrame>
245+
{showRequiredAsterisks && (
246+
<PhotoRequiredLabel>
247+
Profile Photo <RequiredAsterisk>*</RequiredAsterisk>
248+
</PhotoRequiredLabel>
249+
)}
250+
{validationErrors.profilePhoto && <FieldError>Please upload a profile photo</FieldError>}
231251
{!readOnly && (
232252
<input
233253
type="file"
@@ -241,9 +261,16 @@ export const Nametag = ({
241261

242262
<NametagRight>
243263
<NametagInputGroup>
244-
<NametagLabel>HELLO my name is</NametagLabel>
264+
<NametagLabel>
265+
HELLO my name is
266+
{showRequiredAsterisks && <RequiredAsterisk> *</RequiredAsterisk>}
267+
</NametagLabel>
245268
<InputWithHelpContainer>
246-
<NametagInputWrapper $fontSize="1.5rem" $fontWeight="700">
269+
<NametagInputWrapper
270+
$fontSize="1.5rem"
271+
$fontWeight="700"
272+
$error={validationErrors.fullName}
273+
>
247274
<TextInput
248275
variant="secondary"
249276
size="default"
@@ -256,10 +283,11 @@ export const Nametag = ({
256283
}
257284
}}
258285
placeholder="Your Name"
259-
required
286+
error={validationErrors.fullName}
260287
/>
261288
</NametagInputWrapper>
262289
</InputWithHelpContainer>
290+
{validationErrors.fullName && <FieldError>Please enter your name</FieldError>}
263291
</NametagInputGroup>
264292

265293
<NametagInputGroup>
@@ -277,7 +305,6 @@ export const Nametag = ({
277305
}
278306
}}
279307
placeholder="Title"
280-
required
281308
/>
282309
</NametagInputWrapper>
283310
<HelpInfoButton>Your job title or role.</HelpInfoButton>
@@ -299,7 +326,6 @@ export const Nametag = ({
299326
}
300327
}}
301328
placeholder="Affiliation"
302-
required
303329
/>
304330
</NametagInputWrapper>
305331
<HelpInfoButton>Your company, organization, or school name.</HelpInfoButton>
@@ -533,16 +559,17 @@ const PhotoOverlay = styled.div`
533559
pointer-events: none;
534560
`
535561

536-
const PhotoFrame = styled.div`
562+
const PhotoFrame = styled.div<{ $error?: boolean }>`
537563
width: 120px;
538564
height: 120px;
539565
border-radius: 8px;
540566
overflow: hidden;
541567
background-color: rgba(255, 255, 255, 0.1);
542-
border: 2px solid rgba(255, 255, 255, 0.3);
568+
border: 2px solid ${(props) => (props.$error ? "var(--error-color)" : "rgba(255, 255, 255, 0.3)")};
543569
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
544570
cursor: pointer;
545571
position: relative;
572+
transition: border-color 0.2s ease;
546573
547574
&:hover ${PhotoOverlay} {
548575
opacity: 1;
@@ -600,27 +627,34 @@ const InputWithHelpContainer = styled.div`
600627
position: relative;
601628
`
602629

603-
const NametagInputWrapper = styled.div<{ $fontSize?: string; $fontWeight?: string }>`
630+
const NametagInputWrapper = styled.div<{
631+
$fontSize?: string
632+
$fontWeight?: string
633+
$error?: boolean
634+
}>`
604635
flex: 1;
605636
width: 100%;
606637
607638
input {
608639
background: transparent;
609640
border: none;
610-
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
641+
border-bottom: 2px solid
642+
${(props) => (props.$error ? "var(--error-color)" : "rgba(255, 255, 255, 0.2)")};
611643
padding: 0.25rem 0;
612644
font-size: ${(props) => props.$fontSize || "1rem"};
613645
font-weight: ${(props) => props.$fontWeight || "normal"};
614646
color: rgba(255, 255, 255, 0.95);
615647
width: 100%;
648+
transition: border-bottom-color 0.2s ease;
616649
617650
&::placeholder {
618651
color: rgba(255, 255, 255, 0.5);
619652
}
620653
621654
&:focus {
622655
outline: none;
623-
border-bottom-color: rgba(156, 163, 255, 0.8);
656+
border-bottom-color: ${(props) =>
657+
props.$error ? "var(--error-color)" : "rgba(156, 163, 255, 0.8)"};
624658
background: rgba(255, 255, 255, 0.05);
625659
}
626660
}
@@ -632,3 +666,23 @@ const NametagDisplayText = styled.div<{ $fontSize?: string; $fontWeight?: string
632666
color: rgba(255, 255, 255, 0.95);
633667
padding: 0.25rem 0;
634668
`
669+
670+
const RequiredAsterisk = styled.span`
671+
color: var(--error-color);
672+
font-weight: 700;
673+
`
674+
675+
const PhotoRequiredLabel = styled.div`
676+
color: rgba(255, 255, 255, 0.7);
677+
font-size: 0.75rem;
678+
font-weight: 500;
679+
text-align: center;
680+
margin-top: 0.5rem;
681+
`
682+
683+
const FieldError = styled.p`
684+
color: var(--error-color);
685+
font-size: 1.1rem;
686+
font-weight: 500;
687+
margin-top: 0.5rem;
688+
`

app/components/TextInput.tsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,26 @@ type BaseInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">
99
interface TextInputProps extends BaseInputProps {
1010
variant?: "primary" | "secondary"
1111
size?: "small" | "default"
12+
error?: boolean
1213
}
1314

1415
// Components //
1516

1617
export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
17-
({ variant = "secondary", size = "small", ...props }, ref) => {
18-
return <StyledInput ref={ref} $variant={variant} $size={size} {...props} />
18+
({ variant = "secondary", size = "small", error = false, ...props }, ref) => {
19+
return <StyledInput ref={ref} $variant={variant} $size={size} $error={error} {...props} />
1920
}
2021
)
2122

2223
TextInput.displayName = "TextInput"
2324

2425
// Styled Components //
2526

26-
const StyledInput = styled.input<{ $variant: "primary" | "secondary"; $size: "small" | "default" }>`
27+
const StyledInput = styled.input<{
28+
$variant: "primary" | "secondary"
29+
$size: "small" | "default"
30+
$error?: boolean
31+
}>`
2732
padding: ${(props) => (props.$size === "small" ? "0.5rem 1rem" : "0.75rem 1.5rem")};
2833
border-radius: 0.25rem;
2934
font-weight: ${(props) => (props.$size === "small" ? "500" : "600")};
@@ -35,14 +40,31 @@ const StyledInput = styled.input<{ $variant: "primary" | "secondary"; $size: "sm
3540
box-sizing: border-box;
3641
background-color: ${(props) => (props.$variant === "primary" ? "white" : "transparent")};
3742
color: ${(props) => (props.$variant === "primary" ? "black" : "white")};
38-
border: ${(props) =>
39-
props.$variant === "secondary"
40-
? "1px solid rgba(255, 255, 255, 0.3)"
41-
: "1px solid rgba(0, 0, 0, 0.2)"};
43+
border: ${(props) => {
44+
if (props.$error) {
45+
return "1px solid var(--error-color)"
46+
}
47+
48+
if (props.$variant === "secondary") {
49+
return "1px solid rgba(255, 255, 255, 0.3)"
50+
}
51+
52+
return "1px solid rgba(0, 0, 0, 0.2)"
53+
}};
4254
4355
&:focus {
4456
outline: none;
45-
border-color: ${(props) => (props.$variant === "secondary" ? "white" : "rgba(0, 0, 0, 0.4)")};
57+
border-color: ${(props) => {
58+
if (props.$error) {
59+
return "var(--error-color)"
60+
}
61+
62+
if (props.$variant === "secondary") {
63+
return "white"
64+
}
65+
66+
return "rgba(0, 0, 0, 0.4)"
67+
}};
4668
background-color: ${(props) =>
4769
props.$variant === "primary" ? "white" : "rgba(255, 255, 255, 0.05)"};
4870
}

app/globals.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
@tailwind components;
33
@tailwind utilities;
44

5+
:root {
6+
--error-color: #f87171;
7+
}
58
:root {
69
--b1: #000000;
710
--n: 21% 0.003 296.813;

0 commit comments

Comments
 (0)