1import { css } from '@emotion/react'; 2import { spacing, theme, ArrowUpRightIcon, iconSize, SnackLogo } from '@expo/styleguide'; 3import { useEffect, useRef, useState, PropsWithChildren } from 'react'; 4 5import { Snippet } from '../Snippet'; 6import { SnippetAction } from '../SnippetAction'; 7import { SnippetContent } from '../SnippetContent'; 8import { SnippetHeader } from '../SnippetHeader'; 9 10import { SNACK_URL, getSnackFiles } from '~/common/snack'; 11import { PageApiVersionContextType, usePageApiVersion } from '~/providers/page-api-version'; 12import versions from '~/public/static/constants/versions.json'; 13 14const DEFAULT_PLATFORM = 'android'; 15const { LATEST_VERSION } = versions; 16 17type Props = PropsWithChildren<{ 18 dependencies: string[]; 19 label?: string; 20 defaultPlatform?: string; 21 templateId?: string; 22 files?: Record<string, string>; 23 platforms?: string[]; 24 buttonTitle?: string; 25 contentHidden?: boolean; 26}>; 27 28export const SnackInline = ({ 29 dependencies = [], 30 label, 31 defaultPlatform, 32 templateId, 33 files, 34 platforms, 35 buttonTitle, 36 contentHidden, 37 children, 38}: Props) => { 39 const contentRef = useRef<HTMLDivElement>(null); 40 const [isReady, setReady] = useState(false); 41 const context = usePageApiVersion(); 42 43 useEffect(() => setReady(true), []); 44 45 // Filter out `latest` and use the concrete latest version instead. We want to 46 // keep `unversioned` in for the selected docs version though. This is used to 47 // find the examples in the static dir, and we don't have a `latest` version 48 // there, but we do have `unversioned`. 49 const getSelectedDocsVersion = () => { 50 const { version } = context as PageApiVersionContextType; 51 return version === 'latest' ? LATEST_VERSION : version; 52 }; 53 54 // Get a SDK version that Snack will understand. `latest` and `unversioned` 55 // are meaningless to Snack so we filter those out and use `LATEST_VERSION` instead 56 const getSnackSdkVersion = () => { 57 let version = getSelectedDocsVersion(); 58 if (version === 'unversioned') { 59 version = LATEST_VERSION; 60 } 61 62 return version.replace('v', ''); 63 }; 64 65 const getExamplesPath = () => { 66 return `${document.location.origin}/static/examples/${getSelectedDocsVersion()}`; 67 }; 68 69 const getCode = () => { 70 const code = contentRef.current ? contentRef.current.textContent || '' : ''; 71 return code.replace(/%%placeholder-start%%.*%%placeholder-end%%/g, ''); 72 }; 73 74 return ( 75 <Snippet css={inlineSnackWrapperStyle}> 76 <SnippetHeader title={label || 'Example'}> 77 <form action={SNACK_URL} method="POST" target="_blank"> 78 <input type="hidden" name="platform" value={defaultPlatform || DEFAULT_PLATFORM} /> 79 <input type="hidden" name="name" value={label || 'Example'} /> 80 <input type="hidden" name="dependencies" value={dependencies.join(',')} /> 81 <input type="hidden" name="sdkVersion" value={getSnackSdkVersion()} /> 82 {platforms && ( 83 <input type="hidden" name="supportedPlatforms" value={platforms.join(',')} /> 84 )} 85 {isReady && ( 86 <input 87 type="hidden" 88 name="files" 89 value={JSON.stringify( 90 getSnackFiles({ 91 templateId, 92 code: getCode(), 93 files, 94 baseURL: getExamplesPath(), 95 }) 96 )} 97 /> 98 )} 99 <SnippetAction 100 disabled={!isReady} 101 icon={<SnackLogo height={iconSize.regular} />} 102 iconRight={<ArrowUpRightIcon size={iconSize.small} color={theme.icon.secondary} />} 103 type="submit"> 104 {buttonTitle || 'Open in Snack'} 105 </SnippetAction> 106 </form> 107 </SnippetHeader> 108 <SnippetContent ref={contentRef} css={contentHidden && css({ display: 'none' })} skipPadding> 109 {children} 110 </SnippetContent> 111 </Snippet> 112 ); 113}; 114 115const inlineSnackWrapperStyle = css({ 116 display: 'flex', 117 flexDirection: 'column', 118 marginBottom: spacing[3], 119 120 pre: { 121 margin: 0, 122 border: 0, 123 }, 124}); 125