github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/web/src/ResourceNameFilter.tsx (about) 1 import { InputAdornment } from "@material-ui/core" 2 import { InputProps as StandardInputProps } from "@material-ui/core/Input/Input" 3 import React from "react" 4 import styled from "styled-components" 5 import { ReactComponent as CloseSvg } from "./assets/svg/close.svg" 6 import { ReactComponent as SearchSvg } from "./assets/svg/search.svg" 7 import { 8 InstrumentedButton, 9 InstrumentedTextField, 10 } from "./instrumentedComponents" 11 import { useResourceListOptions } from "./ResourceListOptionsContext" 12 import { 13 Color, 14 Font, 15 FontSize, 16 mixinResetButtonStyle, 17 SizeUnit, 18 } from "./style-helpers" 19 20 export function matchesResourceName( 21 resourceName: string, 22 filter: string 23 ): boolean { 24 filter = filter.trim() 25 // this is functionally redundant but probably an important enough case to make its own thing 26 if (filter === "") { 27 return true 28 } 29 // a resource matches the query if the resource name contains all tokens in the query 30 return filter 31 .split(" ") 32 .every((token) => resourceName.toLowerCase().includes(token.toLowerCase())) 33 } 34 35 export const ResourceNameFilterTextField = styled(InstrumentedTextField)` 36 & .MuiOutlinedInput-root { 37 border-radius: ${SizeUnit(0.5)}; 38 border: 1px solid ${Color.gray40}; 39 background-color: ${Color.gray30}; 40 41 & fieldset { 42 border-color: 1px solid ${Color.gray40}; 43 } 44 &:hover fieldset { 45 border: 1px solid ${Color.gray40}; 46 } 47 &.Mui-focused fieldset { 48 border: 1px solid ${Color.gray40}; 49 } 50 & .MuiOutlinedInput-input { 51 padding: ${SizeUnit(0.2)}; 52 } 53 } 54 55 margin-top: ${SizeUnit(0.4)}; 56 margin-bottom: ${SizeUnit(0.4)}; 57 flex-grow: 1; 58 59 & .MuiInputBase-input { 60 font-family: ${Font.monospace}; 61 color: ${Color.offWhite}; 62 font-size: ${FontSize.small}; 63 } 64 ` 65 66 export const ClearResourceNameFilterButton = styled(InstrumentedButton)` 67 ${mixinResetButtonStyle}; 68 display: flex; 69 align-items: center; 70 ` 71 72 export function ResourceNameFilter(props: { className?: string }) { 73 const { 74 options: { resourceNameFilter }, 75 setOptions, 76 } = useResourceListOptions() 77 78 function setResourceNameFilter(newValue: string) { 79 setOptions({ resourceNameFilter: newValue }) 80 } 81 82 let inputProps: Partial<StandardInputProps> = { 83 "aria-label": "Filter resources by name", 84 startAdornment: ( 85 <InputAdornment position="start"> 86 <SearchSvg fill={Color.grayLightest} /> 87 </InputAdornment> 88 ), 89 } 90 91 // only show the "x" to clear if there's any input to clear 92 if (resourceNameFilter.length) { 93 const onClearClick = () => setResourceNameFilter("") 94 95 inputProps.endAdornment = ( 96 <InputAdornment position="end"> 97 <ClearResourceNameFilterButton 98 onClick={onClearClick} 99 analyticsName="ui.web.clearResourceNameFilter" 100 aria-label="Clear name filter" 101 > 102 <CloseSvg role="presentation" fill={Color.grayLightest} /> 103 </ClearResourceNameFilterButton> 104 </InputAdornment> 105 ) 106 } 107 108 return ( 109 <ResourceNameFilterTextField 110 className={props.className} 111 value={resourceNameFilter ?? ""} 112 onChange={(e) => setResourceNameFilter(e.target.value)} 113 placeholder="Filter resources by name" 114 InputProps={inputProps} 115 variant="outlined" 116 analyticsName="ui.web.resourceNameFilter" 117 /> 118 ) 119 }