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 41export function ExpoKitCollapsible({ children }: CollapsibleProps) { 42 return ( 43 <div css={configDetailsStyle}> 44 <Collapsible summary="ExpoKit">{children}</Collapsible> 45 </div> 46 ); 47} 48 49export function BareWorkflowCollapsible({ children }: CollapsibleProps) { 50 return ( 51 <div css={configDetailsStyle}> 52 <Collapsible summary="Bare Workflow">{children}</Collapsible> 53 </div> 54 ); 55} 56 57const detailsStyle = css({ 58 overflow: 'hidden', 59 background: theme.background.default, 60 border: `1px solid ${theme.border.default}`, 61 borderRadius: borderRadius.medium, 62 padding: 0, 63 marginBottom: spacing[3], 64 65 '&[open]': { 66 boxShadow: shadows.micro, 67 }, 68 69 'h4 + &, li > &': { 70 marginTop: spacing[3], 71 }, 72}); 73 74const summaryStyle = css({ 75 display: 'flex', 76 flexDirection: 'row', 77 alignItems: 'center', 78 userSelect: 'none', 79 listStyle: 'none', 80 backgroundColor: theme.background.secondary, 81 padding: spacing[1.5], 82 paddingRight: spacing[3], 83 margin: 0, 84 cursor: 'pointer', 85 86 '&:hover span': { 87 color: theme.text.secondary, 88 }, 89}); 90 91const markerWrapperStyle = css({ 92 alignSelf: 'baseline', 93 marginTop: 5, 94 marginLeft: spacing[1.5], 95 marginRight: spacing[2], 96}); 97 98const markerStyle = css({ 99 transform: 'rotate(-90deg)', 100 transition: `transform 200ms`, 101 102 'details[open] &': { transform: 'rotate(0)' }, 103}); 104 105const contentStyle = css(typography.body.paragraph, { 106 padding: `${spacing[4]}px ${spacing[5]}px 0`, 107 108 p: { 109 marginLeft: 0, 110 }, 111 112 'pre > pre': { 113 marginTop: 0, 114 }, 115}); 116 117const configDetailsStyle = css({ 118 marginTop: spacing[3], 119 120 '& details[open]': { 121 paddingBottom: spacing[4], 122 }, 123}); 124