Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5a449bf

Browse files
chore: Add user autocomplete (#4210)
* chore: Add user autocomplete* Update value type* fix initial load and option updates* cleaned up styling* PR comments* prettierCo-authored-by: Kira Pilot <kira.pilot23@gmail.com>
1 parenta7e08db commit5a449bf

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
importCircularProgressfrom"@material-ui/core/CircularProgress"
2+
import{makeStyles}from"@material-ui/core/styles"
3+
importTextFieldfrom"@material-ui/core/TextField"
4+
importAutocompletefrom"@material-ui/lab/Autocomplete"
5+
import{useMachine}from"@xstate/react"
6+
import{User}from"api/typesGenerated"
7+
import{AvatarData}from"components/AvatarData/AvatarData"
8+
importdebouncefrom"just-debounce-it"
9+
import{ChangeEvent,useEffect,useState}from"react"
10+
import{searchUserMachine}from"xServices/users/searchUserXService"
11+
12+
exporttypeUserAutocompleteProps={
13+
value?:User
14+
onChange:(user:User|null)=>void
15+
}
16+
17+
exportconstUserAutocomplete:React.FC<UserAutocompleteProps>=({ value, onChange})=>{
18+
conststyles=useStyles()
19+
const[isAutocompleteOpen,setIsAutocompleteOpen]=useState(false)
20+
const[searchState,sendSearch]=useMachine(searchUserMachine)
21+
const{ searchResults}=searchState.context
22+
const[selectedValue,setSelectedValue]=useState<User|null>(value||null)
23+
24+
// seed list of options on the first page load if a user pases in a value
25+
// since some organizations have long lists of users, we do not load all options on page load.
26+
useEffect(()=>{
27+
if(value){
28+
sendSearch("SEARCH",{query:value.email})
29+
}
30+
// eslint-disable-next-line react-hooks/exhaustive-deps
31+
},[])
32+
33+
consthandleFilterChange=debounce((event:ChangeEvent<HTMLInputElement>)=>{
34+
sendSearch("SEARCH",{query:event.target.value})
35+
},1000)
36+
37+
return(
38+
<Autocomplete
39+
value={selectedValue}
40+
id="user-autocomplete"
41+
open={isAutocompleteOpen}
42+
onOpen={()=>{
43+
setIsAutocompleteOpen(true)
44+
}}
45+
onClose={()=>{
46+
setIsAutocompleteOpen(false)
47+
}}
48+
onChange={(_,newValue)=>{
49+
if(newValue===null){
50+
sendSearch("CLEAR_RESULTS")
51+
}
52+
53+
setSelectedValue(newValue)
54+
onChange(newValue)
55+
}}
56+
getOptionSelected={(option:User,value:User)=>option.username===value.username}
57+
getOptionLabel={(option)=>option.email}
58+
renderOption={(option:User)=>(
59+
<AvatarData
60+
title={option.username}
61+
subtitle={option.email}
62+
highlightTitle
63+
avatar={
64+
option.avatar_url ?(
65+
<img
66+
className={styles.avatar}
67+
alt={`${option.username}'s Avatar`}
68+
src={option.avatar_url}
69+
/>
70+
) :null
71+
}
72+
/>
73+
)}
74+
options={searchResults}
75+
loading={searchState.matches("searching")}
76+
className={styles.autocomplete}
77+
renderInput={(params)=>(
78+
<TextField
79+
{...params}
80+
margin="none"
81+
variant="outlined"
82+
placeholder="User email or username"
83+
InputProps={{
84+
...params.InputProps,
85+
onChange:handleFilterChange,
86+
endAdornment:(
87+
<>
88+
{searchState.matches("searching") ?<CircularProgresssize={16}/> :null}
89+
{params.InputProps.endAdornment}
90+
</>
91+
),
92+
}}
93+
/>
94+
)}
95+
/>
96+
)
97+
}
98+
exportconstuseStyles=makeStyles((theme)=>{
99+
return{
100+
autocomplete:{
101+
width:"100%",
102+
103+
"& .MuiFormControl-root":{
104+
width:"100%",
105+
},
106+
107+
"& .MuiInputBase-root":{
108+
width:"100%",
109+
// Match button small height
110+
height:40,
111+
},
112+
113+
"& input":{
114+
fontSize:14,
115+
padding:`${theme.spacing(0,0.5,0,0.5)} !important`,
116+
},
117+
},
118+
119+
avatar:{
120+
width:theme.spacing(4.5),
121+
height:theme.spacing(4.5),
122+
borderRadius:"100%",
123+
},
124+
}
125+
})
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import{getUsers}from"api/api"
2+
import{User}from"api/typesGenerated"
3+
import{queryToFilter}from"util/filters"
4+
import{assign,createMachine}from"xstate"
5+
6+
exporttypeAutocompleteEvent={type:"SEARCH";query:string}|{type:"CLEAR_RESULTS"}
7+
8+
exportconstsearchUserMachine=createMachine(
9+
{
10+
id:"searchUserMachine",
11+
schema:{
12+
context:{}as{
13+
searchResults?:User[]
14+
},
15+
events:{}asAutocompleteEvent,
16+
services:{}as{
17+
searchUsers:{
18+
data:User[]
19+
}
20+
},
21+
},
22+
context:{
23+
searchResults:[],
24+
},
25+
tsTypes:{}asimport("./searchUserXService.typegen").Typegen0,
26+
initial:"idle",
27+
states:{
28+
idle:{
29+
on:{
30+
SEARCH:"searching",
31+
CLEAR_RESULTS:{
32+
actions:["clearResults"],
33+
target:"idle",
34+
},
35+
},
36+
},
37+
searching:{
38+
invoke:{
39+
src:"searchUsers",
40+
onDone:{
41+
target:"idle",
42+
actions:["assignSearchResults"],
43+
},
44+
},
45+
},
46+
},
47+
},
48+
{
49+
services:{
50+
searchUsers:(_,{ query})=>getUsers(queryToFilter(query)),
51+
},
52+
actions:{
53+
assignSearchResults:assign({
54+
searchResults:(_,{ data})=>data,
55+
}),
56+
clearResults:assign({
57+
searchResults:(_)=>undefined,
58+
}),
59+
},
60+
},
61+
)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp