1import { css } from '@emotion/react'; 2import { 3 borderRadius, 4 iconSize, 5 shadows, 6 spacing, 7 theme, 8 typography, 9 TriangleDownIcon, 10} from '@expo/styleguide'; 11import React, { PropsWithChildren, ReactNode } from 'react'; 12 13import { HEADLINE } from '~/ui/components/Text'; 14 15type CollapsibleProps = PropsWithChildren<{ 16 /** 17 * The content of the collapsible summary. 18 */ 19 summary: ReactNode; 20 /** 21 * If the collapsible should be rendered "open" by default. 22 */ 23 open?: boolean; 24 testID?: string; 25}>; 26 27export function Collapsible({ summary, open, testID, children }: CollapsibleProps) { 28 return ( 29 <details css={detailsStyle} open={open} data-testid={testID}> 30 <summary css={summaryStyle}> 31 <div css={markerWrapperStyle}> 32 <TriangleDownIcon css={markerStyle} size={iconSize.small} /> 33 </div> 34 <HEADLINE tag="span">{summary}</HEADLINE> 35 </summary> 36 <div css={contentStyle}>{children}</div> 37 </details> 38 ); 39} 40 41const detailsStyle = css({ 42 overflow: 'hidden', 43 background: theme.background.default, 44 border: `1px solid ${theme.border.default}`, 45 borderRadius: borderRadius.medium, 46 padding: 0, 47 marginBottom: spacing[3], 48 49 '&[open]': { 50 boxShadow: shadows.micro, 51 }, 52 53 'h4 + &, li > &': { 54 marginTop: spacing[3], 55 }, 56}); 57 58const summaryStyle = css({ 59 display: 'flex', 60 flexDirection: 'row', 61 alignItems: 'center', 62 userSelect: 'none', 63 listStyle: 'none', 64 backgroundColor: theme.background.secondary, 65 padding: spacing[1.5], 66 paddingRight: spacing[3], 67 margin: 0, 68 cursor: 'pointer', 69 70 '&:hover span': { 71 color: theme.text.secondary, 72 }, 73 74 '::-webkit-details-marker': { 75 display: 'none', 76 }, 77 78 h4: { 79 marginTop: 0, 80 marginBottom: 0, 81 }, 82}); 83 84const markerWrapperStyle = css({ 85 alignSelf: 'baseline', 86 marginTop: 5, 87 marginLeft: spacing[1.5], 88 marginRight: spacing[2], 89}); 90 91const markerStyle = css({ 92 transform: 'rotate(-90deg)', 93 transition: `transform 200ms`, 94 95 'details[open] &': { transform: 'rotate(0)' }, 96}); 97 98const contentStyle = css(typography.body.paragraph, { 99 padding: `${spacing[4]}px ${spacing[5]}px 0`, 100 101 p: { 102 marginLeft: 0, 103 }, 104 105 'pre > pre': { 106 marginTop: 0, 107 }, 108}); 109