1import * as React from 'react'
2import * as Styles from '../styles'
3import ClickableBox from './clickable-box'
4import {Box2} from './box'
5import BoxGrow from './box-grow'
6import Divider from './divider'
7import './list-item2.css'
8
9const Kb = {
10  Box2,
11  BoxGrow,
12  ClickableBox,
13  Divider,
14}
15
16// List item following stylesheet specs. TODO deprecate list-item.*.js
17
18type Props = {
19  type: 'Small' | 'Large'
20  icon?: React.ReactNode
21  statusIcon?: React.ReactNode
22  body: React.ReactNode
23  firstItem: boolean
24  fullDivider?: boolean
25  action?: React.ReactNode
26  hideHover?: boolean
27  // If 'grow' is used, the width of action cannot exceed 64. If larger width
28  // is needed, bump the `maxWidth: 64` below to a larger value. Note that if
29  // it's too large, the animation would also seem much faster.
30  onlyShowActionOnHover?: 'fade' | 'grow' | null
31  onClick?: () => void
32  onMouseDown?: (evt: React.BaseSyntheticEvent) => void // desktop only
33  height?: number // optional, for non-standard heights
34  style?: Styles.StylesCrossPlatform
35  iconStyleOverride?: Styles.StylesCrossPlatform
36  containerStyleOverride?: Styles.StylesCrossPlatform
37}
38
39const ListItem = (props: Props) => (
40  <Kb.ClickableBox
41    onClick={props.onClick || (props.onMouseDown ? () => {} : undefined)} // make sure clickable box applies click styles if just onMouseDown is given.
42    onMouseDown={props.onMouseDown}
43    style={Styles.collapseStyles([
44      props.type === 'Small' ? styles.clickableBoxSmall : styles.clickableBoxLarge,
45      !!props.height && {minHeight: props.height},
46      props.style,
47    ])}
48  >
49    <Kb.Box2
50      className={Styles.classNames({
51        listItem2: !props.hideHover,
52      })}
53      direction="horizontal"
54      style={Styles.collapseStyles([
55        props.type === 'Small' ? styles.rowSmall : styles.rowLarge,
56        !!props.height && {minHeight: props.height},
57      ])}
58      fullWidth={true}
59    >
60      {!props.firstItem && !!props.fullDivider && <Divider style={styles.divider} />}
61      {props.statusIcon && (
62        <Kb.Box2
63          direction="vertical"
64          style={getStatusIconStyle(props)}
65          alignSelf="flex-start"
66          alignItems="center"
67        >
68          {props.statusIcon}
69        </Kb.Box2>
70      )}
71      {props.icon && (
72        <Kb.Box2
73          direction="vertical"
74          style={getIconStyle(props)}
75          centerChildren={true}
76          alignSelf="flex-start"
77        >
78          {props.icon}
79        </Kb.Box2>
80      )}
81      <Kb.Box2 direction="horizontal" style={getContainerStyles(props)}>
82        {!props.firstItem && !props.fullDivider && <Divider style={styles.divider} />}
83        <Kb.BoxGrow>
84          <Kb.Box2 fullHeight={true} direction="horizontal" style={styles.bodyContainer}>
85            {props.body}
86          </Kb.Box2>
87        </Kb.BoxGrow>
88        <Kb.Box2
89          direction="horizontal"
90          className={Styles.classNames({
91            fade: props.onlyShowActionOnHover === 'fade',
92            grow: props.onlyShowActionOnHover === 'grow',
93          })}
94          style={getActionStyle(props)}
95          alignSelf="flex-start"
96        >
97          {props.action}
98        </Kb.Box2>
99      </Kb.Box2>
100    </Kb.Box2>
101  </Kb.ClickableBox>
102)
103
104export const smallHeight = Styles.isMobile ? 56 : 48
105export const largeHeight = Styles.isMobile ? 64 : 56
106const smallIconWidth = Styles.isMobile ? 64 : 64
107const largeIconWidth = Styles.isMobile ? 72 : 72
108const statusIconWidth = Styles.isMobile ? 48 : 44
109const afterStatusIconItemLeftDistance = statusIconWidth - (Styles.isMobile ? 10 : 14)
110
111const styles = Styles.styleSheetCreate(() => {
112  const _styles = {
113    actionLargeContainer: {
114      alignItems: 'center',
115      flexGrow: 0,
116      flexShrink: 0,
117      justifyContent: 'flex-start',
118      marginRight: 8,
119      position: 'relative',
120    } as const,
121    actionSmallContainer: {
122      alignItems: 'center',
123      flexGrow: 0,
124      flexShrink: 0,
125      justifyContent: 'flex-start',
126      marginRight: 8,
127      position: 'relative',
128    } as const,
129    bodyContainer: {
130      alignItems: 'center',
131      flexGrow: 1,
132      flexShrink: 1,
133      justifyContent: 'flex-start',
134      maxWidth: '100%',
135      position: 'relative',
136    } as const,
137    clickableBoxLarge: {
138      flexShrink: 0,
139      minHeight: largeHeight,
140      width: '100%',
141    } as const,
142    clickableBoxSmall: {
143      flexShrink: 0,
144      minHeight: smallHeight,
145      width: '100%',
146    } as const,
147    contentContainer: {
148      flexGrow: 1,
149      position: 'relative',
150    } as const,
151    divider: {
152      left: 0,
153      position: 'absolute',
154      right: 0,
155      top: 0,
156    } as const,
157    heightLarge: {minHeight: largeHeight} as const,
158    heightSmall: {minHeight: smallHeight} as const,
159    icon: {
160      position: 'absolute',
161    } as const,
162    iconLarge: {
163      minHeight: largeHeight,
164      width: largeIconWidth,
165    } as const,
166    iconSmall: {
167      minHeight: smallHeight,
168      width: smallIconWidth,
169    } as const,
170    rowLarge: {
171      alignItems: 'center',
172      minHeight: largeHeight,
173      position: 'relative',
174    } as const,
175    rowSmall: {
176      alignItems: 'center',
177      minHeight: smallHeight,
178      position: 'relative',
179    } as const,
180    statusIcon: {
181      justifyContent: 'center',
182      position: 'absolute',
183      width: statusIconWidth,
184    } as const,
185  }
186
187  const _statusIconStyles = {
188    statusIconLarge: {
189      ..._styles.statusIcon,
190      ..._styles.heightLarge,
191    },
192    statusIconSmall: {
193      ..._styles.statusIcon,
194      ..._styles.heightSmall,
195    },
196  }
197
198  const _iconStyles = {
199    iconLargeWithNoStatusIcon: {
200      ..._styles.icon,
201      ..._styles.iconLarge,
202    },
203    iconLargeWithStatusIcon: {
204      ..._styles.icon,
205      ..._styles.iconLarge,
206      left: afterStatusIconItemLeftDistance,
207    },
208    iconSmallWithNoStatusIcon: {
209      ..._styles.icon,
210      ..._styles.iconSmall,
211    },
212    iconSmallWithStatusIcon: {
213      ..._styles.icon,
214      ..._styles.iconSmall,
215      left: afterStatusIconItemLeftDistance,
216    },
217  }
218
219  // Using margin and keeping the icon on the let using absolute is to work around an issue in RN where
220  // the nested views won't grow and still retain their widths correctly
221  const _containerStyles = {
222    containerLargeNoStatusIconNoIcon: {
223      ..._styles.contentContainer,
224      ..._styles.heightLarge,
225      marginLeft: Styles.globalMargins.tiny,
226    },
227    containerLargeNoStatusIconWithIcon: {
228      ..._styles.contentContainer,
229      ..._styles.heightLarge,
230      marginLeft: largeIconWidth,
231    },
232    containerLargeWithStatusIconNoIcon: {
233      ..._styles.contentContainer,
234      ..._styles.heightLarge,
235      marginLeft: afterStatusIconItemLeftDistance,
236    },
237    containerLargeWithStatusIconWithIcon: {
238      ..._styles.contentContainer,
239      ..._styles.heightLarge,
240      marginLeft: largeIconWidth + afterStatusIconItemLeftDistance,
241    },
242    containerSmallNoStatusIconNoIcon: {
243      ..._styles.contentContainer,
244      ..._styles.heightSmall,
245      marginLeft: Styles.globalMargins.tiny,
246    },
247    containerSmallNoStatusIconWithIcon: {
248      ..._styles.contentContainer,
249      ..._styles.heightSmall,
250      marginLeft: smallIconWidth,
251    },
252    containerSmallWithStatusIconNoIcon: {
253      ..._styles.contentContainer,
254      ..._styles.heightSmall,
255      marginLeft: afterStatusIconItemLeftDistance,
256    },
257    containerSmallWithStatusIconWithIcon: {
258      ..._styles.contentContainer,
259      ..._styles.heightSmall,
260      marginLeft: smallIconWidth + afterStatusIconItemLeftDistance,
261    },
262  }
263
264  const _actionStyles = {
265    actionLargeIsGrowOnHover: {
266      ..._styles.actionLargeContainer,
267      ..._styles.heightLarge,
268      justifyContent: 'flex-end',
269    } as const,
270    actionLargeNotGrowOnHover: {
271      ..._styles.actionLargeContainer,
272      ..._styles.heightLarge,
273    } as const,
274    actionSmallIsGrowOnHover: {
275      ..._styles.actionSmallContainer,
276      ..._styles.heightSmall,
277      justifyContent: 'flex-end',
278    } as const,
279    actionSmallNotGrowOnHover: {
280      ..._styles.actionSmallContainer,
281      ..._styles.heightSmall,
282    } as const,
283  }
284  return {
285    ..._styles,
286    ..._statusIconStyles,
287    ..._iconStyles,
288    ..._containerStyles,
289    ..._actionStyles,
290  }
291})
292
293const getStatusIconStyle = (props: Props) =>
294  props.type === 'Small' ? styles.statusIconSmall : styles.statusIconLarge
295
296const getIconStyle = (props: Props) =>
297  props.iconStyleOverride ??
298  (props.type === 'Small'
299    ? props.statusIcon
300      ? styles.iconSmallWithStatusIcon
301      : styles.iconSmallWithNoStatusIcon
302    : props.statusIcon
303    ? styles.iconLargeWithStatusIcon
304    : styles.iconLargeWithNoStatusIcon)
305
306const getContainerStyles = (props: Props) =>
307  Styles.collapseStyles([
308    props.type === 'Small'
309      ? props.statusIcon
310        ? props.icon
311          ? styles.containerSmallWithStatusIconWithIcon
312          : styles.containerSmallWithStatusIconNoIcon
313        : props.icon
314        ? styles.containerSmallNoStatusIconWithIcon
315        : styles.containerSmallNoStatusIconNoIcon
316      : props.statusIcon
317      ? props.icon
318        ? styles.containerLargeWithStatusIconWithIcon
319        : styles.containerLargeWithStatusIconNoIcon
320      : props.icon
321      ? styles.containerLargeNoStatusIconWithIcon
322      : styles.containerLargeNoStatusIconNoIcon,
323    // If this becomes a problem, memoize different heights we use.
324    !!props.height && {minHeight: props.height},
325    props.containerStyleOverride,
326  ])
327
328const getActionStyle = (props: Props) =>
329  Styles.collapseStyles([
330    props.type === 'Small'
331      ? props.onlyShowActionOnHover
332        ? styles.actionSmallIsGrowOnHover
333        : styles.actionSmallNotGrowOnHover
334      : props.onlyShowActionOnHover
335      ? styles.actionLargeIsGrowOnHover
336      : styles.actionLargeNotGrowOnHover,
337    !!props.height && {minHeight: props.height},
338  ])
339
340export default ListItem
341