Forráskód Böngészése

Feature: Switch component added.

lfabl 1 hónapja
szülő
commit
d84af4f14a

+ 16 - 1
example/src/pages/home/index.tsx

@@ -24,6 +24,7 @@ import {
     RowCard,
     Button,
     Dialog,
+    Switch,
     Text
 } from "ncore-ui-kit-mobile";
 import {
@@ -220,6 +221,11 @@ const Home = () => {
         setIsRadioActive
     ] = useState(false);
 
+    const [
+        isSwitchActive,
+        setIsSwitchActive
+    ] = useState(false);
+
     return <PageContainer
         isScrollable={true}
         scrollViewStyle={stylesheet.container}
@@ -229,6 +235,15 @@ const Home = () => {
             }
         }}
     >
+        <Switch
+            spreadBehaviour="stretch"
+            isActive={isSwitchActive}
+            title="dsgojksdgpojk"
+            subTitle="SJFAJdfa"
+            onPress={() => {
+                setIsSwitchActive(!isSwitchActive);
+            }}
+        />
         <RowCard
             title="Merhaba"
             icon={({
@@ -293,7 +308,7 @@ const Home = () => {
             isRequired={true}
             data={X}
             onMoreLoad={(props) => {
-                console.log("GELMİŞEEEE:", props);
+                console.log("More Loaded:", props);
             }}
         />
         <TextInput

+ 4 - 0
src/components/index.ts

@@ -81,3 +81,7 @@ export {
 export {
     default as RowCard
 } from "./rowCard";
+
+export {
+    default as Switch
+} from "./switch";

+ 275 - 0
src/components/switch/index.tsx

@@ -0,0 +1,275 @@
+import {
+    type FC,
+    useEffect,
+    useRef
+} from "react";
+import {
+    TouchableOpacity,
+    Animated,
+    Easing,
+    View
+} from "react-native";
+import type ISwitchProps from "./type";
+import stylesheet, {
+    getSwitchType,
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import type ITextProps from "../text/type";
+import Loading from "../loading";
+import Text from "../text";
+
+const Switch: FC<ISwitchProps> = ({
+    displayBehaviourWhileLoading = "disabled",
+    spreadBehaviour = "baseline",
+    animationDuration = 100,
+    isOptional = false,
+    isDisabled = false,
+    type = "neutral",
+    isFlip = false,
+    customLocalize,
+    subTitleStyle,
+    optionalText,
+    customTheme,
+    titleStyle,
+    isLoading,
+    isActive,
+    subTitle,
+    onPress,
+    title,
+    style,
+    ...props
+}) => {
+    const {
+        inlineSpaces,
+        colors,
+        spaces
+    } = NCoreUIKitTheme.useContext(customTheme);
+
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext(customLocalize);
+
+    const indicatorAnim = useRef(new Animated.Value(0)).current;
+
+    const currentType = getSwitchType({
+        type
+    });
+
+    const SWITCH_INDICATOR_SIZE = 20;
+
+    const {
+        indicatorContainer: indicatorContainerDynamicStyle,
+        contentContainer: contentContainerDynamicStyle,
+        titleContainer: titleContainerDynamicStyle,
+        optionalText: optionalTextDynamicStyle,
+        container: containerDynamicStyle,
+        indicator: indicatorDynamicStyle,
+        subTitle: subTitleDynamicStyle,
+        loading: loadingDynamicStyle,
+        overlay: overlayDynamicStyle,
+        title: titleDynamicStyle
+    } = useStyles({
+        displayBehaviourWhileLoading,
+        SWITCH_INDICATOR_SIZE,
+        spreadBehaviour,
+        inlineSpaces,
+        currentType,
+        isDisabled,
+        isLoading,
+        isActive,
+        isFlip,
+        colors,
+        spaces,
+        type
+    });
+
+    useEffect(() => {
+        if(isActive) {
+            Animated.timing(indicatorAnim, {
+                toValue: (SWITCH_INDICATOR_SIZE / 2) + spaces.spacingSm,
+                duration: animationDuration,
+                useNativeDriver: true,
+                easing: Easing.linear
+            }).start();
+        } else {
+            Animated.timing(indicatorAnim, {
+                duration: animationDuration,
+                useNativeDriver: true,
+                easing: Easing.linear,
+                toValue: 0
+            }).start();
+        }
+    }, [isActive]);
+
+    const titleProps: ITextProps = {
+        color: currentType.titleColor,
+    };
+
+    const subTitleProps: ITextProps = {
+        color: currentType.subTitleColor,
+    };
+
+    const indicatorIconProps: {
+        color: keyof NCoreUIKit.ContainerContentColors;
+        customColor?: string;
+    } = {
+        color: currentType.indicatorColor
+    };
+
+    if (isDisabled || isLoading) {
+        const stateType = type === "danger" ? "error" : type;
+
+        subTitleProps.customColor = colors.system.state.content.disabled[stateType];
+        titleProps.customColor = colors.system.state.content.disabled[stateType];
+
+        if(isActive && stateType === "neutral") {
+            indicatorIconProps.customColor = colors.system.state.border.disabled.primary;
+        } else {
+            indicatorIconProps.customColor = colors.system.state.border.disabled[stateType];
+        }
+    }
+
+    const renderOptionalText = () => {
+        if(!isOptional && !optionalText) {
+            return null;
+        }
+
+        return <Text
+            variant="labelLargeSize"
+            color={titleProps.color}
+            style={[
+                optionalTextDynamicStyle
+            ]}
+            {...titleProps}
+        >
+            ( {isOptional ? localize("is-optional") : optionalText} )
+        </Text>;
+    };
+
+    const renderTitle = () => {
+        if (!title) {
+            return null;
+        }
+
+        return <View
+            style={[
+                stylesheet.titleContainer,
+                titleContainerDynamicStyle
+            ]}
+        >
+            <Text
+                variant="bodyMediumSize"
+                style={[
+                    titleStyle,
+                    stylesheet.title,
+                    titleDynamicStyle
+                ]}
+                {...titleProps}
+            >
+                {title}
+            </Text>
+            {renderOptionalText()}
+        </View>;
+    };
+
+    const renderIndicator = () => {
+        return <Animated.View
+            style={[
+                stylesheet.indicator,
+                indicatorDynamicStyle,
+                {
+                    transform: [{
+                        translateX: indicatorAnim
+                    }]
+                }
+            ]}
+        />;
+    };
+
+    const renderIndicatorContainer = () => {
+        if (isLoading) {
+            return <Loading
+                style={[
+                    loadingDynamicStyle
+                ]}
+            />;
+        }
+
+        return <View
+            style={[
+                stylesheet.indicatorContainer,
+                indicatorContainerDynamicStyle
+            ]}
+        >
+            {renderIndicator()}
+
+            {renderOverlay()}
+        </View>;
+    };
+
+    const renderOverlay = () => {
+        return <View
+            style={[
+                stylesheet.overlay,
+                overlayDynamicStyle
+            ]}
+        />;
+    };
+
+    const renderSubtitle = () => {
+        if(!subTitle) {
+            return null;
+        }
+
+        return <Text
+            {...subTitleProps}
+            style={[
+                subTitleStyle,
+                stylesheet.subTitle,
+                subTitleDynamicStyle
+            ]}
+        >
+            {subTitle}
+        </Text>;
+    };
+
+    const renderContent = () => {
+        if(!title) {
+            return null;
+        }
+
+        return <View
+            style={[
+                stylesheet.contentContainer,
+                contentContainerDynamicStyle
+            ]}
+        >
+            {renderTitle()}
+            {renderSubtitle()}
+        </View>;
+    };
+
+    return (
+        <TouchableOpacity
+            {...props}
+            onPress={isDisabled || isLoading ? () => null : onPress ? onPress : undefined}
+            disabled={isDisabled || isLoading || !onPress}
+            style={[
+                style,
+                stylesheet.container,
+                containerDynamicStyle
+            ]}
+        >
+            {isFlip ? null : renderIndicatorContainer()}
+
+            {renderContent()}
+
+            {isFlip ? renderIndicatorContainer() : null}
+        </TouchableOpacity>
+    );
+};
+export default Switch;

+ 213 - 0
src/components/switch/stylesheet.ts

@@ -0,0 +1,213 @@
+import {
+    type TextStyle,
+    type ViewStyle,
+    StyleSheet
+} from "react-native";
+import {
+    type SwitchDynamicStyleType,
+    type SwitchTypeConstantType,
+    type SwitchTypes,
+    type SwitchType
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+
+export const SWITCH_TYPE_STYLES: Record<
+    SwitchType,
+    {
+        containerColor: keyof NCoreUIKit.ContainerContentColors;
+        indicatorColor: keyof NCoreUIKit.ContainerContentColors;
+        subTitleColor: keyof NCoreUIKit.TextContentColors;
+        titleColor: keyof NCoreUIKit.TextContentColors;
+    }
+> = {
+    primary: {
+        indicatorColor: "default",
+        containerColor: "mid",
+        subTitleColor: "low",
+        titleColor: "mid"
+    },
+    danger: {
+        subTitleColor: "dangerLow",
+        indicatorColor: "danger",
+        containerColor: "danger",
+        titleColor: "danger"
+    },
+    success: {
+        subTitleColor: "successLow",
+        indicatorColor: "success",
+        containerColor: "success",
+        titleColor: "success"
+    },
+    warning: {
+        subTitleColor: "warningLow",
+        indicatorColor: "warning",
+        containerColor: "warning",
+        titleColor: "warning"
+    },
+    info: {
+        subTitleColor: "infoLow",
+        indicatorColor: "info",
+        containerColor: "info",
+        titleColor: "info"
+    },
+    neutral: {
+        indicatorColor: "default",
+        containerColor: "mid",
+        subTitleColor: "low",
+        titleColor: "mid"
+    }
+};
+
+export const getSwitchType = ({
+    type
+}: SwitchTypeConstantType): SwitchTypes => {
+    const currentType = SWITCH_TYPE_STYLES[type];
+
+    return currentType;
+};
+
+const stylesheet = StyleSheet.create({
+    container: {
+        backgroundColor: "transparent",
+        borderColor: "transparent",
+        flexDirection: "row",
+        borderStyle: "solid",
+        alignItems: "center",
+        display: "flex"
+    },
+    indicatorContainer: {
+        position: "relative"
+    },
+    indicator: {
+
+    },
+    contentContainer: {
+        justifyContent: "center",
+        flexDirection: "column"
+    },
+    titleContainer: {
+        justifyContent: "flex-start",
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    title: {
+        textAlign: "left",
+        margin: 0
+    },
+    subTitle: {
+        textAlign: "left"
+    },
+    loading: {},
+    overlay: {
+        position: "absolute",
+        display: "none",
+        zIndex: 999,
+        bottom: -2,
+        right: -2,
+        left: -2,
+        top: -2
+    }
+});
+
+export const useStyles = ({
+    displayBehaviourWhileLoading,
+    SWITCH_INDICATOR_SIZE,
+    spreadBehaviour,
+    inlineSpaces,
+    currentType,
+    isDisabled,
+    isLoading,
+    isActive,
+    colors,
+    isFlip,
+    spaces,
+    type
+}: SwitchDynamicStyleType) => {
+    const styleType = type === "danger" ? "error" : type;
+
+    const styles = {
+        container: {
+            padding: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        indicatorContainer: {
+            backgroundColor: colors.content.container[currentType.containerColor],
+            borderRadius: (SWITCH_INDICATOR_SIZE + (spaces.spacingSm * 2)) / 2,
+            width: (SWITCH_INDICATOR_SIZE * 1.5) + (spaces.spacingSm * 2),
+            padding: spaces.spacingXs
+        } as Mutable<ViewStyle>,
+        indicator: {
+            backgroundColor: colors.content.container[currentType.indicatorColor],
+            borderRadius: SWITCH_INDICATOR_SIZE / 2,
+            height: SWITCH_INDICATOR_SIZE,
+            width: SWITCH_INDICATOR_SIZE
+        } as Mutable<ViewStyle>,
+        contentContainer: {
+            marginLeft: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        titleContainer: {
+
+        } as Mutable<ViewStyle>,
+        title: {
+
+        } as Mutable<TextStyle>,
+        subTitle: {
+
+        } as Mutable<TextStyle>,
+        loading: {
+        } as Mutable<ViewStyle>,
+        overlay: {
+            borderRadius: (SWITCH_INDICATOR_SIZE + (spaces.spacingSm * 2)) / 2
+        } as Mutable<ViewStyle>,
+        optionalText: {
+            marginLeft: inlineSpaces.subTitle
+        } as Mutable<TextStyle>
+    };
+
+    if (isLoading) {
+        if (displayBehaviourWhileLoading === "disabled") {
+            styles.overlay.backgroundColor = colors.system.state.overlay.disabled[styleType];
+            styles.overlay.display = "flex";
+        }
+    }
+
+    if (isFlip) {
+        styles.contentContainer.marginRight = spaces.spacingSm;
+        styles.titleContainer.justifyContent = "flex-end";
+        styles.contentContainer.marginLeft = 0;
+        styles.subTitle.textAlign = "right";
+        styles.title.textAlign = "right";
+    }
+
+    if (spreadBehaviour === "baseline") {
+        styles.container.alignSelf = spreadBehaviour;
+        styles.container.width = "auto";
+    } else if (spreadBehaviour === "stretch") {
+        styles.contentContainer.alignSelf = spreadBehaviour;
+        styles.container.alignSelf = spreadBehaviour;
+        styles.container.justifyContent = "center";
+        styles.contentContainer.flexShrink = 1;
+        styles.contentContainer.width = "100%";
+        styles.container.flexShrink = 1;
+        styles.container.width = "100%";
+    }
+
+    if(isActive) {
+        if(styleType === "neutral") {
+            styles.indicatorContainer.backgroundColor = colors.content.container.primary;
+        }
+    }
+
+    if (isDisabled) {
+        if(styleType === "neutral" && isActive) {
+            styles.indicatorContainer.backgroundColor = colors.system.state.border.disabled.primary;
+        }
+
+        styles.overlay.backgroundColor = colors.system.state.overlay.disabled[styleType];
+        styles.overlay.display = "flex";
+    }
+
+    return styles;
+};
+export default stylesheet;

+ 68 - 0
src/components/switch/type.ts

@@ -0,0 +1,68 @@
+import {
+    type ViewStyle,
+    type StyleProp,
+    type TextStyle
+} from "react-native";
+
+export type SwitchDynamicStyleType = {
+    displayBehaviourWhileLoading?: SwitchDisplayBehaviourWhileLoading;
+    inlineSpaces: NCoreUIKit.ActivePalette["inlineSpaces"];
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    spreadBehaviour?: SwitchSpreadBehaviour;
+    SWITCH_INDICATOR_SIZE: number;
+    currentType: SwitchTypes;
+    isDisabled?: boolean;
+    isLoading?: boolean;
+    isActive?: boolean;
+    isFlip?: boolean;
+    type: SwitchType;
+};
+
+export type SwitchTypes = {
+    containerColor: keyof NCoreUIKit.ContainerContentColors;
+    indicatorColor: keyof NCoreUIKit.ContainerContentColors;
+    subTitleColor: keyof NCoreUIKit.TextContentColors;
+    titleColor: keyof NCoreUIKit.TextContentColors;
+};
+
+export type SwitchTypeConstantType = {
+    type: SwitchType;
+};
+
+export type SwitchType = "primary" | "danger" | "warning" | "neutral" | "success" | "info";
+
+export type SwitchDisplayBehaviourWhileLoading = "none" | "disabled";
+
+export type SwitchSpreadBehaviour = "baseline" | "stretch" | "free";
+
+interface ISwitchProps {
+    displayBehaviourWhileLoading?: SwitchDisplayBehaviourWhileLoading;
+    subTitleStyle?: StyleProp<TextStyle>[] | StyleProp<TextStyle>;
+    titleStyle?: StyleProp<TextStyle>[] | StyleProp<TextStyle>;
+    style?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
+    customTheme?: {
+        gapPropagation?: NCoreUIKit.GapPropagationKey;
+        sharpness?: NCoreUIKit.SharpnessKey;
+        paletteKey?: NCoreUIKit.PaletteKey;
+        themeKey?: NCoreUIKit.ThemeKey;
+    };
+    spreadBehaviour?: SwitchSpreadBehaviour;
+    customLocalize?: {
+        activeLocale?: NCoreUIKit.LocaleKey;
+    };
+    animationDuration?: number;
+    optionalText?: string;
+    isDisabled?: boolean;
+    isOptional?: boolean;
+    onPress?: () => void;
+    isLoading?: boolean;
+    isActive?: boolean;
+    subTitle?: string;
+    type?: SwitchType;
+    isFlip?: boolean;
+    title?: string;
+}
+export type {
+    ISwitchProps as default
+};

+ 1 - 0
src/index.tsx

@@ -26,6 +26,7 @@ export {
     RowCard,
     Loading,
     Dialog,
+    Switch,
     Button,
     Modal,
     Toast,