Skip to content

Commit

Permalink
separate event filter into its own component
Browse files Browse the repository at this point in the history
  • Loading branch information
SheepTester committed Sep 29, 2024
1 parent 89fce1d commit ef61648
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 97 deletions.
22 changes: 19 additions & 3 deletions src/components/common/Dropdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,19 @@ interface DropdownProps {
options: (Option | typeof DIVIDER)[];
value: string;
onChange: (value: string) => void;
readOnly?: boolean;
className?: string;
}

const Dropdown = ({ name, ariaLabel, options, value, onChange, className }: DropdownProps) => {
const Dropdown = ({
name,
ariaLabel,
options,
value,
onChange,
readOnly,
className,
}: DropdownProps) => {
const [open, setOpen] = useState(false);

useEffect(() => {
Expand Down Expand Up @@ -61,8 +70,11 @@ const Dropdown = ({ name, ariaLabel, options, value, onChange, className }: Drop
// mouse/touch users.
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div
className={`${styles.dropdownWrapper} ${className}`}
className={`${styles.dropdownWrapper} ${className} ${readOnly ? styles.readOnly : ''}`}
onClick={e => {
if (readOnly) {
return;
}
// Using the keyboard to select an option fires the click event on
// <select>; prevent it from opening the fake dropdown. The <select> has
// pointer-events: none so it shouldn't receive the click event any
Expand All @@ -82,7 +94,11 @@ const Dropdown = ({ name, ariaLabel, options, value, onChange, className }: Drop
>
{options.map(option =>
option !== DIVIDER ? (
<option value={option.value} key={option.value}>
<option
value={option.value}
key={option.value}
disabled={value !== option.value && readOnly}
>
{option.label}
</option>
) : null
Expand Down
4 changes: 4 additions & 0 deletions src/components/common/Dropdown/style.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
grid-area: 1 / 2;
position: relative;

&.readOnly {
cursor: unset;
}

select {
appearance: none;
background: none;
Expand Down
1 change: 1 addition & 0 deletions src/components/common/Dropdown/style.module.scss.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type Styles = {
contents: string;
dropdownWrapper: string;
option: string;
readOnly: string;
};

export type ClassNames = keyof Styles;
Expand Down
111 changes: 111 additions & 0 deletions src/components/events/EventFilter/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Dropdown } from '@/components/common';
import { DIVIDER } from '@/components/common/Dropdown';
import { FilterEventOptions } from '@/lib/types/client';
import { getYears } from '@/lib/utils';
import { useMemo } from 'react';
import styles from './style.module.scss';

export interface FilterOptions {
search: string;
communityFilter: string;
dateFilter: string;
attendanceFilter: string;
}

interface EventFilterProps {
filters: FilterOptions;
onFilter?: (param: string, value: string) => void;
loggedOut?: boolean;
}

export const DEFAULT_FILTER_STATE: FilterEventOptions = {
community: 'all',
date: 'all-time',
attendance: 'any',
search: '',
};

const EventFilter = ({
filters: { search, communityFilter, dateFilter, attendanceFilter },
onFilter,
loggedOut,
}: EventFilterProps) => {
const years = useMemo(getYears, []);

return (
<div className={styles.controls}>
<input
className={styles.search}
type="search"
placeholder="Search Events"
aria-label="Search Events"
value={search}
onChange={e => {
onFilter?.('search', e.currentTarget.value);
}}
readOnly={!onFilter}
/>
<div className={styles.filterOption}>
<Dropdown
name="communityOptions"
ariaLabel="Filter events by community"
options={[
{ value: 'all', label: 'All Communities' },
{ value: 'general', label: 'General' },
{ value: 'ai', label: 'AI' },
{ value: 'cyber', label: 'Cyber' },
{ value: 'design', label: 'Design' },
{ value: 'hack', label: 'Hack' },
]}
value={communityFilter}
onChange={v => {
onFilter?.('community', v);
}}
readOnly={!onFilter}
/>
</div>

<div className={styles.filterOption}>
<Dropdown
name="dateOptions"
ariaLabel="Filter events by date"
options={[
{ value: 'upcoming', label: 'Upcoming' },
{ value: 'past-week', label: 'Past Week' },
{ value: 'past-month', label: 'Past Month' },
{ value: 'past-year', label: 'Past Year' },
{ value: 'all-time', label: 'All Time' },
DIVIDER,
...years,
]}
value={dateFilter}
onChange={v => {
onFilter?.('date', v);
}}
readOnly={!onFilter}
/>
</div>

{loggedOut ? null : (
<div className={styles.filterOption}>
<Dropdown
name="attendanceOptions"
ariaLabel="Filter events by attendance"
options={[
{ value: 'any', label: 'Any Attendance' },
{ value: 'attended', label: 'Attended' },
{ value: 'not-attended', label: 'Not Attended' },
]}
value={attendanceFilter}
onChange={v => {
onFilter?.('attendance', v);
}}
readOnly={!onFilter}
/>
</div>
)}
</div>
);
};

export default EventFilter;
105 changes: 14 additions & 91 deletions src/components/events/EventList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Dropdown, LoginAppeal, PaginationControls, Typography } from '@/components/common';
import { DIVIDER } from '@/components/common/Dropdown';
import { LoginAppeal, PaginationControls, Typography } from '@/components/common';
import EventDisplay from '@/components/events/EventDisplay';
import EventFilter, { DEFAULT_FILTER_STATE } from '@/components/events/EventFilter';
import { config } from '@/lib';
import useQueryState from '@/lib/hooks/useQueryState';
import { PublicAttendance, PublicEvent } from '@/lib/types/apiResponses';
Expand All @@ -12,13 +12,12 @@ import {
} from '@/lib/types/client';
import { formatSearch, getDateRange, getYears } from '@/lib/utils';
import { useMemo, useState } from 'react';
import styles from './style.module.scss';

interface EventListProps {
events: PublicEvent[];
attendances?: PublicAttendance[];
initialFilters?: FilterEventOptions;
loggedOut?: boolean;
attendances: PublicAttendance[];
initialFilters: FilterEventOptions;
loggedOut: boolean;
}

interface FilterOptions {
Expand Down Expand Up @@ -63,21 +62,9 @@ const filterEvent = (
return true;
};

export const DEFAULT_FILTER_STATE: FilterEventOptions = {
community: 'all',
date: 'all-time',
attendance: 'any',
search: '',
};

const ROWS_PER_PAGE = 25;

const EventList = ({
events,
attendances = [],
initialFilters = { ...DEFAULT_FILTER_STATE },
loggedOut = false,
}: EventListProps) => {
const EventList = ({ events, attendances, initialFilters, loggedOut }: EventListProps) => {
const [page, setPage] = useState(0);
const years = useMemo(getYears, []);

Expand Down Expand Up @@ -138,78 +125,14 @@ const EventList = ({
of thousands.
</LoginAppeal>
) : null}
<div className={styles.controls}>
<input
className={styles.search}
type="search"
placeholder="Search Events"
aria-label="Search Events"
value={search}
onChange={e => {
setStates('search', e.currentTarget.value);
setPage(0);
}}
/>
<div className={styles.filterOption}>
<Dropdown
name="communityOptions"
ariaLabel="Filter events by community"
options={[
{ value: 'all', label: 'All Communities' },
{ value: 'general', label: 'General' },
{ value: 'ai', label: 'AI' },
{ value: 'cyber', label: 'Cyber' },
{ value: 'design', label: 'Design' },
{ value: 'hack', label: 'Hack' },
]}
value={communityFilter}
onChange={v => {
setStates('community', v);
setPage(0);
}}
/>
</div>

<div className={styles.filterOption}>
<Dropdown
name="dateOptions"
ariaLabel="Filter events by date"
options={[
{ value: 'upcoming', label: 'Upcoming' },
{ value: 'past-week', label: 'Past Week' },
{ value: 'past-month', label: 'Past Month' },
{ value: 'past-year', label: 'Past Year' },
{ value: 'all-time', label: 'All Time' },
DIVIDER,
...years,
]}
value={dateFilter}
onChange={v => {
setStates('date', v);
setPage(0);
}}
/>
</div>

{loggedOut ? null : (
<div className={styles.filterOption}>
<Dropdown
name="attendanceOptions"
ariaLabel="Filter events by attendance"
options={[
{ value: 'any', label: 'Any Attendance' },
{ value: 'attended', label: 'Attended' },
{ value: 'not-attended', label: 'Not Attended' },
]}
value={attendanceFilter}
onChange={v => {
setStates('attendance', v);
setPage(0);
}}
/>
</div>
)}
</div>
<EventFilter
filters={{ search, communityFilter, dateFilter, attendanceFilter }}
onFilter={(param, value) => {
setStates(param, value);
setPage(0);
}}
loggedOut={loggedOut}
/>
<EventDisplay events={displayedEvents} attendances={attendances} />

{filteredEvents.length > 0 ? (
Expand Down
3 changes: 2 additions & 1 deletion src/components/events/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export { default as CheckInModal } from './CheckInModal';
export { default as EventCard } from './EventCard';
export { default as EventCarousel } from './EventCarousel';
export { default as EventDisplay } from './EventDisplay';
export { DEFAULT_FILTER_STATE, default as EventList } from './EventList';
export { DEFAULT_FILTER_STATE, default as EventFilter } from './EventFilter';
export { default as EventList } from './EventList';
export { default as EventModal } from './EventModal';
18 changes: 16 additions & 2 deletions src/components/onboarding/Events/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EventList } from '@/components/events';
import { Typography } from '@/components/common';
import { DEFAULT_FILTER_STATE, EventCard, EventFilter } from '@/components/events';
import { PublicEvent } from '@/lib/types/apiResponses';
import styles from './style.module.scss';

Expand Down Expand Up @@ -65,7 +66,20 @@ const dummyEvents: PublicEvent[] = [
const Events = () => {
return (
<div className={styles.page}>
<EventList events={dummyEvents} />
<Typography variant="headline/heavy/small">Events</Typography>
<EventFilter
filters={{
search: DEFAULT_FILTER_STATE.search,
communityFilter: DEFAULT_FILTER_STATE.community,
dateFilter: DEFAULT_FILTER_STATE.date,
attendanceFilter: DEFAULT_FILTER_STATE.attendance,
}}
/>
<div className={styles.events}>
{dummyEvents.map(event => (
<EventCard key={event.uuid} event={event} attended={false} showYear />
))}
</div>
</div>
);
};
Expand Down
6 changes: 6 additions & 0 deletions src/components/onboarding/Events/style.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@
flex-direction: column;
gap: 1.5rem;
margin-top: 1.5rem;

.events {
display: flex;
gap: 1rem;
justify-content: space-between;
}
}
1 change: 1 addition & 0 deletions src/components/onboarding/Events/style.module.scss.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type Styles = {
events: string;
page: string;
};

Expand Down

0 comments on commit ef61648

Please sign in to comment.