feat(app): updates to project avatar (#30964)

This commit is contained in:
Aarav Sareen 2026-06-06 03:43:04 +05:30 committed by GitHub
commit 24347f336c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 77 additions and 33 deletions

View file

@ -1,14 +1,24 @@
[data-component="project-avatar-v2"] { [data-component="project-avatar-v2"] {
position: relative;
flex-shrink: 0;
width: 16px;
height: 16px;
}
[data-component="project-avatar-v2"][data-unread] {
overflow: visible;
}
[data-slot="project-avatar-surface"] {
--project-avatar-bg: var(--v2-avatar-bg-gray); --project-avatar-bg: var(--v2-avatar-bg-gray);
--project-avatar-border: var(--v2-avatar-border-gray); --project-avatar-border: var(--v2-avatar-border-gray);
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
flex-shrink: 0; width: 100%;
height: 100%;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 16px;
height: 16px;
overflow: hidden; overflow: hidden;
border-radius: 4px; border-radius: 4px;
background: background:
@ -26,56 +36,56 @@
-webkit-user-select: none; -webkit-user-select: none;
} }
[data-component="project-avatar-v2"][data-variant="orange"] { [data-slot="project-avatar-surface"][data-variant="orange"] {
--project-avatar-bg: var(--v2-avatar-bg-orange); --project-avatar-bg: var(--v2-avatar-bg-orange);
--project-avatar-border: var(--v2-avatar-border-orange); --project-avatar-border: var(--v2-avatar-border-orange);
} }
[data-component="project-avatar-v2"][data-variant="yellow"] { [data-slot="project-avatar-surface"][data-variant="yellow"] {
--project-avatar-bg: var(--v2-avatar-bg-yellow); --project-avatar-bg: var(--v2-avatar-bg-yellow);
--project-avatar-border: var(--v2-avatar-border-yellow); --project-avatar-border: var(--v2-avatar-border-yellow);
} }
[data-component="project-avatar-v2"][data-variant="cyan"] { [data-slot="project-avatar-surface"][data-variant="cyan"] {
--project-avatar-bg: var(--v2-avatar-bg-cyan); --project-avatar-bg: var(--v2-avatar-bg-cyan);
--project-avatar-border: var(--v2-avatar-border-cyan); --project-avatar-border: var(--v2-avatar-border-cyan);
} }
[data-component="project-avatar-v2"][data-variant="green"] { [data-slot="project-avatar-surface"][data-variant="green"] {
--project-avatar-bg: var(--v2-avatar-bg-green); --project-avatar-bg: var(--v2-avatar-bg-green);
--project-avatar-border: var(--v2-avatar-border-green); --project-avatar-border: var(--v2-avatar-border-green);
} }
[data-component="project-avatar-v2"][data-variant="red"] { [data-slot="project-avatar-surface"][data-variant="red"] {
--project-avatar-bg: var(--v2-avatar-bg-red); --project-avatar-bg: var(--v2-avatar-bg-red);
--project-avatar-border: var(--v2-avatar-border-red); --project-avatar-border: var(--v2-avatar-border-red);
} }
[data-component="project-avatar-v2"][data-variant="pink"] { [data-slot="project-avatar-surface"][data-variant="pink"] {
--project-avatar-bg: var(--v2-avatar-bg-pink); --project-avatar-bg: var(--v2-avatar-bg-pink);
--project-avatar-border: var(--v2-avatar-border-pink); --project-avatar-border: var(--v2-avatar-border-pink);
} }
[data-component="project-avatar-v2"][data-variant="blue"] { [data-slot="project-avatar-surface"][data-variant="blue"] {
--project-avatar-bg: var(--v2-avatar-bg-blue); --project-avatar-bg: var(--v2-avatar-bg-blue);
--project-avatar-border: var(--v2-avatar-border-blue); --project-avatar-border: var(--v2-avatar-border-blue);
} }
[data-component="project-avatar-v2"][data-variant="purple"] { [data-slot="project-avatar-surface"][data-variant="purple"] {
--project-avatar-bg: var(--v2-avatar-bg-purple); --project-avatar-bg: var(--v2-avatar-bg-purple);
--project-avatar-border: var(--v2-avatar-border-purple); --project-avatar-border: var(--v2-avatar-border-purple);
} }
[data-component="project-avatar-v2"][data-variant="gray"] { [data-slot="project-avatar-surface"][data-variant="gray"] {
--project-avatar-bg: var(--v2-avatar-bg-gray); --project-avatar-bg: var(--v2-avatar-bg-gray);
--project-avatar-border: var(--v2-avatar-border-gray); --project-avatar-border: var(--v2-avatar-border-gray);
} }
[data-component="project-avatar-v2"][data-has-image] { [data-slot="project-avatar-surface"][data-has-image] {
background: var(--project-avatar-bg); background: var(--project-avatar-bg);
} }
[data-component="project-avatar-v2"] [data-slot="project-avatar-image"] { [data-slot="project-avatar-surface"] [data-slot="project-avatar-image"] {
position: relative; position: relative;
z-index: 1; z-index: 1;
display: block; display: block;
@ -88,11 +98,11 @@
-webkit-user-drag: none; -webkit-user-drag: none;
} }
[data-component="project-avatar-v2"] [data-slot="project-avatar-loader"] { [data-slot="project-avatar-surface"] [data-slot="project-avatar-loader"] {
position: absolute; position: absolute;
inset: 0; inset: -3px;
z-index: 2; z-index: 2;
border-radius: 4px; border-radius: 0;
background: conic-gradient( background: conic-gradient(
from 180deg at 50% 50%, from 180deg at 50% 50%,
var(--v2-grey-100) 0deg, var(--v2-grey-100) 0deg,
@ -111,6 +121,27 @@
} }
} }
[data-component="project-avatar-v2"][data-unread] [data-slot="project-avatar-surface"] {
-webkit-mask-image: radial-gradient(
circle 4.5px at calc(100% - 1px) 1px,
transparent 4.5px,
black 4.5px
);
mask-image: radial-gradient(circle 4.5px at calc(100% - 1px) 1px, transparent 4.5px, black 4.5px);
}
[data-slot="project-avatar-unread-dot"] {
position: absolute;
z-index: 3;
width: 6px;
height: 6px;
right: -2px;
top: -2px;
border-radius: 9999px;
background: var(--v2-background-bg-accent);
pointer-events: none;
}
[data-slot="project-avatar-slot"] { [data-slot="project-avatar-slot"] {
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;
@ -120,9 +151,3 @@
height: 22px; height: 22px;
overflow: visible; overflow: visible;
} }
[data-component="project-avatar-v2"][data-unread] {
overflow: visible;
outline: 2px solid var(--v2-background-bg-accent);
outline-offset: 1px;
}

View file

@ -3,7 +3,7 @@ import { For } from "solid-js"
import { ProjectAvatar, PROJECT_AVATAR_VARIANTS } from "./project-avatar-v2" import { ProjectAvatar, PROJECT_AVATAR_VARIANTS } from "./project-avatar-v2"
const docs = `### Overview const docs = `### Overview
Saturated 16px project avatar with color variants and optional unread ring. Saturated 16px project avatar with color variants and optional unread dot.
### API ### API
- Required: \`fallback\` string. - Required: \`fallback\` string.
@ -12,7 +12,7 @@ Saturated 16px project avatar with color variants and optional unread ring.
### Variants ### Variants
- Color: orange, yellow, cyan, green, red, pink, blue, purple, gray. - Color: orange, yellow, cyan, green, red, pink, blue, purple, gray.
- Image vs initial content state. - Image vs initial content state.
- Unread ring when \`unread\` is set. - Unread dot with corner mask when \`unread\` is set.
### Theming ### Theming
- Uses \`--v2-avatar-bg-*\` and \`--v2-avatar-border-*\` tokens with inset box-shadow borders. - Uses \`--v2-avatar-bg-*\` and \`--v2-avatar-border-*\` tokens with inset box-shadow borders.
@ -70,6 +70,18 @@ export const Unread = {
}, },
} }
export const AllVariantsUnread = {
render: () => (
<div style={{ display: "flex", gap: "16px", "align-items": "center" }}>
<For each={PROJECT_AVATAR_VARIANTS}>
{(variant) => (
<ProjectAvatar fallback={variant[0].toUpperCase()} variant={variant} unread />
)}
</For>
</div>
),
}
export const Loading = { export const Loading = {
args: { args: {
fallback: "O", fallback: "O",

View file

@ -50,21 +50,28 @@ export function ProjectAvatar(props: ProjectAvatarProps) {
<div <div
{...rest} {...rest}
data-component="project-avatar-v2" data-component="project-avatar-v2"
data-variant={split.variant ?? "gray"}
data-has-image={src ? "" : undefined}
data-unread={split.unread ? "" : undefined} data-unread={split.unread ? "" : undefined}
data-loading={split.loading ? "" : undefined}
classList={{ classList={{
...split.classList, ...split.classList,
[split.class ?? ""]: !!split.class, [split.class ?? ""]: !!split.class,
}} }}
style={typeof split.style === "object" ? split.style : undefined} style={typeof split.style === "object" ? split.style : undefined}
> >
<Show when={src} fallback={first(split.fallback)}> <div
{(value) => <img src={value()} draggable={false} data-slot="project-avatar-image" />} data-slot="project-avatar-surface"
</Show> data-variant={split.variant ?? "gray"}
<Show when={split.loading}> data-has-image={src ? "" : undefined}
<span data-slot="project-avatar-loader" aria-hidden="true" /> data-loading={split.loading ? "" : undefined}
>
<Show when={src} fallback={first(split.fallback)}>
{(value) => <img src={value()} draggable={false} data-slot="project-avatar-image" />}
</Show>
<Show when={split.loading}>
<span data-slot="project-avatar-loader" aria-hidden="true" />
</Show>
</div>
<Show when={split.unread}>
<span data-slot="project-avatar-unread-dot" aria-hidden="true" />
</Show> </Show>
</div> </div>
) )