1
1
import * as React from "react"
2
2
import upperCase from "lodash/upperCase"
3
+ import { tv , type VariantProps } from "tailwind-variants"
3
4
import * as AvatarPrimitive from "@radix-ui/react-avatar"
4
5
5
6
import { cn } from "@/lib/utils/cn"
@@ -8,20 +9,67 @@ import { Center } from "./flex"
8
9
import { BaseLink , type LinkProps } from "./Link"
9
10
import { LinkBox , LinkOverlay } from "./link-box"
10
11
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
12
56
13
57
const AvatarBase = React . forwardRef <
14
58
React . ElementRef < typeof AvatarPrimitive . Root > ,
15
59
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 >
25
73
) )
26
74
AvatarBase . displayName = AvatarPrimitive . Root . displayName
27
75
@@ -40,17 +88,22 @@ AvatarImage.displayName = AvatarPrimitive.Image.displayName
40
88
41
89
const AvatarFallback = React . forwardRef <
42
90
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
+ } )
54
107
AvatarFallback . displayName = AvatarPrimitive . Fallback . displayName
55
108
56
109
export type AvatarProps = AvatarBaseProps &
@@ -68,10 +121,11 @@ const Avatar = React.forwardRef<
68
121
React . ElementRef < "span" > | React . ElementRef < "div" > ,
69
122
AvatarProps
70
123
> ( ( props , ref ) => {
71
- const { href, src, name, label, direction = "row" } = props
124
+ const { href, src, name, size , label, direction = "row" } = props
72
125
73
126
const commonLinkProps = {
74
127
href,
128
+ className : "not-[:hover]:no-underline" ,
75
129
}
76
130
77
131
const fallbackInitials = upperCase (
@@ -95,12 +149,14 @@ const Avatar = React.forwardRef<
95
149
>
96
150
< LinkOverlay
97
151
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
+ ) }
100
156
>
101
157
< BaseLink { ...commonLinkProps } > { label } </ BaseLink >
102
158
</ LinkOverlay >
103
- < AvatarBase >
159
+ < AvatarBase size = { size } >
104
160
< AvatarImage src = { src } />
105
161
< AvatarFallback > { fallbackInitials } </ AvatarFallback >
106
162
</ AvatarBase >
@@ -109,7 +165,7 @@ const Avatar = React.forwardRef<
109
165
}
110
166
111
167
return (
112
- < AvatarBase ref = { ref } asChild >
168
+ < AvatarBase ref = { ref } size = { size } asChild >
113
169
< BaseLink { ...commonLinkProps } >
114
170
< AvatarImage src = { src } />
115
171
< AvatarFallback > { fallbackInitials } </ AvatarFallback >
0 commit comments