-
-
Notifications
You must be signed in to change notification settings - Fork 11
788-feat: Add filter for merch #904
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 76 commits
e993b26
2e66950
9eafcbe
109650d
0647f7b
bacb488
ed4c779
ef8479a
098bc07
b647fbc
3cacc8f
451f808
b4eb099
eb3e9b0
070a452
e7b66a3
44fbb78
104400c
f21c63f
38716c9
f52c3d4
e9feef7
2c627e1
59c698b
ff1e958
c74342c
efd3913
c110b6c
56370fb
10e879f
3397741
259929a
9d65fe1
557fc02
5a21bdc
c24c321
5aa6bbe
0788559
f4093e5
8d66e3a
4661af6
721f47c
ac9facc
8393739
d04327c
f3c3abb
dd6d294
7e11672
2daf47e
d5d137f
75ecdb3
b677394
0a0e99d
183ba6f
d46541e
54c14c1
3ec6252
67107a1
cd91693
f28b0e2
93de97a
6e6037e
eb16ce9
de8702c
602865e
c06df4e
4f91993
2513a78
4eb9fcb
2e5a05b
54fb4fb
7ec328c
e9afa06
9163d3e
038c5ad
08f9764
17635a4
a11807c
cbd277d
323c5d8
3efe1d5
b41a734
45aecd9
78b3ceb
7350e09
6310c48
99c07d5
dd602a2
7a17877
5596d8b
ce8cff2
bf68d8b
9cbbd07
a35a717
2bd7b77
3c206f5
3828ae9
e5970a9
69c1988
6bce762
cff58ee
e6beba4
8580b04
186e3f6
3111766
8704931
3c94901
15ffda1
1648d5f
b52966e
f49bb1c
7777cd5
5da143f
862ed92
76ceffd
99e7efd
e2fb541
721c5b5
bfe7d5e
d6c5b54
ed53bc4
727411a
c945c44
ee870b6
c8e0952
bfa5e25
8ac199a
aa23fb5
abd47c7
413ee5a
b1baeb6
39c523a
1acca22
f966d26
fb13147
93bc735
e54a06c
b18310f
02a1a75
06fe2d1
219ea8b
c938f37
1a579d3
51172c0
fd5f74d
6493835
1f9f40c
ae1dcb0
c46a631
05b0f1a
84d9be9
07ed6aa
aa85120
f323d5a
0552f74
112be4f
984e590
1911092
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| /// <reference types="next" /> | ||
| /// <reference types="next/image-types/global" /> | ||
| /// <reference path="./.next/types/routes.d.ts" /> | ||
| /// <reference path="./build/types/routes.d.ts" /> | ||
|
|
||
| // NOTE: This file should not be edited | ||
| // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. | ||
ansivgit marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
ansivgit marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { useEffect, useState } from 'react'; | ||
|
|
||
| export const useMediaQuery = (query: string): boolean => { | ||
| const [matches, setMatches] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| if (typeof window === 'undefined') { | ||
| return; | ||
| } | ||
| const media = window.matchMedia(query); | ||
| const listener = () => setMatches(media.matches); | ||
|
|
||
| listener(); | ||
| media.addEventListener('change', listener); | ||
| return () => media.removeEventListener('change', listener); | ||
| }, [query]); | ||
| return matches; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| .input { | ||
| width: 100%; | ||
| height: 35px; | ||
| padding: $gap-xxs $gap-xs; | ||
| border: 1px solid $color-gray-400; | ||
| border-radius: $border-radius-xxs; | ||
|
|
||
| outline-offset: -2px; | ||
|
|
||
| &:focus { | ||
| outline: 2px solid $color-blue-500; | ||
| } | ||
| } |
ansivgit marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import classNames from 'classnames/bind'; | ||
|
|
||
| import styles from './search-input.module.scss'; | ||
|
|
||
| const cx = classNames.bind(styles); | ||
|
|
||
| type SearchInputProps = { | ||
| value: string; | ||
| onChange: (value: string) => void; | ||
| placeholder?: string; | ||
| ariaLabel?: string; | ||
| name?: string; | ||
| }; | ||
|
|
||
| export default function SearchInput({ | ||
ansivgit marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| value, | ||
| onChange, | ||
| placeholder, | ||
ansivgit marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ariaLabel, | ||
| name, | ||
| }: SearchInputProps) { | ||
| return ( | ||
| <input | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're using |
||
| className={cx('input')} | ||
ansivgit marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| type="text" | ||
| placeholder={placeholder} | ||
| value={value} | ||
| onChange={(e) => onChange(e.target.value)} | ||
| aria-label={ariaLabel} | ||
| name={name} | ||
| /> | ||
| ); | ||
| } | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { MerchSection } from './ui/merch-section'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import { render, screen } from '@testing-library/react'; | ||
| import userEvent from '@testing-library/user-event'; | ||
| import { describe, expect, it, vi } from 'vitest'; | ||
|
|
||
| import { DesktopMerchFilters } from './desktop-merch-filters'; | ||
|
|
||
| vi.mock('@/shared/ui/subtitle', () => ({ Subtitle: (props: { children: React.ReactNode }) => <div>{props.children}</div> })); | ||
|
|
||
| describe('DesktopMerchFilters', () => { | ||
| const user = userEvent.setup(); | ||
|
|
||
| const mockSearchFilters = <div data-testid="search-filters">Mock Search</div>; | ||
| const mockTagFilters = <div data-testid="tag-filters">Mock Tags</div>; | ||
|
|
||
| it('should render the title and the provided filter slots', () => { | ||
| render( | ||
| <DesktopMerchFilters | ||
| searchFilters={mockSearchFilters} | ||
| tagFilters={mockTagFilters} | ||
| hasActiveFilters={false} | ||
| onClearFilters={() => {}} | ||
| />, | ||
| ); | ||
|
|
||
| expect(screen.getByText('Filter merch')).toBeInTheDocument(); | ||
| expect(screen.getByTestId('search-filters')).toBeInTheDocument(); | ||
| expect(screen.getByTestId('tag-filters')).toBeInTheDocument(); | ||
ansivgit marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }); | ||
|
|
||
| it('should render the "Clear" button without "active" class if filters are not active', () => { | ||
|
||
| render( | ||
| <DesktopMerchFilters | ||
| searchFilters={mockSearchFilters} | ||
| tagFilters={mockTagFilters} | ||
| hasActiveFilters={false} | ||
| onClearFilters={() => {}} | ||
| />, | ||
| ); | ||
|
|
||
| const clearButton = screen.getByRole('button', { name: /Clear/i }); | ||
|
|
||
| expect(clearButton).toBeInTheDocument(); | ||
| expect(clearButton).not.toHaveClass('active'); | ||
| }); | ||
|
|
||
| it('should render the "Clear" button with "active" class if filters are active', () => { | ||
| render( | ||
| <DesktopMerchFilters | ||
| searchFilters={mockSearchFilters} | ||
| tagFilters={mockTagFilters} | ||
| hasActiveFilters={true} | ||
| onClearFilters={() => {}} | ||
| />, | ||
| ); | ||
|
|
||
| const clearButton = screen.getByRole('button', { name: /Clear/i }); | ||
|
|
||
| expect(clearButton).toBeInTheDocument(); | ||
| expect(clearButton).toHaveClass('active'); | ||
| }); | ||
|
|
||
| it('should call onClearFilters when the "Clear" button is clicked', async () => { | ||
|
||
| const handleClearFiltersMock = vi.fn(); | ||
|
|
||
| render( | ||
| <DesktopMerchFilters | ||
| searchFilters={mockSearchFilters} | ||
| tagFilters={mockTagFilters} | ||
| hasActiveFilters={true} | ||
| onClearFilters={handleClearFiltersMock} | ||
| />, | ||
| ); | ||
|
|
||
| const clearButton = screen.getByRole('button', { name: /Clear/i }); | ||
|
|
||
| await user.click(clearButton); | ||
|
|
||
| expect(handleClearFiltersMock).toHaveBeenCalledTimes(1); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import classNames from 'classnames/bind'; | ||
|
|
||
| import { LayoutProps } from '../../types'; | ||
| import { Subtitle } from '@/shared/ui/subtitle'; | ||
|
|
||
| import styles from '../layouts.module.scss'; | ||
|
|
||
| const cx = classNames.bind(styles); | ||
|
|
||
| export const DesktopMerchFilters = ({ | ||
| hasActiveFilters, | ||
| searchFilters, | ||
| tagFilters, | ||
| onClearFilters, | ||
| }: LayoutProps) => { | ||
| return ( | ||
| <div className={cx('controls-wrapper')}> | ||
| <div className={cx('desktop-actions-wrapper')}> | ||
| <Subtitle size="extra-small" weight="bold"> | ||
| Filter merch | ||
| </Subtitle> | ||
| <button | ||
| type="button" | ||
| className={cx('button', 'secondary', { active: hasActiveFilters })} | ||
| onClick={onClearFilters} | ||
| > | ||
| Clear | ||
| </button> | ||
| </div> | ||
|
|
||
| {searchFilters} | ||
|
|
||
| {tagFilters} | ||
|
||
| </div> | ||
| ); | ||
| }; | ||
ansivgit marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| .controls-wrapper { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: $gap-xs; | ||
| } | ||
|
|
||
| .desktop-actions-wrapper { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| } | ||
|
|
||
| .filter-accordion { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: $gap-xs; | ||
| } | ||
|
|
||
| .button { | ||
| @extend %transition-all; | ||
|
|
||
| cursor: pointer; | ||
|
|
||
| gap: $gap-xs; | ||
| place-content: center center; | ||
| align-items: center; | ||
|
|
||
| font-weight: $font-weight-medium; | ||
| letter-spacing: 0; | ||
| white-space: pre; | ||
| } | ||
|
|
||
| .secondary { | ||
| width: max-content; | ||
| padding: $gap-xxs $gap-s; | ||
| border: 1px solid $color-gray-900; | ||
|
|
||
| font-size: $font-size-xxs; | ||
ansivgit marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| color: $color-gray-900; | ||
|
|
||
| visibility: hidden; | ||
| opacity: 0; | ||
ansivgit marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| background: none; | ||
|
|
||
| &:hover { | ||
| background-color: $color-gray-100; | ||
| } | ||
|
|
||
| &.active { | ||
| visibility: visible; | ||
| opacity: $opacity-100; | ||
| transition-delay: 0s; | ||
| } | ||
|
|
||
| @include media-tablet-large { | ||
| width: 10%; | ||
|
|
||
| &.active { | ||
| max-height: 35px; | ||
| padding: $gap-xs; | ||
| } | ||
| } | ||
|
|
||
| @include media-mobile-landscape { | ||
| width: 20%; | ||
| } | ||
| } | ||
|
|
||
| .tablet-actions-wrapper { | ||
| display: flex; | ||
| gap: $gap-xs; | ||
| } | ||
|
|
||
| .tablet-toggle-button { | ||
| cursor: pointer; | ||
|
|
||
| position: relative; | ||
|
|
||
| display: inline-flex; | ||
| gap: $gap-xs; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
|
|
||
| width: 100%; | ||
| padding: $gap-s; | ||
| border: none; | ||
| border-radius: $border-radius-xxs; | ||
|
|
||
| font-size: $font-size-s; | ||
| font-weight: $font-weight-medium; | ||
|
|
||
| background-color: transparent; | ||
|
|
||
| transition: background-color 0.2s ease; | ||
|
|
||
| &.expanded { | ||
| background-color: $color-gray-50; | ||
| } | ||
|
|
||
| &:focus-visible { | ||
| outline: 1px solid $color-gray-600; | ||
| outline-offset: 1px; | ||
| } | ||
|
|
||
| &.has-active-filters::after { | ||
| content: ''; | ||
| display: block; | ||
| width: 6px; | ||
| height: 6px; | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| .filter-toggle-arrow { | ||
| margin-left: auto; | ||
| transition: transform 0.2s ease-in-out; | ||
|
|
||
| &.rotate { | ||
| transform: rotate(180deg); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.