mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2026-04-03 02:48:43 +08:00
[questions][ui] improve browse sort ui
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { TextInput } from '@tih/ui';
|
||||
|
||||
import { useProtectedCallback } from '~/utils/questions/useProtectedCallback';
|
||||
|
||||
@@ -27,22 +26,13 @@ export default function ContributeQuestionCard({
|
||||
return (
|
||||
<div className="w-full">
|
||||
<button
|
||||
className="flex w-full flex-1 justify-between gap-2 rounded-md border border-slate-300 bg-white p-4 text-left hover:bg-slate-100"
|
||||
className="flex w-full flex-1 justify-between gap-2 rounded-md border border-slate-300 bg-white p-4 text-left transition hover:bg-slate-100"
|
||||
type="button"
|
||||
onClick={handleOpenContribute}>
|
||||
<div className="w-full">
|
||||
<TextInput
|
||||
disabled={true}
|
||||
isLabelHidden={true}
|
||||
label="Question"
|
||||
placeholder="Contribute a question"
|
||||
onChange={handleOpenContribute}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-end justify-start gap-2">
|
||||
<h1 className="bg-primary-600 hover:bg-primary-700 rounded-full px-3 py-2 text-white">
|
||||
Contribute
|
||||
</h1>
|
||||
<div className="w-full rounded-md border border-slate-300 bg-slate-100 py-2 px-4 text-slate-400">
|
||||
<p className="font-semibold">
|
||||
Just completed your interview? Contribute your questions
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
<ContributeQuestionDialog
|
||||
|
||||
@@ -2,10 +2,13 @@ import {
|
||||
AdjustmentsHorizontalIcon,
|
||||
MagnifyingGlassIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { Button, TextInput } from '@tih/ui';
|
||||
import { Button, Tabs, TextInput } from '@tih/ui';
|
||||
|
||||
import { SORT_ORDERS } from '~/utils/questions/constants';
|
||||
|
||||
import type { SortOptionsSelectProps } from './SortOptionsSelect';
|
||||
import SortOptionsSelect from './SortOptionsSelect';
|
||||
|
||||
import { SortOrder, SortType } from '~/types/questions.d';
|
||||
|
||||
export type QuestionSearchBarProps = SortOptionsSelectProps & {
|
||||
onFilterOptionsToggle: () => void;
|
||||
@@ -13,6 +16,22 @@ export type QuestionSearchBarProps = SortOptionsSelectProps & {
|
||||
query: string;
|
||||
};
|
||||
|
||||
function getSortOrderLabel(sortOrder: SortOrder, sortType: SortType): string {
|
||||
switch (sortType) {
|
||||
case SortType.NEW:
|
||||
return sortOrder === SortOrder.ASC ? 'Oldest first' : 'Newest first';
|
||||
case SortType.TOP:
|
||||
return sortOrder === SortOrder.ASC
|
||||
? 'Least upvotes first'
|
||||
: 'Most upvotes first';
|
||||
case SortType.ENCOUNTERS:
|
||||
return sortOrder === SortOrder.ASC
|
||||
? 'Least received first'
|
||||
: 'Most received first';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export default function QuestionSearchBar({
|
||||
onFilterOptionsToggle,
|
||||
onQueryChange,
|
||||
@@ -20,43 +39,74 @@ export default function QuestionSearchBar({
|
||||
...sortOptionsSelectProps
|
||||
}: QuestionSearchBarProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-stretch gap-x-4 gap-y-4 lg:flex-row lg:items-end">
|
||||
<div className="flex flex-1 gap-2">
|
||||
<div className="flex-1">
|
||||
<TextInput
|
||||
isLabelHidden={true}
|
||||
label="Search by content"
|
||||
placeholder="Search by content"
|
||||
startAddOn={MagnifyingGlassIcon}
|
||||
startAddOnType="icon"
|
||||
value={query}
|
||||
onChange={(value) => {
|
||||
onQueryChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="sm:hidden">
|
||||
<Button
|
||||
addonPosition="start"
|
||||
icon={AdjustmentsHorizontalIcon}
|
||||
isLabelHidden={true}
|
||||
label="Filters"
|
||||
variant="tertiary"
|
||||
onClick={onFilterOptionsToggle}
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden sm:block lg:hidden">
|
||||
<Button
|
||||
addonPosition="start"
|
||||
icon={AdjustmentsHorizontalIcon}
|
||||
label="Filters"
|
||||
variant="tertiary"
|
||||
onClick={onFilterOptionsToggle}
|
||||
/>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-stretch gap-x-2 gap-y-4 lg:flex-row lg:items-end">
|
||||
<div className="flex flex-1 gap-2">
|
||||
<div className="flex-1">
|
||||
<TextInput
|
||||
isLabelHidden={true}
|
||||
label="Search by content"
|
||||
placeholder="Search by content"
|
||||
startAddOn={MagnifyingGlassIcon}
|
||||
startAddOnType="icon"
|
||||
value={query}
|
||||
onChange={(value) => {
|
||||
onQueryChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="sm:hidden">
|
||||
<Button
|
||||
addonPosition="start"
|
||||
icon={AdjustmentsHorizontalIcon}
|
||||
isLabelHidden={true}
|
||||
label="Filters"
|
||||
variant="tertiary"
|
||||
onClick={onFilterOptionsToggle}
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden sm:block lg:hidden">
|
||||
<Button
|
||||
addonPosition="start"
|
||||
icon={AdjustmentsHorizontalIcon}
|
||||
label="Filters"
|
||||
variant="tertiary"
|
||||
onClick={onFilterOptionsToggle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <SortOptionsSelect {...sortOptionsSelectProps} /> */}
|
||||
</div>
|
||||
<div className="flex items-end justify-end gap-4">
|
||||
<SortOptionsSelect {...sortOptionsSelectProps} />
|
||||
<div className="flex justify-start gap-4">
|
||||
<div>
|
||||
<Tabs
|
||||
label="Sort by"
|
||||
tabs={sortOptionsSelectProps.sortTypeOptions ?? []}
|
||||
value={sortOptionsSelectProps.sortTypeValue}
|
||||
onChange={sortOptionsSelectProps.onSortTypeChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-l" />
|
||||
<div>
|
||||
<Tabs
|
||||
label="Order by"
|
||||
tabs={(sortOptionsSelectProps.sortOrderOptions ?? SORT_ORDERS).map(
|
||||
(option) => {
|
||||
const newLabel = getSortOrderLabel(
|
||||
option.value,
|
||||
sortOptionsSelectProps.sortTypeValue,
|
||||
);
|
||||
|
||||
return {
|
||||
...option,
|
||||
label: newLabel,
|
||||
};
|
||||
},
|
||||
)}
|
||||
value={sortOptionsSelectProps.sortOrderValue}
|
||||
onChange={sortOptionsSelectProps.onSortOrderChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function SortOptionsSelect({
|
||||
const sortOrders = sortOrderOptions ?? SORT_ORDERS;
|
||||
|
||||
return (
|
||||
<div className="flex items-end justify-end gap-4">
|
||||
<div className="flex items-end justify-end gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
display="inline"
|
||||
|
||||
@@ -119,7 +119,7 @@ export type BaseQuestionCardProps = ActionButtonProps &
|
||||
hideCard?: boolean;
|
||||
questionId: string;
|
||||
showHover?: boolean;
|
||||
timestamp: string | null;
|
||||
timestamp: Date | null;
|
||||
truncateContent?: boolean;
|
||||
type: QuestionsQuestionType;
|
||||
};
|
||||
@@ -215,7 +215,14 @@ export default function BaseQuestionCard({
|
||||
<QuestionAggregateBadge statistics={roles} variant="danger" />
|
||||
</>
|
||||
)}
|
||||
{timestamp !== null && <p className="text-xs">{timestamp}</p>}
|
||||
{timestamp !== null && (
|
||||
<p className="text-xs">
|
||||
{timestamp.toLocaleDateString(undefined, {
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{showAddToList && (
|
||||
<div className="pl-4">
|
||||
<AddToListDropdown questionId={questionId} />
|
||||
@@ -317,7 +324,7 @@ export default function BaseQuestionCard({
|
||||
<article
|
||||
className={clsx(
|
||||
'group flex gap-4 border-slate-300',
|
||||
showHover && 'hover:bg-slate-50',
|
||||
showHover && 'transition hover:bg-slate-50',
|
||||
!hideCard && 'rounded-md border bg-white p-4',
|
||||
)}>
|
||||
{cardContent}
|
||||
|
||||
@@ -221,12 +221,7 @@ export default function ContributeQuestionForm({
|
||||
createEncounterButtonText="Yes, this is my question"
|
||||
questionId={question.id}
|
||||
roles={roleCounts}
|
||||
timestamp={
|
||||
question.seenAt.toLocaleDateString(undefined, {
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
}) ?? null
|
||||
}
|
||||
timestamp={question.seenAt}
|
||||
type={question.type}
|
||||
onReceivedSubmit={async (data) => {
|
||||
await addEncounterAsync({
|
||||
|
||||
@@ -210,10 +210,7 @@ export default function QuestionPage() {
|
||||
questionId={question.id}
|
||||
receivedCount={undefined}
|
||||
roles={relabeledAggregatedEncounters?.roleCounts ?? {}}
|
||||
timestamp={question.seenAt.toLocaleDateString(undefined, {
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
})}
|
||||
timestamp={question.seenAt}
|
||||
upvoteCount={question.numVotes}
|
||||
onReceivedSubmit={async (data) => {
|
||||
await addEncounterAsync({
|
||||
|
||||
@@ -558,13 +558,9 @@ export default function QuestionsBrowsePage() {
|
||||
questionId={question.id}
|
||||
receivedCount={question.receivedCount}
|
||||
roles={roleCounts}
|
||||
timestamp={question.seenAt.toLocaleDateString(
|
||||
undefined,
|
||||
{
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
},
|
||||
)}
|
||||
timestamp={
|
||||
question.aggregatedQuestionEncounters.latestSeenAt
|
||||
}
|
||||
type={question.type}
|
||||
upvoteCount={question.numVotes}
|
||||
/>
|
||||
|
||||
@@ -220,13 +220,7 @@ export default function ListPage() {
|
||||
questionId={question.id}
|
||||
receivedCount={question.receivedCount}
|
||||
roles={roleCounts}
|
||||
timestamp={question.seenAt.toLocaleDateString(
|
||||
undefined,
|
||||
{
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
},
|
||||
)}
|
||||
timestamp={question.seenAt}
|
||||
type={question.type}
|
||||
onDelete={() => {
|
||||
deleteQuestionEntry({ id: entryId });
|
||||
|
||||
@@ -69,38 +69,38 @@ export const QUESTION_AGES: FilterChoices<QuestionAge> = [
|
||||
] as const;
|
||||
|
||||
export const SORT_ORDERS = [
|
||||
{
|
||||
label: 'Ascending',
|
||||
value: SortOrder.ASC,
|
||||
},
|
||||
{
|
||||
label: 'Descending',
|
||||
value: SortOrder.DESC,
|
||||
},
|
||||
{
|
||||
label: 'Ascending',
|
||||
value: SortOrder.ASC,
|
||||
},
|
||||
];
|
||||
|
||||
export const SORT_TYPES = [
|
||||
{
|
||||
label: 'New',
|
||||
value: SortType.NEW,
|
||||
label: 'Upvotes',
|
||||
value: SortType.TOP,
|
||||
},
|
||||
{
|
||||
label: 'Top',
|
||||
value: SortType.TOP,
|
||||
label: 'Date',
|
||||
value: SortType.NEW,
|
||||
},
|
||||
];
|
||||
|
||||
export const QUESTION_SORT_TYPES = [
|
||||
{
|
||||
label: 'New',
|
||||
value: SortType.NEW,
|
||||
},
|
||||
{
|
||||
label: 'Top',
|
||||
label: 'Upvotes',
|
||||
value: SortType.TOP,
|
||||
},
|
||||
{
|
||||
label: 'Encounters',
|
||||
label: 'Age',
|
||||
value: SortType.NEW,
|
||||
},
|
||||
{
|
||||
label: 'Received',
|
||||
value: SortType.ENCOUNTERS,
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user