Migrate daisy tooltips to our own to fix cutoffs (#748)
* Make all tooltips use our component * Stop mobile tooltip crop (daisy -> floating-ui) * Show tooltip on tap for touch devices Except tooltips on buttons * migrate another daisy tooltip to ours * Prevent hidden tooltip from covering click/hover
This commit is contained in:
		
							parent
							
								
									d2b634c775
								
							
						
					
					
						commit
						df858f916b
					
				|  | @ -25,14 +25,15 @@ | |||
|   "main": "functions/src/index.js", | ||||
|   "dependencies": { | ||||
|     "@amplitude/node": "1.10.0", | ||||
|     "@floating-ui/react-dom": "1.0.0", | ||||
|     "@google-cloud/functions-framework": "3.1.2", | ||||
|     "@tiptap/core": "2.0.0-beta.181", | ||||
|     "@tiptap/extension-image": "2.0.0-beta.30", | ||||
|     "@tiptap/extension-link": "2.0.0-beta.43", | ||||
|     "@tiptap/extension-mention": "2.0.0-beta.102", | ||||
|     "@tiptap/starter-kit": "2.0.0-beta.190", | ||||
|     "dayjs": "1.11.4", | ||||
|     "cors": "2.8.5", | ||||
|     "dayjs": "1.11.4", | ||||
|     "express": "4.18.1", | ||||
|     "firebase-admin": "10.0.0", | ||||
|     "firebase-functions": "3.21.2", | ||||
|  |  | |||
|  | @ -31,6 +31,7 @@ import { useUser } from 'web/hooks/use-user' | |||
| import { track } from '@amplitude/analytics-browser' | ||||
| import { trackCallback } from 'web/lib/service/analytics' | ||||
| import { getMappedValue } from 'common/pseudo-numeric' | ||||
| import { Tooltip } from '../tooltip' | ||||
| 
 | ||||
| export function ContractCard(props: { | ||||
|   contract: Contract | ||||
|  | @ -333,22 +334,19 @@ export function PseudoNumericResolutionOrExpectation(props: { | |||
|           {resolution === 'CANCEL' ? ( | ||||
|             <CancelLabel /> | ||||
|           ) : ( | ||||
|             <div | ||||
|               className={clsx('tooltip', textColor)} | ||||
|               data-tip={value.toFixed(2)} | ||||
|             > | ||||
|             <Tooltip className={textColor} text={value.toFixed(2)}> | ||||
|               {formatLargeNumber(value)} | ||||
|             </div> | ||||
|             </Tooltip> | ||||
|           )} | ||||
|         </> | ||||
|       ) : ( | ||||
|         <> | ||||
|           <div | ||||
|             className={clsx('tooltip text-3xl', textColor)} | ||||
|             data-tip={value.toFixed(2)} | ||||
|           <Tooltip | ||||
|             className={clsx('text-3xl', textColor)} | ||||
|             text={value.toFixed(2)} | ||||
|           > | ||||
|             {formatLargeNumber(value)} | ||||
|           </div> | ||||
|           </Tooltip> | ||||
|           <div className={clsx('text-base', textColor)}>expected</div> | ||||
|         </> | ||||
|       )} | ||||
|  |  | |||
|  | @ -1,9 +1,8 @@ | |||
| import React from 'react' | ||||
| import dayjs from 'dayjs' | ||||
| import utc from 'dayjs/plugin/utc' | ||||
| import timezone from 'dayjs/plugin/timezone' | ||||
| import advanced from 'dayjs/plugin/advancedFormat' | ||||
| import { ClientRender } from './client-render' | ||||
| import { Tooltip } from './tooltip' | ||||
| 
 | ||||
| dayjs.extend(utc) | ||||
| dayjs.extend(timezone) | ||||
|  | @ -13,23 +12,16 @@ export function DateTimeTooltip(props: { | |||
|   time: number | ||||
|   text?: string | ||||
|   children?: React.ReactNode | ||||
|   noTap?: boolean | ||||
| }) { | ||||
|   const { time, text } = props | ||||
|   const { time, text, noTap } = props | ||||
| 
 | ||||
|   const formattedTime = dayjs(time).format('MMM DD, YYYY hh:mm a z') | ||||
|   const toolTip = text ? `${text} ${formattedTime}` : formattedTime | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       <ClientRender> | ||||
|         <span | ||||
|           className="tooltip hidden cursor-default sm:inline-block" | ||||
|           data-tip={toolTip} | ||||
|         > | ||||
|           {props.children} | ||||
|         </span> | ||||
|       </ClientRender> | ||||
|       <span className="whitespace-nowrap sm:hidden">{props.children}</span> | ||||
|     </> | ||||
|     <Tooltip text={toolTip} noTap={noTap}> | ||||
|       {props.children} | ||||
|     </Tooltip> | ||||
|   ) | ||||
| } | ||||
|  |  | |||
|  | @ -30,6 +30,7 @@ import { | |||
| } from '@heroicons/react/solid' | ||||
| import { MarketModal } from './editor/market-modal' | ||||
| import { insertContent } from './editor/utils' | ||||
| import { Tooltip } from './tooltip' | ||||
| 
 | ||||
| const DisplayImage = Image.configure({ | ||||
|   HTMLAttributes: { | ||||
|  | @ -146,15 +147,15 @@ export function TextEditor(props: { | |||
|           <EditorContent editor={editor} /> | ||||
|           {/* Toolbar, with buttons for images and embeds */} | ||||
|           <div className="flex h-9 items-center gap-5 pl-4 pr-1"> | ||||
|             <div className="tooltip flex items-center" data-tip="Add image"> | ||||
|             <Tooltip className="flex items-center" text="Add image" noTap> | ||||
|               <FileUploadButton | ||||
|                 onFiles={upload.mutate} | ||||
|                 className="-m-2.5 flex h-10 w-10 items-center justify-center rounded-full text-gray-400 hover:text-gray-500" | ||||
|               > | ||||
|                 <PhotographIcon className="h-5 w-5" aria-hidden="true" /> | ||||
|               </FileUploadButton> | ||||
|             </div> | ||||
|             <div className="tooltip flex items-center" data-tip="Add embed"> | ||||
|             </Tooltip> | ||||
|             <Tooltip className="flex items-center" text="Add embed" noTap> | ||||
|               <button | ||||
|                 type="button" | ||||
|                 onClick={() => setIframeOpen(true)} | ||||
|  | @ -167,8 +168,8 @@ export function TextEditor(props: { | |||
|                 /> | ||||
|                 <CodeIcon className="h-5 w-5" aria-hidden="true" /> | ||||
|               </button> | ||||
|             </div> | ||||
|             <div className="tooltip flex items-center" data-tip="Add market"> | ||||
|             </Tooltip> | ||||
|             <Tooltip className="flex items-center" text="Add market" noTap> | ||||
|               <button | ||||
|                 type="button" | ||||
|                 onClick={() => setMarketOpen(true)} | ||||
|  | @ -184,7 +185,7 @@ export function TextEditor(props: { | |||
|                   aria-hidden="true" | ||||
|                 /> | ||||
|               </button> | ||||
|             </div> | ||||
|             </Tooltip> | ||||
|             {/* Spacer that also focuses editor on click */} | ||||
|             <div | ||||
|               className="grow cursor-text self-stretch" | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ export function CopyLinkDateTimeComponent(props: { | |||
|   } | ||||
|   return ( | ||||
|     <div className={clsx('inline', className)}> | ||||
|       <DateTimeTooltip time={createdTime}> | ||||
|       <DateTimeTooltip time={createdTime} noTap> | ||||
|         <Link href={`/${prefix}/${slug}#${elementId}`} passHref={true}> | ||||
|           <a | ||||
|             onClick={(event) => copyLinkToComment(event)} | ||||
|  |  | |||
|  | @ -1,10 +1,11 @@ | |||
| import { InformationCircleIcon } from '@heroicons/react/outline' | ||||
| import { Tooltip } from './tooltip' | ||||
| 
 | ||||
| export function InfoTooltip(props: { text: string }) { | ||||
|   const { text } = props | ||||
|   return ( | ||||
|     <div className="tooltip" data-tip={text}> | ||||
|     <Tooltip text={text}> | ||||
|       <InformationCircleIcon className="h-5 w-5 text-gray-500" /> | ||||
|     </div> | ||||
|     </Tooltip> | ||||
|   ) | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import clsx from 'clsx' | ||||
| import { ReactNode } from 'react' | ||||
| import { Answer } from 'common/answer' | ||||
| import { getProbability } from 'common/calculate' | ||||
| import { getValueFromBucket } from 'common/calculate-dpm' | ||||
|  | @ -11,7 +10,7 @@ import { | |||
|   resolution, | ||||
| } from 'common/contract' | ||||
| import { formatLargeNumber, formatPercent } from 'common/util/format' | ||||
| import { ClientRender } from './client-render' | ||||
| import { Tooltip } from './tooltip' | ||||
| 
 | ||||
| export function OutcomeLabel(props: { | ||||
|   contract: Contract | ||||
|  | @ -91,13 +90,13 @@ export function FreeResponseOutcomeLabel(props: { | |||
|   const chosen = contract.answers?.find((answer) => answer.id === resolution) | ||||
|   if (!chosen) return <AnswerNumberLabel number={resolution} /> | ||||
|   return ( | ||||
|     <FreeResponseAnswerToolTip text={chosen.text}> | ||||
|     <Tooltip text={chosen.text}> | ||||
|       <AnswerLabel | ||||
|         answer={chosen} | ||||
|         truncate={truncate} | ||||
|         className={answerClassName} | ||||
|       /> | ||||
|     </FreeResponseAnswerToolTip> | ||||
|     </Tooltip> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
|  | @ -174,23 +173,3 @@ export function AnswerLabel(props: { | |||
|     </span> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| function FreeResponseAnswerToolTip(props: { | ||||
|   text: string | ||||
|   children?: ReactNode | ||||
| }) { | ||||
|   const { text } = props | ||||
|   return ( | ||||
|     <> | ||||
|       <ClientRender> | ||||
|         <span | ||||
|           className="tooltip hidden cursor-default sm:inline-block" | ||||
|           data-tip={text} | ||||
|         > | ||||
|           {props.children} | ||||
|         </span> | ||||
|       </ClientRender> | ||||
|       <span className="whitespace-nowrap sm:hidden">{props.children}</span> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  |  | |||
|  | @ -103,8 +103,10 @@ function DownTip(props: { onClick?: () => void }) { | |||
|   const { onClick } = props | ||||
|   return ( | ||||
|     <Tooltip | ||||
|       className="tooltip-bottom h-6 w-6" | ||||
|       className="h-6 w-6" | ||||
|       placement="bottom" | ||||
|       text={onClick && `-${formatMoney(5)}`} | ||||
|       noTap | ||||
|     > | ||||
|       <button | ||||
|         className="hover:text-red-600 disabled:text-gray-300" | ||||
|  | @ -122,8 +124,10 @@ function UpTip(props: { onClick?: () => void; value: number }) { | |||
|   const IconKind = value >= 10 ? ChevronDoubleRightIcon : ChevronRightIcon | ||||
|   return ( | ||||
|     <Tooltip | ||||
|       className="tooltip-bottom h-6 w-6" | ||||
|       className="h-6 w-6" | ||||
|       placement="bottom" | ||||
|       text={onClick && `Tip ${formatMoney(5)}`} | ||||
|       noTap | ||||
|     > | ||||
|       <button | ||||
|         className="hover:text-primary disabled:text-gray-300" | ||||
|  |  | |||
|  | @ -1,14 +1,79 @@ | |||
| import { | ||||
|   arrow, | ||||
|   autoUpdate, | ||||
|   flip, | ||||
|   offset, | ||||
|   Placement, | ||||
|   shift, | ||||
|   useFloating, | ||||
| } from '@floating-ui/react-dom' | ||||
| import clsx from 'clsx' | ||||
| import { ReactNode, useRef } from 'react' | ||||
| 
 | ||||
| // See https://floating-ui.com/docs/react-dom
 | ||||
| 
 | ||||
| export function Tooltip(props: { | ||||
|   text: string | false | undefined | null | ||||
|   children: ReactNode | ||||
|   className?: string | ||||
|   placement?: Placement | ||||
|   noTap?: boolean | ||||
| }) { | ||||
|   const { text, children, className, placement = 'top', noTap } = props | ||||
| 
 | ||||
|   const arrowRef = useRef(null) | ||||
| 
 | ||||
|   const { x, y, refs, reference, floating, strategy, middlewareData } = | ||||
|     useFloating({ | ||||
|       whileElementsMounted: autoUpdate, | ||||
|       placement, | ||||
|       middleware: [ | ||||
|         offset(8), | ||||
|         flip(), | ||||
|         shift({ padding: 4 }), | ||||
|         arrow({ element: arrowRef }), | ||||
|       ], | ||||
|     }) | ||||
| 
 | ||||
|   const { x: arrowX, y: arrowY } = middlewareData.arrow ?? {} | ||||
| 
 | ||||
|   // which side of tooltip arrow is on. like: if tooltip is top-left, arrow is on bottom of tooltip
 | ||||
|   const arrowSide = { | ||||
|     top: 'bottom', | ||||
|     right: 'left', | ||||
|     bottom: 'top', | ||||
|     left: 'right ', | ||||
|   }[placement.split('-')[0]] as string | ||||
| 
 | ||||
| export function Tooltip( | ||||
|   props: { | ||||
|     text: string | false | undefined | null | ||||
|   } & JSX.IntrinsicElements['div'] | ||||
| ) { | ||||
|   const { text, children, className } = props | ||||
|   return text ? ( | ||||
|     <div className={clsx(className, 'tooltip z-10')} data-tip={text}> | ||||
|       {children} | ||||
|     <div className="contents"> | ||||
|       <div | ||||
|         className={clsx('peer inline-block', className)} | ||||
|         ref={reference} | ||||
|         tabIndex={noTap ? undefined : 0} | ||||
|         onTouchStart={() => (refs.reference.current as HTMLElement).focus()} | ||||
|       > | ||||
|         {children} | ||||
|       </div> | ||||
|       <div | ||||
|         role="tooltip" | ||||
|         ref={floating} | ||||
|         style={{ position: strategy, top: y ?? 0, left: x ?? 0 }} | ||||
|         className="-z-10 max-w-xs rounded bg-slate-700 px-2 py-1 text-center text-sm text-white opacity-0 transition-opacity peer-hover:z-10 peer-hover:opacity-100 peer-focus:z-10 peer-focus:opacity-100" | ||||
|       > | ||||
|         {text} | ||||
|         <div | ||||
|           ref={arrowRef} | ||||
|           className="absolute h-2 w-2 rotate-45 bg-slate-700" | ||||
|           style={{ | ||||
|             top: arrowY != null ? arrowY : '', | ||||
|             left: arrowX != null ? arrowX : '', | ||||
|             right: '', | ||||
|             bottom: '', | ||||
|             [arrowSide]: '-4px', | ||||
|           }} | ||||
|         /> | ||||
|       </div> | ||||
|     </div> | ||||
|   ) : ( | ||||
|     <>{children}</> | ||||
|  |  | |||
							
								
								
									
										19
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								yarn.lock
									
									
									
									
									
								
							|  | @ -2178,6 +2178,25 @@ | |||
|   resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.6.1.tgz#0c74724ba6e9ea6ad25a391eab60a79eaba4c556" | ||||
|   integrity sha512-9FqhNjKQWpQ3fGnSOCovHOm+yhhiorKEqYLAfd525jWavunDJcx8rOW6i6ozAh+FbwcYMkL7b+3j4UR/30MpoQ== | ||||
| 
 | ||||
| "@floating-ui/core@^1.0.1": | ||||
|   version "1.0.1" | ||||
|   resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.0.1.tgz#00e64d74e911602c8533957af0cce5af6b2e93c8" | ||||
|   integrity sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA== | ||||
| 
 | ||||
| "@floating-ui/dom@^1.0.0": | ||||
|   version "1.0.1" | ||||
|   resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.0.1.tgz#3321d4e799d6ac2503e729131d07ad0e714aabeb" | ||||
|   integrity sha512-wBDiLUKWU8QNPNOTAFHiIAkBv1KlHauG2AhqjSeh2H+wR8PX+AArXfz8NkRexH5PgMJMmSOS70YS89AbWYh5dA== | ||||
|   dependencies: | ||||
|     "@floating-ui/core" "^1.0.1" | ||||
| 
 | ||||
| "@floating-ui/react-dom@1.0.0": | ||||
|   version "1.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.0.0.tgz#e0975966694433f1f0abffeee5d8e6bb69b7d16e" | ||||
|   integrity sha512-uiOalFKPG937UCLm42RxjESTWUVpbbatvlphQAU6bsv+ence6IoVG8JOUZcy8eW81NkU+Idiwvx10WFLmR4MIg== | ||||
|   dependencies: | ||||
|     "@floating-ui/dom" "^1.0.0" | ||||
| 
 | ||||
| "@google-cloud/firestore@^4.5.0": | ||||
|   version "4.15.1" | ||||
|   resolved "https://registry.yarnpkg.com/@google-cloud/firestore/-/firestore-4.15.1.tgz#ed764fc76823ce120e68fe8c27ef1edd0650cd93" | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user