|  | 
|  | 1 | +import React, { useState } from 'react'; | 
|  | 2 | +import { | 
|  | 3 | +  StyleProp, | 
|  | 4 | +  StyleSheet, | 
|  | 5 | +  ViewStyle, | 
|  | 6 | +  TouchableHighlight, | 
|  | 7 | +  View, | 
|  | 8 | +} from 'react-native'; | 
|  | 9 | + | 
|  | 10 | +import { original as theme } from '../common/themes'; | 
|  | 11 | +import { border, padding, margin, blockSizes } from '../common/styles'; | 
|  | 12 | +import { Border } from '../common/styleElements'; | 
|  | 13 | +import { Text, Panel } from '..'; | 
|  | 14 | + | 
|  | 15 | +type TabsProps = { | 
|  | 16 | +  children?: React.ReactNode; | 
|  | 17 | +  style?: StyleProp<ViewStyle>; | 
|  | 18 | +  value: any; | 
|  | 19 | +  onChange?: () => void; | 
|  | 20 | +  stretch?: boolean; | 
|  | 21 | +}; | 
|  | 22 | + | 
|  | 23 | +const Tabs = ({ | 
|  | 24 | +  value, | 
|  | 25 | +  onChange, | 
|  | 26 | +  children, | 
|  | 27 | +  stretch = false, | 
|  | 28 | +  ...rest | 
|  | 29 | +}: TabsProps) => { | 
|  | 30 | +  const childrenWithProps = React.Children.map(children, child => { | 
|  | 31 | +    if (!React.isValidElement(child)) { | 
|  | 32 | +      return null; | 
|  | 33 | +    } | 
|  | 34 | +    const tabProps = { | 
|  | 35 | +      selected: child.props.value === value, | 
|  | 36 | +      onPress: onChange, | 
|  | 37 | +      stretch, | 
|  | 38 | +    }; | 
|  | 39 | +    return React.cloneElement(child, tabProps); | 
|  | 40 | +  }); | 
|  | 41 | + | 
|  | 42 | +  return ( | 
|  | 43 | +    <View style={[styles.tabs]} {...rest}> | 
|  | 44 | +      {childrenWithProps} | 
|  | 45 | +      <View style={[styles.tabBodyBorder]} /> | 
|  | 46 | +    </View> | 
|  | 47 | +  ); | 
|  | 48 | +}; | 
|  | 49 | + | 
|  | 50 | +type TabBodyProps = { | 
|  | 51 | +  children?: React.ReactNode; | 
|  | 52 | +  style?: StyleProp<ViewStyle>; | 
|  | 53 | +}; | 
|  | 54 | + | 
|  | 55 | +const Body = ({ children, style, ...rest }: TabBodyProps) => { | 
|  | 56 | +  return ( | 
|  | 57 | +    <Panel style={[styles.body, style]} {...rest}> | 
|  | 58 | +      {children} | 
|  | 59 | +    </Panel> | 
|  | 60 | +  ); | 
|  | 61 | +}; | 
|  | 62 | + | 
|  | 63 | +type TabProps = { | 
|  | 64 | +  children?: React.ReactNode; | 
|  | 65 | +  style?: StyleProp<ViewStyle>; | 
|  | 66 | +  value: any; | 
|  | 67 | +  onPress?: () => void; | 
|  | 68 | +  selected?: boolean; | 
|  | 69 | +  stretch?: boolean; | 
|  | 70 | +}; | 
|  | 71 | + | 
|  | 72 | +const Tab = ({ | 
|  | 73 | +  value, | 
|  | 74 | +  onPress, | 
|  | 75 | +  selected, | 
|  | 76 | +  stretch, | 
|  | 77 | +  children, | 
|  | 78 | +  ...rest | 
|  | 79 | +}: TabProps) => { | 
|  | 80 | +  const [isPressed, setIsPressed] = useState(false); | 
|  | 81 | + | 
|  | 82 | +  return ( | 
|  | 83 | +    <TouchableHighlight | 
|  | 84 | +      onPress={() => onPress(value)} | 
|  | 85 | +      onHideUnderlay={() => setIsPressed(false)} | 
|  | 86 | +      onShowUnderlay={() => setIsPressed(true)} | 
|  | 87 | +      underlayColor='none' | 
|  | 88 | +      style={[ | 
|  | 89 | +        styles.tab, | 
|  | 90 | +        { zIndex: selected ? 1 : 0 }, | 
|  | 91 | +        stretch ? { flexGrow: 1 } : { width: 'auto' }, | 
|  | 92 | +        selected ? margin(0, -8) : margin(0, 0), | 
|  | 93 | +      ]} | 
|  | 94 | +      {...rest} | 
|  | 95 | +    > | 
|  | 96 | +      <View | 
|  | 97 | +        pointerEvents='none' | 
|  | 98 | +        style={[ | 
|  | 99 | +          styles.tabContent, | 
|  | 100 | +          { | 
|  | 101 | +            height: selected ? blockSizes.md + 4 : blockSizes.md, | 
|  | 102 | +          }, | 
|  | 103 | +          selected ? padding(0, 16) : padding(0, 10), | 
|  | 104 | +        ]} | 
|  | 105 | +      > | 
|  | 106 | +        {/* TODO: add 'background' boolean prop to Border component since its usually used with background color */} | 
|  | 107 | +        <Border | 
|  | 108 | +          radius={6} | 
|  | 109 | +          style={[ | 
|  | 110 | +            { | 
|  | 111 | +              backgroundColor: theme.material, | 
|  | 112 | +            }, | 
|  | 113 | +          ]} | 
|  | 114 | +          sharedStyle={{ | 
|  | 115 | +            borderBottomWidth: 0, | 
|  | 116 | +            borderBottomLeftRadius: 0, | 
|  | 117 | +            borderBottomRightRadius: 0, | 
|  | 118 | +          }} | 
|  | 119 | +        /> | 
|  | 120 | +        <Text>{children}</Text> | 
|  | 121 | +        <View style={[styles.mask]} /> | 
|  | 122 | +        {isPressed && <View style={[styles.focusOutline]} />} | 
|  | 123 | +      </View> | 
|  | 124 | +    </TouchableHighlight> | 
|  | 125 | +  ); | 
|  | 126 | +}; | 
|  | 127 | + | 
|  | 128 | +const styles = StyleSheet.create({ | 
|  | 129 | +  tabs: { | 
|  | 130 | +    display: 'flex', | 
|  | 131 | +    flexDirection: 'row', | 
|  | 132 | +    alignItems: 'flex-end', | 
|  | 133 | +    paddingHorizontal: 8, | 
|  | 134 | +    zIndex: 1, | 
|  | 135 | +    bottom: -2, | 
|  | 136 | +  }, | 
|  | 137 | +  body: { | 
|  | 138 | +    display: 'flex', | 
|  | 139 | +    padding: 16, | 
|  | 140 | +  }, | 
|  | 141 | +  tab: { | 
|  | 142 | +    alignSelf: 'flex-end', | 
|  | 143 | +  }, | 
|  | 144 | +  tabContent: { | 
|  | 145 | +    justifyContent: 'center', | 
|  | 146 | +    width: 'auto', | 
|  | 147 | +  }, | 
|  | 148 | +  tabBodyBorder: { | 
|  | 149 | +    height: 4, | 
|  | 150 | +    position: 'absolute', | 
|  | 151 | +    left: 4, | 
|  | 152 | +    right: 4, | 
|  | 153 | +    bottom: -2, | 
|  | 154 | +    backgroundColor: theme.borderLight, | 
|  | 155 | +    borderTopWidth: 2, | 
|  | 156 | +    borderTopColor: theme.borderLightest, | 
|  | 157 | +  }, | 
|  | 158 | +  mask: { | 
|  | 159 | +    height: 4, | 
|  | 160 | +    position: 'absolute', | 
|  | 161 | +    left: 4, | 
|  | 162 | +    right: 4, | 
|  | 163 | +    bottom: -2, | 
|  | 164 | +    backgroundColor: theme.material, | 
|  | 165 | +  }, | 
|  | 166 | +  focusOutline: { | 
|  | 167 | +    position: 'absolute', | 
|  | 168 | +    left: 6, | 
|  | 169 | +    top: 6, | 
|  | 170 | +    bottom: 4, | 
|  | 171 | +    right: 6, | 
|  | 172 | +    ...border.focusOutline, | 
|  | 173 | +  }, | 
|  | 174 | +}); | 
|  | 175 | + | 
|  | 176 | +Tabs.Tab = Tab; | 
|  | 177 | +Tabs.Body = Body; | 
|  | 178 | + | 
|  | 179 | +export default Tabs; | 
0 commit comments