From ca6a4dffadc00450274d7bdb2c771dd4730617ec Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Mon, 26 Sep 2022 19:19:49 +0000 Subject: [PATCH 1/6] chore: Add user autocomplete --- .../UserAutocomplete/UserAutocomplete.tsx | 105 ++++++++++++++++++ .../src/xServices/users/searchUserXService.ts | 55 +++++++++ 2 files changed, 160 insertions(+) create mode 100644 site/src/components/UserAutocomplete/UserAutocomplete.tsx create mode 100644 site/src/xServices/users/searchUserXService.ts diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.tsx new file mode 100644 index 0000000000000..1e2c8f1dc5f13 --- /dev/null +++ b/site/src/components/UserAutocomplete/UserAutocomplete.tsx @@ -0,0 +1,105 @@ +import CircularProgress from "@material-ui/core/CircularProgress" +import { makeStyles } from "@material-ui/core/styles" +import TextField from "@material-ui/core/TextField" +import Autocomplete from "@material-ui/lab/Autocomplete" +import { useMachine } from "@xstate/react" +import { User } from "api/typesGenerated" +import { AvatarData } from "components/AvatarData/AvatarData" +import debounce from "just-debounce-it" +import { ChangeEvent, useState } from "react" +import { searchUserMachine } from "xServices/users/searchUserXService" + +export type UserAutocompleteProps = { + value: User + onChange: (user: User | null) => void +} + +export const UserAutocomplete: React.FC = ({ value, onChange }) => { + const styles = useStyles() + const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false) + const [searchState, sendSearch] = useMachine(searchUserMachine) + const { searchResults } = searchState.context + + const handleFilterChange = debounce((event: ChangeEvent) => { + sendSearch("SEARCH", { query: event.target.value }) + }, 1000) + + return ( + { + setIsAutocompleteOpen(true) + }} + onClose={() => { + setIsAutocompleteOpen(false) + }} + onChange={(event, newValue) => { + onChange(newValue) + }} + getOptionSelected={(option: User, value: User) => option.username === value.username} + getOptionLabel={(option) => option.email} + renderOption={(option: User) => ( + + ) : null + } + /> + )} + options={searchResults} + loading={searchState.matches("searching")} + className={styles.autocomplete} + renderInput={(params) => ( + + {searchState.matches("searching") ? : null} + {params.InputProps.endAdornment} + + ), + }} + /> + )} + /> + ) +} +export const useStyles = makeStyles((theme) => { + return { + autocomplete: { + "& .MuiInputBase-root": { + width: 300, + // Match button small height + height: 36, + }, + + "& input": { + fontSize: 14, + padding: `${theme.spacing(0, 0.5, 0, 0.5)} !important`, + }, + }, + + avatar: { + width: theme.spacing(4.5), + height: theme.spacing(4.5), + borderRadius: "100%", + }, + } +}) diff --git a/site/src/xServices/users/searchUserXService.ts b/site/src/xServices/users/searchUserXService.ts new file mode 100644 index 0000000000000..6d7f31d23da91 --- /dev/null +++ b/site/src/xServices/users/searchUserXService.ts @@ -0,0 +1,55 @@ +import { getUsers } from "api/api" +import { User } from "api/typesGenerated" +import { queryToFilter } from "util/filters" +import { assign, createMachine } from "xstate" + +export const searchUserMachine = createMachine( + { + id: "searchUserMachine", + schema: { + context: {} as { + searchResults: User[] + }, + events: {} as { + type: "SEARCH" + query: string + }, + services: {} as { + searchUsers: { + data: User[] + } + }, + }, + context: { + searchResults: [], + }, + tsTypes: {} as import("./searchUserXService.typegen").Typegen0, + initial: "idle", + states: { + idle: { + on: { + SEARCH: "searching", + }, + }, + searching: { + invoke: { + src: "searchUsers", + onDone: { + target: "idle", + actions: ["assignSearchResults"], + }, + }, + }, + }, + }, + { + services: { + searchUsers: (_, { query }) => getUsers(queryToFilter(query)), + }, + actions: { + assignSearchResults: assign({ + searchResults: (_, { data }) => data, + }), + }, + }, +) From 4dfc22d548af6bb619aa3fef487ff219a8e54ecb Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Mon, 26 Sep 2022 19:26:39 +0000 Subject: [PATCH 2/6] Update value type --- site/src/components/UserAutocomplete/UserAutocomplete.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.tsx index 1e2c8f1dc5f13..bc714c72abff5 100644 --- a/site/src/components/UserAutocomplete/UserAutocomplete.tsx +++ b/site/src/components/UserAutocomplete/UserAutocomplete.tsx @@ -10,7 +10,7 @@ import { ChangeEvent, useState } from "react" import { searchUserMachine } from "xServices/users/searchUserXService" export type UserAutocompleteProps = { - value: User + value: User | null onChange: (user: User | null) => void } From e276c907f9c448b2945406651e0d500c88e54739 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 26 Sep 2022 22:32:18 +0000 Subject: [PATCH 3/6] fix initial load and option updates --- .../UserAutocomplete/UserAutocomplete.tsx | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.tsx index 1e2c8f1dc5f13..e8698343cfb8f 100644 --- a/site/src/components/UserAutocomplete/UserAutocomplete.tsx +++ b/site/src/components/UserAutocomplete/UserAutocomplete.tsx @@ -6,11 +6,11 @@ import { useMachine } from "@xstate/react" import { User } from "api/typesGenerated" import { AvatarData } from "components/AvatarData/AvatarData" import debounce from "just-debounce-it" -import { ChangeEvent, useState } from "react" +import { ChangeEvent, useEffect, useState } from "react" import { searchUserMachine } from "xServices/users/searchUserXService" export type UserAutocompleteProps = { - value: User + value?: User onChange: (user: User | null) => void } @@ -19,6 +19,21 @@ export const UserAutocomplete: React.FC = ({ value, onCha const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false) const [searchState, sendSearch] = useMachine(searchUserMachine) const { searchResults } = searchState.context + const [selectedValue, setSelectedValue] = useState(value || null) + + // seed list of options on the first page load + useEffect(() => { + const query = value ? value.email : "" + sendSearch("SEARCH", { query }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + // when selected value changes, update search terms + useEffect(() => { + const query = selectedValue ? selectedValue.email : "" + sendSearch("SEARCH", { query }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedValue]) const handleFilterChange = debounce((event: ChangeEvent) => { sendSearch("SEARCH", { query: event.target.value }) @@ -26,9 +41,8 @@ export const UserAutocomplete: React.FC = ({ value, onCha return ( { setIsAutocompleteOpen(true) @@ -37,6 +51,7 @@ export const UserAutocomplete: React.FC = ({ value, onCha setIsAutocompleteOpen(false) }} onChange={(event, newValue) => { + setSelectedValue(newValue) onChange(newValue) }} getOptionSelected={(option: User, value: User) => option.username === value.username} From 59ab375af76a777f4a09301b2f868d53294af8ef Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 26 Sep 2022 22:47:13 +0000 Subject: [PATCH 4/6] cleaned up styling --- .../components/UserAutocomplete/UserAutocomplete.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.tsx index e8698343cfb8f..71a466f2852ff 100644 --- a/site/src/components/UserAutocomplete/UserAutocomplete.tsx +++ b/site/src/components/UserAutocomplete/UserAutocomplete.tsx @@ -99,10 +99,16 @@ export const UserAutocomplete: React.FC = ({ value, onCha export const useStyles = makeStyles((theme) => { return { autocomplete: { + width: "100%", + + "& .MuiFormControl-root": { + width: "100%", + }, + "& .MuiInputBase-root": { - width: 300, + width: "100%", // Match button small height - height: 36, + height: 40, }, "& input": { From 6b8e5edfb58d5fcc9865733ac662080a516b5187 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Tue, 27 Sep 2022 13:57:40 +0000 Subject: [PATCH 5/6] PR comments --- .../UserAutocomplete/UserAutocomplete.tsx | 21 +++++++++---------- .../src/xServices/users/searchUserXService.ts | 18 +++++++++++----- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.tsx index 71a466f2852ff..2cd30da85c32b 100644 --- a/site/src/components/UserAutocomplete/UserAutocomplete.tsx +++ b/site/src/components/UserAutocomplete/UserAutocomplete.tsx @@ -21,20 +21,15 @@ export const UserAutocomplete: React.FC = ({ value, onCha const { searchResults } = searchState.context const [selectedValue, setSelectedValue] = useState(value || null) - // seed list of options on the first page load + // seed list of options on the first page load if a user pases in a value + // since some organizations have long lists of users, we do not load all options on page load. useEffect(() => { - const query = value ? value.email : "" - sendSearch("SEARCH", { query }) + if (value) { + sendSearch("SEARCH", { query: value.email }) + } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - // when selected value changes, update search terms - useEffect(() => { - const query = selectedValue ? selectedValue.email : "" - sendSearch("SEARCH", { query }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedValue]) - const handleFilterChange = debounce((event: ChangeEvent) => { sendSearch("SEARCH", { query: event.target.value }) }, 1000) @@ -50,7 +45,11 @@ export const UserAutocomplete: React.FC = ({ value, onCha onClose={() => { setIsAutocompleteOpen(false) }} - onChange={(event, newValue) => { + onChange={(_, newValue) => { + if (newValue === null) { + sendSearch("CLEAR_RESULTS") + } + setSelectedValue(newValue) onChange(newValue) }} diff --git a/site/src/xServices/users/searchUserXService.ts b/site/src/xServices/users/searchUserXService.ts index 6d7f31d23da91..4ec60a039adb6 100644 --- a/site/src/xServices/users/searchUserXService.ts +++ b/site/src/xServices/users/searchUserXService.ts @@ -3,17 +3,18 @@ import { User } from "api/typesGenerated" import { queryToFilter } from "util/filters" import { assign, createMachine } from "xstate" +export type AutocompleteEvent = + | { type: "SEARCH"; query: string; } + | { type: "CLEAR_RESULTS" } + export const searchUserMachine = createMachine( { id: "searchUserMachine", schema: { context: {} as { - searchResults: User[] - }, - events: {} as { - type: "SEARCH" - query: string + searchResults?: User[] }, + events: {} as AutocompleteEvent, services: {} as { searchUsers: { data: User[] @@ -29,6 +30,10 @@ export const searchUserMachine = createMachine( idle: { on: { SEARCH: "searching", + CLEAR_RESULTS: { + actions: ["clearResults"], + target: "idle", + } }, }, searching: { @@ -50,6 +55,9 @@ export const searchUserMachine = createMachine( assignSearchResults: assign({ searchResults: (_, { data }) => data, }), + clearResults: assign({ + searchResults: (_) => undefined, + }) }, }, ) From 133da7ea8d48c4dcf96bf7f5e12faebd38905975 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Tue, 27 Sep 2022 14:02:12 +0000 Subject: [PATCH 6/6] prettier --- site/src/xServices/users/searchUserXService.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/site/src/xServices/users/searchUserXService.ts b/site/src/xServices/users/searchUserXService.ts index 4ec60a039adb6..28fcb043d8e00 100644 --- a/site/src/xServices/users/searchUserXService.ts +++ b/site/src/xServices/users/searchUserXService.ts @@ -3,9 +3,7 @@ import { User } from "api/typesGenerated" import { queryToFilter } from "util/filters" import { assign, createMachine } from "xstate" -export type AutocompleteEvent = - | { type: "SEARCH"; query: string; } - | { type: "CLEAR_RESULTS" } +export type AutocompleteEvent = { type: "SEARCH"; query: string } | { type: "CLEAR_RESULTS" } export const searchUserMachine = createMachine( { @@ -33,7 +31,7 @@ export const searchUserMachine = createMachine( CLEAR_RESULTS: { actions: ["clearResults"], target: "idle", - } + }, }, }, searching: { @@ -57,7 +55,7 @@ export const searchUserMachine = createMachine( }), clearResults: assign({ searchResults: (_) => undefined, - }) + }), }, }, ) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy