search providers & more

This commit is contained in:
Georgi Gardev
2023-11-18 17:13:59 +02:00
parent 7819a5e807
commit 18756b2a60
11 changed files with 116 additions and 36 deletions
+11
View File
@@ -0,0 +1,11 @@
import { createResource } from 'solid-js';
import { SearchProvider } from '~/types';
async function getSearchProviders(): Promise<SearchProvider[]> {
return await import('../../data/search-providers.json').then((m) => m.default);
}
export function createSearchProviders() {
const [searchProviders] = createResource<SearchProvider[]>(getSearchProviders);
return searchProviders;
}
+8 -3
View File
@@ -7,14 +7,19 @@
.Header { .Header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; gap: max(1vw, 1rem);
margin-bottom: 1rem; margin-bottom: 1rem;
padding-bottom: 8px; padding-bottom: 8px;
font-size: 2rem; font-size: max(1.8vw, 1.3rem);
border-bottom: 1px solid var(--color-text);
text-shadow: 1px 1px 5px #000; text-shadow: 1px 1px 5px #000;
} }
.Close {
font-size: 2rem;
cursor: pointer;
color: #ccc;
}
.AppList { .AppList {
display: flex; display: flex;
gap: .5rem; gap: .5rem;
+2 -2
View File
@@ -21,10 +21,10 @@ export function AppList({
return ( return (
<div class={style.AppListWrap}> <div class={style.AppListWrap}>
{tag?.() ? ( {tag?.() ? (
<div> <div style={{ width: '100%' }}>
<h2 class={style.Header}> <h2 class={style.Header}>
{tag()} {tag()}
<span style={{ cursor: 'pointer' }} onClick={resetTag}> <span class={style.Close} onClick={resetTag}>
× ×
</span> </span>
</h2> </h2>
+4 -4
View File
@@ -12,15 +12,15 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
text-align: right; text-align: right;
gap: max(1vw, .5rem); gap: clamp(.5rem, 1vw, .5rem);
font-size: max(1.8vw, 1.3rem); font-size: clamp(1.3rem, 1.8vw, 3rem);
} }
.Menu li { .Menu li {
position: relative; position: relative;
cursor: pointer; cursor: pointer;
text-shadow: 1px 1px 5px #000; text-shadow: 1px 1px 5px #000;
padding: max(1vw, .5rem) 1.5rem; padding: clamp(.5rem, 1vw, 1.5rem) 1.5rem;
} }
.Menu li::after { .Menu li::after {
@@ -37,6 +37,6 @@
transition: height 0.2s ease; transition: height 0.2s ease;
} }
.Menu li:hover::after { .Menu li:hover::after, .Menu li.Active::after {
height: 100%; height: 100%;
} }
+5 -3
View File
@@ -2,7 +2,8 @@ import { createTagOrder } from '~/api/get-apps';
import style from './Sidebar.module.css'; import style from './Sidebar.module.css';
type SidebarProps = { type SidebarProps = {
onSelectTag?(tag: string): void; activeTag?: string | null;
onSelectTag?(tag: string, mode: 'permanent' | 'temporary'): void;
}; };
export function Sidebar(props: SidebarProps) { export function Sidebar(props: SidebarProps) {
@@ -13,8 +14,9 @@ export function Sidebar(props: SidebarProps) {
<ul class={style.Menu}> <ul class={style.Menu}>
{tagOrder()?.map((tag) => ( {tagOrder()?.map((tag) => (
<li <li
onClick={() => props.onSelectTag?.(tag)} class={props.activeTag === tag ? style.Active : ''}
onMouseEnter={() => props.onSelectTag?.(tag)} onClick={() => props.onSelectTag?.(tag, 'permanent')}
onMouseEnter={() => props.onSelectTag?.(tag, 'temporary')}
> >
{tag} {tag}
</li> </li>
+19 -1
View File
@@ -1,6 +1,24 @@
.SearchWrapper {
display: flex;
align-items: center;
gap: 1px;
width: clamp(300px, 40vw, 600px);
}
.Provider {
text-transform: uppercase;
backdrop-filter: blur(10px);
padding: 11px 20px;
width: 100px;
text-align: center;
background-color: rgba(0, 0, 0, 0.2);
border-bottom: 2px solid rgba(0, 0, 0, 0.2);
font-family: Nova Mono;
}
.Search { .Search {
flex-grow: 1;
padding: 10px 20px; padding: 10px 20px;
width: max(40vw, 300px);
border: 0; border: 0;
font-size: 1rem; font-size: 1rem;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
+51 -16
View File
@@ -1,24 +1,59 @@
import { createSignal } from 'solid-js'; import { createComputed, createMemo, createSignal } from 'solid-js';
import { createSearchProviders } from '~/api/get-search-providers';
import style from './Search.module.css'; import style from './Search.module.css';
import { SearchProvider } from '~/types';
export function Search() { export function Search() {
const [term, setTerm] = createSignal(''); const [term, setTerm] = createSignal('');
const searchProviders = createSearchProviders();
const defaultProvider = createMemo(
() => searchProviders()?.find((provider) => provider.default) ?? searchProviders()?.[0]
);
const searchPerPrefix = createMemo(() =>
searchProviders()?.reduce<Record<string, SearchProvider>>((acc, provider) => {
acc[provider.prefix] = provider;
return acc;
}, {})
);
const activeProvider = createMemo(() => {
if (term().startsWith('!') && searchPerPrefix()?.[term().charAt(1)]) {
return searchPerPrefix()?.[term().charAt(1)];
} else {
return defaultProvider();
}
});
const searchTerm = createMemo(() => {
if (term().startsWith('!') && searchPerPrefix()?.[term().charAt(1)]) {
return term().slice(2);
} else {
return term();
}
});
return ( return (
<input <div class={style.SearchWrapper}>
id="search" <div class={style.Provider}>{activeProvider()?.name}</div>
class={style.Search} <input
type="text" id="search"
placeholder="Search kagi..." class={style.Search}
value={term()} type="text"
onInput={(e) => setTerm(e.target.value)} placeholder={`Lorem ipsum`}
onKeyPress={(e) => { value={term()}
e.stopPropagation(); onInput={(e) => setTerm(e.target.value)}
if (e.key === 'Enter') { onKeyPress={(e) => {
window.open(`https://kagi.com/search?q=${term()}`, '_blank', 'noreferrer'); e.stopPropagation();
setTerm(''); if (e.key === 'Enter') {
} window.open(`${activeProvider()?.url}${searchTerm()}`, '_blank', 'noreferrer');
}} setTerm('');
/> }
}}
/>
</div>
); );
} }
+2 -2
View File
@@ -1,5 +1,5 @@
import { format } from 'date-fns'; import { format } from 'date-fns';
export const getDate = () => format(new Date(), 'EEEE, dd MMMM yyyy'); export const getDate = () => format(new Date(), 'EEEE, dd MMM yyyy');
export const getTime = () => format(new Date(), 'HH:mm:ss'); export const getTime = (time: Date | number = new Date()) => format(time, 'HH:mm:ss');
+1 -1
View File
@@ -10,7 +10,7 @@
gap: 2rem; gap: 2rem;
overflow: scroll; overflow: scroll;
padding: 0 max(10vw, 2rem); padding: 0 clamp(1rem, 10vw, 10rem);
} }
.Widgets { .Widgets {
+6 -4
View File
@@ -21,11 +21,13 @@ export default function Home() {
const [activeTag, setActiveTag] = createSignal<string | null>(null); const [activeTag, setActiveTag] = createSignal<string | null>(null);
const [timeout, storeTimeout] = createSignal<NodeJS.Timeout | null>(null); const [timeout, storeTimeout] = createSignal<NodeJS.Timeout | null>(null);
const switchToTag = (tag: string) => { const switchToTag = (tag: string, mode: 'permanent' | 'temporary') => {
setActiveTag(tag); setActiveTag(tag);
clearTimeout(timeout() as NodeJS.Timeout); clearTimeout(timeout() as NodeJS.Timeout);
storeTimeout(setTimeout(() => setActiveTag(null), 3000)); if (mode === 'temporary') {
storeTimeout(setTimeout(() => setActiveTag(null), 3000));
}
}; };
const resetTag = () => { const resetTag = () => {
@@ -59,15 +61,15 @@ export default function Home() {
return ( return (
<main class={style.SidebarPage}> <main class={style.SidebarPage}>
<Sidebar onSelectTag={switchToTag} /> <Sidebar onSelectTag={switchToTag} activeTag={activeTag()} />
<div class={style.ContentWrapper}> <div class={style.ContentWrapper}>
<div class={style.Widgets}> <div class={style.Widgets}>
{activeTag() ? ( {activeTag() ? (
<AppList apps={apps() ?? []} tag={activeTag} resetTag={resetTag} /> <AppList apps={apps() ?? []} tag={activeTag} resetTag={resetTag} />
) : ( ) : (
<> <>
<Search />
<DateView /> <DateView />
<Search />
<Weather /> <Weather />
</> </>
)} )}
+7
View File
@@ -7,6 +7,13 @@ export type AppDefinition = {
shortcut?: string; shortcut?: string;
}; };
export type SearchProvider = {
name: string;
prefix: string;
url: string;
default?: boolean;
};
export type WeatherData = { export type WeatherData = {
id: string; id: string;
main: string; main: string;