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:first-child': { 48 marginTop: 0, 49 }, 50 51 'h4 + &, p + &, li > &': { 52 marginTop: spacing[3], 53 }, 54}); 55 56const summaryStyle = css({ 57 display: 'flex', 58 flexDirection: 'row', 59 alignItems: 'center', 60 userSelect: 'none', 61 listStyle: 'none', 62 backgroundColor: theme.background.subtle, 63 padding: spacing[1.5], 64 paddingRight: spacing[3], 65 margin: 0, 66 cursor: 'pointer', 67 68 '&:hover span': { 69 color: theme.text.secondary, 70 }, 71 72 '::-webkit-details-marker': { 73 display: 'none', 74 }, 75 76 h4: { 77 marginTop: 0, 78 marginBottom: 0, 79 }, 80 81 code: { 82 backgroundColor: theme.background.element, 83 display: 'inline', 84 fontSize: '90%', 85 }, 86}); 87 88const markerWrapperStyle = css({ 89 alignSelf: 'baseline', 90 marginTop: 5, 91 marginLeft: spacing[1.5], 92 marginRight: spacing[2], 93}); 94 95const markerStyle = css({ 96 transform: 'rotate(-90deg)', 97 transition: `transform 200ms`, 98 99 'details[open] &': { transform: 'rotate(0)' }, 100}); 101 102const contentStyle = css({ 103 padding: `${spacing[4]}px ${spacing[5]}px 0`, 104 105 p: { 106 marginLeft: 0, 107 }, 108 109 'pre > pre': { 110 marginTop: 0, 111 }, 112}); 113