113 lines
2.8 KiB
TypeScript
113 lines
2.8 KiB
TypeScript
|
import {
|
||
|
Button,
|
||
|
MenuItem,
|
||
|
Checkbox,
|
||
|
ListItemText,
|
||
|
} from "@suid/material";
|
||
|
import {
|
||
|
createMemo,
|
||
|
createSignal,
|
||
|
createUniqueId,
|
||
|
For,
|
||
|
} from "solid-js";
|
||
|
import Menu from "../material/Menu";
|
||
|
import { FilterList, FilterListOff } from "@suid/icons-material";
|
||
|
|
||
|
type Props<Filters extends Record<string, string>> = {
|
||
|
options: Filters;
|
||
|
applied: Record<keyof Filters, boolean | undefined>;
|
||
|
disabledKeys?: (keyof Filters)[];
|
||
|
|
||
|
onApply(value: Record<keyof Filters, boolean | undefined>): void;
|
||
|
};
|
||
|
|
||
|
function TootFilterButton<F extends Record<string, string>>(props: Props<F>) {
|
||
|
const buttonId = createUniqueId();
|
||
|
const [open, setOpen] = createSignal(false);
|
||
|
|
||
|
const getTextForMultipleEntities = (texts: string[]) => {
|
||
|
switch (texts.length) {
|
||
|
case 0:
|
||
|
return "Nothing";
|
||
|
case 1:
|
||
|
return texts[0];
|
||
|
case 2:
|
||
|
return `${texts[0]} and ${texts[1]}`;
|
||
|
case 3:
|
||
|
return `${texts[0]}, ${texts[1]} and ${texts[2]}`;
|
||
|
default:
|
||
|
return `${texts[0]} and ${texts.length - 1} other${texts.length > 2 ? "s" : ""}`;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const optionKeys = () => Object.keys(props.options);
|
||
|
|
||
|
const appliedKeys = createMemo(() => {
|
||
|
const applied = props.applied;
|
||
|
return optionKeys().filter((k) => applied[k]);
|
||
|
});
|
||
|
|
||
|
const text = () => {
|
||
|
const keys = optionKeys();
|
||
|
const napplied = appliedKeys().length;
|
||
|
switch (napplied) {
|
||
|
case keys.length:
|
||
|
return "All";
|
||
|
default:
|
||
|
return getTextForMultipleEntities(
|
||
|
appliedKeys().map((k) => props.options[k]),
|
||
|
);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const toggleKey = (key: keyof F) => {
|
||
|
props.onApply(
|
||
|
Object.assign({}, props.applied, {
|
||
|
[key]: !props.applied[key],
|
||
|
}),
|
||
|
);
|
||
|
};
|
||
|
|
||
|
return (
|
||
|
<>
|
||
|
<Button size="large" onClick={[setOpen, true]} id={buttonId}>
|
||
|
{appliedKeys().length === optionKeys().length ? (
|
||
|
<FilterListOff />
|
||
|
) : (
|
||
|
<FilterList />
|
||
|
)}
|
||
|
|
||
|
<span style={{ "margin-left": "0.5em" }}>{text()}</span>
|
||
|
</Button>
|
||
|
<Menu
|
||
|
open={open()}
|
||
|
onClose={[setOpen, false]}
|
||
|
anchor={() =>
|
||
|
document.getElementById(buttonId)!.getBoundingClientRect()
|
||
|
}
|
||
|
>
|
||
|
<For each={Object.keys(props.options)}>
|
||
|
{(item, idx) => (
|
||
|
<>
|
||
|
<MenuItem
|
||
|
data-sort={idx()}
|
||
|
onClick={[toggleKey, item]}
|
||
|
disabled={props.disabledKeys?.includes(item)}
|
||
|
>
|
||
|
<ListItemText>{props.options[item]}</ListItemText>
|
||
|
<Checkbox
|
||
|
checked={props.applied[item]}
|
||
|
sx={{ marginRight: "-8px" }}
|
||
|
disabled={props.disabledKeys?.includes(item)}
|
||
|
></Checkbox>
|
||
|
</MenuItem>
|
||
|
</>
|
||
|
)}
|
||
|
</For>
|
||
|
</Menu>
|
||
|
</>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
export default TootFilterButton;
|