ProfileCard
A person's account card (avatar, name, role, presence, local time, team, quick actions) that opens from any avatar, name or mention on hover, focus or tap, plus the mention chip and presence dot it is built from.
Code
import { Avatar, ProfileHoverCard, type PersonProfile } from "@fadymondy/nasaq/web";const sara: PersonProfile = { id: "u1", name: "Sara Nasser", handle: "sara", role: "Design lead", presence: "online", timeZone: "Asia/Riyadh", teams: ["Design"] };export function Author() { return ( <ProfileHoverCard person={sara} onMessage={(p) => openChat(p.id)} onViewProfile={(p) => go(`/people/${p.id}`)}> <a href="/people/u1">Sara Nasser</a> </ProfileHoverCard> );}declare function openChat(id: string): void;declare function go(path: string): void;Account · beta
Live examples and controls: ProfileCard in the lab.
Install
npx shadcn@latest add https://docs.nasaqui.com/r/profile-card.jsonOne card for a person, used everywhere a person shows up. ProfileCard is the card itself. ProfileHoverCard wraps any
avatar, name or @mention so the card opens from it. MentionChip and MentionText render @name in saved text. UserMenu
and MembersManager reuse the same card, so a person looks the same in the menu, in lists and in comments.
When to use
- A person's name or avatar appears in a list, comment, activity feed or table and you want a peek without leaving.
- Showing saved text that contains mentions.
- A presence indicator next to an avatar.
When not to use
- A full profile page: link to it with
onViewProfile. - A team or group:
MentionChipshows them with an icon and no card.
Import
import { MentionChip, MentionText, PresenceAvatar, ProfileCard, ProfileHoverCard } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"Quick start
import { Avatar, ProfileHoverCard, type PersonProfile } from "@fadymondy/nasaq/web";
const sara: PersonProfile = { id: "u1", name: "Sara Nasser", handle: "sara", role: "Design lead", presence: "online", timeZone: "Asia/Riyadh", teams: ["Design"] };
export function Author() {
return (
<ProfileHoverCard person={sara} onMessage={(p) => openChat(p.id)} onViewProfile={(p) => go(`/people/${p.id}`)}>
<a href="/people/u1">Sara Nasser</a>
</ProfileHoverCard>
);
}
declare function openChat(id: string): void;
declare function go(path: string): void;Anatomy
ProfileHoverCard trigger + card
└─ ProfileCard data-slot="profile-card"
├─ PresenceAvatar avatar with a PresenceDot
├─ name, @handle, role, status text
├─ local time and how far apart you are
├─ team badges
└─ Message · Mention · View profile (only the ones you pass)
MentionChip / MentionText inline @nameAPI
PersonProfile: { id, name, handle?, email?, avatar?, role?, presence?, statusText?, timeZone?, teams?, location? }.
presence is online, away, busy or offline.
ProfileCard: div props plus person, viewerTimeZone (default the browser's), now (freeze the clock), onMessage,
onMention, onViewProfile (each adds its button), children (extra content) and labels.
ProfileHoverCard: everything above plus children (the trigger), delay (300), closeDelay (150), side, align, className.
PresenceDot / PresenceAvatar: presence, decorative, labels. An avatar plus a dot, sized with size.
MentionChip: name, kind (person, team, group), person (opens the card), the action callbacks.
MentionText: text, mentions (from MentionTextarea), resolve(id) giving { person?, kind? }, the action callbacks.
Pure helpers (isTimeZone, offsetFrom, localTimeLabel, isNightIn, describeOffset, presenceRank) are exported too.
Examples
Saved comment with mention chips
import { MentionText, type PersonProfile } from "@fadymondy/nasaq/web";
declare const people: Record<string, PersonProfile>;
export const Comment = () => (
<MentionText
text="Thanks @Sara, looping in @Design"
mentions={[
{ id: "u1", name: "Sara", start: 7, end: 12 },
{ id: "t1", name: "Design", start: 25, end: 32 },
]}
resolve={(id) => (id === "t1" ? { kind: "team" } : { person: people[id] })}
/>
);Accessibility
- Opens on hover, keyboard focus and tap. Escape closes it. The trigger stays a real link or button.
- Presence is never colour alone: each state has a different shape (offline is a hollow ring) and text for screen readers.
- Local time uses Latin digits so it reads the same in every locale.
RTL & i18n
- English and Arabic are built in and follow the Nasaq locale; pass
labelsto override. - The card mirrors with logical spacing. Emails and handles stay left to right inside
<bdi>.
Styling & tokens
- Tokens only. Built from
HoverCard,Avatar,BadgeandButton.
Do / Don't
- Do pass the viewer's time zone when you know it, for an accurate "3 h ahead".
- Do give the trigger a real link when the person has a profile page.
- Don't show a card for a team: use the chip's icon.
- Don't rely on presence colour alone in your own UI.
Related
Lab
https://docs.nasaqui.com/?path=/docs/components-account-profile-card--docs
NotificationPreferences
The notification settings screen. A matrix of kinds by channel (email, push, WhatsApp, desktop), quiet hours, a daily cap with batching, a digest schedule and extra email or webhook destinations with a test send. Every change saves at once and rolls back if it fails.
ProfileForm
The profile settings form. Photo, display name, username with a live availability check, email with a verified badge, resend and a password-protected change, phone, bio, language and time zone, and a sticky Save / Discard bar that shows only when something changed.