Skip to content

Commit f3b56a1

Browse files
committed
chore(content-explorer): Migrate Header
1 parent dabaae2 commit f3b56a1

File tree

15 files changed

+420
-85
lines changed

15 files changed

+420
-85
lines changed

i18n/en-US.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,8 @@ be.rootBreadcrumb = All Files
642642
be.save = Save
643643
# Shown as the title in the sub header while searching.
644644
be.searchBreadcrumb = Search Results
645+
# Aria label for the clear button in the search box.
646+
be.searchClear = Clear search
645647
# Shown as a placeholder in the search box.
646648
be.searchPlaceholder = Search files and folders
647649
# Message shown when there are no search results.
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,52 @@
1-
/**
2-
* @flow
3-
* @file Header bar
4-
* @author Box
5-
*/
6-
71
import * as React from 'react';
8-
import { injectIntl } from 'react-intl';
2+
import { useIntl } from 'react-intl';
3+
import { SearchInput } from '@box/blueprint-web';
94
import Logo from './Logo';
105
import messages from '../messages';
116
import { VIEW_FOLDER, VIEW_SEARCH } from '../../../constants';
127
import type { View } from '../../../common/types/core';
138

149
import './Header.scss';
1510

16-
type Props = {
17-
intl: any,
11+
type HeaderProps = {
1812
isHeaderLogoVisible?: boolean,
19-
isSmall: boolean,
2013
logoUrl?: string,
21-
onSearch: Function,
14+
onSearch: any,
2215
searchQuery: string,
23-
view: View,
16+
view: View
2417
};
2518

2619
// eslint-disable-next-line react/prop-types
27-
const Header = ({ isHeaderLogoVisible = true, view, isSmall, searchQuery, onSearch, logoUrl, intl }: Props) => {
28-
const { formatMessage } = intl;
29-
const search = ({ currentTarget }: { currentTarget: HTMLInputElement }) => onSearch(currentTarget.value);
20+
const Header = ({
21+
isHeaderLogoVisible = true,
22+
view,
23+
searchQuery,
24+
onSearch,
25+
logoUrl,
26+
}: HeaderProps) => {
27+
const { formatMessage } = useIntl();
3028
const searchMessage = formatMessage(messages.searchPlaceholder);
3129
const isFolder = view === VIEW_FOLDER;
3230
const isSearch = view === VIEW_SEARCH;
3331

3432
return (
3533
<div className="be-header">
36-
{isHeaderLogoVisible && <Logo isSmall={isSmall} url={logoUrl} />}
34+
{isHeaderLogoVisible && <Logo url={logoUrl} />}
3735
<div className="be-search">
38-
<input
39-
aria-label={searchMessage}
40-
data-testid="be-Header-searchInput"
36+
<SearchInput
37+
className="my-class"
38+
defaultValue=""
4139
disabled={!isFolder && !isSearch}
42-
onChange={search}
40+
onChange={onSearch}
4341
placeholder={searchMessage}
44-
type="search"
42+
searchInputAriaLabel={searchMessage}
43+
searchInputClearAriaLabel={formatMessage(messages.searchClear)}
4544
value={searchQuery}
45+
variant="global"
4646
/>
4747
</div>
4848
</div>
4949
);
5050
};
5151

52-
export { Header as HeaderBase };
53-
export default injectIntl(Header);
52+
export default Header;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as React from 'react';
2+
import { useIntl } from 'react-intl';
3+
import { SearchInput } from '@box/blueprint-web';
4+
import Logo from './Logo';
5+
import messages from '../messages';
6+
import { VIEW_FOLDER, VIEW_SEARCH } from '../../../constants';
7+
import type { View } from '../../../common/types/core';
8+
9+
import './Header.scss';
10+
11+
export interface HeaderProps {
12+
isHeaderLogoVisible?: boolean;
13+
logoUrl?: string;
14+
onSearch: (value: string) => void;
15+
view: View;
16+
}
17+
18+
// eslint-disable-next-line react/prop-types
19+
const Header = ({ isHeaderLogoVisible = true, logoUrl, onSearch, view }: HeaderProps) => {
20+
const { formatMessage } = useIntl();
21+
const searchMessage = formatMessage(messages.searchPlaceholder);
22+
const isFolder = view === VIEW_FOLDER;
23+
const isSearch = view === VIEW_SEARCH;
24+
25+
return (
26+
<div className="be-header">
27+
{isHeaderLogoVisible && <Logo url={logoUrl} />}
28+
<div className="be-search">
29+
<SearchInput
30+
defaultValue=""
31+
disabled={!isFolder && !isSearch}
32+
onChange={onSearch}
33+
placeholder={searchMessage}
34+
searchInputAriaLabel={searchMessage}
35+
searchInputClearAriaLabel={formatMessage(messages.searchClear)}
36+
variant="global"
37+
/>
38+
</div>
39+
</div>
40+
);
41+
};
42+
43+
export default Header;
Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
1-
/**
2-
* @flow
3-
* @file Logo for the header
4-
* @author Box
5-
*/
6-
71
import * as React from 'react';
82
import { FormattedMessage } from 'react-intl';
93
import IconLogo from '../../../icons/general/IconLogo';
104
import messages from '../messages';
115
import './Logo.scss';
126

137
type Props = {
14-
url?: string,
8+
url?: string
159
};
1610

1711
function getLogo(url?: string) {
@@ -29,7 +23,9 @@ function getLogo(url?: string) {
2923
);
3024
}
3125

32-
const Logo = ({ url }: Props) => (
26+
const Logo = ({
27+
url,
28+
}: Props) => (
3329
<div className="be-logo" data-testid="be-Logo">
3430
{getLogo(url)}
3531
</div>

src/elements/common/header/Logo.scss

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
padding-left: 20px;
55

66
.be-logo-custom {
7-
max-width: 80px;
8-
max-height: 32px;
7+
max-width: 240px;
8+
max-height: 40px;
99

1010
.be-is-small & {
1111
max-width: 75px;
@@ -16,8 +16,8 @@
1616
display: flex;
1717
align-items: center;
1818
justify-content: center;
19-
width: 75px;
20-
height: 32px;
19+
width: 208px;
20+
height: 48px;
2121
background-color: $bdl-gray-10;
2222
border: 1px dashed;
2323

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import * as React from 'react';
2+
import { FormattedMessage } from 'react-intl';
3+
import { BoxLogo } from '@box/blueprint-web-assets/icons/Logo';
4+
import messages from '../messages';
5+
import './Logo.scss';
6+
7+
export interface LogoProps {
8+
url?: string;
9+
}
10+
11+
function getLogo(url?: string) {
12+
if (url === 'box') {
13+
return <BoxLogo width={45} height={25} />;
14+
}
15+
16+
if (typeof url === 'string') {
17+
return <img alt="" className="be-logo-custom" src={url} />;
18+
}
19+
20+
return (
21+
<div className="be-logo-placeholder">
22+
<FormattedMessage {...messages.logo} />
23+
</div>
24+
);
25+
}
26+
27+
const Logo = ({ url }: LogoProps) => (
28+
<div className="be-logo" data-testid="be-Logo">
29+
{getLogo(url)}
30+
</div>
31+
);
32+
33+
export default Logo;

src/elements/common/header/__tests__/Header.test.js

Lines changed: 0 additions & 40 deletions
This file was deleted.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as React from 'react';
2+
import { userEvent } from '@testing-library/user-event';
3+
import { render, screen } from '../../../../test-utils/testing-library';
4+
import Header, { HeaderProps } from '../Header';
5+
6+
jest.mock('@box/blueprint-web-assets/icons/Logo', () => {
7+
return {
8+
BoxLogo: () => <div>BoxLogo</div>,
9+
};
10+
});
11+
12+
describe('elements/common/header/Header', () => {
13+
const renderComponent = (props?: Partial<HeaderProps>) =>
14+
render(<Header onSearch={jest.fn()} view="folder" {...props} />);
15+
16+
test('disables search input when view is not `folder` and not `search`', () => {
17+
renderComponent({ view: 'recents' });
18+
expect(screen.getByRole('searchbox', { name: 'Search files and folders' })).toBeInTheDocument();
19+
expect(screen.getByRole('searchbox', { name: 'Search files and folders' })).toBeDisabled();
20+
});
21+
22+
test.each(['folder', 'search'])('does not disable search input when view is %s', view => {
23+
renderComponent({ view });
24+
25+
expect(screen.getByRole('searchbox', { name: 'Search files and folders' })).toBeInTheDocument();
26+
expect(screen.getByRole('searchbox', { name: 'Search files and folders' })).not.toBeDisabled();
27+
});
28+
29+
test('onSearch is called when search input changes', async () => {
30+
const onSearch = jest.fn();
31+
renderComponent({ onSearch });
32+
const searchInput = screen.getByRole('searchbox', { name: 'Search files and folders' });
33+
34+
expect(onSearch).not.toHaveBeenCalled();
35+
await userEvent.type(searchInput, 'test');
36+
expect(onSearch).toHaveBeenCalled();
37+
});
38+
39+
describe('Logo', () => {
40+
test('renders Logo component when isHeaderLogoVisible is `true`', () => {
41+
renderComponent({ isHeaderLogoVisible: true });
42+
expect(screen.getByRole('searchbox', { name: 'Search files and folders' })).toBeInTheDocument();
43+
44+
expect(screen.getByText('Logo')).toBeInTheDocument();
45+
});
46+
47+
test('does not render Logo component when isHeaderLogoVisible is `false`', () => {
48+
renderComponent({ isHeaderLogoVisible: false });
49+
expect(screen.queryByText('Logo')).not.toBeInTheDocument();
50+
expect(screen.getByRole('searchbox', { name: 'Search files and folders' })).toBeInTheDocument();
51+
});
52+
53+
test('renders BoxLogo component when logoUrl is `box`', () => {
54+
renderComponent({ logoUrl: 'box' });
55+
expect(screen.getByText('BoxLogo')).toBeInTheDocument();
56+
});
57+
58+
test('renders custom logo component when logoUrl is a string', () => {
59+
renderComponent({ logoUrl: 'https://example.com/logo.png' });
60+
expect(screen.getByRole('presentation')).toBeInTheDocument();
61+
});
62+
});
63+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {default} from './Header';
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
// @flow
21
export { default } from './Header';

0 commit comments

Comments
 (0)