Feature/add localization #21

Merged
chris-kruining merged 6 commits from feature/add-localization into main 2025-01-06 14:54:11 +00:00
2 changed files with 143 additions and 0 deletions
Showing only changes of commit 490cf0c677 - Show all commits

View file

@ -0,0 +1,76 @@
.box {
display: contents;
inline-size: max-content;
&:has(> :popover-open) > .button {
background-color: var(--surface-500);
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
.button {
display: grid;
grid-template-columns: inherit;
place-items: center start;
inline-size: max-content;
padding: var(--padding-m);
background-color: transparent;
border: none;
border-radius: var(--radii-m);
font-size: 1rem;
cursor: pointer;
}
.dialog {
display: none;
grid-template-columns: inherit;
inset-inline-start: anchor(start);
inset-block-start: anchor(end);
position-try-fallbacks: flip-inline;
inline-size: anchor-size(self-inline);
background-color: var(--surface-500);
padding: var(--padding-m);
border: none;
box-shadow: var(--shadow-2);
&:popover-open {
display: grid;
}
& > header {
display: grid;
grid-column: 1 / -1;
gap: var(--padding-s);
}
& > main {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
row-gap: var(--padding-s);
}
}
.option {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
place-items: center start;
border-radius: var(--radii-m);
padding: var(--padding-s);
margin-inline: calc(-1 * var(--padding-s));
cursor: pointer;
&.selected {
background-color: oklch(from var(--info) l c h / .1);
}
}

View file

@ -0,0 +1,67 @@
import { createMemo, createSignal, For, JSX, Setter, createEffect, Show } from "solid-js";
import css from './index.module.css';
interface ComboBoxProps<T, K extends string> {
id: string;
class?: string;
value: K;
setValue?: Setter<K>;
values: Record<K, T>;
open?: boolean;
children: (key: K, value: T) => JSX.Element;
filter?: (query: string, key: K, value: T) => boolean;
}
export function ComboBox<T, K extends string>(props: ComboBoxProps<T, K>) {
const [dialog, setDialog] = createSignal<HTMLDialogElement>();
const [value, setValue] = createSignal<K>(props.value);
const [open, setOpen] = createSignal<boolean>(props.open ?? false);
const [query, setQuery] = createSignal<string>('');
const values = createMemo(() => {
let entries = Object.entries<T>(props.values) as [K, T][];
const filter = props.filter;
const q = query();
if (filter) {
entries = entries.filter(([k, v]) => filter(q, k, v));
}
return entries;
});
createEffect(() => {
props.setValue?.(() => value());
});
createEffect(() => {
dialog()?.[open() ? 'showPopover' : 'hidePopover']();
});
return <section class={`${css.box} ${props.class}`}>
<button id={`${props.id}_button`} popoverTarget={`${props.id}_dialog`} class={css.button}>
{props.children(value(), props.values[value()])}
</button>
<dialog ref={setDialog} id={`${props.id}_dialog`} anchor={`${props.id}_button`} popover class={css.dialog} onToggle={e => setOpen(e.newState === 'open')}>
<Show when={props.filter !== undefined}>
<header>
<input value={query()} onInput={e => setQuery(e.target.value)} />
</header>
</Show>
<main>
<For each={values()}>{
([k, v]) => {
const selected = createMemo(() => value() === k);
return <span class={`${css.option} ${selected() ? css.selected : ''}`} onpointerdown={() => {
setValue(() => k);
dialog()?.hidePopover();
}}>{props.children(k, v)}</span>;
}
}</For>
</main>
</dialog>
</section>;
}