sendou.ink/app/components/elements/Select.tsx
Kalle b4cc185d1d
Some checks are pending
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run
Scrims (#2211)
* Initial

* Progress

* Initial UI

* Can submit request

* Progress

* Show text if no scrims

* Can cancel request, tabs

* Delete post

* Popover if can't delete

* Request rows

* Progress

* Scrim page initial

* Fix migration order

* Progress

* Progress

* Works again

* Make it compile

* Make it compile again

* Work

* Progress

* Progress

* Progress

* Associations initial

* Association visibility work

* notFoundVisibility form fields initial

* Progress

* Association leave/join + reset invite code

* Progress

* Select test

* Merge branch 'rewrite' into scrims

* Remeda for groupBy

* Select with search

* Outline styling for select

* Select done?

* Fix prop names

* Paginated badges

* Less important

* Select no results

* Handle limiting select width

* UserSearch non-working

* Fix problem from merge

* Remove UserSearch for now

* Remove todo

* Flaggable

* Remove TODOs

* i18n start + styling

* Progress

* i18n done

* Add association e2e test

* E2E tests

* Done?

* Couple leftovers
2025-04-20 22:51:23 +03:00

115 lines
2.7 KiB
TypeScript

import clsx from "clsx";
import type {
ListBoxItemProps,
SelectProps,
ValidationResult,
} from "react-aria-components";
import {
Autocomplete,
Button,
FieldError,
Input,
Label,
ListBox,
ListBoxItem,
ListLayout,
Popover,
SearchField,
Select,
SelectValue,
Text,
Virtualizer,
useFilter,
} from "react-aria-components";
import { useTranslation } from "react-i18next";
import { ChevronUpDownIcon } from "~/components/icons/ChevronUpDown";
import { CrossIcon } from "../icons/Cross";
import { SearchIcon } from "../icons/Search";
import styles from "./Select.module.css";
interface SendouSelectProps<T extends object>
extends Omit<SelectProps<T>, "children"> {
label?: string;
description?: string;
errorMessage?: string | ((validation: ValidationResult) => string);
items?: Iterable<T>;
children: React.ReactNode | ((item: T) => React.ReactNode);
search?: {
placeholder?: string;
};
}
export function SendouSelect<T extends object>({
label,
description,
errorMessage,
children,
items,
search,
...props
}: SendouSelectProps<T>) {
const { t } = useTranslation(["common"]);
const { contains } = useFilter({ sensitivity: "base" });
return (
<Select {...props}>
{label ? <Label>{label}</Label> : null}
<Button className={styles.button}>
<SelectValue className={styles.selectValue} />
<span aria-hidden="true">
<ChevronUpDownIcon className={styles.icon} />
</span>
</Button>
{description && <Text slot="description">{description}</Text>}
<FieldError>{errorMessage}</FieldError>
<Popover className={styles.popover}>
<Autocomplete filter={contains}>
{search ? (
<SearchField
aria-label="Search"
autoFocus
className={styles.searchField}
>
<SearchIcon aria-hidden className={styles.smallIcon} />
<Input
placeholder={search.placeholder}
className={clsx("plain", styles.searchInput)}
/>
<Button className={styles.searchClearButton}>
<CrossIcon className={styles.smallIcon} />
</Button>
</SearchField>
) : null}
<Virtualizer layout={ListLayout} layoutOptions={{ rowHeight: 33 }}>
<ListBox
items={items}
className={styles.listBox}
renderEmptyState={() => (
<div className={styles.noResults}>{t("common:noResults")}</div>
)}
>
{children}
</ListBox>
</Virtualizer>
</Autocomplete>
</Popover>
</Select>
);
}
interface SendouSelectItemProps extends ListBoxItemProps {}
export function SendouSelectItem(props: SendouSelectItemProps) {
return (
<ListBoxItem
{...props}
className={({ isFocused, isSelected }) =>
clsx(styles.item, {
[styles.itemFocused]: isFocused,
[styles.itemSelected]: isSelected,
})
}
/>
);
}