Skip to content

Commit

Permalink
feat(project): add and implement SearchBar component
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed Jun 9, 2021
1 parent 7ad0361 commit 939860f
Show file tree
Hide file tree
Showing 19 changed files with 379 additions and 84 deletions.
66 changes: 58 additions & 8 deletions src/components/Header/Header.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,68 @@
display: none;
}

//
// Header menu
//
.iconButton {
width: 36px;
height: 36px;
}

//
// Header brand
//
.brand {
align-self: center;
margin-right: variables.$base-spacing;
}

//
// Header navigation
//
.nav {
display: inline-block;
flex: 1;
align-items: center;

> a {
height: 36px;
min-height: 36px;
margin: 0 6px;
}
}

//
// Header search
//
.search {
width: 180px;
}

//
// Header mobile search
//
.mobileSearch {
position: absolute;
right: 0;
left: 0;
display: flex;

> div:first-child {
flex: 1;
}
}

//
// Header modifier when mobile search is active
//
.mobileSearchActive {
.menu,
.brand {
display: none;
}
}

//
// mediaQueries
// --------------------------------
Expand All @@ -76,9 +122,19 @@
display: block;
}

.brand {
flex: 1;
margin: 0;
text-align: center;
}

.nav {
display: none;
}

.search {
text-align: right;
}
}

@include responsive.mobile-only() {
Expand All @@ -87,11 +143,11 @@
}

.menu {
transition: opacity 0.2s ease;
width: 40px;
}

.search {
width: 25px;
width: 40px;
}
}

Expand All @@ -100,9 +156,3 @@
width: 180px;
}
}

@include responsive.desktop-only() {
.nav {
text-align: center;
}
}
12 changes: 11 additions & 1 deletion src/components/Header/Header.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,17 @@ import Header from './Header';
describe('<Header />', () => {
test('renders header', () => {
const playlistMenuItems = [<ButtonLink key="key" label="Home" to="/" />];
const { container } = render(<Header onMenuButtonClick={jest.fn()} playlistMenuItems={playlistMenuItems} />);
const { container } = render((
<Header
onMenuButtonClick={jest.fn()}
playlistMenuItems={playlistMenuItems}
searchBarProps={{
query: '',
onQueryChange: jest.fn()
}}
searchEnabled
/>
));

expect(container).toMatchSnapshot();
});
Expand Down
80 changes: 61 additions & 19 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React from 'react';
import React, { useState } from 'react';
import classNames from 'classnames';

import SearchBar, { Props as SearchBarProps } from '../SearchBar/SearchBar';
import ButtonLink from '../ButtonLink/ButtonLink';
import Logo from '../Logo/Logo';
import Menu from '../../icons/Menu';
import SearchIcon from '../../icons/Search';
import CloseIcon from '../../icons/Close';
import IconButton from '../../components/IconButton/IconButton';
import useBreakpoint, { Breakpoint } from '../../hooks/useBreakpoint';

import styles from './Header.module.scss';

Expand All @@ -15,27 +19,65 @@ type Props = {
onMenuButtonClick: () => void;
playlistMenuItems: JSX.Element[];
logoSrc?: string;
searchBarProps: SearchBarProps;
searchEnabled: boolean;
};

const Header: React.FC<Props> = ({ headerType = 'static', onMenuButtonClick, playlistMenuItems, logoSrc }) => {
return (
<header className={classNames(styles.header, styles[headerType])}>
<div className={styles.container}>
<div className={styles.menu}>
<IconButton aria-label="open menu" onClick={onMenuButtonClick}>
<Menu />
const Header: React.FC<Props> = (
{
headerType = 'static',
onMenuButtonClick,
playlistMenuItems,
logoSrc,
searchBarProps,
searchEnabled,
}
) => {
const [mobileSearchActive, setMobileSearchActive] = useState(false);
const breakpoint = useBreakpoint();
const headerClassName = classNames(styles.header, styles[headerType], {
[styles.mobileSearchActive]: mobileSearchActive && breakpoint <= Breakpoint.sm
});

const search = breakpoint <= Breakpoint.sm ?
mobileSearchActive ? (
<div className={styles.mobileSearch}>
<SearchBar {...searchBarProps} />
<IconButton className={styles.iconButton} aria-label="Close search" onClick={() => setMobileSearchActive(false)}>
<CloseIcon />
</IconButton>
</div>
{logoSrc && <Logo src={logoSrc} />}
<nav className={styles.nav} aria-label="menu">
<ButtonLink label="Home" to="/" />
{playlistMenuItems}
<ButtonLink label="Settings" to="/u" />
</nav>
<div className={styles.search}></div>
</div>
</header>
);
};
) : (
<IconButton className={styles.iconButton} aria-label="Open search" onClick={() => setMobileSearchActive(true)}>
<SearchIcon />
</IconButton>
) : (
<SearchBar {...searchBarProps} />
);

return (
<header className={headerClassName}>
<div className={styles.container}>
<div className={styles.menu}>
<IconButton className={styles.iconButton} aria-label="open menu" onClick={onMenuButtonClick}>
<Menu />
</IconButton>
</div>
{logoSrc && (
<div className={styles.brand}>
<Logo src={logoSrc} />
</div>
)}
<nav className={styles.nav} aria-label="menu">
<ButtonLink label="Home" to="/" />
{playlistMenuItems}
<ButtonLink label="Settings" to="/u" />
</nav>
<div className={styles.search}>{searchEnabled ? search : null}</div>
</div>
</header>
);
}
;

export default Header;
29 changes: 27 additions & 2 deletions src/components/Header/__snapshots__/Header.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ exports[`<Header /> renders header 1`] = `
>
<div
aria-label="open menu"
class="iconButton"
class="iconButton iconButton"
role="button"
tabindex="0"
>
Expand Down Expand Up @@ -69,7 +69,32 @@ exports[`<Header /> renders header 1`] = `
</nav>
<div
class="search"
/>
>
<div
class="searchBar"
>
<svg
aria-hidden="true"
class="icon icon"
focusable="false"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<g>
<path
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
/>
</g>
</svg>
<input
aria-label="Search"
class="input"
placeholder="Search..."
type="text"
value=""
/>
</div>
</div>
</div>
</header>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/IconButton/IconButton.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
align-items: center;
width: 40px;
height: 40px;
outline-color: var(--highlight-color, white);

cursor: pointer;
opacity: 0.7;
transition: transform 0.1s ease;
outline-color: var(--highlight-color, white);

&:hover {
transform: scale(1.1);
Expand Down
9 changes: 8 additions & 1 deletion src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ type LayoutProps = {
};

const Layout: FC<LayoutProps> = ({ children }) => {
const { menu, assets, options } = useContext(ConfigContext);
const { menu, assets, options, searchPlaylist } = useContext(ConfigContext);
const { blurImage } = useContext(UIStateContext);
const [sideBarOpen, setSideBarOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const hasDynamicBlur = options.dynamicBlur === true;

return (
Expand All @@ -29,6 +30,12 @@ const Layout: FC<LayoutProps> = ({ children }) => {
<ButtonLink key={item.playlistId} label={item.label} to={`/p/${item.playlistId}`} />
))}
logoSrc={assets.banner}
searchEnabled={!!searchPlaylist}
searchBarProps={{
query: searchQuery,
onQueryChange: (event) => setSearchQuery(event.target.value),
onClearButtonClick: () => setSearchQuery(''),
}}
/>
<Sidebar
isOpen={sideBarOpen}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Layout/__snapshots__/Layout.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ exports[`<Layout /> renders layout 1`] = `
>
<div
aria-label="open menu"
class="iconButton"
class="iconButton iconButton"
role="button"
tabindex="0"
>
Expand Down
26 changes: 2 additions & 24 deletions src/components/Logo/Logo.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@
// Logo
// --------------------------------

.brand {
align-self: center;
text-align: center;
}

.logo {
max-width: 100%;
max-height: variables.$header-height - 10px;
Expand All @@ -23,24 +18,7 @@
// --------------------------------

@include responsive.mobile-and-tablet() {
.brand {
flex: 1;

.logo {
max-height: variables.$header-height-mobile - 20px;
}
}
}

@include responsive.mobile-only() {
.brand {
transition: opacity 0.2s ease;
}
}

@include responsive.desktop-only() {
.brand {
width: 180px;
text-align: left;
.logo {
max-height: variables.$header-height-mobile - 20px;
}
}
14 changes: 4 additions & 10 deletions src/components/Logo/Logo.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { Link } from 'react-router-dom';

import styles from './Logo.module.scss';

Expand All @@ -8,16 +8,10 @@ type Props = {
};

const Logo: React.FC<Props> = ({ src }: Props) => {
const history = useHistory();

const handleClick = () => {
history.push('/');
};

return (
<div className={styles.brand}>
<img className={styles.logo} alt="logo" src={src} onClick={handleClick} />
</div>
<Link to="/">
<img className={styles.logo} alt="logo" src={src} />
</Link>
);
};

Expand Down
Loading

0 comments on commit 939860f

Please sign in to comment.