1import { Link04Icon } from '@expo/styleguide-icons'; 2import React, { PropsWithChildren } from 'react'; 3 4import { SnippetHeader } from '../Snippet/SnippetHeader'; 5import { RuntimePopup } from './RuntimePopup'; 6 7type ProtocolType = 'expo-go' | 'custom' | 'web'; 8 9const mapping: { name: string; id: ProtocolType }[] = [ 10 { name: 'Expo Go', id: 'expo-go' }, 11 { name: 'Custom', id: 'custom' }, 12 { name: 'Web', id: 'web' }, 13]; 14 15const SharedContext = React.createContext<{ 16 type: ProtocolType; 17 setType: (type: ProtocolType) => void; 18} | null>(null); 19 20/** 21 * Wraps a group of route urls and shares the preference. 22 */ 23export function RouteUrlGroup({ children }: { children: React.ReactNode }) { 24 const [type, setType] = React.useState<ProtocolType>('custom'); 25 return <SharedContext.Provider value={{ type, setType }}>{children}</SharedContext.Provider>; 26} 27 28function getProtocol(type: ProtocolType) { 29 return { 'expo-go': 'exp://127.0.0.1:8081/--/', web: 'acme.dev/', custom: 'acme://' }[type]; 30} 31 32export const RouteUrl = (props: PropsWithChildren) => { 33 const context = React.useContext(SharedContext); 34 const [type, setType] = React.useState<ProtocolType>('custom'); 35 36 if (context) { 37 return <RouteUrlInner {...props} {...context} />; 38 } 39 40 return <RouteUrlInner {...props} type={type} setType={setType} />; 41}; 42 43const RouteUrlInner = ({ 44 children, 45 type, 46 setType, 47}: PropsWithChildren<{ type: ProtocolType; setType: (type: ProtocolType) => void }>) => { 48 const protocol = getProtocol(type); 49 50 const childrenString = React.useMemo( 51 () => 52 React.Children.map(children, child => { 53 // return strings 54 if (typeof child === 'string') { 55 return child; 56 } 57 return null; 58 }) 59 ?.filter(Boolean) 60 .join('') 61 .trim() || '/', 62 [children] 63 ); 64 65 const [parsedProtocol, inputUrl] = React.useMemo<[string, string]>(() => { 66 if (type === 'custom') { 67 if (childrenString === '/') { 68 // remove last slash 69 return [protocol.replace(/\/$/, ''), childrenString]; 70 } else { 71 // ensure no starting slashes 72 return [protocol, childrenString.replace(/^\/+/, '')]; 73 } 74 } 75 // remove last slash 76 return [protocol.replace(/\/$/, ''), childrenString]; 77 }, [type, childrenString, protocol]); 78 79 return ( 80 <SnippetHeader 81 float 82 title={ 83 <span className="select-all"> 84 <span className="text-icon-secondary ">{parsedProtocol}</span> 85 {inputUrl} 86 </span> 87 } 88 Icon={Link04Icon}> 89 <RuntimePopup items={mapping} selected={type} onSelect={value => setType(value)} /> 90 </SnippetHeader> 91 ); 92}; 93