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

Badge #865

Open
spolesciuc opened this issue Feb 4, 2020 · 6 comments
Open

Badge #865

spolesciuc opened this issue Feb 4, 2020 · 6 comments
Labels
💡 Proposal 📱 Components components module-specific

Comments

@spolesciuc
Copy link

spolesciuc commented Feb 4, 2020

🚀 Feature Proposal

Hey.
It would be great if you added the ability to add to Bottom Navigation
"Badge".

Example

https://imgur.com/bKniATG
https://react-native-elements.github.io/react-native-elements/docs/badge.html

@spolesciuc spolesciuc changed the title badge Badge Feb 4, 2020
@artyorsh
Copy link
Collaborator

artyorsh commented Feb 5, 2020

Hi @stanislavpoleshuk, thanks for the proposal. Is there any workaround to achieve this currently?

@spolesciuc
Copy link
Author

spolesciuc commented Feb 5, 2020

Yes, it turned out to be done.
Created a wrapper over the BottomNavigationTab component


interface ComponentProps {
  badgeText?: string;
}

export type EmptyDataProps = ThemedComponentProps & BottomNavigationTabProps & ComponentProps;

interface State {
}

class EmptyDataContainer extends React.PureComponent<EmptyDataProps, State> {

  constructor(props: EmptyDataProps) {
    super(props);
    this.state = {};
  }

  public render(): React.ReactNode {
    const {themedStyle, title, icon, titleStyle, selected, badgeText, onSelect} = this.props;

    return (
      <View style={themedStyle.container}>
        <BottomNavigationTab
          titleStyle={titleStyle}
          selected={selected}
          onSelect={onSelect}
          title={title}
          icon={icon}
        />
        {
          badgeText &&
          <View style={themedStyle.badge}>
            <Text style={themedStyle.badgeText}>
              {badgeText}
            </Text>
          </View>
        }
      </View>
    );
  }
}

export const CustomBottomNavigationTab = withStyles(EmptyDataContainer, (theme: ThemeType) => ({
  container: {
    flex: 1,
  },
  badge: {
    position: 'absolute',
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    width: '100%'
  },
  badgeText: {
    ...textStyle.headline,
    borderWidth: 2,
    borderColor: Colors.white,
    padding: 2,
    backgroundColor: 'red',
    borderRadius: 20,
    fontSize: 9,
    lineHeight: 11,
    color: Colors.white,
    minWidth: 19,
    textAlignVertical: 'center',
    textAlign: 'center',
    left: 10,
    top: -2,
  },
}));

@artyorsh artyorsh added the 📱 Components components module-specific label Feb 11, 2020
@artyorsh artyorsh mentioned this issue Jul 27, 2020
@potatowave
Copy link

Yes, it turned out to be done.
Created a wrapper over the BottomNavigationTab component


interface ComponentProps {
  badgeText?: string;
}

export type EmptyDataProps = ThemedComponentProps & BottomNavigationTabProps & ComponentProps;

interface State {
}

class EmptyDataContainer extends React.PureComponent<EmptyDataProps, State> {

  constructor(props: EmptyDataProps) {
    super(props);
    this.state = {};
  }

  public render(): React.ReactNode {
    const {themedStyle, title, icon, titleStyle, selected, badgeText, onSelect} = this.props;

    return (
      <View style={themedStyle.container}>
        <BottomNavigationTab
          titleStyle={titleStyle}
          selected={selected}
          onSelect={onSelect}
          title={title}
          icon={icon}
        />
        {
          badgeText &&
          <View style={themedStyle.badge}>
            <Text style={themedStyle.badgeText}>
              {badgeText}
            </Text>
          </View>
        }
      </View>
    );
  }
}

export const CustomBottomNavigationTab = withStyles(EmptyDataContainer, (theme: ThemeType) => ({
  container: {
    flex: 1,
  },
  badge: {
    position: 'absolute',
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    width: '100%'
  },
  badgeText: {
    ...textStyle.headline,
    borderWidth: 2,
    borderColor: Colors.white,
    padding: 2,
    backgroundColor: 'red',
    borderRadius: 20,
    fontSize: 9,
    lineHeight: 11,
    color: Colors.white,
    minWidth: 19,
    textAlignVertical: 'center',
    textAlign: 'center',
    left: 10,
    top: -2,
  },
}));

Has anyone actually made this work? For me it displays, but clicking the Tab does nothing.

@ncryer
Copy link

ncryer commented Nov 1, 2020

You don't actually need any fancy stuff to add a badge (as it turns out). Consider something like this:

// ...
const selectedIndex: number = 0;
const colors = {primary: 'blue'}; 
const unreadNotifications: number = 3; 
// ... 
<BottomNavigationTab
        icon={(props) => (
          <View>
            <Icon
              {...props}
              name="bell-outline"
              fill={selectedIndex == 0 ? colors.primary : "grey"}
            />
            <View
              style={{
                height: 15,
                width: 15,
                backgroundColor: "red",
                borderRadius: 10,
                position: "absolute",
                right: 0,
              }}
            >
              <Text
                style={{ color: "#fff", textAlign: "center", fontSize: 10 }}
              >
                {unreadNotifications}
              </Text>
            </View>
          </View>
        )}
      />

unreadNotifications is a number, and in this example the icon fill is determined by the footer state. But the point remains: you can just make the BottomNavigationTab render a custom View with its icon prop.

As such, you can make the presence of the badge view and the unreadNotifications to be functions of state.

@firuzcanh
Copy link

firuzcanh commented Apr 20, 2021

Maybe it can be useful

// badge.js
import React from "react";
import { View } from "react-native";
import { Text, styled } from "@ui-kitten/components";
import styles from "./badge.style";

@styled("Badge")
class Badge extends React.Component {
  rendeBadgeComponent = () => {
    const { eva, style, count, children, ...restProps } = this.props;
    switch (typeof count) {
      case "number":
      case "string":
        return (
          <View style={[eva.style, styles.badge, style]} {...restProps}>
            <Text
              style={{
                color: eva.style.textColor,
                fontSize: eva.style.textFontSize,
                fontWeight: eva.style.textFontWeight,
              }}
            >
              {this.props.count}
            </Text>
          </View>
        );
      case "function":
        return count();
    }
  };
  render() {
    const { children } = this.props;
    return (
      <View style={styles.badgeContainer}>
        <View style={children ? styles.badgeContainerAbsolute : null}>
          {this.rendeBadgeComponent()}
        </View>
        {children}
      </View>
    );
  }
}
// badge.style.js
import { StyleSheet } from "react-native";

export default StyleSheet.create({
  badgeContainer: {
    position: "relative",
  },
  badgeContainerAbsolute: {
    position: "absolute",
    zIndex: 1,
    right: 0,
    top: 0,
    transform: [{ translateX: "5%" }, { translateY: "-5%" }],
  },
  badge: {
    alignItems: "center",
    justifyContent: "center",
  },
});
// mapping.json
    "Badge": {
      "meta": {
        "scope": "all",
        "parameters": {
          "height": {
            "type": "number"
          },
          "paddingHorizontal": {
            "type": "number"
          },
          "textColor": {
            "type": "string"
          },
          "textFontSize": {
            "type": "number"
          },
          "textFontWeight": {
            "type": "string"
          },
          "textFontFamily": {
            "type": "string"
          },
          "borderRadius": {
            "type": "number"
          },
          "borderColor": {
            "type": "string"
          },
          "borderWidth": {
            "type": "number"
          },
          "backgroundColor": {
            "type": "string"
          }
        },
        "appearances": {
          "filled": {
            "default": true
          },
          "outline": {
            "default": false
          }
        },
        "variantGroups": {
          "status": {
            "primary": {
              "default": true
            },
            "success": {
              "default": false
            },
            "info": {
              "default": false
            },
            "warning": {
              "default": false
            },
            "danger": {
              "default": false
            }
          },
          "size": {
            "small": {
              "default": false
            },
            "medium": {
              "default": true
            },
            "large": {
              "default": false
            }
          }
        },
        "states": {
          "active": {
            "default": false,
            "priority": 2,
            "scope": "all"
          }
        }
      },
      "appearances": {
        "filled": {
          "mapping": {
            "borderRadius": "border-radius-full",
            "textFontFamily": "$text-font-family"
          },
          "variantGroups": {
            "status": {
              "primary": {
                "borderColor": "color-primary-default-border",
                "backgroundColor": "color-primary-default",
                "textColor": "text-control-color"
              },
              "success": {
                "borderColor": "color-success-default-border",
                "backgroundColor": "color-success-default",
                "textColor": "text-control-color"
              },
              "info": {
                "borderColor": "color-info-default-border",
                "backgroundColor": "color-info-default",
                "textColor": "text-control-color"
              },
              "warning": {
                "borderColor": "color-warning-default-border",
                "backgroundColor": "color-warning-default",
                "textColor": "text-control-color"
              },
              "danger": {
                "borderColor": "color-danger-default-border",
                "backgroundColor": "color-danger-default",
                "textColor": "text-control-color"
              }
            },
            "size": {
              "small": {
                "height": 15,
                "paddingHorizontal": 4,
                "borderWidth": "border-width",
                "textFontSize": 10,
                "textFontWeight": "text-caption-1-font-weight"
              },
              "medium": {
                "height": 20,
                "paddingHorizontal": 6,
                "borderWidth": "border-width",
                "textFontSize": 12,
                "textFontWeight": "bold"
              },
              "large": {
                "height": 25,
                "paddingHorizontal": 8,
                "borderWidth": "border-width",
                "textFontSize": 14,
                "textFontWeight": "bold"
              }
            }
          }
        },
        "outline": {
          "variantGroups": {
            "status": {
              "primary": {
                "borderColor": "color-primary-default-border",
                "backgroundColor": "transparent",
                "textColor": "color-primary-default"
              },
              "success": {
                "borderColor": "color-success-default-border",
                "backgroundColor": "transparent",
                "textColor": "color-success-default"
              },
              "info": {
                "borderColor": "color-info-default-border",
                "backgroundColor": "transparent",
                "textColor": "color-info-default"
              },
              "warning": {
                "borderColor": "color-warning-default-border",
                "backgroundColor": "transparent",
                "textColor": "color-warning-default"
              },
              "danger": {
                "borderColor": "color-danger-default-border",
                "backgroundColor": "transparent",
                "textColor": "color-danger-default"
              }
            }
          }
        }
      }
    }

Usage

<Badge count={1} appearance="filled" status="danger" size="medium" />
//or
<Badge count={1} appearance="filled" status="danger" size="medium">
    <CustomComponent />
</Badge>
//or
<Badge count={() => <CustomIcon />} />
// badge.d.ts
import React from 'react';
export interface BadgeProps {
  count: any;
  status: 'primary' | 'success' | 'danger' | 'warning' | 'info' | string;
  appearance: 'filled' | 'outline' | string;
  size: 'small' | 'medium' | 'large' | string;
}
export default class Badge extends React.Component<BadgeProps> {}

@domlimm
Copy link

domlimm commented Jul 9, 2021

You don't actually need any fancy stuff to add a badge (as it turns out). Consider something like this:

// ...
const selectedIndex: number = 0;
const colors = {primary: 'blue'}; 
const unreadNotifications: number = 3; 
// ... 
<BottomNavigationTab
        icon={(props) => (
          <View>
            <Icon
              {...props}
              name="bell-outline"
              fill={selectedIndex == 0 ? colors.primary : "grey"}
            />
            <View
              style={{
                height: 15,
                width: 15,
                backgroundColor: "red",
                borderRadius: 10,
                position: "absolute",
                right: 0,
              }}
            >
              <Text
                style={{ color: "#fff", textAlign: "center", fontSize: 10 }}
              >
                {unreadNotifications}
              </Text>
            </View>
          </View>
        )}
      />

unreadNotifications is a number, and in this example the icon fill is determined by the footer state. But the point remains: you can just make the BottomNavigationTab render a custom View with its icon prop.

As such, you can make the presence of the badge view and the unreadNotifications to be functions of state.

Hello, I got this error. May I ask if there's any workaround? Thanks so much!

image

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

6 participants