-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(foundation): added Avatar component
- Loading branch information
Showing
11 changed files
with
282 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 17 additions & 2 deletions
19
packages/uikit-react-native-foundation/src/theme/Typography.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
packages/uikit-react-native-foundation/src/ui/Avatar/AvatarGroup.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import React, { useMemo } from 'react'; | ||
import { StyleProp, View, ViewStyle } from 'react-native'; | ||
|
||
const MAX = 4; | ||
|
||
type Props = { | ||
size?: number; | ||
containerStyle?: StyleProp<ViewStyle>; | ||
}; | ||
const AvatarGroup: React.FC<Props> = ({ children, containerStyle, size = 56 }) => { | ||
const childAmount = React.Children.count(children); | ||
if (childAmount === 1) return <View style={containerStyle}>{children}</View>; | ||
|
||
const avatars = useMemo(() => { | ||
return ( | ||
React.Children.map(children, (child, index) => { | ||
if (index + 1 > MAX) return child; | ||
if (!React.isValidElement(child)) return child; | ||
|
||
const top = getTopPoint(index, childAmount) * size; | ||
const left = getLeftPoint(index) * size; | ||
const width = getWidthPoint(index, childAmount) * size; | ||
const height = getHeightPoint(index, childAmount) * size; | ||
const innerLeft = -getInnerLeft(index, childAmount) * size; | ||
const innerTop = -getInnerTop(childAmount) * size; | ||
|
||
return ( | ||
<View style={{ overflow: 'hidden', position: 'absolute', top, left, width, height }}> | ||
{React.cloneElement(child, { size, square: true, containerStyle: { left: innerLeft, top: innerTop } })} | ||
</View> | ||
); | ||
})?.slice(0, 4) ?? [] | ||
); | ||
}, [children]); | ||
|
||
return ( | ||
<View style={[containerStyle, { overflow: 'hidden', width: size, height: size, borderRadius: size }]}> | ||
{avatars} | ||
</View> | ||
); | ||
}; | ||
|
||
const getHeightPoint = (_: number, total: number) => { | ||
if (total === 2) return 1; | ||
return 0.5; | ||
}; | ||
const getWidthPoint = (idx: number, total: number) => { | ||
if (total === 3 && idx === 0) return 1; | ||
return 0.5; | ||
}; | ||
const getTopPoint = (idx: number, total: number) => { | ||
if (total === 2) return -0.025; | ||
if (total === 3 && idx === 0) return -0.025; | ||
if (total === 3 && idx !== 0) return 0.525; | ||
if (idx === 0 || idx === 1) return -0.025; | ||
return 0.525; | ||
}; | ||
const getLeftPoint = (idx: number) => { | ||
if (idx === 0 || idx === 2) return -0.025; | ||
return 0.525; | ||
}; | ||
const getInnerLeft = (idx: number, total: number) => { | ||
if (total === 3 && idx === 0) return 0; | ||
return 0.25; | ||
}; | ||
const getInnerTop = (total: number) => { | ||
if (total === 2) return 0; | ||
return 0.25; | ||
}; | ||
|
||
export default AvatarGroup; |
40 changes: 40 additions & 0 deletions
40
packages/uikit-react-native-foundation/src/ui/Avatar/AvatarIcon.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import React from 'react'; | ||
import { StyleProp, View, ViewStyle } from 'react-native'; | ||
|
||
import createStyleSheet from '../../styles/createStyleSheet'; | ||
import useUIKitTheme from '../../theme/useUIKitTheme'; | ||
import Icon from '../Icon'; | ||
|
||
type Props = { | ||
icon: keyof typeof Icon.Assets; | ||
size?: number; | ||
backgroundColor?: string; | ||
containerStyle?: StyleProp<ViewStyle>; | ||
}; | ||
const AvatarIcon: React.FC<Props> = ({ size = 56, icon, containerStyle, backgroundColor }) => { | ||
const { colors, palette } = useUIKitTheme(); | ||
return ( | ||
<View | ||
style={[ | ||
styles.container, | ||
{ | ||
width: size, | ||
height: size, | ||
borderRadius: size / 2, | ||
backgroundColor: backgroundColor ?? palette.background300, | ||
}, | ||
containerStyle, | ||
]} | ||
> | ||
<Icon icon={icon} size={size / 2} color={colors.onBackgroundReverse01} /> | ||
</View> | ||
); | ||
}; | ||
const styles = createStyleSheet({ | ||
container: { | ||
overflow: 'hidden', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
}, | ||
}); | ||
export default AvatarIcon; |
58 changes: 58 additions & 0 deletions
58
packages/uikit-react-native-foundation/src/ui/Avatar/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import React from 'react'; | ||
import { Image, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; | ||
|
||
import createStyleSheet from '../../styles/createStyleSheet'; | ||
import useUIKitTheme from '../../theme/useUIKitTheme'; | ||
import Icon from '../Icon'; | ||
import AvatarGroup from './AvatarGroup'; | ||
import AvatarIcon from './AvatarIcon'; | ||
|
||
type SubComponents = { Group: typeof AvatarGroup; Icon: typeof AvatarIcon }; | ||
type Props = { | ||
uri?: string; | ||
size?: number; | ||
square?: boolean; | ||
muted?: boolean; | ||
containerStyle?: StyleProp<ViewStyle>; | ||
}; | ||
const Avatar: React.FC<Props> & SubComponents = ({ uri, square, muted = false, size = 56, containerStyle }) => { | ||
const { colors, palette } = useUIKitTheme(); | ||
return ( | ||
<View | ||
style={[ | ||
styles.container, | ||
{ width: size, height: size, borderRadius: square ? 0 : size / 2, backgroundColor: palette.background300 }, | ||
containerStyle, | ||
]} | ||
> | ||
{uri ? ( | ||
<Image source={{ uri }} resizeMode={'cover'} style={StyleSheet.absoluteFill} /> | ||
) : ( | ||
<Icon icon={'user'} size={size / 2} color={colors.onBackgroundReverse01} /> | ||
)} | ||
{muted && <MutedOverlay size={size} />} | ||
</View> | ||
); | ||
}; | ||
|
||
const MutedOverlay: React.FC<{ size: number }> = ({ size }) => { | ||
const { palette } = useUIKitTheme(); | ||
return ( | ||
<View style={[styles.container, StyleSheet.absoluteFill]}> | ||
<View style={[StyleSheet.absoluteFill, { backgroundColor: palette.primary300, opacity: 0.5 }]} /> | ||
<Icon color={palette.onBackgroundDark01} icon={'mute'} size={size * 0.72} /> | ||
</View> | ||
); | ||
}; | ||
|
||
const styles = createStyleSheet({ | ||
container: { | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
overflow: 'hidden', | ||
}, | ||
}); | ||
|
||
Avatar.Group = AvatarGroup; | ||
Avatar.Icon = AvatarIcon; | ||
export default Avatar; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import type { ComponentMeta, ComponentStory } from '@storybook/react-native'; | ||
import React from 'react'; | ||
|
||
import { Avatar as AvatarComponent, useUIKitTheme } from '@sendbird/uikit-react-native-foundation'; | ||
|
||
import { getMockImage } from './constant'; | ||
|
||
const AvatarMeta: ComponentMeta<typeof AvatarComponent> = { | ||
title: 'Avatar', | ||
component: AvatarComponent, | ||
argTypes: {}, | ||
args: {}, | ||
}; | ||
|
||
export default AvatarMeta; | ||
|
||
type AvatarStory = ComponentStory<typeof AvatarComponent>; | ||
export const Avatar: AvatarStory = () => <DefaultAvatar />; | ||
export const AvatarGroup: AvatarStory = () => <GroupedAvatar />; | ||
|
||
const margin = { marginBottom: 12 }; | ||
|
||
const DefaultAvatar: React.FC = () => { | ||
const { colors } = useUIKitTheme(); | ||
|
||
return ( | ||
<> | ||
<AvatarComponent uri={getMockImage()} containerStyle={margin} /> | ||
<AvatarComponent uri={getMockImage()} muted containerStyle={margin} /> | ||
<AvatarComponent containerStyle={margin} /> | ||
<AvatarComponent.Icon icon={'broadcast'} backgroundColor={colors.secondary} containerStyle={margin} /> | ||
<AvatarComponent.Icon icon={'channels'} containerStyle={margin} /> | ||
</> | ||
); | ||
}; | ||
const GroupedAvatar: React.FC = () => { | ||
return ( | ||
<> | ||
<AvatarComponent.Group containerStyle={margin}> | ||
<AvatarComponent uri={getMockImage()} /> | ||
</AvatarComponent.Group> | ||
|
||
<AvatarComponent.Group containerStyle={margin}> | ||
<AvatarComponent uri={getMockImage()} /> | ||
<AvatarComponent uri={getMockImage()} /> | ||
</AvatarComponent.Group> | ||
|
||
<AvatarComponent.Group containerStyle={margin}> | ||
<AvatarComponent /> | ||
<AvatarComponent uri={getMockImage()} /> | ||
<AvatarComponent uri={getMockImage()} /> | ||
</AvatarComponent.Group> | ||
|
||
<AvatarComponent.Group> | ||
<AvatarComponent /> | ||
<AvatarComponent uri={getMockImage()} /> | ||
<AvatarComponent muted uri={getMockImage()} /> | ||
<AvatarComponent uri={getMockImage()} /> | ||
</AvatarComponent.Group> | ||
</> | ||
); | ||
}; |
Oops, something went wrong.