Minor Search updates

This commit is contained in:
Sean Edmunds
2025-09-05 15:02:57 -05:00
parent 2e05002c63
commit fed970834b
4 changed files with 296 additions and 255 deletions

View File

@@ -8,7 +8,7 @@ import {
Text, Text,
} from '@mantine/core'; } from '@mantine/core';
import { useDisclosure, useElementSize } from '@mantine/hooks'; import { useDisclosure, useElementSize } from '@mantine/hooks';
import { AppItem, HelpModal, WeatherModal } from 'components'; import { HelpModal, WeatherModal } from 'components';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import * as fa6 from 'react-icons/fa6'; import * as fa6 from 'react-icons/fa6';
@@ -23,7 +23,9 @@ import type {
Provider, Provider,
SearchHistoryItem, SearchHistoryItem,
} from 'types'; } from 'types';
import SearchSelectItem from './SearchSelectItem'; import RecommendedApplications from './components/RecommendedApplications';
import RecommendedBookmarks from './components/RecommendedBookmarks';
import SearchSelectItem from './components/SearchSelectItem';
export default function Search({ export default function Search({
current, current,
@@ -90,12 +92,12 @@ export default function Search({
} = useCommonHooks(); } = useCommonHooks();
const [placeholderValue, setPlaceholderValue] = useState<string>(''); const [placeholderValue, setPlaceholderValue] = useState<string>('');
const [value, setValue] = useState(0); const [value, setValue] = useState(0);
const [historyMult, setHistoryMult] = useState(1); const [historyMultiplier, setHistoryMultiplier] = useState(1);
const handleResetSearch = () => { const handleResetSearch = () => {
setSearchValue(''); setSearchValue('');
setListIndex(-1); setListIndex(-1);
setHistoryMult(1); setHistoryMultiplier(1);
setProviderLabel(defaultProviderLabel); setProviderLabel(defaultProviderLabel);
setProviderURL(defaultProviderURL); setProviderURL(defaultProviderURL);
}; };
@@ -310,10 +312,10 @@ export default function Search({
.some((word: string) => word.includes(searchWord)), .some((word: string) => word.includes(searchWord)),
); );
}) })
.slice(0, 10 * historyMult), .slice(0, 10 * historyMultiplier),
]; ];
if (historyOptions.length >= 10 * historyMult) { if (historyOptions.length >= 10 * historyMultiplier) {
return [ return [
...historyOptions, ...historyOptions,
{ {
@@ -330,6 +332,72 @@ export default function Search({
return historyOptions; return historyOptions;
}; };
const renderItem: any = (item: any) => {
const Icon = allIcons[item.option.icon];
return !searchValue.startsWith('!') ? (
item.option.value === 'load' ? (
<Group
pos={'relative'}
m={0}
p={0}
gap={0}
w={'100%'}
h={'2rem'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setHistoryMultiplier((prev: number) => prev + 1);
}}
>
<Text
w={'100%'}
c={'var(--mantine-color-accent-0)'}
style={{ textAlign: 'center' }}
>
{item.option.label}
</Text>
</Group>
) : (
<SearchSelectItem
item={item}
handleSearchHistoryItem={handleSearchHistoryItem}
searchEndpoint={searchEndpoint}
setSearchValue={setSearchValue}
/>
)
) : (
<Group
pos={'relative'}
m={0}
p={0}
gap={0}
justify='center'
w={'100%'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleAppLaunchItem(item);
}}
>
<Text pos={'absolute'} left={'1rem'} c={'var(--mantine-color-accent-0)'}>
Launch
</Text>
{Icon && (
<Icon
style={{
color: 'var(--mantine-color-primary-0)',
height: '2rem',
width: '2rem',
}}
/>
)}
<Text pos={'absolute'} right={'1rem'} c={'var(--mantine-color-accent-0)'}>
{item.option.name}
</Text>
</Group>
);
};
useEffect(() => { useEffect(() => {
if (providerData && providerDataIsFetched) { if (providerData && providerDataIsFetched) {
setProviders(providerData); setProviders(providerData);
@@ -368,7 +436,7 @@ export default function Search({
if (historyData && historyDataIsFetched) { if (historyData && historyDataIsFetched) {
setSearchHistory(historyData); setSearchHistory(historyData);
if (filteredHistory.length === 0) { if (filteredHistory.length === 0) {
setFilteredHistory(historyData.slice(0, 10 * historyMult)); setFilteredHistory(historyData.slice(0, 10 * historyMultiplier));
} }
} }
}, [historyData, historyDataIsFetched]); }, [historyData, historyDataIsFetched]);
@@ -417,10 +485,10 @@ export default function Search({
padding: '.3rem', padding: '.3rem',
display: position === 'center' ? 'inline-flex' : 'inline-block', display: position === 'center' ? 'inline-flex' : 'inline-block',
transition: 'margin-top .5s ease', transition: 'margin-top .5s ease',
width: '100%',
}} }}
align='center' align='center'
justify='center' justify='center'
w={'100%'}
> >
<Select <Select
classNames={selectClasses} classNames={selectClasses}
@@ -461,71 +529,7 @@ export default function Search({
style: { color: 'var(--mantine-color-accent-0)', marginRight: '.5rem' }, style: { color: 'var(--mantine-color-accent-0)', marginRight: '.5rem' },
}} }}
scrollAreaProps={{ viewportRef: ref }} scrollAreaProps={{ viewportRef: ref }}
renderOption={(item: any) => { renderOption={(item: any) => renderItem(item)}
const Icon = allIcons[item.option.icon];
return !searchValue.startsWith('!') ? (
item.option.value === 'load' ? (
<Group
pos={'relative'}
m={0}
p={0}
gap={0}
w={'100%'}
h={'2rem'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setHistoryMult((prev: number) => prev + 1);
}}
>
<Text
w={'100%'}
c={'var(--mantine-color-accent-0)'}
style={{ textAlign: 'center' }}
>
{item.option.label}
</Text>
</Group>
) : (
<SearchSelectItem
item={item}
handleSearchHistoryItem={handleSearchHistoryItem}
searchEndpoint={searchEndpoint}
setSearchValue={setSearchValue}
/>
)
) : (
<Group
pos={'relative'}
m={0}
p={0}
gap={0}
justify='center'
w={'100%'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleAppLaunchItem(item);
}}
>
<Text pos={'absolute'} left={'1rem'} c={'var(--mantine-color-accent-0)'}>
Launch
</Text>
{Icon && (
<Icon
style={{
color: 'var(--mantine-color-primary-0)',
height: '2rem',
width: '2rem',
}}
/>
)}
<Text pos={'absolute'} right={'1rem'} c={'var(--mantine-color-accent-0)'}>
{item.option.name}
</Text>
</Group>
);
}}
onBlur={() => { onBlur={() => {
setPlaceholderValue(''); setPlaceholderValue('');
setListIndex(-1); setListIndex(-1);
@@ -546,190 +550,68 @@ export default function Search({
onInput={onInput} onInput={onInput}
onKeyUp={handleKeyUp} onKeyUp={handleKeyUp}
/> />
{dropdownOpened && searchValue !== '' && ( {dropdownOpened && (
<Group w={'100%'} justify='center'> <Card
<Card w={'100%'}
w={'100%'} mt={height > 0 ? `calc(${height}px + 1.5rem)` : '3.75rem'}
mt={height > 0 ? `calc(${height}px + 1.5rem)` : '3.75rem'} pos={'relative'}
pos={'relative'} style={{
style={{ zIndex: 1000,
zIndex: 1000, opacity:
opacity: searchValue &&
searchValue && dropdownOpened &&
dropdownOpened && (bookmarks.length > 0 || filteredApplications.length > 0)
(bookmarks.length > 0 || filteredApplications.length > 0) ? 1
? 1 : 0,
: 0, background: 'transparent',
background: 'transparent', padding: '0px',
padding: '0px', transition: 'margin-top .5s ease-in-out, opacity .5s ease-in-out .5s',
transition: 'margin-top .5s ease-in-out, opacity .5s ease-in-out .5s', }}
}} >
> <Stack gap={8} w={'100%'}>
<Stack gap={8} w={'100%'}> <Text
<Group w={'100%'}
w={'100%'} p={'1rem'}
p={'1rem'} c={'var(--mantine-color-primary-0)'}
style={{ fz={'1.5rem'}
backgroundColor: 'var(--mantine-color-search-0)', style={{
backdropFilter: 'blur(15px) brightness(1.75)', backgroundColor: 'var(--mantine-color-search-0)',
borderRadius: '4px', backdropFilter: 'blur(15px) brightness(1.75)',
}} borderRadius: '4px',
> }}
<Text c={'var(--mantine-color-primary-0)'} fz={'1.5rem'}> >
Recommended items: Recommended items:
</Text> </Text>
</Group> <Group align='start' justify='space-between'>
<Group align='start' justify='space-between'> <RecommendedBookmarks
<Group bookmarks={bookmarks}
w={'49%'} searchValue={searchValue}
p={'1rem'} dropdownOpened={dropdownOpened}
justify='center' />
style={{ <RecommendedApplications
backgroundColor: 'var(--mantine-color-search-0)', searchValue={searchValue}
backdropFilter: 'blur(15px) brightness(1.75)', dropdownOpened={dropdownOpened}
borderRadius: '4px', filteredApplications={filteredApplications}
opacity: />
searchValue && dropdownOpened && bookmarks.length > 0 ? 1 : 0, </Group>
}} </Stack>
> </Card>
<Stack w={'100%'} mah={'636px'}>
<Text
c={'var(--mantine-color-primary-0)'}
fz={'1.25rem'}
style={{ fontWeight: 'bold', textDecoration: 'underline' }}
>
Bookmarks
</Text>
<Group
w={'100%'}
gap={8}
justify='space-between'
style={{ overflow: 'hidden', whiteSpace: 'nowrap' }}
>
<Stack
w={bookmarks.length <= 5 ? '98%' : '48%'}
style={{ transition: 'all .4s ease-in-out' }}
>
{bookmarks
.map((bookmark: Bookmark) => (
<Text
key={bookmark.name}
component='a'
href={bookmark.url}
c={'var(--mantine-color-primary-0)'}
style={{
backgroundColor: 'var(--mantine-color-search-0)',
padding: '.5rem',
borderRadius: '10px',
overflow: 'ellipsis',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}
>
{bookmark.name}
</Text>
))
.toSorted((a: any, b: any) => (a.key > b.key ? 1 : -1))
.slice(0, 5)}
</Stack>
<Stack
w={bookmarks.length <= 5 ? '0%' : '48%'}
style={{ transition: 'all .4s ease-in-out' }}
>
{bookmarks.length > 5 &&
bookmarks
.map((bookmark: Bookmark) => (
<Text
key={bookmark.name}
component='a'
href={bookmark.url}
c={'var(--mantine-color-primary-0)'}
style={{
backgroundColor: 'var(--mantine-color-search-0)',
padding: '.5rem',
borderRadius: '10px',
overflow: 'ellipsis',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}
>
{bookmark.name}
</Text>
))
.toSorted((a: any, b: any) => (a.key > b.key ? 1 : -1))
.slice(5, 10)}
</Stack>
</Group>
</Stack>
</Group>
<Group
w={'49%'}
p={'1rem'}
justify='center'
style={{
backgroundColor: 'var(--mantine-color-search-0)',
backdropFilter: 'blur(15px) brightness(1.75)',
borderRadius: '4px',
opacity:
searchValue && dropdownOpened && filteredApplications.length > 0
? 1
: 0,
}}
>
<Stack w={'100%'} mah={'636px'}>
<Text
c={'var(--mantine-color-primary-0)'}
fz={'1.25rem'}
style={{ fontWeight: 'bold', textDecoration: 'underline' }}
>
Applications
</Text>
<Group
w={'100%'}
gap={8}
justify='space-between'
style={{ overflow: 'hidden', whiteSpace: 'nowrap' }}
>
<Stack w={'98%'} style={{ transition: 'all .4s ease-in-out' }}>
{filteredApplications
.map((application: Application) => (
<AppItem
key={application.name}
url={application.url}
name={application.name}
icon={application.icon}
enabled={application.enabled}
scale={1}
linksActive={true}
/>
))
.toSorted((a: any, b: any) => (a.key > b.key ? 1 : -1))
.slice(0, 5)}
</Stack>
</Group>
</Stack>
</Group>
</Group>
</Stack>
</Card>
</Group>
)} )}
<HelpModal
handleResetSearch={handleResetSearch}
applicationData={applications}
providerData={providers}
providerModalOpened={providerModalOpened}
closeProviderModal={closeProviderModal}
/>
<WeatherModal
current={current}
extendedForecast={extendedForecast}
weatherModalOpened={weatherModalOpened}
handleResetSearch={handleResetSearch}
closeWeatherModal={closeWeatherModal}
/>
</Stack> </Stack>
<HelpModal
handleResetSearch={handleResetSearch}
applicationData={applications}
providerData={providers}
providerModalOpened={providerModalOpened}
closeProviderModal={closeProviderModal}
/>
<WeatherModal
current={current}
extendedForecast={extendedForecast}
weatherModalOpened={weatherModalOpened}
handleResetSearch={handleResetSearch}
closeWeatherModal={closeWeatherModal}
/>
</> </>
); );
} }

View File

@@ -0,0 +1,61 @@
import { Group, Stack, Text } from '@mantine/core';
import { AppItem } from 'components';
import type { Application } from 'types';
export default function RecommendedApplications({
searchValue,
dropdownOpened,
filteredApplications,
}: {
searchValue: string;
dropdownOpened: boolean;
filteredApplications: Application[];
}) {
return (
<Group
w={'49%'}
p={'1rem'}
justify='center'
style={{
backgroundColor: 'var(--mantine-color-search-0)',
backdropFilter: 'blur(15px) brightness(1.75)',
borderRadius: '4px',
opacity: searchValue && dropdownOpened && filteredApplications.length > 0 ? 1 : 0,
}}
>
<Stack w={'100%'} mah={'636px'}>
<Text
c={'var(--mantine-color-primary-0)'}
fz={'1.25rem'}
style={{ fontWeight: 'bold', textDecoration: 'underline' }}
>
Applications
</Text>
<Group
w={'100%'}
gap={8}
justify='space-between'
style={{ overflow: 'hidden', whiteSpace: 'nowrap' }}
>
<Stack w={'98%'} style={{ transition: 'all .4s ease-in-out' }}>
{filteredApplications
.map((application: Application) => (
<AppItem
key={application.name}
url={application.url}
name={application.name}
icon={application.icon}
enabled={application.enabled}
scale={1}
linksActive={true}
/>
))
.toSorted((a: any, b: any) => (a.key > b.key ? 1 : -1))
.slice(0, 5)}
</Stack>
</Group>
</Stack>
</Group>
);
}

View File

@@ -0,0 +1,98 @@
import { Group, Stack, Text } from '@mantine/core';
import type { Bookmark } from 'types';
export default function RecommendedBookmarks({
bookmarks,
searchValue,
dropdownOpened,
}: {
bookmarks: Bookmark[];
searchValue: string;
dropdownOpened: boolean;
}) {
return (
<Group
w={'49%'}
p={'1rem'}
justify='center'
style={{
backgroundColor: 'var(--mantine-color-search-0)',
backdropFilter: 'blur(15px) brightness(1.75)',
borderRadius: '4px',
opacity: searchValue && dropdownOpened && bookmarks.length > 0 ? 1 : 0,
}}
>
<Stack w={'100%'} mah={'636px'}>
<Text
c={'var(--mantine-color-primary-0)'}
fz={'1.25rem'}
style={{ fontWeight: 'bold', textDecoration: 'underline' }}
>
Bookmarks
</Text>
<Group
w={'100%'}
h={'100%'}
gap={8}
justify='space-between'
align='start'
style={{ overflow: 'hidden', whiteSpace: 'nowrap' }}
>
<Stack
w={bookmarks.length <= 5 ? '98%' : '48%'}
style={{ transition: 'all .4s ease-in-out' }}
>
{bookmarks
.map((bookmark: Bookmark) => (
<Text
key={bookmark.name}
component='a'
href={bookmark.url}
c={'var(--mantine-color-primary-0)'}
style={{
backgroundColor: 'var(--mantine-color-search-0)',
padding: '.5rem',
borderRadius: '10px',
overflow: 'ellipsis',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}
>
{bookmark.name}
</Text>
))
.toSorted((a: any, b: any) => (a.key > b.key ? 1 : -1))
.slice(0, 5)}
</Stack>
<Stack
w={bookmarks.length <= 5 ? '0%' : '48%'}
style={{ transition: 'all .4s ease-in-out' }}
>
{bookmarks.length > 5 &&
bookmarks
.map((bookmark: Bookmark) => (
<Text
key={bookmark.name}
component='a'
href={bookmark.url}
c={'var(--mantine-color-primary-0)'}
style={{
backgroundColor: 'var(--mantine-color-search-0)',
padding: '.5rem',
borderRadius: '10px',
overflow: 'ellipsis',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}
>
{bookmark.name}
</Text>
))
.toSorted((a: any, b: any) => (a.key > b.key ? 1 : -1))
.slice(5, 10)}
</Stack>
</Group>
</Stack>
</Group>
);
}