Ver Fonte

Feature: Avatar Group component completed.

lfabl há 8 horas atrás
pai
commit
92dd16dc3a

+ 14 - 2
example/src/pages/home/index.tsx

@@ -12,9 +12,10 @@ import {
     PaletteSwitcher,
     PageContainer,
     ThemeSwitcher,
+    AvatarGroup,
     RowCard,
-    Text,
-    Avatar
+    Avatar,
+    Text
 } from "ncore-ui-kit";
 import {
     useNavigation
@@ -67,6 +68,17 @@ const Home = () => {
             size="large"
             // imageUrl="https://fotolifeakademi.com/uploads/2020/04/manzara-fotografi-cekmek-724x394.webp"
         />
+        <AvatarGroup
+            isWorkWithAction={true}
+            avatars={[
+                {
+                    title: "Furkan Atakan BOZKURT"
+                },
+                {
+                    title: "Mahmut PEKER"
+                }
+            ]}
+        />
         <View
             style={{
                 justifyContent: "center",

+ 4 - 2
src/components/avatar/index.tsx

@@ -24,9 +24,9 @@ const Avatar = ({
     titleAbbreviationType = "first-last",
     isShowStatusIndicatorSplicer = true,
     avatarBackgroundColor = "default",
-    statusIndicatorType = "success",
-    borderColor = "emphasized",
+    statusIndicatorType = "online",
     titleColor = "onPrimary",
+    isShowBorder = false,
     isStatusIndicator,
     isWorkWithAction,
     image: ImageProp,
@@ -35,6 +35,7 @@ const Avatar = ({
     icon: IconProp,
     imageSource,
     customTheme,
+    borderColor,
     isDisabled,
     imageProps,
     isLoading,
@@ -71,6 +72,7 @@ const Avatar = ({
         statusIndicatorType,
         image: ImageProp,
         backgroundColor,
+        isShowBorder,
         borderColor,
         currentSize,
         isDisabled,

+ 18 - 3
src/components/avatar/stylesheet.ts

@@ -8,7 +8,8 @@ import type {
     AvatarDynamicStyleType,
     AvatarMeasuresKeys,
     AvatarSizeType,
-    AvatarMeasures
+    AvatarMeasures,
+    AvatarStatusIndicatorType
 } from "./type";
 import type {
     Mutable
@@ -78,9 +79,18 @@ export const getAvatarSize = ({
     };
 };
 
+export const AvatarStatusCheatsheet: Record<AvatarStatusIndicatorType, keyof NCoreUIKit.IconContentColors> = {
+    "offline": "default",
+    "online": "success",
+    "away": "warning",
+    "busy": "danger",
+    "idle": "info"
+};
+
 const stylesheet = StyleSheet.create({
     container: {
         position: "relative",
+        overflow: "visible",
         display: "flex"
     },
     image: {
@@ -100,6 +110,7 @@ export const useStyles = ({
     avatarBackgroundColor,
     statusIndicatorType,
     backgroundColor,
+    isShowBorder,
     borderColor,
     currentSize,
     isDisabled,
@@ -112,8 +123,8 @@ export const useStyles = ({
 }: AvatarDynamicStyleType) => {
     const styles = {
         container: {
+            borderColor: borderColor && borderColor !== "transparent" ? colors.content.border[borderColor] : avatarBackgroundColor ? colors.content.container[avatarBackgroundColor] : "transparent",
             backgroundColor: backgroundColor ? backgroundColor === "transparent" ? "transparent" : colors.content.container[backgroundColor] : colors.content.container.subtle,
-            borderColor: borderColor ? borderColor : "transparent",
             borderRadius: currentSize.size / 2,
             height: currentSize.size,
             width: currentSize.size
@@ -125,14 +136,18 @@ export const useStyles = ({
         icon: {
         } as Mutable<ViewStyle>,
         statusIndicator: {
+            backgroundColor: colors.content.icon[AvatarStatusCheatsheet[statusIndicatorType]],
             borderColor: colors.content.container[avatarBackgroundColor],
-            backgroundColor: colors.content.icon[statusIndicatorType],
             borderRadius: currentSize.statusIndicatorSize / 2,
             height: currentSize.statusIndicatorSize,
             width: currentSize.statusIndicatorSize
         } as Mutable<ViewStyle>
     };
 
+    if(isShowBorder) {
+        styles.container.borderWidth = borders.subtract;
+    }
+
     if(!title && !image && !imageUrl && !backgroundColor) {
         styles.container.backgroundColor = colors.content.container.subtle;
 

+ 6 - 4
src/components/avatar/type.ts

@@ -13,14 +13,15 @@ import type {
 
 export type AvatarDynamicStyleType = {
     backgroundColor?: keyof NCoreUIKit.ContainerContentColors | "transparent";
-    borderColor: keyof NCoreUIKit.BorderContentColors | "transparent";
+    borderColor?: keyof NCoreUIKit.BorderContentColors | "transparent";
     avatarBackgroundColor: keyof NCoreUIKit.ContainerContentColors;
-    statusIndicatorType: AvatarStatusIndıcatorType;
+    statusIndicatorType: AvatarStatusIndicatorType;
     borders: NCoreUIKit.ActivePalette["borders"];
     colors: NCoreUIKit.ActivePalette["colors"];
     isShowStatusIndicatorSplicer?: boolean;
     image?: ComponentType<ImageProps>;
     currentSize: AvatarMeasures;
+    isShowBorder?: boolean;
     isDisabled?: boolean;
     isLoading?: boolean;
     imageUrl?: string;
@@ -46,7 +47,7 @@ export type AvatarSizeConstantType = {
     size: AvatarSizeType;
 };
 
-export type AvatarStatusIndıcatorType = keyof NCoreUIKit.IconContentColors;
+export type AvatarStatusIndicatorType = "online" | "offline" | "busy" | "away" | "idle";
 
 export type AvatarTitleAbbreviationTypes = "first-last" | "first-next" | "every-first";
 
@@ -65,7 +66,7 @@ interface IAvatarProps {
     };
     style?: StyleProp<TextStyle>[] | StyleProp<TextStyle>;
     titleAbbreviationType?: AvatarTitleAbbreviationTypes;
-    statusIndicatorType?: AvatarStatusIndıcatorType;
+    statusIndicatorType?: AvatarStatusIndicatorType;
     titleColor?: keyof NCoreUIKit.TextContentColors;
     isShowStatusIndicatorSplicer?: boolean;
     image?: ComponentType<ImageProps>;
@@ -73,6 +74,7 @@ interface IAvatarProps {
     isStatusIndicator?: boolean;
     isWorkWithAction?: boolean;
     imageProps?: ImageProps;
+    isShowBorder?: boolean;
     icon?: NCoreUIKitIcon;
     size?: AvatarSizeType;
     isDisabled?: boolean;

+ 172 - 0
src/components/avatarGroup/index.tsx

@@ -0,0 +1,172 @@
+import {
+    Fragment
+} from "react";
+import {
+    TouchableOpacity,
+    View
+} from "react-native";
+import type IAvatarProps from "./type";
+import stylesheet, {
+    getAvatarGroupSize,
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import {
+    UserRound as UserRoundIcon
+} from "lucide-react-native";
+import Loading from "../loading";
+import Avatar from "../avatar";
+import Text from "../text";
+
+const AvatarGroup = ({
+    titleAbbreviationType = "first-last",
+    isShowStatusIndicatorSplicer = true,
+    avatarBackgroundColor = "default",
+    isStatusIndicator,
+    isWorkWithAction,
+    size = "medium",
+    borderColor,
+    customTheme,
+    isDisabled,
+    imageProps,
+    isLoading,
+    avatars,
+    onPress,
+    style
+}: IAvatarProps) => {
+    const {
+        borders,
+        colors
+    } = NCoreUIKitTheme.useContext(customTheme);
+
+    const currentSize = getAvatarGroupSize({
+        size
+    });
+
+    const currentAvatars = avatars && avatars.length > 2 ? avatars?.slice(0, 2) : avatars;
+
+    const {
+        moreAvatarsContainer: moreAvatarsContainerDynamicStyle,
+        iconContainer: iconContainerDynamicStyle,
+        container: containerDynamicStyle,
+        avatar: avatarDynamicStyle,
+        icon: iconDynamicStyle
+    } = useStyles({
+        avatarBackgroundColor,
+        currentSize,
+        borderColor,
+        isDisabled,
+        isLoading,
+        avatars,
+        borders,
+        colors
+    });
+
+    const renderIcon = () => {
+        if(isLoading) {
+            return renderLoading();
+        }
+
+        return <UserRoundIcon
+            color={colors.content.icon.default}
+            size={currentSize.iconSize}
+            style={{
+                ...iconDynamicStyle
+            }}
+        />;
+    };
+
+    const renderLoading = () => {
+        return <Loading
+            color="default"
+        />;
+    };
+
+    const renderAvatars = () => {
+        if(!currentAvatars || !currentAvatars.length) {
+            return null;
+        }
+
+        return currentAvatars.map((avatarItem, avatarIndex) => {
+            return <Avatar
+                {...avatarItem}
+                isShowStatusIndicatorSplicer={isShowStatusIndicatorSplicer}
+                titleAbbreviationType={titleAbbreviationType}
+                avatarBackgroundColor={avatarBackgroundColor}
+                isStatusIndicator={isStatusIndicator}
+                isWorkWithAction={undefined}
+                borderColor={borderColor}
+                customTheme={customTheme}
+                imageProps={imageProps}
+                isShowBorder={true}
+                onPress={undefined}
+                isDisabled={false}
+                isLoading={false}
+                size={size}
+                style={{
+                    ...avatarDynamicStyle,
+                    ...avatarItem.style,
+                    transform: [{
+                        translateX: avatarIndex === 0 ? currentSize.size / 3 : 0
+                    }]
+                }}
+            />;
+        });
+    };
+
+    const renderMoreAvatars = () => {
+        if(!avatars) {
+            return null;
+        }
+
+        if(avatars.length < 3) {
+            return null;
+        }
+
+        return <View
+            style={[
+                stylesheet.moreAvatarsContainer,
+                moreAvatarsContainerDynamicStyle
+            ]}
+        >
+            <Text
+                variant="labelLargeSize"
+                color="mid"
+            >
+                +{avatars.length - 2}
+            </Text>
+        </View>;
+    };
+
+    const renderContent = () => {
+        return <Fragment>
+            {renderAvatars()}
+            <View
+                style={[
+                    stylesheet.iconContainer,
+                    iconContainerDynamicStyle
+                ]}
+            >
+                {renderIcon()}
+            </View>
+            {renderMoreAvatars()}
+        </Fragment>;
+    };
+
+    return <TouchableOpacity
+        disabled={!isWorkWithAction || isDisabled || isLoading}
+        onPress={!isWorkWithAction || isDisabled || isLoading ? undefined : () => {
+            if(onPress) onPress();
+        }}
+        style={[
+            style,
+            stylesheet.container,
+            containerDynamicStyle
+        ]}
+    >
+        {renderContent()}
+    </TouchableOpacity>;
+};
+export default AvatarGroup;

+ 138 - 0
src/components/avatarGroup/stylesheet.ts

@@ -0,0 +1,138 @@
+import {
+    type ViewStyle,
+    StyleSheet
+} from "react-native";
+import type {
+    AvatarGroupSizeConstantType,
+    AvatarGroupDynamicStyleType,
+    AvatarGroupMeasuresKeys,
+    AvatarGroupSizeType
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+
+export const AVATAR_GROUP_SIZES: Record<AvatarGroupSizeType, AvatarGroupMeasuresKeys> = {
+    xxSmall: {
+        fontSize: "labelSmallSize",
+        iconSize: 14,
+        size: 24
+    },
+    xSmall: {
+        fontSize: "labelMediumSize",
+        iconSize: 18,
+        size: 32
+    },
+    small: {
+        fontSize: "labelLargeSize",
+        iconSize: 22,
+        size: 40
+    },
+    medium: {
+        fontSize: "bodyLargeSize",
+        iconSize: 26,
+        size: 48
+    },
+    large: {
+        fontSize: "headlineSmallSize",
+        iconSize: 32,
+        size: 64
+    },
+    xLarge: {
+        fontSize: "headlineMediumSize",
+        iconSize: 40,
+        size: 80
+    },
+    xxLarge: {
+        fontSize: "displaySmallSize",
+        iconSize: 60,
+        size: 120
+    },
+    xxxLarge: {
+        fontSize: "displayMediumSize",
+        iconSize: 76,
+        size: 150
+    }
+};
+
+export const getAvatarGroupSize = ({
+    size
+}: AvatarGroupSizeConstantType): AvatarGroupMeasuresKeys => {
+    const currentSize = AVATAR_GROUP_SIZES[size];
+
+    return {
+        fontSize: currentSize.fontSize,
+        iconSize: currentSize.iconSize,
+        size: currentSize.size
+    };
+};
+
+const stylesheet = StyleSheet.create({
+    container: {
+        flexDirection: "row",
+        position: "relative"
+    },
+    iconContainer: {
+        justifyContent: "center",
+        alignItems: "center"
+    },
+    icon: {
+    },
+    moreAvatarsContainer: {
+        justifyContent: "center",
+        alignItems: "center"
+    }
+});
+
+export const useStyles = ({
+    avatarBackgroundColor,
+    currentSize,
+    borderColor,
+    isDisabled,
+    isLoading,
+    avatars,
+    borders,
+    colors
+}: AvatarGroupDynamicStyleType) => {
+    const styles = {
+        container: {
+        } as Mutable<ViewStyle>,
+        iconContainer: {
+            borderColor: borderColor ? borderColor === "transparent" ? "transparent" : colors.content.border[borderColor] : avatarBackgroundColor ? colors.content.container[avatarBackgroundColor] : colors.content.container.default,
+            transform: [{
+                translateX: avatars && avatars.length === 1 ? 0 : (currentSize.size / 3) * -1
+            }],
+            backgroundColor: colors.content.container.subtle,
+            borderRadius: currentSize.size / 2,
+            borderWidth: borders.subtract,
+            height: currentSize.size,
+            width: currentSize.size
+        } as Mutable<ViewStyle>,
+        icon: {
+        } as Mutable<ViewStyle>,
+        moreAvatarsContainer: {
+            borderColor: borderColor ? borderColor === "transparent" ? "transparent" : colors.content.border[borderColor] : avatarBackgroundColor ? colors.content.container[avatarBackgroundColor] : colors.content.container.default,
+            backgroundColor: colors.content.container.subtle,
+            transform: [{
+                translateX: (currentSize.size / 1.5) * -1
+            }],
+            borderRadius: currentSize.size / 2,
+            borderWidth: borders.subtract,
+            height: currentSize.size,
+            width: currentSize.size
+        } as Mutable<ViewStyle>,
+        avatar: {
+        } as Mutable<ViewStyle>
+    };
+
+    if(isDisabled) {
+        styles.container.opacity = 0.33;
+    }
+
+    if(isLoading) {
+        styles.container.opacity = 0.33;
+    }
+
+    return styles;
+};
+export default stylesheet;

+ 74 - 0
src/components/avatarGroup/type.ts

@@ -0,0 +1,74 @@
+import {
+    type ImageProps,
+    type TextStyle,
+    type StyleProp
+} from "react-native";
+import type IAvatarProps from "../avatar/type";
+import type {
+    AvatarTitleAbbreviationTypes,
+    AvatarSizeType
+} from "../avatar/type";
+
+export type AvatarGroupDynamicStyleType = {
+    borderColor?: keyof NCoreUIKit.BorderContentColors | "transparent";
+    avatarBackgroundColor: keyof NCoreUIKit.ContainerContentColors;
+    borders: NCoreUIKit.ActivePalette["borders"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    avatars?: Array<AvatarGroupAvatarsType>;
+    currentSize: AvatarGroupMeasuresKeys;
+    isDisabled?: boolean;
+    isLoading?: boolean;
+};
+
+export type AvatarGroupSizeConstantType = {
+    size: AvatarGroupSizeType;
+};
+
+export type AvatarGroupMeasuresKeys = {
+    fontSize: keyof NCoreUIKit.Typography;
+    iconSize: number;
+    size: number;
+};
+
+export type AvatarGroupTitleAbbreviationTypes = "first-last" | "first-next" | "every-first";
+
+export type AvatarGroupSizeType = "xxSmall" | "xSmall" | "small" | "medium" | "large" | "xLarge" | "xxLarge" | "xxxLarge";
+
+export type AvatarGroupAvatarsType = Omit<
+    IAvatarProps,
+    "isShowStatusIndicatorSplicer" |
+    "avatarBackgroundColor" |
+    "titleAbbreviationType" |
+    "isStatusIndicator" |
+    "isWorkWithAction" |
+    "isDisabled" |
+    "imageProps" |
+    "isLoading" |
+    "onPress" |
+    "size"
+>;
+
+interface IAvatarGroupProps {
+    borderColor?: keyof NCoreUIKit.BorderContentColors | "transparent";
+    avatarBackgroundColor?: keyof NCoreUIKit.ContainerContentColors;
+    customTheme?: {
+        gapPropagation?: keyof NCoreUIKit.GapPropagationKey;
+        sharpness?: keyof NCoreUIKit.SharpnessKey;
+        paletteKey?: keyof NCoreUIKit.PaletteKey;
+        themeKey?: keyof NCoreUIKit.ThemeKey;
+    };
+    style?: StyleProp<TextStyle>[] | StyleProp<TextStyle>;
+    titleAbbreviationType?: AvatarTitleAbbreviationTypes;
+    avatars?: Array<AvatarGroupAvatarsType>;
+    isShowStatusIndicatorSplicer?: boolean;
+    isStatusIndicator?: boolean;
+    isWorkWithAction?: boolean;
+    imageProps?: ImageProps;
+    size?: AvatarSizeType;
+    isDisabled?: boolean;
+    onPress?: () => void;
+    isLoading?: boolean;
+}
+export type {
+    IAvatarGroupProps as default
+};

+ 4 - 0
src/components/index.ts

@@ -154,6 +154,10 @@ export {
     default as Avatar
 } from "./avatar";
 
+export {
+    default as AvatarGroup
+} from "./avatarGroup";
+
 export type {
     EnterMarkdownTypes,
     CodeMarkdownTypes,

+ 4 - 2
src/index.tsx

@@ -27,6 +27,7 @@ export {
     TimeSelector,
     DateSelector,
     BottomSheet,
+    AvatarGroup,
     SelectSheet,
     RadioButton,
     StateCard,
@@ -92,9 +93,10 @@ export {
 } from "./helpers";
 
 export {
-    ChevronRightIcon,
+    NIBGATCommunityIcon,
+    CayCoreIcon,
     LoadingIcon,
-    CleanIcon
+    NIBGATIcon
 } from "./assets/svg";
 
 export type {