minor fixes

This commit is contained in:
Elvy 2025-01-28 19:24:34 +01:00
parent 916d827b1b
commit bc4ed128b6

View file

@ -64,14 +64,14 @@ interface DestinationItem {
} }
interface UnspecificRowProps { interface UnspecificRowProps {
key: string key: string;
destination: DestinationItem, destination: DestinationItem,
rowMode: string rowMode: string;
disabled: boolean, disabled: boolean,
isSelected: boolean, isSelected: boolean,
onPressDestination: (destination: DestinationItem) => void, onPressDestination: (destination: DestinationItem) => void,
"aria-posinset": number, "aria-posinset": number,
"aria-setsize": number "aria-setsize": number;
} }
interface SpecificRowProps extends UnspecificRowProps { interface SpecificRowProps extends UnspecificRowProps {
@ -113,8 +113,18 @@ interface GuildResult {
} }
type Result = UserResult | ChannelResult | GuildResult; type Result = UserResult | ChannelResult | GuildResult;
type SearchType = ("USERS" | "CHANNELS" | "GUILDS")[] | "USERS" | "CHANNELS" | "GUILDS" | "ALL";
const searchTypesToResultTypes = (type: string | string[]) => { export interface SearchModalProps {
modalProps: ModalProps;
onSubmit(selected: DestinationItem[]): void;
input?: string;
searchType?: SearchType;
subText?: string;
excludeIds?: string[],
}
const searchTypesToResultTypes = (type: SearchType) => {
if (type === "ALL") return ["USER", "TEXT_CHANNEL", "VOICE_CHANNEL", "GROUP_DM", "GUILD"]; if (type === "ALL") return ["USER", "TEXT_CHANNEL", "VOICE_CHANNEL", "GROUP_DM", "GUILD"];
if (typeof type === "string") { if (typeof type === "string") {
if (type === "USERS") return ["USER"]; if (type === "USERS") return ["USER"];
@ -125,10 +135,10 @@ const searchTypesToResultTypes = (type: string | string[]) => {
} }
}; };
function searchTypeToText(type: string | string[]) { function searchTypeToText(type: SearchType) {
if (type === undefined || type === "ALL") return "Users, Channels, and Servers"; if (type === undefined || type === "ALL") return "Users, Channels, and Servers";
if (typeof type === "string") { if (typeof type === "string") {
if (type === "GUILD") return "Servers"; if (type === "GUILDS") return "Servers";
else return type.charAt(0) + type.slice(1).toLowerCase(); else return type.charAt(0) + type.slice(1).toLowerCase();
} else { } else {
if (type.length === 1) { if (type.length === 1) {
@ -144,23 +154,17 @@ function searchTypeToText(type: string | string[]) {
/** /**
* SearchModal component for displaying a modal with search functionality, built after Discord's forwarding Modal. * SearchModal component for displaying a modal with search functionality, built after Discord's forwarding Modal.
* *
* @param {Object} props - The props for the SearchModal component. * @param {SearchModalProps} props - The props for the SearchModal component.
* @param {ModalProps} props.modalProps - The modal props. * @param {ModalProps} props.modalProps - The modal props. You get these from the `openModal` function.
* @param {function} props.onSubmit - The function to call when the user submits their selection. * @param {function} props.onSubmit - The function to call when the user submits their selection.
* @param {string} [props.input] - The initial input value for the search bar. * @param {string} [props.input] - The initial input value for the search bar.
* @param {("USERS" | "CHANNELS" | "GUILDS")[] | "USERS" | "CHANNELS" | "GUILDS" | "ALL"} [props.searchType="ALL"] - The type of items to search for. * @param {SearchType} [props.searchType="ALL"] - The type of items to search for.
* @param {string} [props.subText] - Additional text to display below the heading. * @param {string} [props.subText] - Additional text to display below the heading.
* @param {string[]} [props.excludeIds] - An array of IDs to exclude from the search results. * @param {string[]} [props.excludeIds] - An array of IDs to exclude from the search results.
* @returns The rendered SearchModal component. * @returns The rendered SearchModal component.
*/ */
export default function SearchModal({ modalProps, onSubmit, input, searchType = "ALL", subText, excludeIds }: { export default function SearchModal({ modalProps, onSubmit, input, searchType = "ALL", subText, excludeIds }: SearchModalProps) {
modalProps: ModalProps;
onSubmit(selected: DestinationItem[]): void;
input?: string;
searchType?: ("USERS" | "CHANNELS" | "GUILDS")[] | "USERS" | "CHANNELS" | "GUILDS" | "ALL";
subText?: string
excludeIds?: string[],
}) {
const UserIcon = React.memo(function ({ const UserIcon = React.memo(function ({
user, user,
size = SearchBarModule.AvatarSizes.SIZE_32, size = SearchBarModule.AvatarSizes.SIZE_32,
@ -253,14 +257,14 @@ export default function SearchModal({ modalProps, onSubmit, input, searchType =
return ( return (
<Row {...otherProps} <Row {...otherProps}
icon={<UserIcon icon={<UserIcon
aria-hidden={true} aria-hidden={true}
size={SearchBarModule.AvatarSizes.SIZE_32} size={SearchBarModule.AvatarSizes.SIZE_32}
user={user} user={user}
status={userStatus} status={userStatus}
/>} />}
label={nickname ?? username} label={nickname ?? username}
subLabel={userTag} subLabel={userTag}
/> />
); );
} }
@ -321,13 +325,13 @@ export default function SearchModal({ modalProps, onSubmit, input, searchType =
return ( return (
<Row {...otherProps} <Row {...otherProps}
icon={<SearchBarModule.Avatar icon={<SearchBarModule.Avatar
src={guildIcon} src={guildIcon}
size={SearchBarModule.AvatarSizes.SIZE_32} size={SearchBarModule.AvatarSizes.SIZE_32}
aria-hidden={true} aria-hidden={true}
/>} />}
label={guildName} label={guildName}
subLabel={""} subLabel={""}
/> />
); );
} }
@ -352,10 +356,10 @@ export default function SearchModal({ modalProps, onSubmit, input, searchType =
return ( return (
<Row {...otherProps} <Row {...otherProps}
icon={<GroupDMAvatars aria-hidden={true} size={SearchBarModule.AvatarSizes.SIZE_32} icon={<GroupDMAvatars aria-hidden={true} size={SearchBarModule.AvatarSizes.SIZE_32}
channel={channel}/>} channel={channel} />}
label={label} label={label}
subLabel={subLabelValue} subLabel={subLabelValue}
/> />
); );
} }
@ -396,7 +400,7 @@ export default function SearchModal({ modalProps, onSubmit, input, searchType =
hasQuery: boolean; hasQuery: boolean;
frequentChannels: Channel[]; frequentChannels: Channel[];
channelHistory: string[]; channelHistory: string[];
guilds: GuildResult[] guilds: GuildResult[];
}): Result[] { }): Result[] {
const removeDuplicates = (arr: Result[]): Result[] => { const removeDuplicates = (arr: Result[]): Result[] => {
const clean: any[] = []; const clean: any[] = [];
@ -422,7 +426,7 @@ export default function SearchModal({ modalProps, onSubmit, input, searchType =
return removeDuplicates( return removeDuplicates(
[...(selected.length > 0 ? selected.map(e => getItem(e)) : []), [...(selected.length > 0 ? selected.map(e => getItem(e)) : []),
...recentDestinations ...recentDestinations
]); ]);
} }
@ -433,39 +437,39 @@ export default function SearchModal({ modalProps, onSubmit, input, searchType =
return ref_.current; return ref_.current;
} }
function getSearchHandler(searchOptions: Record<string, any>): { search: (e: { query: string, resultTypes: string[] }) => void, results: Result[], query: string } { function getSearchHandler(searchOptions: Record<string, any>): { search: (e: { query: string, resultTypes: string[]; }) => void, results: Result[], query: string; } {
const [results, setResults] = useState<{ results: Result[], query: string }>({ const [results, setResults] = useState<{ results: Result[], query: string; }>({
results: [], results: [],
query: "" query: ""
}); });
const searchHandler: InstanceType<typeof SearchHandler> = getRef(() => { const searchHandler: InstanceType<typeof SearchHandler> = getRef(() => {
const searchHandler = new SearchHandler((r: Result[], q: string) => { const searchHandler = new SearchHandler((r: Result[], q: string) => {
setResults({ setResults({
results: r, results: r,
query: q query: q
}); });
}
);
searchHandler.setLimit(20);
searchHandler.search("");
return searchHandler;
} }
);
searchHandler.setLimit(20);
searchHandler.search("");
return searchHandler;
}
); );
useEffect(() => () => searchHandler.destroy(), [searchHandler]); useEffect(() => () => searchHandler.destroy(), [searchHandler]);
useEffect(() => { useEffect(() => {
searchOptions != null && searchOptions !== searchHandler.options && searchHandler.setOptions(searchOptions); searchOptions != null && searchOptions !== searchHandler.options && searchHandler.setOptions(searchOptions);
}, [searchHandler, searchOptions] }, [searchHandler, searchOptions]
); );
return { return {
search: useCallback(e => { search: useCallback(e => {
const { query, resultTypes } = e; const { query, resultTypes } = e;
if (searchHandler.resultTypes == null || !(resultTypes.length === searchHandler.resultTypes.size && resultTypes.every(e => searchHandler.resultTypes.has(e)))) { if (searchHandler.resultTypes == null || !(resultTypes.length === searchHandler.resultTypes.size && resultTypes.every(e => searchHandler.resultTypes.has(e)))) {
searchHandler.setResultTypes(resultTypes); searchHandler.setResultTypes(resultTypes);
searchHandler.setLimit(resultTypes.length === 1 ? 50 : 20); searchHandler.setLimit(resultTypes.length === 1 ? 50 : 20);
}
searchHandler.search(query.trim() === "" ? "" : query);
} }
searchHandler.search(query.trim() === "" ? "" : query);
}
, [searchHandler]), , [searchHandler]),
...results ...results
}; };
@ -530,11 +534,11 @@ export default function SearchModal({ modalProps, onSubmit, input, searchType =
const rowHeight = useCallback(() => 48, []); const rowHeight = useCallback(() => 48, []);
function ModalScroller({ rowData, handleToggleDestination, paddingBottom, paddingTop }: { rowData: Result[], handleToggleDestination: (destination: DestinationItem) => void, paddingBottom?: number, paddingTop?: number }) { function ModalScroller({ rowData, handleToggleDestination, paddingBottom, paddingTop }: { rowData: Result[], handleToggleDestination: (destination: DestinationItem) => void, paddingBottom?: number, paddingTop?: number; }) {
const sectionCount: number[] = useMemo(() => [rowData.length], [rowData.length]); const sectionCount: number[] = useMemo(() => [rowData.length], [rowData.length]);
const callback = useCallback((e: { section: number, row: number }) => { const callback = useCallback((e: { section: number, row: number; }) => {
const { section, row } = e; const { section, row } = e;
if (section > 0) if (section > 0)
return; return;
@ -580,7 +584,7 @@ export default function SearchModal({ modalProps, onSubmit, input, searchType =
sections={sectionCount} sections={sectionCount}
sectionHeight={0} sectionHeight={0}
renderRow={callback} renderRow={callback}
rowHeight={rowHeight}/>; rowHeight={rowHeight} />;
} }
@ -612,11 +616,11 @@ export default function SearchModal({ modalProps, onSubmit, input, searchType =
<div className={cl("header-text")}> <div className={cl("header-text")}>
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}> <div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
<Heading variant="heading-lg/semibold" <Heading variant="heading-lg/semibold"
style={{ flexGrow: 1 }}>{"Search for " + searchTypeToText(searchType)}</Heading> style={{ flexGrow: 1 }}>{"Search for " + searchTypeToText(searchType)}</Heading>
{subText !== undefined && <Heading variant="heading-sm/normal" {subText !== undefined && <Heading variant="heading-sm/normal"
style={{ color: "var(--header-muted)" }}>{subText}</Heading>} style={{ color: "var(--header-muted)" }}>{subText}</Heading>}
</div> </div>
<ModalCloseButton onClick={modalProps.onClose}/> <ModalCloseButton onClick={modalProps.onClose} />
</div> </div>
<SearchBarWrapper.SearchBar <SearchBarWrapper.SearchBar
size={SearchBarModule.SearchBar.Sizes.MEDIUM} size={SearchBarModule.SearchBar.Sizes.MEDIUM}
@ -639,26 +643,26 @@ export default function SearchModal({ modalProps, onSubmit, input, searchType =
rowData={results} rowData={results}
handleToggleDestination={setSelectedCallback} handleToggleDestination={setSelectedCallback}
/> : <ModalContent className={cl("no-results")}> /> : <ModalContent className={cl("no-results")}>
<div className={cl("no-results-container")}> <div className={cl("no-results-container")}>
<svg <svg
width="48" width="48"
height="48" height="48"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
> >
<path <path
d="M18 6L6 18M6 6L18 18" d="M18 6L6 18M6 6L18 18"
stroke="var(--text-muted)" stroke="var(--text-muted)"
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />
</svg> </svg>
<Text <Text
variant="text-md/normal" variant="text-md/normal"
style={{ color: "var(--text-muted)", marginLeft: "8px" }} style={{ color: "var(--text-muted)", marginLeft: "8px" }}
>No results found</Text> >No results found</Text>
</div> </div>
</ModalContent> </ModalContent>
} }
<ModalFooter> <ModalFooter>
@ -677,7 +681,7 @@ export default function SearchModal({ modalProps, onSubmit, input, searchType =
look={Button.Looks.LINK} look={Button.Looks.LINK}
onClick={modalProps.onClose} onClick={modalProps.onClose}
> >
Cancel Cancel
</Button> </Button>
</ModalFooter> </ModalFooter>