Skip to content

Commit 947eba2

Browse files
feat(avatar): set ShadCN Avatar structure
1 parent 84f09eb commit 947eba2

File tree

2 files changed

+117
-6
lines changed

2 files changed

+117
-6
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Meta, StoryObj } from "@storybook/react"
2+
3+
import { Avatar } from "../avatar"
4+
import { HStack } from "../flex"
5+
6+
const meta = {
7+
title: "Atoms / Media & Icons / UI Avatars",
8+
component: Avatar,
9+
} satisfies Meta<typeof Avatar>
10+
11+
export default meta
12+
13+
type Story = StoryObj<typeof meta>
14+
15+
export const Single: Story = {
16+
args: {
17+
name: "dan abrahmov",
18+
src: "https://bit.ly/dan-abramov",
19+
href: "#",
20+
},
21+
render: (args) => <Avatar {...args} />,
22+
}
23+
24+
export const WithUsername: Story = {
25+
args: {
26+
name: "Dan Abrahmov",
27+
src: "http://bit.ly/dan-abramov",
28+
href: "http://bit.ly/dan-abramov",
29+
label: "daneabrahmov",
30+
},
31+
render: (args) => (
32+
<HStack className="gap-4">
33+
<Avatar {...args} />
34+
<Avatar {...args} direction="column" />
35+
</HStack>
36+
),
37+
}

src/components/ui/avatar.tsx

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import * as React from "react"
2+
import upperCase from "lodash/upperCase"
23
import * as AvatarPrimitive from "@radix-ui/react-avatar"
34

4-
import { cn } from "@/lib/utils"
5+
import { cn } from "@/lib/utils/cn"
56

6-
const Avatar = React.forwardRef<
7+
import { Center } from "./flex"
8+
import { BaseLink, type LinkProps } from "./Link"
9+
import { LinkBox, LinkOverlay } from "./link-box"
10+
11+
type AvatarBaseProps = React.ComponentProps<typeof AvatarPrimitive.Root>
12+
13+
const AvatarBase = React.forwardRef<
714
React.ElementRef<typeof AvatarPrimitive.Root>,
8-
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
15+
AvatarBaseProps
916
>(({ className, ...props }, ref) => (
1017
<AvatarPrimitive.Root
1118
ref={ref}
@@ -16,15 +23,16 @@ const Avatar = React.forwardRef<
1623
{...props}
1724
/>
1825
))
19-
Avatar.displayName = AvatarPrimitive.Root.displayName
26+
AvatarBase.displayName = AvatarPrimitive.Root.displayName
2027

2128
const AvatarImage = React.forwardRef<
2229
React.ElementRef<typeof AvatarPrimitive.Image>,
2330
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
24-
>(({ className, ...props }, ref) => (
31+
>(({ className, alt = "", ...props }, ref) => (
2532
<AvatarPrimitive.Image
2633
ref={ref}
2734
className={cn("aspect-square h-full w-full", className)}
35+
alt={alt}
2836
{...props}
2937
/>
3038
))
@@ -45,4 +53,70 @@ const AvatarFallback = React.forwardRef<
4553
))
4654
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
4755

48-
export { Avatar, AvatarFallback, AvatarImage }
56+
export type AvatarProps = AvatarBaseProps &
57+
Required<Pick<LinkProps, "href">> & {
58+
label?: string
59+
/**
60+
* @default "row"
61+
*/
62+
direction?: "column" | "row"
63+
name: string
64+
src: string
65+
}
66+
67+
const Avatar = React.forwardRef<
68+
React.ElementRef<"span"> | React.ElementRef<"div">,
69+
AvatarProps
70+
>((props, ref) => {
71+
const { href, src, name, label, direction = "row" } = props
72+
73+
const commonLinkProps = {
74+
href,
75+
}
76+
77+
const fallbackInitials = upperCase(
78+
name
79+
.split(" ")
80+
.map((n) => n[0])
81+
.join("")
82+
)
83+
84+
if (label) {
85+
const _direction: "flex-col-reverse" | "flex-row-reverse" =
86+
direction === "row" ? "flex-row-reverse" : "flex-col-reverse"
87+
88+
const _ref = ref as React.ForwardedRef<HTMLDivElement>
89+
return (
90+
<LinkBox
91+
// !! Inconsistent strategy, using `as` prop instead of `asChild` bool
92+
as={Center}
93+
ref={_ref}
94+
className={cn(_direction, "gap-x-1 gap-y-0")}
95+
>
96+
<LinkOverlay
97+
asChild
98+
className="z-overlay inline-flex items-center gap-1 p-1 no-underline"
99+
data-peer
100+
>
101+
<BaseLink {...commonLinkProps}>{label}</BaseLink>
102+
</LinkOverlay>
103+
<AvatarBase>
104+
<AvatarImage src={src} />
105+
<AvatarFallback>{fallbackInitials}</AvatarFallback>
106+
</AvatarBase>
107+
</LinkBox>
108+
)
109+
}
110+
111+
return (
112+
<AvatarBase ref={ref} asChild>
113+
<BaseLink {...commonLinkProps}>
114+
<AvatarImage src={src} />
115+
<AvatarFallback>{fallbackInitials}</AvatarFallback>
116+
</BaseLink>
117+
</AvatarBase>
118+
)
119+
})
120+
Avatar.displayName = "Avatar"
121+
122+
export { Avatar, AvatarBase, AvatarFallback, AvatarImage }

0 commit comments

Comments
 (0)