Skip to content

Commit a00991b

Browse files
committed
chore: wip
1 parent 805d6fb commit a00991b

File tree

4 files changed

+198
-160
lines changed

4 files changed

+198
-160
lines changed

packages/bezier-react/src/components/Avatars/CheckableAvatar/CheckableAvatar.stories.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ const Template: Story<CheckableAvatarPropsWithAdditionalAvatar> = ({ withAdditio
111111
<Wrapper>
112112
<CheckableAvatar
113113
{...args}
114-
isChecked={checked}
114+
checked={checked}
115115
onClick={handleClick}
116116
>
117117
{ withAdditionalAvatar && (
@@ -134,9 +134,7 @@ Primary.args = {
134134
size: AvatarSize.Size24,
135135
showBorder: false,
136136
disabled: false,
137-
isChecked: false,
138-
isCheckable: true,
139-
checkedBackgroundColor: undefined,
137+
checked: false,
140138
withAdditionalAvatar: false,
141139
}
142140

@@ -175,7 +173,7 @@ const TemplateCheckableAvatarList: Story<CheckableAvatarProps> = (args) => {
175173
{...args}
176174
avatarUrl={avatarUrl}
177175
name={name}
178-
isChecked={checkedList[id]}
176+
checked={checkedList[id]}
179177
onClick={() => handleClickList(id)}
180178
/>
181179
<Name>{ name }</Name>
@@ -185,7 +183,6 @@ const TemplateCheckableAvatarList: Story<CheckableAvatarProps> = (args) => {
185183
<CheckableAvatar
186184
avatarUrl="https://bit.ly/dan-abramov"
187185
name="Dan Abramov"
188-
isCheckable={false}
189186
disabled
190187
/>
191188
<Name>Disabled Dan</Name>
@@ -201,8 +198,6 @@ CheckableAvatarList.args = {
201198
size: AvatarSize.Size24,
202199
showBorder: false,
203200
disabled: false,
204-
isChecked: false,
205-
isCheckable: true,
206-
checkedBackgroundColor: undefined,
201+
checked: false,
207202
}
208203

Lines changed: 115 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,142 @@
1+
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
2+
13
import {
2-
type Foundation,
3-
type SemanticNames,
4-
css,
5-
smoothCorners,
4+
absoluteCenter,
65
styled,
76
} from '~/src/foundation'
87

98
import { ZIndex } from '~/src/constants/ZIndex'
10-
import { SmoothCornersFeature } from '~/src/features'
11-
import type {
12-
InjectedInterpolation,
13-
InterpolationProps,
14-
} from '~/src/types/Foundation'
15-
16-
import { AvatarSize } from '~/src/components/Avatars/Avatar'
17-
import { AvatarImage } from '~/src/components/Avatars/Avatar/Avatar.styled'
9+
10+
import { AlphaSmoothCornersBox } from '~/src/components/AlphaSmoothCornersBox'
11+
import {
12+
AvatarSize,
13+
Avatar as BaseAvatar,
14+
} from '~/src/components/Avatars/Avatar'
1815
import { AVATAR_BORDER_RADIUS_PERCENTAGE } from '~/src/components/Avatars/AvatarStyle'
1916
import {
2017
CheckIcon as CheckIconSource,
2118
Icon,
2219
} from '~/src/components/Icon'
2320

24-
interface CheckableAvatarWrapperProps extends InterpolationProps {
25-
isChecked: boolean
26-
isCheckable: boolean
27-
}
28-
29-
interface CheckIconProps {
30-
avatarSize: AvatarSize
31-
}
32-
33-
const getCheckIconSize = (avatarSize: AvatarSize) => ({
34-
[AvatarSize.Size20]: 14,
35-
[AvatarSize.Size24]: 16,
36-
[AvatarSize.Size30]: 20,
37-
[AvatarSize.Size36]: 22,
38-
[AvatarSize.Size42]: 24,
39-
[AvatarSize.Size48]: 28,
40-
[AvatarSize.Size90]: 46,
41-
[AvatarSize.Size120]: 60,
42-
}[avatarSize])
43-
44-
export const CheckIcon = styled(Icon).attrs({ source: CheckIconSource })<CheckIconProps>`
21+
export const CheckIcon = styled(Icon).attrs({
22+
source: CheckIconSource,
23+
color: 'bgtxt-absolute-white-normal',
24+
})`
4525
position: absolute;
4626
z-index: ${ZIndex.Float};
47-
pointer-events: none;
48-
opacity: 0;
49-
50-
${({ avatarSize }) => {
51-
const iconSize = getCheckIconSize(avatarSize)
52-
return css`
53-
width: ${iconSize}px;
54-
height: ${iconSize}px;
55-
`
56-
}};
27+
visibility: hidden;
28+
29+
${absoluteCenter()}
30+
31+
&.size-${AvatarSize.Size20} {
32+
width: 14px;
33+
height: 14px;
34+
}
35+
36+
&.size-${AvatarSize.Size24} {
37+
width: 16px;
38+
height: 16px;
39+
}
40+
41+
&.size-${AvatarSize.Size30} {
42+
width: 20px;
43+
height: 20px;
44+
}
45+
46+
&.size-${AvatarSize.Size36} {
47+
width: 22px;
48+
height: 22px;
49+
}
50+
51+
&.size-${AvatarSize.Size42} {
52+
width: 24px;
53+
height: 24px;
54+
}
55+
56+
&.size-${AvatarSize.Size48} {
57+
width: 28px;
58+
height: 28px;
59+
}
60+
61+
&.size-${AvatarSize.Size72} {
62+
width: 42px;
63+
height: 42px;
64+
}
65+
66+
&.size-${AvatarSize.Size90} {
67+
width: 52px;
68+
height: 52px;
69+
}
70+
71+
&.size-${AvatarSize.Size120} {
72+
width: 68px;
73+
height: 68px;
74+
}
5775
`
5876

59-
const getBackgroundColor = (isChecked: boolean, checkedBackgroundColor: SemanticNames, foundation?: Foundation) =>
60-
foundation?.theme?.[isChecked ? checkedBackgroundColor : 'bg-grey-dark']
61-
62-
/* eslint-disable @typescript-eslint/indent */
63-
export const getAvatarImageStyle = (
64-
isChecked: boolean,
65-
isCheckable: boolean,
66-
checkedBackgroundColor: SemanticNames,
67-
interpolation?: InjectedInterpolation,
68-
) => css`
69-
${isCheckable && css`
70-
&::before {
71-
display: block;
72-
width: 100%;
73-
height: 100%;
74-
content: '';
75-
opacity: ${isChecked ? 1 : 0};
76-
${'' /**
77-
* NOTE: (@ed) smooth corner가 적용된 상태에선 background 색상을 background-color 속성을 통해 그리지 않으므로 (background: paint(smooth-corners))
78-
* smooth-corners가 사용 가능한 브라우저에선 background-color 트랜지션또한 불가능합니다.
79-
* 발생하지 않는 트랜지션에 will-change 속성을 주는 건 불필요하므로, will-change 속성에서 background-color를 제거합니다.
80-
*/}
81-
will-change: ${SmoothCornersFeature.activated ? 'opacity' : 'opacity, background-color'};
82-
83-
${({ foundation }) => foundation?.transition.getTransitionsCSS(['opacity', 'background-color'])}
84-
85-
${({ foundation }) => smoothCorners({
86-
backgroundColor: getBackgroundColor(isChecked, checkedBackgroundColor, foundation),
87-
borderRadius: `${AVATAR_BORDER_RADIUS_PERCENTAGE}%`,
88-
})};
89-
}
90-
`}
77+
export const Avatar = styled(BaseAvatar)``
9178

92-
${interpolation}
93-
`
79+
export const Container = styled(AlphaSmoothCornersBox).attrs({
80+
borderRadius: `${AVATAR_BORDER_RADIUS_PERCENTAGE}%`,
81+
})``
82+
83+
export const CheckboxPrimitiveRoot = styled(CheckboxPrimitive.Root)`
84+
all: unset;
85+
position: relative;
86+
z-index: ${ZIndex.Base};
87+
cursor: pointer;
88+
outline: none;
9489
95-
const getCheckableStyle = (isChecked: boolean, isCheckable: boolean) =>
96-
(!isCheckable
97-
? css`
90+
&[data-disabled] {
9891
cursor: not-allowed;
99-
`
100-
: css`
101-
cursor: pointer;
92+
}
10293
94+
&:not([data-state='unchecked']) {
10395
${CheckIcon} {
104-
opacity: ${isChecked ? 1 : 0};
105-
will-change: opacity;
106-
107-
${({ foundation }) => foundation?.transition.getTransitionsCSS('opacity')}
96+
visibility: visible;
10897
}
10998
110-
&:hover ${CheckIcon},
111-
&:hover ${AvatarImage}::before {
112-
opacity: 1;
99+
${Avatar} {
100+
--bezier-alpha-smooth-corners-box-background-image: none !important;
101+
--bezier-alpha-smooth-corners-box-background-color: var(--bgtxt-green-normal) !important;
102+
}
103+
}
104+
105+
&:not(&[data-disabled]) {
106+
&:focus-visible {
107+
${Container} {
108+
--bezier-alpha-smooth-corners-box-padding: 4px !important;
109+
--bezier-alpha-smooth-corners-box-shadow-spread-radius: 2px !important;
110+
--bezier-alpha-smooth-corners-box-shadow-color: var(--bgtxt-blue-light) !important;
111+
}
113112
}
114-
`)
115-
/* eslint-enable @typescript-eslint/indent */
116113
117-
export const CheckableAvatarWrapper = styled.div<CheckableAvatarWrapperProps>`
118-
position: relative;
119-
display: flex;
120-
align-items: center;
121-
justify-content: center;
122-
user-select: none;
114+
&:hover,
115+
&:focus-visible {
116+
${CheckIcon} {
117+
visibility: visible;
118+
}
123119
124-
${({ isChecked, isCheckable }) => getCheckableStyle(isChecked, isCheckable)}
120+
${Avatar} {
121+
--bezier-alpha-smooth-corners-box-background-image: none !important;
122+
}
123+
}
125124
126-
${({ interpolation }) => interpolation}
125+
&[data-state='unchecked'] {
126+
&:hover,
127+
&:focus-visible {
128+
${Avatar} {
129+
--bezier-alpha-smooth-corners-box-background-color: var(--bg-grey-dark) !important;
130+
}
131+
}
132+
}
133+
134+
&:not([data-state='unchecked']) {
135+
&:hover {
136+
${Avatar} {
137+
--bezier-alpha-smooth-corners-box-background-color: var(--bgtxt-green-dark) !important;
138+
}
139+
}
140+
}
141+
}
127142
`
Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,63 @@
1-
import React from 'react'
1+
import React, { forwardRef } from 'react'
22

3-
import {
4-
Avatar,
5-
AvatarSize,
6-
} from '~/src/components/Avatars/Avatar'
3+
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
74

8-
import type CheckableAvatarProps from './CheckableAvatar.types'
5+
import useId from '~/src/hooks/useId'
96

10-
import {
11-
CheckIcon,
12-
CheckableAvatarWrapper,
13-
getAvatarImageStyle,
14-
} from './CheckableAvatar.styled'
7+
import { AvatarSize } from '~/src/components/Avatars/Avatar'
8+
import { VisuallyHidden } from '~/src/components/VisuallyHidden'
159

16-
// TODO: 테스트 코드 작성
17-
const CHECKABLE_AVATAR_TEST_ID = 'bezier-react-checkable-avatar'
10+
import type CheckableAvatarProps from './CheckableAvatar.types'
1811

19-
const CHECKED_DEFAULT_SEMANTIC_COLOR = 'bgtxt-green-normal'
12+
import * as Styled from './CheckableAvatar.styled'
2013

21-
export function CheckableAvatar({
22-
size = AvatarSize.Size24,
23-
isChecked = false,
24-
isCheckable = true,
25-
checkedBackgroundColor = CHECKED_DEFAULT_SEMANTIC_COLOR,
26-
checkableWrapperClassName,
27-
checkableWrapperInterpolation,
28-
interpolation,
14+
export const CheckableAvatar = forwardRef<HTMLButtonElement, CheckableAvatarProps>(function CheckableAvatar({
2915
children,
16+
id: idProp,
17+
name,
18+
size = AvatarSize.Size24,
19+
disabled,
20+
avatarUrl,
21+
fallbackUrl,
22+
status,
23+
showBorder,
3024
...props
31-
}: CheckableAvatarProps) {
25+
}, forwardedRef) {
26+
const id = useId(idProp, 'bezier-checkable-avatar')
27+
3228
return (
33-
<CheckableAvatarWrapper
34-
data-testid={CHECKABLE_AVATAR_TEST_ID}
35-
isChecked={isChecked}
36-
isCheckable={isCheckable}
37-
className={checkableWrapperClassName}
38-
interpolation={checkableWrapperInterpolation}
29+
<Styled.CheckboxPrimitiveRoot
30+
ref={forwardedRef}
31+
id={id}
32+
name={name}
33+
disabled={disabled}
34+
{...props}
3935
>
40-
<CheckIcon
41-
avatarSize={size}
42-
color="bgtxt-absolute-white-normal"
43-
/>
44-
<Avatar
45-
{...props}
46-
size={size}
47-
interpolation={getAvatarImageStyle(isChecked, isCheckable, checkedBackgroundColor, interpolation)}
48-
>
49-
{ children }
50-
</Avatar>
51-
</CheckableAvatarWrapper>
36+
<Styled.Container>
37+
<CheckboxPrimitive.Indicator
38+
asChild
39+
forceMount
40+
>
41+
<Styled.CheckIcon className={`size-${size}`} />
42+
</CheckboxPrimitive.Indicator>
43+
44+
<Styled.Avatar
45+
aria-hidden
46+
size={size}
47+
name={name}
48+
disabled={disabled}
49+
avatarUrl={avatarUrl}
50+
fallbackUrl={fallbackUrl}
51+
status={status}
52+
showBorder={showBorder}
53+
>
54+
{ children }
55+
</Styled.Avatar>
56+
57+
<VisuallyHidden>
58+
<label htmlFor={id}>{ name }</label>
59+
</VisuallyHidden>
60+
</Styled.Container>
61+
</Styled.CheckboxPrimitiveRoot>
5262
)
53-
}
63+
})

0 commit comments

Comments
 (0)