react-native#GestureResponderEvent TypeScript Examples

The following examples show how to use react-native#GestureResponderEvent. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: icon.tsx    From THUInfo with MIT License 6 votes vote down vote up
HomeIcon = ({
	title,
	onPress,
	children,
}: {
	title: keyof typeof zh;
	onPress: (event: GestureResponderEvent) => void;
	children: ReactElement;
}) => {
	const themeName = useColorScheme();
	const theme = themes(themeName);

	return (
		<TouchableOpacity
			style={{
				marginTop: 12,
				alignItems: "center",
				flexGrow: 0,
				flexShrink: 0,
				flexBasis: "20%",
			}}
			onPress={onPress}>
			{children}
			<Text
				style={{color: theme.colors.text, marginTop: 8}}
				ellipsizeMode="tail"
				numberOfLines={1}>
				{getStr(title)}
			</Text>
		</TouchableOpacity>
	);
}
Example #2
Source File: useLinkProps.d.ts    From nlw2-proffy with MIT License 6 votes vote down vote up
/**
 * Hook to get props for an anchor tag so it can work with in page navigation.
 *
 * @param props.to Absolute path to screen (e.g. `/feeds/hot`).
 * @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
 */
export default function useLinkProps({ to, action }: Props): {
    href: string;
    accessibilityRole: "link";
    onPress: (e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent) => void;
};
Example #3
Source File: CardButton.tsx    From react-native-ios-context-menu with MIT License 6 votes vote down vote up
/**
 * ```
 * ┌─────────────────────────────┐
 * │ Title                       │
 * │ Subtitle                    │
 * └─────────────────────────────┘
 * ```
 */         
export function CardButton(props: {
  title: string;
  subtitle: string;
  onPress: (event: GestureResponderEvent) => void;
}){
  return(
    <TouchableOpacity 
      style={styles.cardButtonContainer}
      onPress={props.onPress}
    >
      <React.Fragment>
        <Text style={styles.cardButtonTitleText}>
          {props.title}
        </Text>
        <Text style={styles.cardButtonSubtitleText}>
          {props.subtitle}
        </Text>
      </React.Fragment>
    </TouchableOpacity>
  );
}
Example #4
Source File: feedback.tsx    From THUInfo with MIT License 6 votes vote down vote up
BottomButton = ({
	text,
	onPress,
	disabled,
}: {
	text: keyof typeof zh;
	onPress: (event: GestureResponderEvent) => void;
	disabled: boolean;
}) => {
	const dark = useColorScheme() === "dark";
	return (
		<TouchableOpacity
			style={{
				backgroundColor: dark
					? disabled
						? "#FFF4"
						: "#ccc"
					: disabled
					? "#0000"
					: "#0002",
				flex: 1,
				margin: 4,
				borderRadius: 4,
			}}
			disabled={disabled}
			onPress={(e) => !disabled && onPress(e)}>
			<Text style={{textAlign: "center", padding: 10}}>{getStr(text)}</Text>
		</TouchableOpacity>
	);
}
Example #5
Source File: items.tsx    From THUInfo with MIT License 6 votes vote down vote up
SettingsMiddleText = ({
	text,
	onPress,
}: {
	text: string;
	onPress: (event: GestureResponderEvent) => void;
}) => {
	const themeName = useColorScheme();
	const {colors} = themes(themeName);
	const content = (
		<View
			style={{
				padding: 8,
				alignItems: "center",
			}}>
			<Text style={{color: colors.text}}>{text}</Text>
		</View>
	);
	return Platform.OS === "ios" ? (
		<TouchableHighlight underlayColor="#0002" onPress={onPress}>
			{content}
		</TouchableHighlight>
	) : (
		<TouchableNativeFeedback
			background={TouchableNativeFeedback.Ripple("#0002", false)}
			onPress={onPress}>
			{content}
		</TouchableNativeFeedback>
	);
}
Example #6
Source File: gitlab.tsx    From THUInfo with MIT License 6 votes vote down vote up
BranchItem = ({
	name,
	onPress,
}: {
	name: string;
	onPress: (event: GestureResponderEvent) => void;
}) => {
	const themeName = useColorScheme();
	const {colors} = themes(themeName);
	const content = (
		<View
			style={{
				padding: 8,
				paddingRight: 16,
				flexDirection: "row",
				justifyContent: "space-between",
			}}>
			<Text style={{fontSize: 17, marginHorizontal: 10, color: colors.text}}>
				{name}
			</Text>
		</View>
	);
	return Platform.OS === "ios" ? (
		<TouchableHighlight underlayColor="#0002" onPress={onPress}>
			{content}
		</TouchableHighlight>
	) : (
		<TouchableNativeFeedback
			background={TouchableNativeFeedback.Ripple("#0002", false)}
			onPress={onPress}>
			{content}
		</TouchableNativeFeedback>
	);
}
Example #7
Source File: Link.tsx    From nlw2-proffy with MIT License 6 votes vote down vote up
/**
 * Component to render link to another screen using a path.
 * Uses an anchor tag on the web.
 *
 * @param props.to Absolute path to screen (e.g. `/feeds/hot`).
 * @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
 * @param props.children Child elements to render the content.
 */
export default function Link({ to, action, ...rest }: Props) {
  const props = useLinkProps({ to, action });

  const onPress = (
    e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
  ) => {
    if ('onPress' in rest) {
      rest.onPress?.(e);
    }

    props.onPress(e);
  };

  return React.createElement(Text, {
    ...props,
    ...rest,
    ...Platform.select({
      web: { onClick: onPress } as any,
      default: { onPress },
    }),
  });
}
Example #8
Source File: gitlab.tsx    From THUInfo with MIT License 5 votes vote down vote up
ProjectItem = ({
	project,
	onPress,
}: {
	project: Project;
	onPress: (event: GestureResponderEvent) => void;
}) => {
	const themeName = useColorScheme();
	const {colors} = themes(themeName);
	const content = (
		<View
			style={{
				padding: 8,
				flexDirection: "row",
				justifyContent: "space-between",
			}}>
			<View
				style={{flexDirection: "column", flex: 3, alignItems: "flex-start"}}>
				<Text style={{fontSize: 13, marginHorizontal: 10, color: "grey"}}>
					{project.path_with_namespace}
				</Text>
				<Text style={{fontSize: 17, marginHorizontal: 10, color: colors.text}}>
					{project.name}
				</Text>
			</View>
			<View style={{flexDirection: "column", flex: 1, alignItems: "flex-end"}}>
				<Text style={{fontSize: 14, marginHorizontal: 6, color: colors.text}}>
					{getStr("gitlabLastUpdate")}
				</Text>
				<Text style={{fontSize: 14, marginHorizontal: 6, color: colors.text}}>
					<TimeAgo time={project.last_activity_at} />
				</Text>
			</View>
		</View>
	);
	return Platform.OS === "ios" ? (
		<TouchableHighlight underlayColor="#0002" onPress={onPress}>
			{content}
		</TouchableHighlight>
	) : (
		<TouchableNativeFeedback
			background={TouchableNativeFeedback.Ripple("#0002", false)}
			onPress={onPress}>
			{content}
		</TouchableNativeFeedback>
	);
}
Example #9
Source File: gitlab.tsx    From THUInfo with MIT License 5 votes vote down vote up
FileItem = ({
	file,
	onPress,
	onLongPress,
}: {
	file: File;
	onPress: (event: GestureResponderEvent) => void;
	onLongPress?: (event: GestureResponderEvent) => void;
}) => {
	const themeName = useColorScheme();
	const {colors} = themes(themeName);
	const content = (
		<View
			style={{
				padding: 8,
				flexDirection: "row",
				justifyContent: "flex-start",
				alignItems: "center",
			}}>
			<Feather name={file.type === "tree" ? "folder" : "code"} size={20} />
			<Text style={{fontSize: 17, marginHorizontal: 10, color: colors.text}}>
				{file.name}
			</Text>
		</View>
	);
	return Platform.OS === "ios" ? (
		<TouchableHighlight
			underlayColor="#0002"
			onPress={onPress}
			onLongPress={onLongPress}>
			{content}
		</TouchableHighlight>
	) : (
		<TouchableNativeFeedback
			background={TouchableNativeFeedback.Ripple("#0002", false)}
			onPress={onPress}
			onLongPress={onLongPress}>
			{content}
		</TouchableNativeFeedback>
	);
}
Example #10
Source File: Radio.tsx    From react-native-design-kit with MIT License 5 votes vote down vote up
export default function Radio({
  containerStyle,
  radioIds,
  radioComponent,
  defaultId,
  onSelect,
  onPress,
  ...props
}: RadioProps) {
  const [selected, setSelected] = useState(defaultId);

  const handlePressRadioItem = useCallback(
    (id: string, event: GestureResponderEvent) => {
      onPress && onPress(event);
      onSelect(id);
      setSelected(id);
    },
    [onPress, onSelect],
  );

  const handleRenderRadioItem = useCallback(
    (id: string) => {
      const isSelected = selected === id;
      const component = radioComponent && radioComponent({id, isSelected});
      const title =
        typeof component === 'string'
          ? component
          : component === undefined
          ? id
          : undefined;

      return (
        <RadioItem
          {...props}
          testID="radio-item"
          key={id}
          title={title}
          isSelected={isSelected}
          onPress={event => handlePressRadioItem(id, event)}>
          {component !== undefined &&
            typeof component !== 'string' &&
            component}
        </RadioItem>
      );
    },
    [props, selected, radioComponent, handlePressRadioItem],
  );

  const handleRenderListRadioItem = useMemo(
    () => radioIds.map(value => handleRenderRadioItem(value)),
    [radioIds, handleRenderRadioItem],
  );

  return <View style={containerStyle}>{handleRenderListRadioItem}</View>;
}
Example #11
Source File: items.tsx    From THUInfo with MIT License 5 votes vote down vote up
SettingsItem = ({
	text,
	onPress,
	icon,
	badge,
	normalText,
}: {
	text: string;
	onPress: (event: GestureResponderEvent) => void;
	icon: ReactElement | undefined;
	badge?: string;
	normalText?: boolean;
}) => {
	const themeName = useColorScheme();
	const {colors} = themes(themeName);
	const content = (
		<View
			style={{
				padding: 8,
				paddingRight: 16,
				flexDirection: "row",
				justifyContent: "space-between",
			}}>
			<View style={{flexDirection: "row", alignItems: "center"}}>
				{setIconWidth(icon, colors)}
				<Text
					style={
						normalText
							? {color: colors.text}
							: {fontSize: 17, marginHorizontal: 10, color: colors.text}
					}
					numberOfLines={1}>
					{text}
					{badge && <Text style={{color: "red", fontSize: 12}}>[{badge}]</Text>}
				</Text>
			</View>
			<Icon name="angle-right" size={24} color="lightgrey" />
		</View>
	);
	return Platform.OS === "ios" ? (
		<TouchableHighlight underlayColor="#0002" onPress={onPress}>
			{content}
		</TouchableHighlight>
	) : (
		<TouchableNativeFeedback
			background={TouchableNativeFeedback.Ripple("#0002", false)}
			onPress={onPress}>
			{content}
		</TouchableNativeFeedback>
	);
}
Example #12
Source File: items.tsx    From THUInfo with MIT License 5 votes vote down vote up
SettingsDoubleText = ({
	textLeft,
	textRight,
	onPress,
	icon,
}: {
	textLeft: string;
	textRight: string;
	onPress: ((event: GestureResponderEvent) => void) | undefined;
	icon: ReactElement | undefined;
}) => {
	const themeName = useColorScheme();
	const {colors} = themes(themeName);
	const content = (
		<View
			style={{
				padding: 8,
				paddingRight: 16,
				flexDirection: "row",
				justifyContent: "space-between",
			}}>
			<View style={{flexDirection: "row", alignItems: "center"}}>
				{setIconWidth(icon, colors)}
				<Text style={{fontSize: 17, marginHorizontal: 10, color: colors.text}}>
					{textLeft}
				</Text>
			</View>
			<Text style={{fontSize: 17, marginHorizontal: 10, color: colors.text}}>
				{textRight}
			</Text>
		</View>
	);
	return onPress === undefined ? (
		content
	) : Platform.OS === "ios" ? (
		<TouchableHighlight underlayColor="#0002" onPress={onPress}>
			{content}
		</TouchableHighlight>
	) : (
		<TouchableNativeFeedback
			background={TouchableNativeFeedback.Ripple("#0002", false)}
			onPress={onPress}>
			{content}
		</TouchableNativeFeedback>
	);
}
Example #13
Source File: Modal.tsx    From react-native-design-kit with MIT License 5 votes vote down vote up
export default function Modal({
  containerStyle,
  hasBackdrop = true,
  backdropContainerStyle,
  onPressBackdrop,
  visible = false,
  transparent,
  children,
  ...props
}: ModalProps) {
  const [toggle, setToggle] = useState(visible);

  const handlePressBackdrop = useCallback(
    (event: GestureResponderEvent) => {
      onPressBackdrop && onPressBackdrop(event);
      setToggle(!toggle);
    },
    [toggle, onPressBackdrop],
  );

  const handleRenderBackdrop = useMemo(
    () =>
      hasBackdrop && (
        <Touchable
          testID="backdrop"
          touchableType="normal"
          onPress={handlePressBackdrop}>
          <View
            style={StyleSheet.flatten([
              !transparent &&
                StyleSheet.flatten([
                  styles.backdropContainer,
                  backdropContainerStyle,
                ]),
              styles.fixedBackdropContainer,
            ])}
          />
        </Touchable>
      ),
    [hasBackdrop, transparent, backdropContainerStyle, handlePressBackdrop],
  );

  useDidUpdate(() => {
    setToggle(visible);
  }, [visible]);

  return (
    <ModalRN {...props} transparent visible={toggle}>
      <View style={StyleSheet.flatten([styles.container, containerStyle])}>
        {handleRenderBackdrop}
        {children}
      </View>
    </ModalRN>
  );
}
Example #14
Source File: items.tsx    From THUInfo with MIT License 5 votes vote down vote up
SettingsLargeButton = ({
	text,
	onPress,
	disabled,
	redText,
}: {
	text: string;
	onPress: (event: GestureResponderEvent) => void;
	disabled: boolean;
	redText: boolean;
}) => {
	const themeName = useColorScheme();
	const {colors} = themes(themeName);
	return (
		<TouchableOpacity
			style={{
				backgroundColor:
					themeName === "dark"
						? disabled
							? "#FFF4"
							: "#ccc4"
						: disabled
						? "#0000"
						: "#0002",
				marginHorizontal: 20,
				borderRadius: 4,
				justifyContent: "center",
			}}
			disabled={disabled}
			onPress={(e) => !disabled && onPress(e)}>
			<Text
				style={{
					textAlign: "center",
					padding: 12,
					fontSize: 20,
					color: redText ? "red" : colors.text,
				}}>
				{text}
			</Text>
		</TouchableOpacity>
	);
}
Example #15
Source File: invoice.tsx    From THUInfo with MIT License 5 votes vote down vote up
InvoiceItem = ({
	invoice,
	onPress,
}: {
	invoice: Invoice;
	onPress: (event: GestureResponderEvent) => void;
}) => {
	const themeName = useColorScheme();
	const {colors} = themes(themeName);
	const content = (
		<View style={{padding: 8}}>
			<View
				style={{
					flexDirection: "row",
					justifyContent: "space-between",
				}}>
				<View
					style={{flexDirection: "column", flex: 3, alignItems: "flex-start"}}>
					<Text style={{fontSize: 13, marginHorizontal: 10, color: "grey"}}>
						{invoice.inv_no}
					</Text>
					<Text
						style={{fontSize: 17, marginHorizontal: 10, color: colors.text}}>
						{invoice.financial_item_name}
					</Text>
					<Text style={{fontSize: 14, marginHorizontal: 10, color: "grey"}}>
						{invoice.financial_dept_name}/{invoice.payment_item_type_name}
					</Text>
				</View>
				<View
					style={{flexDirection: "column", flex: 1, alignItems: "flex-end"}}>
					<Text style={{fontSize: 18, marginHorizontal: 6, color: colors.text}}>
						{invoice.inv_amount}
					</Text>
					<Text style={{fontSize: 12, marginHorizontal: 6, color: "grey"}}>
						{invoice.inv_date}
					</Text>
				</View>
			</View>
			<Text style={{fontSize: 14, marginHorizontal: 10, color: "grey"}}>
				{invoice.inv_note}
			</Text>
		</View>
	);
	return Platform.OS === "ios" ? (
		<TouchableHighlight underlayColor="#0002" onPress={onPress}>
			{content}
		</TouchableHighlight>
	) : (
		<TouchableNativeFeedback
			onPress={onPress}
			background={TouchableNativeFeedback.Ripple("#0002", false)}>
			{content}
		</TouchableNativeFeedback>
	);
}
Example #16
Source File: reservesLibWelcome.tsx    From THUInfo with MIT License 5 votes vote down vote up
BookItem = ({
	book,
	onPress,
}: {
	book: SearchResultItem;
	onPress: (event: GestureResponderEvent) => void;
}) => {
	const themeName = useColorScheme();
	const {colors} = themes(themeName);
	const content = (
		<View
			style={{
				padding: 8,
				flexDirection: "row",
				justifyContent: "space-between",
			}}>
			<View
				style={{flexDirection: "column", flex: 3, alignItems: "flex-start"}}>
				<Text style={{fontSize: 13, marginHorizontal: 10, color: "grey"}}>
					{book.author}
				</Text>
				<Text style={{fontSize: 17, marginHorizontal: 10, color: colors.text}}>
					{book.title}
				</Text>
			</View>
			<View style={{flexDirection: "column", flex: 1, alignItems: "flex-end"}}>
				<Text style={{fontSize: 14, marginHorizontal: 6, color: colors.text}}>
					{book.publisher}
				</Text>
			</View>
		</View>
	);
	return Platform.OS === "ios" ? (
		<TouchableHighlight underlayColor="#0002" onPress={onPress}>
			{content}
		</TouchableHighlight>
	) : (
		<TouchableNativeFeedback
			background={TouchableNativeFeedback.Ripple("#0002", false)}
			onPress={onPress}>
			{content}
		</TouchableNativeFeedback>
	);
}
Example #17
Source File: useLinkProps.tsx    From nlw2-proffy with MIT License 5 votes vote down vote up
/**
 * Hook to get props for an anchor tag so it can work with in page navigation.
 *
 * @param props.to Absolute path to screen (e.g. `/feeds/hot`).
 * @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
 */
export default function useLinkProps({ to, action }: Props) {
  const navigation = React.useContext(NavigationHelpersContext);
  const linkTo = useLinkTo();

  const onPress = (
    e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
  ) => {
    let shouldHandle = false;

    if (Platform.OS !== 'web' || !e) {
      shouldHandle = e ? !e.defaultPrevented : true;
    } else if (
      !e.defaultPrevented && // onPress prevented default
      // @ts-expect-error: these properties exist on web, but not in React Native
      !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
      // @ts-expect-error: these properties exist on web, but not in React Native
      (e.button == null || e.button === 0) && // ignore everything but left clicks
      // @ts-expect-error: these properties exist on web, but not in React Native
      [undefined, null, '', 'self'].includes(e.currentTarget?.target) // let browser handle "target=_blank" etc.
    ) {
      e.preventDefault();
      shouldHandle = true;
    }

    if (shouldHandle) {
      if (action) {
        if (navigation) {
          navigation.dispatch(action);
        } else {
          throw new Error("Couldn't find a navigation object.");
        }
      } else {
        if (typeof to !== 'string') {
          throw new Error(
            `To 'to' option is invalid (found '${String(
              to
            )}'. It must be a valid string for navigation.`
          );
        }

        linkTo(to);
      }
    }
  };

  return {
    href: to,
    accessibilityRole: 'link' as const,
    onPress,
  };
}
Example #18
Source File: ScalingButton.tsx    From nyxo-app with GNU General Public License v3.0 5 votes vote down vote up
ScalingButton: FC<Props> = ({
  analyticsEvent,
  children,
  onPress,
  disabled
}) => {
  const scaleIn = useRef(new Animated.Value(0)).current

  const pressIn = () => {
    scaleIn.setValue(0)
    Animated.timing(scaleIn, {
      toValue: 1,
      duration: 150,
      useNativeDriver: true
    }).start()
  }

  const pressOut = () => {
    scaleIn.setValue(1)
    Animated.timing(scaleIn, {
      toValue: 0,
      duration: 150,
      useNativeDriver: true
    }).start()
  }

  const handlePress = (event: GestureResponderEvent) => {
    Analytics.trackEvent('Button pressed', {
      buttonType: analyticsEvent
    })
    ReactNativeHapticFeedback.trigger('impactMedium', {
      enableVibrateFallback: true
    })

    onPress(event)
  }

  const transform = (animated: Animated.Value) => {
    const interpolation = animated.interpolate({
      inputRange: [0, 1],
      outputRange: [1, 0.95]
    })

    return {
      transform: [{ scale: interpolation }]
    }
  }

  return (
    <TouchableWithoutFeedback
      onPress={handlePress}
      disabled={disabled}
      onPressIn={pressIn}
      onPressOut={pressOut}>
      <Button style={transform(scaleIn)}>{children}</Button>
    </TouchableWithoutFeedback>
  )
}
Example #19
Source File: AnalogClock.tsx    From react-native-paper-dates with MIT License 4 votes vote down vote up
function AnalogClock({
  hours,
  minutes,
  focused,
  is24Hour,
  onChange,
}: {
  hours: number
  minutes: number
  focused: PossibleClockTypes
  is24Hour: boolean
  onChange: (hoursMinutesAndFocused: {
    hours: number
    minutes: number
    focused?: undefined | PossibleClockTypes
  }) => any
}) {
  const theme = useTheme()
  const { mode } = React.useContext(DisplayModeContext)
  // used to make pointer shorter if hours are selected and above 12
  const shortPointer = (hours === 0 || hours > 12) && is24Hour
  const clockRef = React.useRef<View | null>(null)
  // Hooks are nice, sometimes... :-)..
  // We need the latest values, since the onPointerMove uses a closure to the function
  const hoursRef = useLatest(hours)
  const onChangeRef = useLatest(onChange)
  const minutesRef = useLatest(minutes)
  const focusedRef = useLatest(focused)
  const is24HourRef = useLatest(is24Hour)
  const modeRef = useLatest(mode)
  const onPointerMove = React.useCallback(
    (e: GestureResponderEvent, final: boolean) => {
      let x = e.nativeEvent.locationX
      let y = e.nativeEvent.locationY
      let angle = getAngle(x, y, circleSize)
      if (focusedRef.current === clockTypes.hours) {
        let hours24 = is24HourRef.current
        let previousHourType = getHourType(hoursRef.current)
        let pickedHours = getHours(angle, previousHourType)

        let hours12AndPm = !hours24 && modeRef.current === 'AM'

        let hourTypeFromOffset = getHourTypeFromOffset(x, y, circleSize)
        let hours24AndPM = hours24 && hourTypeFromOffset === hourTypes.pm

        // Avoiding the "24h"
        // Should be 12h for 12 hours and PM mode

        if (hours12AndPm || hours24AndPM) {
          pickedHours += 12
        }
        if (modeRef.current === 'AM' && pickedHours === 12) {
          pickedHours = 0
        }

        if (
          (!hours24 && modeRef.current === 'AM' && pickedHours === 12) ||
          pickedHours === 24
        ) {
          pickedHours = 0
        }

        if (hoursRef.current !== pickedHours || final) {
          onChangeRef.current({
            hours: pickedHours,
            minutes: minutesRef.current,
            focused: final ? clockTypes.minutes : undefined,
          })
        }
      } else if (focusedRef.current === clockTypes.minutes) {
        let pickedMinutes = getMinutes(angle)
        if (minutesRef.current !== pickedMinutes) {
          onChangeRef.current({
            hours: hoursRef.current,
            minutes: pickedMinutes,
          })
        }
      }
    },
    [focusedRef, is24HourRef, hoursRef, onChangeRef, minutesRef, modeRef]
  )
  const panResponder = React.useRef(
    PanResponder.create({
      onPanResponderGrant: (e) => onPointerMove(e, false),
      onPanResponderMove: (e) => onPointerMove(e, false),
      onPanResponderRelease: (e) => onPointerMove(e, true),
      onStartShouldSetPanResponder: returnTrue,
      onStartShouldSetPanResponderCapture: () => false,
      onMoveShouldSetPanResponder: returnTrue,
      onMoveShouldSetPanResponderCapture: returnTrue,
      onPanResponderTerminationRequest: returnTrue,
      onShouldBlockNativeResponder: returnTrue,
    })
  ).current
  const dynamicSize = focused === clockTypes.hours && shortPointer ? 33 : 0
  const pointerNumber = focused === clockTypes.hours ? hours : minutes
  const degreesPerNumber = focused === clockTypes.hours ? 30 : 6
  return (
    <View
      ref={clockRef}
      {...panResponder.panHandlers}
      style={[
        styles.clock,
        {
          backgroundColor: theme.dark
            ? Color(theme.colors.surface).lighten(1.2).hex()
            : Color(theme.colors.surface).darken(0.1).hex(),
        },
      ]}
      // @ts-ignore -> https://github.com/necolas/react-native-web/issues/506
      cursor={'pointer'}
    >
      <View
        style={[
          styles.line,
          {
            backgroundColor: theme.colors.primary,
            transform: [
              { rotate: -90 + pointerNumber * degreesPerNumber + 'deg' },
              {
                translateX: circleSize / 4 - 4 - dynamicSize / 2,
              },
            ],
            width: circleSize / 2 - 4 - dynamicSize,
          },
        ]}
        pointerEvents="none"
      >
        <View
          style={[styles.endPoint, { backgroundColor: theme.colors.primary }]}
        />
      </View>
      <View
        style={[StyleSheet.absoluteFill, styles.center]}
        pointerEvents="none"
      >
        <View
          style={[
            styles.middlePoint,
            {
              backgroundColor: theme.colors.primary,
            },
          ]}
        />
      </View>
      <AnimatedClockSwitcher
        focused={focused}
        hours={<AnalogClockHours is24Hour={is24Hour} hours={hours} />}
        minutes={<AnalogClockMinutes minutes={minutes} />}
      />
    </View>
  )
}
Example #20
Source File: Ripple.tsx    From mobile with Apache License 2.0 4 votes vote down vote up
Ripple = ({
  children,
  disabled,
  rippleColor = 'rgb(0, 0, 0)',
  rippleCentered = false,
  rippleOpacity = 0.3,
  rippleExpandDuration = 500,
  rippleFadeDuration = 200,
  rippleContainerBorderRadius = 0,
  rippleSize = 0,
  ...touchableWithoutFeedbackProps
}: RippleProps) => {
  const [width, setWidth] = useState(0);
  const [height, setHeight] = useState(0);
  const uuid = useRef(0);
  const [ripples, setRipples] = useState<AnimatedRipple[]>([]);
  const [fadings, setFadings] = useState<number[]>([]);

  const startFade = useCallback(
    (ripple: AnimatedRipple, duration: number) => {
      if (fadings.indexOf(ripple.id) >= 0) {
        return;
      }
      setFadings([...fadings, ripple.id]);
      Animated.timing(ripple.fadeAnimatedValue, {
        toValue: 1,
        easing: Easing.out(Easing.ease),
        duration,
        useNativeDriver: true,
      }).start(() => {
        setRipples(ripples.filter(item => item !== ripple));
      });
    },
    [fadings, ripples],
  );

  const startExpand = useCallback(
    (event: GestureResponderEvent) => {
      if (!width || !height) {
        return;
      }

      const timestamp = Date.now();
      if (ripples.length > 0 && timestamp < ripples[ripples.length - 1].timestamp + DEBOUNCE) {
        return;
      }

      const w2 = 0.5 * width;
      const h2 = 0.5 * height;

      const {locationX, locationY} = rippleCentered ? {locationX: w2, locationY: h2} : event.nativeEvent;

      const offsetX = Math.abs(w2 - locationX);
      const offsetY = Math.abs(h2 - locationY);

      const radius = rippleSize > 0 ? 0.5 * rippleSize : Math.sqrt((w2 + offsetX) ** 2 + (h2 + offsetY) ** 2);

      const id = uuid.current;
      uuid.current += 1;

      const ripple: AnimatedRipple = {
        id,
        locationX,
        locationY,
        radius,
        timestamp,
        expandAnimatedValue: new Animated.Value(0),
        fadeAnimatedValue: new Animated.Value(0),
      };

      Animated.timing(ripple.expandAnimatedValue, {
        toValue: 1,
        easing: Easing.out(Easing.ease),
        duration: rippleExpandDuration,
        useNativeDriver: true,
      }).start();

      setRipples(ripples.concat(ripple));
    },
    [height, rippleCentered, rippleExpandDuration, rippleSize, ripples, width],
  );

  const onLayout = useCallback(
    (event: LayoutChangeEvent) => {
      const {
        nativeEvent: {
          layout: {height, width},
        },
      } = event;
      setWidth(width);
      setHeight(height);
      touchableWithoutFeedbackProps.onLayout?.(event);
    },
    [touchableWithoutFeedbackProps.onLayout],
  );

  const onPressIn = useCallback(
    (event: GestureResponderEvent) => {
      startExpand(event);
      touchableWithoutFeedbackProps.onPressIn?.(event);
    },
    [startExpand, touchableWithoutFeedbackProps.onPressIn],
  );

  const onPressOut = useCallback(
    (event: GestureResponderEvent) => {
      ripples.forEach(ripple => startFade(ripple, rippleFadeDuration + rippleExpandDuration / 2));
      touchableWithoutFeedbackProps.onPressOut?.(event);
    },
    [rippleExpandDuration, rippleFadeDuration, ripples, startFade, touchableWithoutFeedbackProps.onPressOut],
  );

  const onPress = useCallback(
    (event: GestureResponderEvent) => {
      requestAnimationFrame(() => {
        touchableWithoutFeedbackProps.onPress?.(event);
      });
    },
    [touchableWithoutFeedbackProps.onPress],
  );

  const renderRipple = useCallback(
    ({locationX, locationY, radius, id, expandAnimatedValue, fadeAnimatedValue}: AnimatedRipple) => {
      const rippleStyle = {
        top: locationY - RADIUS,
        [I18nManager.isRTL ? 'right' : 'left']: locationX - RADIUS,
        backgroundColor: rippleColor,
        transform: [
          {
            scale: expandAnimatedValue.interpolate({
              inputRange: [0, 1],
              outputRange: [0.5 / RADIUS, radius / RADIUS],
            }),
          },
        ],
        opacity: fadeAnimatedValue.interpolate({
          inputRange: [0, 1],
          outputRange: [rippleOpacity, 0],
        }),
      };
      return <Animated.View style={[styles.ripple, rippleStyle]} key={id} />;
    },
    [rippleColor, rippleOpacity],
  );

  const style = useMemo(
    () => [
      styles.container,
      {
        borderRadius: rippleContainerBorderRadius,
      },
    ],
    [rippleContainerBorderRadius],
  );

  return (
    <TouchableWithoutFeedback
      {...touchableWithoutFeedbackProps}
      disabled={disabled}
      onPressIn={onPressIn}
      onPressOut={onPressOut}
      onPress={onPress}
      onLayout={onLayout}
    >
      <Animated.View pointerEvents="box-only">
        <View style={style}>{ripples.map(renderRipple)}</View>
        {children}
      </Animated.View>
    </TouchableWithoutFeedback>
  );
}
Example #21
Source File: Slider.tsx    From react-native-design-kit with MIT License 4 votes vote down vote up
export default function Slider({
  containerStyle,
  minValue = 0,
  minTrackContainerStyle,
  maxValue = 100,
  maxTrackContainerStyle,
  initialValue,
  button,
  buttonValue,
  startButton,
  startButtonContainerStyle,
  endButton,
  endButtonContainerStyle,
  thumb,
  thumbContainerStyle,
  trackContainerStyle,
  hitSlop = {top: 10, bottom: 10},
  indicator,
  indicatorStyle,
  indicatorComponent,
  indicatorSubStyle,
  indicatorSubComponent,
  indicatorContainerStyle,
  numberOfSection = 10,
  numberOfSubSection = 2,
  onChangeValue,
}: SliderProps) {
  const [progress, setProgress] = useState(
    (initialValue !== undefined && getProgress(initialValue)) || 0.25,
  );
  const [startProgress, setStartProgress] = useState(progress);
  const [thumbLayout, setThumbLayout] = useState<LayoutRectangle>();
  const [pageX, setPageX] = useState<number>();
  const [width, setWidth] = useState<number>();
  const value = useMemo(() => progress * (maxValue - minValue) + minValue, [
    progress,
    maxValue,
    minValue,
  ]);

  function setValue(val: number) {
    setProgress(getProgress(val));
  }

  function getProgress(val: number) {
    return (
      (Math.max(minValue, Math.min(maxValue, val)) - minValue) /
      (maxValue - minValue)
    );
  }

  const handleRenderIndicator = useMemo(() => {
    const components: ReactElement[] = [];

    for (let index = 0; index <= numberOfSection; index++) {
      components.push(
        <View key={`{indicatorSection: ${index}}`}>
          {indicatorComponent || (
            <View
              style={StyleSheet.flatten([styles.indicator, indicatorStyle])}
            />
          )}
        </View>,
      );

      if (index < numberOfSection) {
        for (let indexSub = 0; indexSub < numberOfSubSection - 1; indexSub++) {
          components.push(
            <View key={`{indicator: ${index}, sub: ${indexSub}}`}>
              {indicatorSubComponent || (
                <View
                  style={StyleSheet.flatten([
                    styles.indicator,
                    styles.indicatorSub,
                    indicatorSubStyle,
                  ])}
                />
              )}
            </View>,
          );
        }
      }
    }

    return components;
  }, [
    numberOfSection,
    numberOfSubSection,
    indicatorComponent,
    indicatorSubComponent,
    indicatorStyle,
    indicatorSubStyle,
  ]);

  const handlePressButtonLeft = useCallback(
    () =>
      setValue(
        value -
          (buttonValue !== undefined
            ? Math.max(0, buttonValue)
            : (maxValue - minValue) * 0.15),
      ),
    [value, buttonValue, maxValue, minValue],
  );

  const handlePressButtonRight = useCallback(
    () =>
      setValue(
        value +
          (buttonValue !== undefined
            ? Math.max(0, buttonValue)
            : (maxValue - minValue) * 0.15),
      ),
    [value, buttonValue, maxValue, minValue],
  );

  const handleResponderStart = useCallback(
    (event: GestureResponderEvent) => {
      if (width !== undefined) {
        setPageX(event.nativeEvent.pageX);
        setStartProgress(event.nativeEvent.locationX / width);
      }
    },
    [width, progress],
  );

  const handleResponderMove = useCallback(
    (event: GestureResponderEvent) => {
      if (pageX !== undefined && width !== undefined) {
        setProgress(
          Math.max(
            0,
            Math.min(
              1,
              startProgress + (event.nativeEvent.pageX - pageX) / width,
            ),
          ),
        );
      }
    },
    [pageX, width],
  );

  const handleRenderButtonLeft = useMemo(
    () =>
      button && (
        <Touchable
          testID="button-start"
          style={StyleSheet.flatten([
            styles.startButtonContainer,
            startButtonContainerStyle,
          ])}
          onPress={handlePressButtonLeft}>
          {startButton || <Icon style={styles.buttonIcon} name="caret-left" />}
        </Touchable>
      ),
    [button, startButtonContainerStyle, startButton, handlePressButtonLeft],
  );

  const handleRenderButtonRight = useMemo(
    () =>
      button && (
        <Touchable
          testID="button-end"
          style={StyleSheet.flatten([
            styles.endButtonContainer,
            endButtonContainerStyle,
          ])}
          onPress={handlePressButtonRight}>
          {endButton || <Icon style={styles.buttonIcon} name="caret-right" />}
        </Touchable>
      ),
    [button, endButtonContainerStyle, endButton, handlePressButtonRight],
  );

  const handleRenderTopIndicator = useMemo(
    () =>
      indicator && (
        <View
          style={StyleSheet.flatten([
            indicatorContainerStyle,
            styles.sectionIndicator,
            styles.sectionIndicatorTop,
          ])}>
          {handleRenderIndicator}
        </View>
      ),
    [indicator, indicatorContainerStyle, handleRenderIndicator],
  );

  const handleRenderBottomIndicator = useMemo(
    () =>
      indicator && (
        <View
          style={StyleSheet.flatten([
            indicatorContainerStyle,
            styles.sectionIndicator,
            styles.sectionIndicatorBottom,
          ])}>
          {handleRenderIndicator}
        </View>
      ),
    [indicator, indicatorContainerStyle, handleRenderIndicator],
  );

  const handleRenderThumb = useMemo(
    () =>
      width ? (
        <View
          testID="thumb-container"
          onLayout={event => setThumbLayout(event.nativeEvent.layout)}
          style={StyleSheet.flatten([
            styles.thumbContainer,
            thumbContainerStyle,
            styles.sectionThumb,
            {
              left:
                progress * width - (thumbLayout ? thumbLayout.width / 2 : 0),
              opacity: thumbLayout ? 1 : 0,
            },
          ])}>
          {thumb || <View style={styles.thumb} />}
        </View>
      ) : null,
    [thumb, thumbContainerStyle, width, progress, width, thumbLayout],
  );

  const handleRenderSlider = useMemo(
    () => (
      <View
        testID="track-container"
        style={styles.sectionTrackContainer}
        pointerEvents="box-only"
        onStartShouldSetResponder={() => true}
        onResponderStart={handleResponderStart}
        onResponderMove={handleResponderMove}
        hitSlop={hitSlop}
        onLayout={event => setWidth(event.nativeEvent.layout.width)}>
        {handleRenderTopIndicator}
        <View style={styles.sectionTrack}>
          <View
            style={StyleSheet.flatten([
              styles.trackContainer,
              trackContainerStyle,
              styles.trackContainerMin,
              minTrackContainerStyle,
              {width: `${progress * 100}%`},
            ])}
          />
          <View
            style={StyleSheet.flatten([
              styles.trackContainer,
              trackContainerStyle,
              styles.trackContainerMax,
              maxTrackContainerStyle,
              {width: `${(1 - progress) * 100}%`},
            ])}
          />
          {handleRenderThumb}
        </View>
        {handleRenderBottomIndicator}
      </View>
    ),
    [
      trackContainerStyle,
      trackContainerStyle,
      minTrackContainerStyle,
      maxTrackContainerStyle,
      progress,
      handleRenderThumb,
      handleRenderTopIndicator,
      handleRenderBottomIndicator,
      handleResponderStart,
      handleResponderMove,
    ],
  );

  useDidUpdate(() => {
    onChangeValue && onChangeValue(value, progress);
  }, [value]);

  return (
    <View style={StyleSheet.flatten([styles.container, containerStyle])}>
      {handleRenderButtonLeft}
      {handleRenderSlider}
      {handleRenderButtonRight}
    </View>
  );
}
Example #22
Source File: Chip.tsx    From react-native-design-kit with MIT License 4 votes vote down vote up
export default function Chip({
  actionType = 'chip',
  containerStyle,
  chips,
  chipContainerStyle,
  chipComponent,
  chipTitleStyle,
  selectedChipContainerStyle,
  selectedChipTitleStyle,
  horizontal,
  horizontalScrollIndicator = false,
  horizontalScrollEnabled = true,
  horizontalScrollButton = true,
  horizontalScrollLeftButton,
  horizontalScrollLeftButtonContainerStyle,
  horizontalScrollRightButton,
  horizontalScrollRightButtonContainerStyle,
  selectedId,
  leftIcon,
  leftIconAction,
  rightIcon,
  rightIconAction,
  onSelect,
  onPress,
  ...props
}: ChipProps) {
  const singleValue = useMemo(() => actionType === 'radio', [actionType]);
  const [chipIds, setChipIds] = useState(chips);
  const [layout, setLayout] = useState<LayoutRectangle>();
  const [size, setSize] = useState<NativeScrollPoint>();
  const [offset, setOffset] = useState<NativeScrollPoint>({x: 0, y: 0});
  const [selected, setSelected] = useState<string[]>(
    filterSelectList(chipIds, selectedId || [], singleValue),
  );
  const refScroll = useRef<FlatList<string>>();
  const initialize = useRef(false);

  const difSize = useMemo(
    () => (layout && size ? size.x - layout.width : undefined),
    [layout],
  );

  const allowScrollLeft = useMemo(() => offset !== undefined && offset.x > 0, [
    offset,
  ]);

  const allowScrollRight = useMemo(
    () => difSize !== undefined && offset.x < difSize,
    [difSize, offset],
  );

  const isSelected = useCallback((id: string) => selected.indexOf(id) >= 0, [
    selected,
  ]);

  const removeChipId = useCallback(
    (id: string) => setChipIds(chipIds.filter(chipId => chipId !== id)),
    [chipIds],
  );

  const getIconAction = useCallback(
    (id: string, iconActionFunction?: ChipIconAction) => {
      if (iconActionFunction) {
        const action = iconActionFunction(id, isSelected(id));

        if (action === 'delete') {
          return () => removeChipId(id);
        } else if (action === 'check') {
          return undefined;
        }

        return action;
      }

      return undefined;
    },
    [isSelected],
  );

  const handleRefList = useCallback((instance: FlatList<string>) => {
    if (instance) {
      refScroll.current = instance;
    }
  }, []);

  const handlePressChipItem = useCallback(
    (id: string, event: GestureResponderEvent) => {
      onPress && onPress(event);

      if (actionType !== 'chip') {
        if (actionType === 'checkbox') {
          const selection = [...selected];

          if (isSelected(id)) {
            selection.splice(selection.indexOf(id), 1);
          } else {
            selection.push(id);
          }

          setSelected(selection);
          onSelect(id, selection);
        } else {
          const selection = [id];

          setSelected([id]);
          onSelect(id, selection);
        }
      } else {
        onSelect(id, selected);
      }
    },
    [actionType, selected, isSelected, onPress, onSelect],
  );

  const handlePressScrollLeftButton = useCallback(
    () =>
      refScroll.current &&
      refScroll.current.scrollToOffset({
        offset: Math.max(0, offset.x - 125),
        animated: true,
      }),
    [refScroll.current, offset],
  );

  const handlePressScrollRightButton = useCallback(
    () =>
      refScroll.current &&
      difSize &&
      refScroll.current.scrollToOffset({
        offset: Math.min(difSize, offset.x + 125),
        animated: true,
      }),
    [refScroll.current, difSize, offset],
  );

  const handleRenderIcon = useCallback(
    (
      id: string,
      iconFunction?: ChipIcon,
      iconActionFunction?: ChipIconAction,
    ) => {
      if (iconFunction) {
        return iconFunction({id, isSelected: isSelected(id)});
      }

      if (iconActionFunction) {
        const action = iconActionFunction(id, isSelected(id));

        if (action === 'delete') {
          return (
            <Icon
              testID="icon-delete"
              style={styles.icon}
              name="times-circle"
            />
          );
        } else if (action === 'check' && isSelected(id)) {
          return (
            <Icon
              style={StyleSheet.flatten([styles.icon, styles.iconCheck])}
              name="check"
            />
          );
        }
      }

      return undefined;
    },
    [isSelected],
  );

  const handleRenderChipItem = useCallback(
    (id: string) => {
      const component =
        chipComponent && chipComponent({id, isSelected: isSelected(id)});
      const title =
        typeof component === 'string'
          ? component
          : component === undefined
          ? id
          : undefined;

      return (
        <ChipItem
          {...props}
          key={id}
          containerStyle={StyleSheet.flatten([
            typeof chipContainerStyle === 'function'
              ? chipContainerStyle(id)
              : chipContainerStyle,
            isSelected(id)
              ? StyleSheet.flatten([
                  styles.selectedChipContainer,
                  typeof selectedChipContainerStyle === 'function'
                    ? selectedChipContainerStyle(id)
                    : selectedChipContainerStyle,
                ])
              : {},
          ])}
          title={title}
          titleStyle={StyleSheet.flatten([
            typeof chipTitleStyle === 'function'
              ? chipTitleStyle(id)
              : chipTitleStyle,
            isSelected(id)
              ? typeof selectedChipTitleStyle === 'function'
                ? selectedChipTitleStyle(id)
                : selectedChipTitleStyle
              : {},
          ])}
          leftIcon={handleRenderIcon(id, leftIcon, leftIconAction)}
          leftIconAction={getIconAction(id, leftIconAction)}
          rightIcon={handleRenderIcon(id, rightIcon, rightIconAction)}
          rightIconAction={getIconAction(id, rightIconAction)}
          onPress={event => handlePressChipItem(id, event)}>
          {component !== undefined &&
            typeof component !== 'string' &&
            component}
        </ChipItem>
      );
    },
    [
      props,
      chipTitleStyle,
      chipContainerStyle,
      selectedChipTitleStyle,
      selectedChipContainerStyle,
      leftIcon,
      leftIconAction,
      rightIcon,
      rightIconAction,
      chipComponent,
      isSelected,
      handleRenderIcon,
      handlePressChipItem,
    ],
  );

  const handleRenderScrollLeftButton = useMemo(
    () =>
      horizontalScrollButton && (
        <Touchable
          testID="button-left"
          disabled={!allowScrollLeft}
          style={StyleSheet.flatten([
            styles.scrollContainer,
            styles.scrollLeftIconContainer,
            horizontalScrollLeftButtonContainerStyle,
            !allowScrollLeft ? styles.scrollContainerDisabled : {},
          ])}
          onPress={handlePressScrollLeftButton}>
          {horizontalScrollLeftButton || <Icon name="chevron-left" />}
        </Touchable>
      ),
    [
      allowScrollLeft,
      horizontalScrollButton,
      horizontalScrollLeftButton,
      horizontalScrollLeftButtonContainerStyle,
      handlePressScrollLeftButton,
    ],
  );

  const handleRenderScrollRightButton = useMemo(() => {
    return (
      horizontalScrollButton && (
        <Touchable
          testID="button-right"
          disabled={!allowScrollRight}
          style={StyleSheet.flatten([
            styles.scrollContainer,
            styles.scrollRightIconContainer,
            horizontalScrollRightButtonContainerStyle,
            !allowScrollRight ? styles.scrollContainerDisabled : {},
          ])}
          onPress={handlePressScrollRightButton}>
          {horizontalScrollRightButton || <Icon name="chevron-right" />}
        </Touchable>
      )
    );
  }, [
    allowScrollRight,
    horizontalScrollButton,
    horizontalScrollRightButton,
    horizontalScrollRightButtonContainerStyle,
    handlePressScrollRightButton,
  ]);

  const handleRenderListChipItem = useMemo(
    () => chipIds.map(id => handleRenderChipItem(id)),
    [chipIds, handleRenderChipItem],
  );

  useEffect(() => {
    setChipIds(chips);
  }, [chips]);

  useEffect(() => {
    if (initialize.current) {
      setSelected(filterSelectList(chipIds, selectedId || [], singleValue));
    } else {
      initialize.current = true;
    }
  }, [singleValue, chipIds, selectedId]);

  return horizontal ? (
    <View style={StyleSheet.flatten([containerStyle, styles.containerNoWrap])}>
      {handleRenderScrollLeftButton}
      <FlatList
        horizontal
        testID="list"
        ref={handleRefList}
        onLayout={event => setLayout(event.nativeEvent.layout)}
        data={chipIds}
        scrollEnabled={horizontalScrollEnabled}
        onContentSizeChange={(w, h) => setSize({x: w, y: h})}
        onScroll={event => setOffset(event.nativeEvent.contentOffset)}
        contentContainerStyle={styles.sectionWrap}
        showsHorizontalScrollIndicator={horizontalScrollIndicator}
        keyExtractor={item => item}
        renderItem={({item}) => handleRenderChipItem(item)}
      />
      {handleRenderScrollRightButton}
    </View>
  ) : (
    <View style={StyleSheet.flatten([containerStyle, styles.containerWrap])}>
      {handleRenderListChipItem}
    </View>
  );
}
Example #23
Source File: Checkbox.tsx    From react-native-design-kit with MIT License 4 votes vote down vote up
export default function Checkbox({
  containerStyle,
  checkboxIds,
  checkboxComponent,
  checkboxIndeterminateContainerStyle,
  defaultIds,
  onSelect,
  onPress,
  ...props
}: CheckboxProps) {
  const [selected, setSelected] = useState<string[]>(
    defaultIds !== undefined ? filterId(defaultIds) : [],
  );

  function checkId(
    id: string,
    checkboxIdenfitifer: CheckboxIdentifier[],
  ): boolean {
    for (const value of checkboxIdenfitifer) {
      if (typeof value === 'string') {
        if (value === id) {
          return true;
        }
      } else {
        return checkId(id, value.checkboxIds);
      }
    }

    return false;
  }

  function filterId(id: string | string[]) {
    const selection: string[] = [];

    if (Array.isArray(id)) {
      for (const check of id) {
        if (checkId(check, checkboxIds)) {
          selection.push(check);
        }
      }
    } else if (checkId(id, checkboxIds)) {
      selection.push(id);
    }

    return selection;
  }

  const isSelected = useCallback((id: string) => selected.indexOf(id) >= 0, [
    selected,
  ]);

  const checkIndeterminateStatus = useCallback(
    (
      checkboxIdenfitifer: CheckboxIdentifier[],
      checked: boolean,
      hasEmpty?: boolean,
    ): CheckboxCategoryStatus => {
      for (const indeterminate of checkboxIdenfitifer) {
        if (typeof indeterminate === 'string') {
          if (!hasEmpty && !isSelected(indeterminate)) {
            hasEmpty = true;
          } else if (!checked && isSelected(indeterminate)) {
            checked = true;
          }
        } else {
          return checkIndeterminateStatus(
            indeterminate.checkboxIds,
            checked,
            hasEmpty,
          );
        }
      }

      return checked
        ? hasEmpty
          ? 'indeterminate'
          : 'selected'
        : 'not-selected';
    },
    [isSelected],
  );

  const filterSelection = useCallback(
    (
      base: string[],
      checkboxIdentifier: CheckboxIdentifier[],
      toggle: boolean,
    ): string[] => {
      const selection = [...base];

      for (const identifier of checkboxIdentifier) {
        if (typeof identifier === 'string') {
          const select = selection.indexOf(identifier) >= 0;

          if (select && !toggle) {
            selection.splice(selection.indexOf(identifier), 1);
          }

          if (!select && toggle) {
            selection.push(identifier);
          }
        } else {
          return filterSelection(selection, identifier.checkboxIds, toggle);
        }
      }

      return selection;
    },
    [],
  );

  const handlePressCheckboxNested = useCallback(
    (
      status: CheckboxCategoryStatus,
      identifier: CheckboxIdentifier[],
      event: GestureResponderEvent,
    ) => {
      onPress && onPress(event);

      const selection = filterSelection(
        selected,
        identifier,
        status === 'not-selected' || status === 'indeterminate',
      );

      setSelected(selection);
    },
    [selected, filterSelection, onPress],
  );

  const handleRenderCheckboxNested = useCallback(
    (key: string, title: string, identifier: CheckboxIdentifier[]) => {
      const status = checkIndeterminateStatus(identifier, false);

      return (
        <CheckboxNested
          {...props}
          key={key}
          title={title}
          checkboxIds={identifier}
          status={status}
          onPress={event =>
            handlePressCheckboxNested(status, identifier, event)
          }
        />
      );
    },
    [props, checkIndeterminateStatus, handlePressCheckboxNested],
  );

  const handlePressCheckboxItem = useCallback(
    (id: string, event: GestureResponderEvent) => {
      onPress && onPress(event);
      const selection = [...selected];

      if (isSelected(id)) {
        selection.splice(selection.indexOf(id), 1);
        onSelect(id, false, selection);
      } else {
        selection.push(id);
        onSelect(id, true, selection);
      }

      setSelected(selection);
    },
    [selected, isSelected, onPress, onSelect],
  );

  const handleRenderCheckboxItem = useCallback(
    (id: string) => {
      const isIdSelected = isSelected(id);
      const component =
        checkboxComponent && checkboxComponent({id, isSelected: isIdSelected});
      const title =
        typeof component === 'string'
          ? component
          : component === undefined
          ? id
          : undefined;

      return (
        <CheckboxItem
          {...props}
          key={id}
          title={title}
          isSelected={isIdSelected}
          onPress={event => handlePressCheckboxItem(id, event)}>
          {component && typeof component !== 'string' && component}
        </CheckboxItem>
      );
    },
    [props, checkboxComponent, isSelected, handlePressCheckboxItem],
  );

  const handleRenderListCheckboxItem = useCallback(
    (checkboxIdenfitifer: CheckboxIdentifier[], category?: string) => {
      const list: ReactNode[] = [];

      for (const value of checkboxIdenfitifer) {
        if (typeof value === 'string') {
          const item = handleRenderCheckboxItem(value);

          list.push(item);
        } else {
          const title = value.title;
          const identifier = value.checkboxIds;
          const key = category !== undefined ? `${category}:${title}` : title;
          const checkboxNested = handleRenderCheckboxNested(
            key,
            title,
            identifier,
          );

          list.push(checkboxNested);
          list.push(handleRenderListCheckboxItem(value.checkboxIds, key));
        }
      }

      return category !== undefined ? (
        <View
          key={`category: ${category}`}
          style={StyleSheet.flatten([
            styles.checkboxIndeterminateContainer,
            checkboxIndeterminateContainerStyle,
          ])}>
          {list}
        </View>
      ) : (
        list
      );
    },
    [
      checkboxIndeterminateContainerStyle,
      handleRenderCheckboxItem,
      handleRenderCheckboxNested,
    ],
  );

  return (
    <View style={containerStyle}>
      {handleRenderListCheckboxItem(checkboxIds)}
    </View>
  );
}
Example #24
Source File: ButtonGroup.tsx    From react-native-design-kit with MIT License 4 votes vote down vote up
export default function ButtonGroup({
  type = 'solid',
  actionType = 'button',
  buttonIds,
  buttonComponent,
  buttonContainerStyle,
  buttonTitleStyle,
  containerStyle,
  containerBorderRadius,
  standbyButtonRaised,
  selectedButtonRaised,
  selectedButtonContainerStyle,
  selectedButtonTitleStyle,
  selectedId,
  onPress,
  onSelect,
  ...props
}: ButtonGroupProps) {
  const singleValue = useMemo(() => actionType === 'radio', [actionType]);
  const [selected, setSelected] = useState<string[]>(
    filterSelectList(buttonIds, selectedId || [], singleValue),
  );

  const isSelected = useCallback((id: string) => selected.indexOf(id) >= 0, [
    selected,
  ]);

  const handlePressButtonItem = useCallback(
    (id: string, event: GestureResponderEvent) => {
      onPress && onPress(event);

      if (actionType !== 'button') {
        if (actionType === 'checkbox') {
          const selection = [...selected];

          if (isSelected(id)) {
            selection.splice(selection.indexOf(id), 1);
          } else {
            selection.push(id);
          }

          setSelected(selection);
          onSelect(id, selection);
        } else {
          const selection = [id];

          setSelected(selection);
          onSelect(id, selection);
        }
      } else {
        onSelect(id, selected);
      }
    },
    [actionType, selected, isSelected, onSelect, onPress],
  );

  const handleRenderButtonItem = useCallback(
    (id: string, index: number) => {
      const component =
        buttonComponent && buttonComponent({id, isSelected: isSelected(id)});
      const title =
        typeof component === 'string'
          ? component
          : component === undefined
          ? id
          : undefined;

      return (
        <Button
          {...props}
          key={id}
          type={type}
          raised={isSelected(id) ? selectedButtonRaised : standbyButtonRaised}
          containerStyle={StyleSheet.flatten([
            styles.buttonContainer,
            buttonContainerStyle,
            isSelected(id)
              ? StyleSheet.flatten([
                  styles.selectedButtonContainer,
                  selectedButtonContainerStyle,
                ])
              : {},
            index === 0
              ? {
                  borderTopLeftRadius: containerBorderRadius,
                  borderBottomLeftRadius: containerBorderRadius,
                }
              : {},
            index === buttonIds.length - 1
              ? {
                  borderTopRightRadius: containerBorderRadius,
                  borderBottomRightRadius: containerBorderRadius,
                }
              : {},
          ])}
          title={title}
          titleStyle={StyleSheet.flatten([
            buttonTitleStyle,
            isSelected(id) &&
              StyleSheet.flatten([
                type !== 'solid' && styles.selectedTitle,
                selectedButtonTitleStyle,
              ]),
          ])}
          onPress={event => handlePressButtonItem(id, event)}>
          {component && typeof component !== 'string' && component}
        </Button>
      );
    },
    [
      props,
      type,
      buttonIds,
      buttonTitleStyle,
      selectedButtonTitleStyle,
      selectedButtonRaised,
      standbyButtonRaised,
      buttonContainerStyle,
      selectedButtonContainerStyle,
      containerBorderRadius,
      isSelected,
      buttonComponent,
      handlePressButtonItem,
    ],
  );

  const handleRenderListButton = useMemo(() => {
    const list: ReactElement[] = [];

    for (let index = 0; index < buttonIds.length; index++) {
      const id = buttonIds[index];
      const button = handleRenderButtonItem(id, index);

      list.push(button);
    }

    return list;
  }, [buttonIds, handleRenderButtonItem]);

  useDidUpdate(() => {
    setSelected(filterSelectList(buttonIds, selectedId || [], singleValue));
  }, [singleValue, buttonIds, selectedId]);

  return (
    <View style={StyleSheet.flatten([styles.container, containerStyle])}>
      {handleRenderListButton}
    </View>
  );
}