go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/frontend/ui/src/components/cl_list/cl_list.tsx (about) 1 // Copyright 2022 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 import { 16 useState, 17 Fragment, 18 MouseEvent, 19 } from 'react'; 20 21 import { styled } from '@mui/material/styles'; 22 import ClickAwayListener from '@mui/material/ClickAwayListener'; 23 import Link from '@mui/material/Link'; 24 import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; 25 26 import { Changelist } from '@/proto/go.chromium.org/luci/analysis/proto/v1/sources.pb'; 27 import { 28 clLink, 29 } from '@/tools/urlHandling/links'; 30 31 interface Props { 32 changelists: readonly Changelist[]; 33 } 34 35 const LightTooltip = styled(({ className, ...props }: TooltipProps) => ( 36 <Tooltip {...props} classes={{ popper: className }} /> 37 ))(({ theme }) => ({ 38 [`& .${tooltipClasses.arrow}`]: { 39 color: theme.palette.common.white, 40 }, 41 [`& .${tooltipClasses.tooltip}`]: { 42 backgroundColor: theme.palette.common.white, 43 color: 'inherit', 44 boxShadow: theme.shadows[1], 45 fontSize: theme.typography.fontSize, 46 fontWeight: theme.typography.fontWeightRegular, 47 }, 48 })); 49 50 const CLList = ({ 51 changelists, 52 }: Props) => { 53 const [tooltipOpen, setTooltipOpen] = useState(false); 54 55 const handleTooltipClose = () => { 56 setTooltipOpen(false); 57 }; 58 59 const handleTooltipOpen = (e: MouseEvent<HTMLAnchorElement>) => { 60 setTooltipOpen(!tooltipOpen); 61 e.preventDefault(); 62 }; 63 64 65 if (changelists.length == 0) { 66 return <></>; 67 } 68 return ( 69 <ClickAwayListener onClickAway={handleTooltipClose}> 70 <div style={{ display: 'inline-block' }}> 71 <Link 72 href={clLink(changelists[0])} 73 target="_blank" 74 > 75 {changelists[0].change} #{changelists[0].patchset} 76 </Link> 77 {changelists.length > 1 ? ( 78 <> 79 {', '} 80 <LightTooltip 81 PopperProps={{ 82 disablePortal: true, 83 }} 84 open={tooltipOpen} 85 onClose={handleTooltipClose} 86 disableFocusListener 87 disableHoverListener 88 disableTouchListener 89 arrow 90 title={ 91 <> 92 { 93 changelists.slice(1).map((cl, i) => { 94 return ( 95 <Fragment key={i.toString()}> 96 {i > 0 ? ', ' : null} 97 <Link 98 href={clLink(cl)} 99 target="_blank" 100 > 101 {cl.change} #{cl.patchset} 102 </Link> 103 </Fragment> 104 ); 105 }) 106 } 107 </> 108 }> 109 <Link href="#" onClick={handleTooltipOpen}>...</Link> 110 </LightTooltip> 111 </>) : null 112 } 113 </div> 114 </ClickAwayListener> 115 ); 116 }; 117 118 export default CLList;