-
Notifications
You must be signed in to change notification settings - Fork 90
Refined View Profile modal — wider layout, backdrop close support #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { mockProfileDetails, mockWhyMatch } from "./mockProfileData"; | |||||||||||||||||||||||||||||
| import { Button } from "../ui/button"; | ||||||||||||||||||||||||||||||
| import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; | ||||||||||||||||||||||||||||||
| import { Badge } from "../ui/badge"; | ||||||||||||||||||||||||||||||
| import { X } from "lucide-react"; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| interface WhyMatchReason { | ||||||||||||||||||||||||||||||
| point: string; | ||||||||||||||||||||||||||||||
|
|
@@ -19,66 +20,151 @@ interface ViewProfileModalProps { | |||||||||||||||||||||||||||||
| whyMatch?: WhyMatchReason[]; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const ViewProfileModal: React.FC<ViewProfileModalProps> = ({ open, onClose, onConnect, matchPercentage = defaultMatch, whyMatch = mockWhyMatch }) => { | ||||||||||||||||||||||||||||||
| const ViewProfileModal: React.FC<ViewProfileModalProps> = ({ | ||||||||||||||||||||||||||||||
| open, | ||||||||||||||||||||||||||||||
| onClose, | ||||||||||||||||||||||||||||||
| onConnect, | ||||||||||||||||||||||||||||||
| matchPercentage = defaultMatch, | ||||||||||||||||||||||||||||||
| whyMatch = mockWhyMatch, | ||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||
| if (!open) return null; | ||||||||||||||||||||||||||||||
| const profile = mockProfileDetails; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => { | ||||||||||||||||||||||||||||||
| if (e.target === e.currentTarget) onClose(); | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||
| <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-20"> | ||||||||||||||||||||||||||||||
| <div className="bg-white rounded-2xl shadow-xl w-full max-w-md p-6 relative flex flex-col"> | ||||||||||||||||||||||||||||||
| <button className="absolute top-4 right-4 text-gray-400 hover:text-gray-700" onClick={onClose} aria-label="Close"> | ||||||||||||||||||||||||||||||
| × | ||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||
| <div className="flex flex-col items-center mb-4"> | ||||||||||||||||||||||||||||||
| <Badge className="bg-yellow-100 text-yellow-700 text-sm font-semibold px-3 py-1 mb-2"> | ||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||
| className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" | ||||||||||||||||||||||||||||||
| onClick={handleOverlayClick} | ||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||
| {/* Modal container */} | ||||||||||||||||||||||||||||||
| <div className="relative w-full max-w-3xl bg-white dark:bg-gray-900 rounded-3xl shadow-2xl overflow-y-auto max-h-[90vh] p-10 mx-4 animate-in fade-in-0 zoom-in-95 border border-gray-100 dark:border-gray-700"> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| {/* Close button */} | ||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||
| onClick={onClose} | ||||||||||||||||||||||||||||||
| aria-label="Close" | ||||||||||||||||||||||||||||||
| className="absolute top-6 right-6 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-all" | ||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||
| <X className="h-6 w-6" /> | ||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+52
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add cursor pointer styling to the close button. The close button lacks Apply this diff: - className="absolute top-6 right-6 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-all"
+ className="absolute top-6 right-6 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-all cursor-pointer"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| {/* Header Section */} | ||||||||||||||||||||||||||||||
| <div className="flex flex-col items-center text-center mb-6"> | ||||||||||||||||||||||||||||||
| <Badge className="bg-yellow-100 text-yellow-700 text-sm font-semibold px-3 py-1 mb-3"> | ||||||||||||||||||||||||||||||
| {matchPercentage}% Match | ||||||||||||||||||||||||||||||
| </Badge> | ||||||||||||||||||||||||||||||
| <Avatar className="h-20 w-20 mb-2"> | ||||||||||||||||||||||||||||||
| <Avatar className="h-24 w-24 mb-3 shadow-md ring-2 ring-yellow-400"> | ||||||||||||||||||||||||||||||
| <AvatarImage src={profile.avatar} alt={profile.name} /> | ||||||||||||||||||||||||||||||
| <AvatarFallback className="bg-gray-200">{profile.name.slice(0,2).toUpperCase()}</AvatarFallback> | ||||||||||||||||||||||||||||||
| <AvatarFallback className="bg-gray-200"> | ||||||||||||||||||||||||||||||
| {profile.name.slice(0, 2).toUpperCase()} | ||||||||||||||||||||||||||||||
| </AvatarFallback> | ||||||||||||||||||||||||||||||
| </Avatar> | ||||||||||||||||||||||||||||||
| <h2 className="text-2xl font-bold text-gray-900 text-center">{profile.name}</h2> | ||||||||||||||||||||||||||||||
| <div className="text-gray-500 text-sm text-center">{profile.contentType} • {profile.location}</div> | ||||||||||||||||||||||||||||||
| <h2 className="text-3xl font-extrabold text-gray-900 dark:text-white"> | ||||||||||||||||||||||||||||||
| {profile.name} | ||||||||||||||||||||||||||||||
| </h2> | ||||||||||||||||||||||||||||||
| <p className="text-gray-500 dark:text-gray-400 text-sm"> | ||||||||||||||||||||||||||||||
| {profile.contentType} • {profile.location} | ||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| <div className="text-gray-700 text-center mb-4">{profile.bio}</div> | ||||||||||||||||||||||||||||||
| <div className="flex justify-center gap-4 mb-4"> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| {/* Bio */} | ||||||||||||||||||||||||||||||
| <p className="text-gray-700 dark:text-gray-300 text-center mb-6 leading-relaxed"> | ||||||||||||||||||||||||||||||
| {profile.bio} | ||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| {/* Social Links */} | ||||||||||||||||||||||||||||||
| <div className="flex justify-center gap-5 mb-8"> | ||||||||||||||||||||||||||||||
| {profile.socialLinks.map((link, idx) => ( | ||||||||||||||||||||||||||||||
| <a key={idx} href={link.url} target="_blank" rel="noopener noreferrer" className="text-gray-500 hover:text-gray-900" title={link.platform}> | ||||||||||||||||||||||||||||||
| {/* icon rendering handled in parent */} | ||||||||||||||||||||||||||||||
| <a | ||||||||||||||||||||||||||||||
| key={idx} | ||||||||||||||||||||||||||||||
| href={link.url} | ||||||||||||||||||||||||||||||
| target="_blank" | ||||||||||||||||||||||||||||||
| rel="noopener noreferrer" | ||||||||||||||||||||||||||||||
| title={link.platform} | ||||||||||||||||||||||||||||||
| className="text-gray-500 hover:text-yellow-500 transition-transform hover:scale-110" | ||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||
| {link.icon === "instagram" && ( | ||||||||||||||||||||||||||||||
| <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><rect width="20" height="20" x="2" y="2" rx="5"/><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/><line x1="17.5" x2="17.51" y1="6.5" y2="6.5"/></svg> | ||||||||||||||||||||||||||||||
| <svg | ||||||||||||||||||||||||||||||
| className="w-6 h-6" | ||||||||||||||||||||||||||||||
| fill="none" | ||||||||||||||||||||||||||||||
| stroke="currentColor" | ||||||||||||||||||||||||||||||
| strokeWidth="2" | ||||||||||||||||||||||||||||||
| viewBox="0 0 24 24" | ||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||
| <rect width="20" height="20" x="2" y="2" rx="5" /> | ||||||||||||||||||||||||||||||
| <path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" /> | ||||||||||||||||||||||||||||||
| <line x1="17.5" x2="17.51" y1="6.5" y2="6.5" /> | ||||||||||||||||||||||||||||||
| </svg> | ||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||
| {link.icon === "youtube" && ( | ||||||||||||||||||||||||||||||
| <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M21.8 8.001a2.752 2.752 0 0 0-1.938-1.948C18.003 6 12 6 12 6s-6.003 0-7.862.053A2.752 2.752 0 0 0 2.2 8.001 28.934 28.934 0 0 0 2 12a28.934 28.934 0 0 0 .2 3.999 2.752 2.752 0 0 0 1.938 1.948C5.997 18 12 18 12 18s6.003 0 7.862-.053a2.752 2.752 0 0 0 1.938-1.948A28.934 28.934 0 0 0 22 12a28.934 28.934 0 0 0-.2-3.999zM10 15V9l5 3-5 3z"/></svg> | ||||||||||||||||||||||||||||||
| <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"> | ||||||||||||||||||||||||||||||
| <path d="M21.8 8.001a2.752 2.752 0 0 0-1.938-1.948C18.003 6 12 6 12 6s-6.003 0-7.862.053A2.752 2.752 0 0 0 2.2 8.001 28.934 28.934 0 0 0 2 12a28.934 28.934 0 0 0 .2 3.999 2.752 2.752 0 0 0 1.938 1.948C5.997 18 12 18 12 18s6.003 0 7.862-.053a2.752 2.752 0 0 0 1.938-1.948A28.934 28.934 0 0 0 22 12a28.934 28.934 0 0 0-.2-3.999zM10 15V9l5 3-5 3z" /> | ||||||||||||||||||||||||||||||
| </svg> | ||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||
| {link.icon === "twitter" && ( | ||||||||||||||||||||||||||||||
| <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53A4.48 4.48 0 0 0 22.4.36a9.09 9.09 0 0 1-2.88 1.1A4.52 4.52 0 0 0 16.11 0c-2.5 0-4.52 2.02-4.52 4.52 0 .35.04.7.11 1.03C7.69 5.4 4.07 3.67 1.64.9c-.38.65-.6 1.4-.6 2.2 0 1.52.77 2.86 1.95 3.65A4.48 4.48 0 0 1 .96 6v.06c0 2.13 1.52 3.91 3.54 4.31-.37.1-.76.16-1.16.16-.28 0-.55-.03-.81-.08.55 1.7 2.16 2.94 4.07 2.97A9.06 9.06 0 0 1 0 20.29a12.8 12.8 0 0 0 6.95 2.04c8.34 0 12.9-6.91 12.9-12.9 0-.2 0-.39-.01-.58A9.22 9.22 0 0 0 23 3z"/></svg> | ||||||||||||||||||||||||||||||
| <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"> | ||||||||||||||||||||||||||||||
| <path d="M23 3a10.9 10.9 0 0 1-3.14 1.53A4.48 4.48 0 0 0 22.4.36a9.09 9.09 0 0 1-2.88 1.1A4.52 4.52 0 0 0 16.11 0c-2.5 0-4.52 2.02-4.52 4.52 0 .35.04.7.11 1.03C7.69 5.4 4.07 3.67 1.64.9c-.38.65-.6 1.4-.6 2.2 0 1.52.77 2.86 1.95 3.65A4.48 4.48 0 0 1 .96 6v.06c0 2.13 1.52 3.91 3.54 4.31-.37.1-.76.16-1.16.16-.28 0-.55-.03-.81-.08.55 1.7 2.16 2.94 4.07 2.97A9.06 9.06 0 0 1 0 20.29a12.8 12.8 0 0 0 6.95 2.04c8.34 0 12.9-6.91 12.9-12.9 0-.2 0-.39-.01-.58A9.22 9.22 0 0 0 23 3z" /> | ||||||||||||||||||||||||||||||
| </svg> | ||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| <div className="grid grid-cols-2 gap-4 text-xs text-gray-700 mb-4"> | ||||||||||||||||||||||||||||||
| <div><span className="font-semibold">Followers</span><br />{profile.followers}</div> | ||||||||||||||||||||||||||||||
| <div><span className="font-semibold">Engagement</span><br />{profile.engagement}</div> | ||||||||||||||||||||||||||||||
| <div><span className="font-semibold">Content</span><br />{profile.content}</div> | ||||||||||||||||||||||||||||||
| <div><span className="font-semibold">Collabs</span><br />{profile.collabs} completed</div> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| {/* Stats */} | ||||||||||||||||||||||||||||||
| <div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm text-gray-800 dark:text-gray-300 mb-6"> | ||||||||||||||||||||||||||||||
| <div className="text-center"> | ||||||||||||||||||||||||||||||
| <div className="font-bold text-lg">{profile.followers}</div> | ||||||||||||||||||||||||||||||
| <div className="text-gray-500 text-xs">Followers</div> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| <div className="text-center"> | ||||||||||||||||||||||||||||||
| <div className="font-bold text-lg">{profile.engagement}</div> | ||||||||||||||||||||||||||||||
| <div className="text-gray-500 text-xs">Engagement</div> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| <div className="text-center"> | ||||||||||||||||||||||||||||||
| <div className="font-bold text-lg">{profile.content}</div> | ||||||||||||||||||||||||||||||
| <div className="text-gray-500 text-xs">Content</div> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| <div className="text-center"> | ||||||||||||||||||||||||||||||
| <div className="font-bold text-lg">{profile.collabs}</div> | ||||||||||||||||||||||||||||||
| <div className="text-gray-500 text-xs">Collabs</div> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| <div className="mb-4 bg-gray-50 border border-gray-100 rounded-lg p-3"> | ||||||||||||||||||||||||||||||
| <div className="font-semibold text-sm mb-1">Why you match</div> | ||||||||||||||||||||||||||||||
| <ul className="space-y-3"> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| {/* Why Match Section */} | ||||||||||||||||||||||||||||||
| <div className="mb-8 bg-gray-50 dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-xl p-5 shadow-sm"> | ||||||||||||||||||||||||||||||
| <h3 className="font-semibold text-gray-800 dark:text-white text-lg mb-3"> | ||||||||||||||||||||||||||||||
| Why You Match | ||||||||||||||||||||||||||||||
| </h3> | ||||||||||||||||||||||||||||||
| <ul className="space-y-4"> | ||||||||||||||||||||||||||||||
| {whyMatch.map((reason, idx) => ( | ||||||||||||||||||||||||||||||
| <li key={idx}> | ||||||||||||||||||||||||||||||
| <div className="font-semibold text-xs text-gray-800 mb-1">{reason.point}</div> | ||||||||||||||||||||||||||||||
| <div className="text-xs text-gray-600 pl-2">{reason.description}</div> | ||||||||||||||||||||||||||||||
| <p className="font-semibold text-sm text-gray-900 dark:text-gray-100"> | ||||||||||||||||||||||||||||||
| {reason.point} | ||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||
| <p className="text-sm text-gray-600 dark:text-gray-400 pl-2 leading-snug"> | ||||||||||||||||||||||||||||||
| {reason.description} | ||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||
| </li> | ||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||
| </ul> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| <Button className="bg-yellow-400 text-white hover:bg-yellow-500 font-semibold rounded-full py-2 mt-2" onClick={onConnect}> | ||||||||||||||||||||||||||||||
| Connect | ||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| {/* Connect Button */} | ||||||||||||||||||||||||||||||
| <div className="flex justify-center"> | ||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||
| className="bg-yellow-400 hover:bg-yellow-500 text-white font-semibold rounded-full py-2 px-8 shadow-md transition-all" | ||||||||||||||||||||||||||||||
| onClick={onConnect} | ||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||
| Connect | ||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export default ViewProfileModal; | ||||||||||||||||||||||||||||||
| export default ViewProfileModal; | ||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add essential accessibility features for the modal dialog.
The modal is missing critical accessibility features:
role="dialog"andaria-modal="true"attributes required for screen readersApply these changes to improve accessibility:
1. Add ARIA attributes to the modal container:
2. Add Escape key handler (add after the component function opens, before the return):
if (!open) return null; const profile = mockProfileDetails; const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => { if (e.target === e.currentTarget) onClose(); }; + + React.useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [onClose]);3. Add id to the heading for aria-labelledby:
Note: For focus trapping, consider using a library like
focus-trap-reactor implementing a custom focus trap to cycle focus within the modal.📝 Committable suggestion
🤖 Prompt for AI Agents