@@ -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
2833export 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+ `
0 commit comments