Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature request] A loading state and animation for buttons #564

Closed
MuhammedKpln opened this issue Aug 4, 2019 · 10 comments
Closed

[feature request] A loading state and animation for buttons #564

MuhammedKpln opened this issue Aug 4, 2019 · 10 comments
Labels
💡 Proposal 📱 Components components module-specific

Comments

@MuhammedKpln
Copy link

Could be useful to have a laoding state for buttons.

@MuhammedKpln
Copy link
Author

We could do it with ActivityIndicator component, but eva design system rules does not allow to add anything other than string in buttons, as related in this issue #488

@artyorsh
Copy link
Collaborator

artyorsh commented Aug 7, 2019

Hi ✋
Thanks for the proposal. This is already under discussion within our team :)
Let's keep it open. I'll share a status

@artyorsh artyorsh added enhancement 📱 Components components module-specific labels Aug 7, 2019
@guilhermelcn
Copy link

guilhermelcn commented Aug 14, 2019

Hi,

This workaround, work for me:

I'm use the prop icon -> https://github.com/akveo/react-native-ui-kitten/blob/master/src/framework/ui/button/button.component.tsx#L274

import {
  Button,
} from 'react-native-ui-kitten';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import {
  ActivityIndicator,
} from 'react-native';

export default class ScButton extends Component {
  static get defaultProps() {
    return {
      loading: false,
      loadingColor: '#FFF',
      loadingSize: 'small',
      children: null,
    };
  }

  static get propTypes() {
    return {
      loading: PropTypes.bool,
      loadingColor: PropTypes.string,
      loadingSize: PropTypes.string,
      children: PropTypes.string,
    };
  }

  renderChildren() {
    const {
      loading,
      children,
    } = this.props;

    if (loading) {
      return null;
    }

    return children;
  }

  renderLoading() {
    const {
      loadingColor,
      loadingSize,
    } = this.props;

    return (
      <ActivityIndicator
        color={loadingColor}
        size={loadingSize}
      />
    );
  }

  render() {
    const {
      loading,
    } = this.props;

    const customProps = {};

    if (loading) {
      Object.assign(customProps, {
        icon: () => this.renderLoading(),
        disabled: true,
      });
    }

    return (
      <Button
        {...this.props}
        {...customProps}
      >
        {this.renderChildren()}
      </Button>
    );
  }
}
import ScButton from './ScButton';

<ScButton loading>
  ButtonText
</ScButton>

image

@artyorsh
Copy link
Collaborator

artyorsh commented Aug 15, 2019

@guilhermelcn good job :) But this will break your build if you'll try do this with TypeScript

@jeloagnasin
Copy link

I also have a workaround:

const [loading, setLoading] = useState(false);
const toggleLoading = useCallback(() => {
  setLoading(!loading);
}, [loading]);

<Button icon={loading ? () => <ActivityIndicator /> : null} onPress={toggleLoading}>
  Tap me to hide/show loading indicator
</Button>

@anthowm
Copy link

anthowm commented Dec 29, 2019

Hi my approach is a custom button based on the original that have an ActivityIndicator passed from props (similar to react native elements approach). Just have a question @artyorsh how can I put a themed color for make it default color (sorry i'm a bit new in react and ui-kitten ^^)

color={this.props.loadingProps ? this.props.loadingProps.color : 'white'}

type IconProp = (style: ImageStyle) => IconElement;

export interface ButtonProps extends StyledComponentProps, TouchableOpacityProps {
    textStyle?: StyleProp<TextStyle>;
    loadingStyle?: StyleProp<ViewStyle>;
    loadingProps?: ActivityIndicatorProps;
    loading?: boolean;
    icon?: IconProp;
    status?: string;
    size?: string;
    children?: string;
}

export type ButtonElement = React.ReactElement<ButtonProps>;

export class ButtonComponent extends React.Component<ButtonProps> implements WebEventResponderCallbacks {

    static styledComponentName: string = 'Button';

    private webEventResponder: WebEventResponderInstance = WebEventResponder.create(this);

    // WebEventResponderCallbacks

    public onMouseEnter = (): void => {
        if (this.props.dispatch) {
            this.props.dispatch([Interaction.HOVER]);
        }
    };

    public onMouseLeave = (): void => {
        if (this.props.dispatch) {
            this.props.dispatch([]);
        }
    };

    public onFocus = (): void => {
        if (this.props.dispatch) {
            this.props.dispatch([Interaction.FOCUSED]);
        }
    };

    public onBlur = (): void => {
        if (this.props.dispatch) {
            this.props.dispatch([]);
        }
    };

    private onPress = (event: GestureResponderEvent): void => {
        if (this.props.onPress) {
            this.props.onPress(event);
        }
    };

    private onPressIn = (event: GestureResponderEvent): void => {
        if (this.props.dispatch) {
            this.props.dispatch([Interaction.ACTIVE]);
        }

        if (this.props.onPressIn) {
            this.props.onPressIn(event);
        }
    };

    private onPressOut = (event: GestureResponderEvent): void => {
        if (this.props.dispatch) {
            this.props.dispatch([]);
        }

        if (this.props.onPressOut) {
            this.props.onPressOut(event);
        }
    };

    private getComponentStyle = (source: StyleType): StyleType => {
        const {
            textColor,
            textFontFamily,
            textFontSize,
            textLineHeight,
            textFontWeight,
            textMarginHorizontal,
            iconWidth,
            iconHeight,
            iconTintColor,
            iconMarginHorizontal,
            ...containerParameters
        } = source;

        return {
            container: containerParameters,
            text: {
                color: textColor,
                fontFamily: textFontFamily,
                fontSize: textFontSize,
                lineHeight: textLineHeight,
                fontWeight: textFontWeight,
                marginHorizontal: textMarginHorizontal,
            },
            icon: {
                width: iconWidth,
                height: iconHeight,
                tintColor: iconTintColor,
                marginHorizontal: iconMarginHorizontal,
            },
        };
    };

    private renderTextElement = (style: TextStyle): TextElement => {
        return (
            <Text
                key={1}
                style={[style, styles.text, this.props.textStyle]}>
                {this.props.children}
            </Text>
        );
    };

    private renderActivityIndicatorElement = (style: ViewStyle) => {
        return (
            <ActivityIndicator
                style={StyleSheet.flatten([styles.loading, this.props.loadingStyle, style])}
                color={this.props.loadingProps ? this.props.loadingProps.color : 'white'}
                size={this.props.loadingProps ? this.props.loadingProps.size : 'small'}
                {...this.props.loadingProps}
            />
        )
    }

    private renderIconElement = (style: ImageStyle): IconElement | null => {
        if (this.props.icon) {
            const iconElement: IconElement = this.props.icon(style);

            return React.cloneElement(iconElement, {
                key: 2,
                style: [style, styles.icon, iconElement.props.style],
            });
        }
        return null;
    };

    private renderComponentChildren = (style: StyleType): React.ReactNodeArray => {
        const { icon, children } = this.props;

        return [
            icon && this.renderIconElement(style.icon),
            isValidString(children as any) && this.renderTextElement(style.text),
            this.renderActivityIndicatorElement(style.loading),
        ];
    };

    public render(): React.ReactElement<TouchableOpacityProps> {
        const { themedStyle, style, ...containerProps } = this.props;
        const { container, ...childStyles } = this.getComponentStyle(themedStyle as any);
        const [iconElement, textElement, activityElement] = this.renderComponentChildren(childStyles);

        return (
            <TouchableOpacity
                activeOpacity={1.0}
                {...containerProps}
                {...this.webEventResponder.eventHandlers}
                style={[container, styles.container, webStyles.container, style]}
                onPress={this.props.loading ? undefined : this.onPress}
                onPressIn={this.onPressIn}
                onPressOut={this.onPressOut}>
                {!this.props.loading && iconElement}
                {!this.props.loading && textElement}
                {this.props.loading && activityElement}
            </TouchableOpacity>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flexDirection: 'row',
        justifyContent: 'center',
        alignItems: 'center',
    },
    text: {},
    icon: {},
    loading: {
        marginVertical: 2,
    },
});

const webStyles = Platform.OS === 'web' && StyleSheet.create({
    container: {
        // @ts-ignore
        outlineWidth: 0,
    },
});

export const ButtonCustom = styled<ButtonProps>(ButtonComponent);

@artyorsh
Copy link
Collaborator

artyorsh commented Dec 30, 2019

@anthowm you can access theme by using withStyles or styled

import { withStyles } from '@ui-kitten/components'

const ScreenComponent = (props) => (
  <ActivityIndicator style={props.themedStyle.indicator}/>
);

export const Screen = withStyles(ScreenComponent, theme => ({
  indicator: { backgroundColor: theme['color-primary-default'] },
}));

@artyorsh
Copy link
Collaborator

artyorsh commented Mar 6, 2020

Woop. Soon in v5 😄(sorry for fps)

https://i.imgur.com/qE5QHUq.gif

@anthowm
Copy link

anthowm commented Mar 6, 2020

@artyorsh Cool I didn't read the v5 roadmap but looks excited 🌊 . thanks for the job 💯

@artyorsh
Copy link
Collaborator

artyorsh commented Apr 8, 2020

Available in v5! 🎉

@artyorsh artyorsh closed this as completed Apr 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💡 Proposal 📱 Components components module-specific
Projects
None yet
Development

No branches or pull requests

5 participants