1import { css } from '@emotion/react'; 2import { shadows, theme } from '@expo/styleguide'; 3import { borderRadius, spacing } from '@expo/styleguide-base'; 4import { TriangleDownIcon } from '@expo/styleguide-icons'; 5import type { PropsWithChildren, ReactNode } from 'react'; 6 7import { DEMI } from '~/ui/components/Text'; 8 9type CollapsibleProps = PropsWithChildren<{ 10 /** 11 * The content of the collapsible summary. 12 */ 13 summary: ReactNode; 14 /** 15 * If the collapsible should be rendered "open" by default. 16 */ 17 open?: boolean; 18 testID?: string; 19}>; 20 21export function Collapsible({ summary, open, testID, children }: CollapsibleProps) { 22 return ( 23 <details css={detailsStyle} open={open} data-testid={testID}> 24 <summary css={summaryStyle}> 25 <div css={markerWrapperStyle}> 26 <TriangleDownIcon className="icon-sm text-icon-default" css={markerStyle} /> 27 </div> 28 <DEMI>{summary}</DEMI> 29 </summary> 30 <div css={contentStyle}>{children}</div> 31 </details> 32 ); 33} 34 35const detailsStyle = css({ 36 overflow: 'hidden', 37 background: theme.background.default, 38 border: `1px solid ${theme.border.default}`, 39 borderRadius: borderRadius.md, 40 padding: 0, 41 marginBottom: spacing[3], 42 43 '&[open]': { 44 boxShadow: shadows.xs, 45 }, 46 47 'h4 + &, p + &, li > &': { 48 marginTop: spacing[3], 49 }, 50}); 51 52const summaryStyle = css({ 53 display: 'flex', 54 flexDirection: 'row', 55 alignItems: 'center', 56 userSelect: 'none', 57 listStyle: 'none', 58 backgroundColor: theme.background.subtle, 59 padding: spacing[1.5], 60 paddingRight: spacing[3], 61 margin: 0, 62 cursor: 'pointer', 63 64 '&:hover span': { 65 color: theme.text.secondary, 66 }, 67 68 '::-webkit-details-marker': { 69 display: 'none', 70 }, 71 72 h4: { 73 marginTop: 0, 74 marginBottom: 0, 75 }, 76 77 code: { 78 backgroundColor: theme.background.element, 79 display: 'inline', 80 fontSize: '90%', 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({ 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