1import { css } from '@emotion/react'; 2import { ExpoGoLogo, shadows, theme, typography } from '@expo/styleguide'; 3import { breakpoints, spacing } from '@expo/styleguide-base'; 4import { ChevronDownIcon, Monitor01DuotoneIcon, Phone01DuotoneIcon } from '@expo/styleguide-icons'; 5import { useEffect, useState } from 'react'; 6 7type PopupActionProps<T extends string> = { 8 items: { name: string; id: T }[]; 9 selected: string; 10 onSelect: (value: T) => void; 11}; 12export function RuntimePopup<T extends string>({ items, selected, onSelect }: PopupActionProps<T>) { 13 const Icon = [ExpoGoLogo, Phone01DuotoneIcon, Monitor01DuotoneIcon][ 14 items.findIndex(item => item.id === selected) 15 ]; 16 const [isLoaded, setLoaded] = useState(false); 17 18 useEffect(function didMount() { 19 setLoaded(true); 20 }, []); 21 22 return ( 23 <div className="relative"> 24 <select 25 aria-label="Runtime URL format selector" 26 title="Select runtime URL format" 27 css={selectStyle} 28 className="focus-visible:-outline-offset-2 border-0 rounded-none border-l border-l-default h-10 leading-10 px-10 hocus:bg-subtle hocus:shadow-none" 29 value={selected} 30 onChange={e => { 31 onSelect(e.target.value as T); 32 }}> 33 {items.map((item, index) => ( 34 <option key={String(index)} value={item.id}> 35 {item.name} 36 </option> 37 ))} 38 </select> 39 {isLoaded && ( 40 <div 41 style={{ lineHeight: 1.3 }} 42 className="absolute inset-x-2.5 inset-y-0 flex items-center justify-between gap-2 text-icon-secondary pointer-events-none select-none"> 43 <Icon className={ICON_CLASSES} /> 44 <ChevronDownIcon className="icon-xs text-icon-secondary pointer-events-none" /> 45 </div> 46 )} 47 </div> 48 ); 49} 50 51const ICON_CLASSES = 'icon-sm text-icon-secondary pointer-events-none inline-block'; 52 53const selectStyle = css` 54 ${typography.fontSizes[14]} 55 display: flex; 56 align-items: center; 57 justify-content: center; 58 color: ${theme.text.default}; 59 line-height: 1.3; 60 padding: 0 ${spacing[8]}px; 61 color: ${theme.text.default}; 62 text-indent: 0; 63 64 box-shadow: ${shadows.xs}; 65 -moz-appearance: none; 66 -webkit-appearance: none; 67 appearance: none; 68 background-color: ${theme.background.default}; 69 cursor: pointer; 70 71 :hover { 72 background-color: ${theme.background.element}; 73 } 74 75 :focus-visible { 76 background-color: ${theme.background.element}; 77 } 78 79 @media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) { 80 padding: 0 0; 81 text-indent: -9999px; 82 } 83`; 84