Skip to content

Commit 625ae0e

Browse files
feat(avatar): set shadcn styling
1 parent 947eba2 commit 625ae0e

File tree

3 files changed

+101
-31
lines changed

3 files changed

+101
-31
lines changed

src/components/Avatar/Avatar.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const Single: Story = {
3131
export const Group: Story = {
3232
args: {
3333
name: "Dan Abrahmov",
34-
src: "https://bit.ly/dan-abramov",
34+
src: "https://bit.ly/dan-abramo",
3535
href: "#",
3636
},
3737
render: (args) => (

src/components/ui/__stories__/Avatar.stories.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Meta, StoryObj } from "@storybook/react"
22

33
import { Avatar } from "../avatar"
4-
import { HStack } from "../flex"
4+
import { HStack, VStack } from "../flex"
55

66
const meta = {
77
title: "Atoms / Media & Icons / UI Avatars",
@@ -18,7 +18,13 @@ export const Single: Story = {
1818
src: "https://bit.ly/dan-abramov",
1919
href: "#",
2020
},
21-
render: (args) => <Avatar {...args} />,
21+
render: (args) => (
22+
<VStack className="gap-4">
23+
{(["lg", "md", "sm", "xs"] as const).map((size) => (
24+
<Avatar key={size} size={size} {...args} />
25+
))}
26+
</VStack>
27+
),
2228
}
2329

2430
export const WithUsername: Story = {
@@ -30,8 +36,16 @@ export const WithUsername: Story = {
3036
},
3137
render: (args) => (
3238
<HStack className="gap-4">
33-
<Avatar {...args} />
34-
<Avatar {...args} direction="column" />
39+
<VStack>
40+
{(["md", "sm"] as const).map((size, idx) => (
41+
<Avatar key={idx} size={size} {...args} />
42+
))}
43+
</VStack>
44+
<VStack>
45+
{(["md", "sm"] as const).map((size, idx) => (
46+
<Avatar key={idx} size={size} direction="column" {...args} />
47+
))}
48+
</VStack>
3549
</HStack>
3650
),
3751
}

src/components/ui/avatar.tsx

Lines changed: 82 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as React from "react"
22
import upperCase from "lodash/upperCase"
3+
import { tv, type VariantProps } from "tailwind-variants"
34
import * as AvatarPrimitive from "@radix-ui/react-avatar"
45

56
import { cn } from "@/lib/utils/cn"
@@ -8,20 +9,67 @@ import { Center } from "./flex"
89
import { BaseLink, type LinkProps } from "./Link"
910
import { LinkBox, LinkOverlay } from "./link-box"
1011

11-
type AvatarBaseProps = React.ComponentProps<typeof AvatarPrimitive.Root>
12+
const avatarStyles = tv({
13+
slots: {
14+
container:
15+
"relative shrink-0 flex overflow-hidden rounded-full focus:outline-4 focus:-outline-offset-1 focus:rounded-full active:shadow-none [&_img]:hover:opacity-70 border border-transparent active:border-primary-hover",
16+
fallback: "bg-body text-body-inverse",
17+
},
18+
variants: {
19+
size: {
20+
xs: {
21+
container:
22+
"size-6 hover:shadow-[2px_2px_0_var(--avatar-base-shadow-color)] peer-hover:shadow-[2px_2px_0_var(--avatar-base-shadow-color)]",
23+
fallback: "text-xs",
24+
},
25+
sm: {
26+
container:
27+
"size-8 hover:shadow-[2px_2px_0_var(--avatar-base-shadow-color)] peer-hover:shadow-[2px_2px_0_var(--avatar-base-shadow-color)]",
28+
fallback: "text-sm",
29+
},
30+
md: {
31+
container:
32+
"size-12 hover:shadow-[4px_4px_0_var(--avatar-base-shadow-color)] peer-hover:shadow-[4px_4px_0_var(--avatar-base-shadow-color)]",
33+
fallback: "text-lg",
34+
},
35+
lg: {
36+
container:
37+
"size-16 hover:shadow-[4px_4px_0_var(--avatar-base-shadow-color)] peer-hover:shadow-[4px_4px_0_var(--avatar-base-shadow-color)]",
38+
fallback: "text-2xl",
39+
},
40+
},
41+
},
42+
defaultVariants: {
43+
size: "md",
44+
},
45+
})
46+
47+
type AvatarVariantProps = VariantProps<typeof avatarStyles>
48+
49+
const AvatarStylesContext =
50+
React.createContext<ReturnType<typeof avatarStyles>>(avatarStyles())
51+
52+
const useAvatarStyles = () => React.useContext(AvatarStylesContext)
53+
54+
type AvatarBaseProps = React.ComponentProps<typeof AvatarPrimitive.Root> &
55+
AvatarVariantProps
1256

1357
const AvatarBase = React.forwardRef<
1458
React.ElementRef<typeof AvatarPrimitive.Root>,
1559
AvatarBaseProps
16-
>(({ className, ...props }, ref) => (
17-
<AvatarPrimitive.Root
18-
ref={ref}
19-
className={cn(
20-
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
21-
className
22-
)}
23-
{...props}
24-
/>
60+
>(({ className, size, ...props }, ref) => (
61+
<AvatarStylesContext.Provider value={avatarStyles({ size })}>
62+
<AvatarPrimitive.Root
63+
ref={ref}
64+
style={
65+
{
66+
"--avatar-base-shadow-color": "hsl(var(--primary-low-contrast))",
67+
} as React.CSSProperties
68+
}
69+
className={avatarStyles({ size }).container({ className })}
70+
{...props}
71+
/>
72+
</AvatarStylesContext.Provider>
2573
))
2674
AvatarBase.displayName = AvatarPrimitive.Root.displayName
2775

@@ -40,17 +88,22 @@ AvatarImage.displayName = AvatarPrimitive.Image.displayName
4088

4189
const AvatarFallback = React.forwardRef<
4290
React.ElementRef<typeof AvatarPrimitive.Fallback>,
43-
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
44-
>(({ className, ...props }, ref) => (
45-
<AvatarPrimitive.Fallback
46-
ref={ref}
47-
className={cn(
48-
"bg-muted flex h-full w-full items-center justify-center rounded-full",
49-
className
50-
)}
51-
{...props}
52-
/>
53-
))
91+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> &
92+
VariantProps<typeof avatarStyles>
93+
>(({ className, ...props }, ref) => {
94+
const { fallback } = useAvatarStyles()
95+
return (
96+
<AvatarPrimitive.Fallback
97+
ref={ref}
98+
className={cn(
99+
"flex h-full w-full items-center justify-center rounded-full",
100+
fallback(),
101+
className
102+
)}
103+
{...props}
104+
/>
105+
)
106+
})
54107
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
55108

56109
export type AvatarProps = AvatarBaseProps &
@@ -68,10 +121,11 @@ const Avatar = React.forwardRef<
68121
React.ElementRef<"span"> | React.ElementRef<"div">,
69122
AvatarProps
70123
>((props, ref) => {
71-
const { href, src, name, label, direction = "row" } = props
124+
const { href, src, name, size, label, direction = "row" } = props
72125

73126
const commonLinkProps = {
74127
href,
128+
className: "not-[:hover]:no-underline",
75129
}
76130

77131
const fallbackInitials = upperCase(
@@ -95,12 +149,14 @@ const Avatar = React.forwardRef<
95149
>
96150
<LinkOverlay
97151
asChild
98-
className="z-overlay inline-flex items-center gap-1 p-1 no-underline"
99-
data-peer
152+
className={cn(
153+
"peer z-overlay inline-flex items-center gap-1 p-1",
154+
size !== "md" ? "text-xs" : "text-sm"
155+
)}
100156
>
101157
<BaseLink {...commonLinkProps}>{label}</BaseLink>
102158
</LinkOverlay>
103-
<AvatarBase>
159+
<AvatarBase size={size}>
104160
<AvatarImage src={src} />
105161
<AvatarFallback>{fallbackInitials}</AvatarFallback>
106162
</AvatarBase>
@@ -109,7 +165,7 @@ const Avatar = React.forwardRef<
109165
}
110166

111167
return (
112-
<AvatarBase ref={ref} asChild>
168+
<AvatarBase ref={ref} size={size} asChild>
113169
<BaseLink {...commonLinkProps}>
114170
<AvatarImage src={src} />
115171
<AvatarFallback>{fallbackInitials}</AvatarFallback>

0 commit comments

Comments
 (0)