[questions][feat] pagination (#410)

* [questions][feat] pagination

* [questions][feat] update aggregated data

* [questions][feat] add next cursors

* [questions][fix] fix bug

* [questions][chore] fix lint error

* [questions][chore] update cursor to support adapter

* [questions][feat] paginate browse queries

* [questions][ui] change page size to 10

* [question][refactor] clean up router code

* [questions][fix] fix type errors

* [questions][feat] add upvotes tracking

* [questions][chore] add default upovte value

Co-authored-by: Jeff Sieu <jeffsy00@gmail.com>
This commit is contained in:
hpkoh
2022-10-24 22:56:50 +08:00
committed by GitHub
parent bf35f97961
commit 471a28be8a
12 changed files with 596 additions and 230 deletions

View File

@@ -17,29 +17,26 @@ export type FilterChoices<V extends string = string> = ReadonlyArray<
FilterChoice<V>
>;
type FilterSectionType<FilterOptions extends Array<FilterOption>> =
type FilterSectionType<V extends string> =
| {
isSingleSelect: true;
onOptionChange: (optionValue: FilterOptions[number]['value']) => void;
onOptionChange: (option: FilterOption<V>) => void;
}
| {
isSingleSelect?: false;
onOptionChange: (
optionValue: FilterOptions[number]['value'],
checked: boolean,
) => void;
onOptionChange: (option: FilterOption<V>) => void;
};
export type FilterSectionProps<FilterOptions extends Array<FilterOption>> =
FilterSectionType<FilterOptions> & {
export type FilterSectionProps<V extends string = string> =
FilterSectionType<V> & {
label: string;
options: FilterOptions;
options: Array<FilterOption<V>>;
} & (
| {
renderInput: (props: {
field: UseFormRegisterReturn<'search'>;
onOptionChange: FilterSectionType<FilterOptions>['onOptionChange'];
options: FilterOptions;
onOptionChange: FilterSectionType<V>['onOptionChange'];
options: Array<FilterOption<V>>;
}) => React.ReactNode;
showAll?: never;
}
@@ -53,16 +50,14 @@ export type FilterSectionFormData = {
search: string;
};
export default function FilterSection<
FilterOptions extends Array<FilterOption>,
>({
export default function FilterSection<V extends string>({
label,
options,
showAll,
onOptionChange,
isSingleSelect,
renderInput,
}: FilterSectionProps<FilterOptions>) {
}: FilterSectionProps<V>) {
const { register, reset } = useForm<FilterSectionFormData>();
const registerSearch = register('search');
@@ -76,7 +71,9 @@ export default function FilterSection<
};
const autocompleteOptions = useMemo(() => {
return options.filter((option) => !option.checked) as FilterOptions;
return options.filter((option) => !option.checked) as Array<
FilterOption<V>
>;
}, [options]);
const selectedCount = useMemo(() => {
@@ -102,11 +99,12 @@ export default function FilterSection<
<div className="z-10">
{renderInput({
field,
onOptionChange: async (
optionValue: FilterOptions[number]['value'],
) => {
onOptionChange: async (option: FilterOption<V>) => {
reset();
return onOptionChange(optionValue, true);
return onOptionChange({
...option,
checked: true,
});
},
options: autocompleteOptions,
})}
@@ -119,7 +117,13 @@ export default function FilterSection<
label={label}
value={options.find((option) => option.checked)?.value}
onChange={(value) => {
onOptionChange(value);
const changedOption = options.find(
(option) => option.value === value,
)!;
onOptionChange({
...changedOption,
checked: !changedOption.checked,
});
}}>
{options.map((option) => (
<RadioList.Item
@@ -140,7 +144,10 @@ export default function FilterSection<
label={option.label}
value={option.checked}
onChange={(checked) => {
onOptionChange(option.value, checked);
onOptionChange({
...option,
checked,
});
}}
/>
))}

View File

@@ -1,4 +1,6 @@
import type { ComponentProps } from 'react';
import { useState } from 'react';
import { useMemo } from 'react';
import { Button, Typeahead } from '@tih/ui';
import type { RequireAllOrNone } from '~/utils/questions/RequireAllOrNone';
@@ -7,6 +9,8 @@ type TypeaheadProps = ComponentProps<typeof Typeahead>;
type TypeaheadOption = TypeaheadProps['options'][number];
export type ExpandedTypeaheadProps = RequireAllOrNone<{
clearOnSelect?: boolean;
filterOption: (option: TypeaheadOption) => boolean;
onSuggestionClick: (option: TypeaheadOption) => void;
suggestedCount: number;
}> &
@@ -15,9 +19,20 @@ export type ExpandedTypeaheadProps = RequireAllOrNone<{
export default function ExpandedTypeahead({
suggestedCount = 0,
onSuggestionClick,
filterOption = () => true,
clearOnSelect = false,
options,
onSelect,
...typeaheadProps
}: ExpandedTypeaheadProps) {
const suggestions = typeaheadProps.options.slice(0, suggestedCount);
const [key, setKey] = useState(0);
const filteredOptions = useMemo(() => {
return options.filter(filterOption);
}, [options, filterOption]);
const suggestions = useMemo(
() => filteredOptions.slice(0, suggestedCount),
[filteredOptions, suggestedCount],
);
return (
<div className="flex flex-wrap gap-x-2">
@@ -32,7 +47,17 @@ export default function ExpandedTypeahead({
/>
))}
<div className="flex-1">
<Typeahead {...typeaheadProps} />
<Typeahead
key={key}
options={filteredOptions}
{...typeaheadProps}
onSelect={(option) => {
if (clearOnSelect) {
setKey((key + 1) % 2);
}
onSelect(option);
}}
/>
</div>
</div>
);