improved mobile-search experience
This commit is contained in:
parent
d3bfdfe1ec
commit
120300643f
18 changed files with 563 additions and 375 deletions
|
@ -98,7 +98,7 @@ const Header = (props: Props) => {
|
||||||
const isPwdResetPage = history.location.pathname.includes(PAGES.AUTH_PASSWORD_RESET);
|
const isPwdResetPage = history.location.pathname.includes(PAGES.AUTH_PASSWORD_RESET);
|
||||||
const hasBackout = Boolean(backout);
|
const hasBackout = Boolean(backout);
|
||||||
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
|
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
|
||||||
const notificationsEnabled = user && user.experimental_ui;
|
const notificationsEnabled = (user && user.experimental_ui) || false;
|
||||||
let channelUrl;
|
let channelUrl;
|
||||||
let identityChannel;
|
let identityChannel;
|
||||||
if (myChannels && myChannels.length >= 1) {
|
if (myChannels && myChannels.length >= 1) {
|
||||||
|
@ -237,7 +237,7 @@ const Header = (props: Props) => {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
className="header__navigation-item header__navigation-item--lbry header__navigation-item--button-mobile"
|
className="header__navigation-item header__navigation-item--lbry"
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
label={'LBRY'}
|
label={'LBRY'}
|
||||||
// @endif
|
// @endif
|
||||||
|
@ -269,134 +269,17 @@ const Header = (props: Props) => {
|
||||||
|
|
||||||
{!authHeader && <WunderBar />}
|
{!authHeader && <WunderBar />}
|
||||||
|
|
||||||
<div className="header__buttons mobile-hidden">
|
<HeaderMenuButtons
|
||||||
{(authenticated || !IS_WEB) && (
|
authenticated={authenticated}
|
||||||
<Menu>
|
notificationsEnabled={notificationsEnabled}
|
||||||
<MenuButton
|
history={history}
|
||||||
aria-label={__('Publish a file, or create a channel')}
|
handleThemeToggle={handleThemeToggle}
|
||||||
title={__('Publish a file, or create a channel')}
|
currentTheme={currentTheme}
|
||||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
channelUrl={channelUrl}
|
||||||
// @if TARGET='app'
|
openSignOutModal={openSignOutModal}
|
||||||
onDoubleClick={e => {
|
email={email}
|
||||||
e.stopPropagation();
|
signOut={signOut}
|
||||||
}}
|
/>
|
||||||
// @endif
|
|
||||||
>
|
|
||||||
<Icon size={18} icon={ICONS.PUBLISH} aria-hidden />
|
|
||||||
</MenuButton>
|
|
||||||
{notificationsEnabled && <NotificationHeaderButton />}
|
|
||||||
<MenuList className="menu__list--header">
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.UPLOAD}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
|
||||||
{__('Upload')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.CHANNEL_NEW}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
|
||||||
{__('New Channel')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Menu>
|
|
||||||
<MenuButton
|
|
||||||
aria-label={__('Settings')}
|
|
||||||
title={__('Settings')}
|
|
||||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
|
||||||
// @if TARGET='app'
|
|
||||||
onDoubleClick={e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
// @endif
|
|
||||||
>
|
|
||||||
<Icon size={18} icon={ICONS.SETTINGS} aria-hidden />
|
|
||||||
</MenuButton>
|
|
||||||
<MenuList className="menu__list--header">
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.SETTINGS}`)}>
|
|
||||||
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
|
|
||||||
{__('Settings')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.HELP}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.HELP} />
|
|
||||||
{__('Help')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
|
|
||||||
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
|
|
||||||
{currentTheme === 'light' ? __('Dark') : __('Light')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
|
|
||||||
{(authenticated || !IS_WEB) && (
|
|
||||||
<Menu>
|
|
||||||
<MenuButton
|
|
||||||
aria-label={__('Your account')}
|
|
||||||
title={__('Your account')}
|
|
||||||
className={classnames('header__navigation-item', {
|
|
||||||
'menu__title header__navigation-item--icon': !channelUrl,
|
|
||||||
'header__navigation-item--profile-pic': channelUrl,
|
|
||||||
})}
|
|
||||||
// @if TARGET='app'
|
|
||||||
onDoubleClick={e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
// @endif
|
|
||||||
>
|
|
||||||
{channelUrl ? (
|
|
||||||
<ChannelThumbnail uri={channelUrl} />
|
|
||||||
) : (
|
|
||||||
<Icon size={18} icon={ICONS.ACCOUNT} aria-hidden />
|
|
||||||
)}
|
|
||||||
</MenuButton>
|
|
||||||
<MenuList className="menu__list--header">
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.UPLOADS}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
|
||||||
{__('Uploads')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.CHANNELS}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
|
||||||
{__('Channels')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
className="menu__link"
|
|
||||||
onSelect={() => history.push(`/$/${PAGES.CREATOR_DASHBOARD}`)}
|
|
||||||
>
|
|
||||||
<Icon aria-hidden icon={ICONS.ANALYTICS} />
|
|
||||||
{__('Creator Analytics')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.REWARDS}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.REWARDS} />
|
|
||||||
{__('Rewards')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.INVITE}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.INVITE} />
|
|
||||||
{__('Invites')}
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
{authenticated ? (
|
|
||||||
<MenuItem onSelect={IS_WEB ? signOut : openSignOutModal}>
|
|
||||||
<div className="menu__link">
|
|
||||||
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
|
|
||||||
{__('Sign Out')}
|
|
||||||
</div>
|
|
||||||
<span className="menu__link-help">{email}</span>
|
|
||||||
</MenuItem>
|
|
||||||
) : !IS_WEB ? (
|
|
||||||
<>
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.AUTH}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.SIGN_UP} />
|
|
||||||
{__('Sign Up')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.AUTH_SIGNIN}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.SIGN_IN} />
|
|
||||||
{__('Sign In')}
|
|
||||||
</MenuItem>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -408,7 +291,7 @@ const Header = (props: Props) => {
|
||||||
button="link"
|
button="link"
|
||||||
aria-label={__('Your wallet')}
|
aria-label={__('Your wallet')}
|
||||||
navigate={`/$/${PAGES.WALLET}`}
|
navigate={`/$/${PAGES.WALLET}`}
|
||||||
className="header__navigation-item menu__title header__navigation-item--balance"
|
className="header__navigation-item menu__title header__navigation-item--balance mobile-hidden"
|
||||||
label={getWalletTitle()}
|
label={getWalletTitle()}
|
||||||
icon={ICONS.LBC}
|
icon={ICONS.LBC}
|
||||||
iconSize={20}
|
iconSize={20}
|
||||||
|
@ -452,4 +335,156 @@ const Header = (props: Props) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type HeaderMenuButtonProps = {
|
||||||
|
authenticated: boolean,
|
||||||
|
notificationsEnabled: boolean,
|
||||||
|
history: { push: string => void },
|
||||||
|
handleThemeToggle: string => void,
|
||||||
|
currentTheme: string,
|
||||||
|
channelUrl: ?string,
|
||||||
|
openSignOutModal: () => void,
|
||||||
|
email: ?string,
|
||||||
|
signOut: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
||||||
|
const {
|
||||||
|
authenticated,
|
||||||
|
notificationsEnabled,
|
||||||
|
history,
|
||||||
|
handleThemeToggle,
|
||||||
|
currentTheme,
|
||||||
|
channelUrl,
|
||||||
|
openSignOutModal,
|
||||||
|
email,
|
||||||
|
signOut,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="header__buttons">
|
||||||
|
{(authenticated || !IS_WEB) && (
|
||||||
|
<Menu>
|
||||||
|
<MenuButton
|
||||||
|
aria-label={__('Publish a file, or create a channel')}
|
||||||
|
title={__('Publish a file, or create a channel')}
|
||||||
|
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
|
||||||
|
// @if TARGET='app'
|
||||||
|
onDoubleClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
// @endif
|
||||||
|
>
|
||||||
|
<Icon size={18} icon={ICONS.PUBLISH} aria-hidden />
|
||||||
|
</MenuButton>
|
||||||
|
|
||||||
|
<MenuList className="menu__list--header">
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.UPLOAD}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||||
|
{__('Upload')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.CHANNEL_NEW}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
||||||
|
{__('New Channel')}
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{notificationsEnabled && <NotificationHeaderButton />}
|
||||||
|
|
||||||
|
<Menu>
|
||||||
|
<MenuButton
|
||||||
|
aria-label={__('Settings')}
|
||||||
|
title={__('Settings')}
|
||||||
|
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
|
||||||
|
// @if TARGET='app'
|
||||||
|
onDoubleClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
// @endif
|
||||||
|
>
|
||||||
|
<Icon size={18} icon={ICONS.SETTINGS} aria-hidden />
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList className="menu__list--header">
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.SETTINGS}`)}>
|
||||||
|
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
|
||||||
|
{__('Settings')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.HELP}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.HELP} />
|
||||||
|
{__('Help')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
|
||||||
|
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
|
||||||
|
{currentTheme === 'light' ? __('Dark') : __('Light')}
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
{(authenticated || !IS_WEB) && (
|
||||||
|
<Menu>
|
||||||
|
<MenuButton
|
||||||
|
aria-label={__('Your account')}
|
||||||
|
title={__('Your account')}
|
||||||
|
className={classnames('header__navigation-item mobile-hidden', {
|
||||||
|
'menu__title header__navigation-item--icon': !channelUrl,
|
||||||
|
'header__navigation-item--profile-pic': channelUrl,
|
||||||
|
})}
|
||||||
|
// @if TARGET='app'
|
||||||
|
onDoubleClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
// @endif
|
||||||
|
>
|
||||||
|
{channelUrl ? <ChannelThumbnail uri={channelUrl} /> : <Icon size={18} icon={ICONS.ACCOUNT} aria-hidden />}
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList className="menu__list--header">
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.UPLOADS}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||||
|
{__('Uploads')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.CHANNELS}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
||||||
|
{__('Channels')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.CREATOR_DASHBOARD}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.ANALYTICS} />
|
||||||
|
{__('Creator Analytics')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.REWARDS}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.REWARDS} />
|
||||||
|
{__('Rewards')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.INVITE}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.INVITE} />
|
||||||
|
{__('Invites')}
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
{authenticated ? (
|
||||||
|
<MenuItem onSelect={IS_WEB ? signOut : openSignOutModal}>
|
||||||
|
<div className="menu__link">
|
||||||
|
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
|
||||||
|
{__('Sign Out')}
|
||||||
|
</div>
|
||||||
|
<span className="menu__link-help">{email}</span>
|
||||||
|
</MenuItem>
|
||||||
|
) : !IS_WEB ? (
|
||||||
|
<>
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.AUTH}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.SIGN_UP} />
|
||||||
|
{__('Sign Up')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.AUTH_SIGNIN}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.SIGN_IN} />
|
||||||
|
{__('Sign In')}
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default withRouter(Header);
|
export default withRouter(Header);
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default function NotificationHeaderButton(props: Props) {
|
||||||
onClick={handleMenuClick}
|
onClick={handleMenuClick}
|
||||||
aria-label={__('Notifications')}
|
aria-label={__('Notifications')}
|
||||||
title={__('Notifications')}
|
title={__('Notifications')}
|
||||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
|
||||||
>
|
>
|
||||||
<Icon size={18} icon={ICONS.NOTIFICATION} aria-hidden />
|
<Icon size={18} icon={ICONS.NOTIFICATION} aria-hidden />
|
||||||
<NotificationBubble />
|
<NotificationBubble />
|
||||||
|
|
|
@ -1,26 +1,10 @@
|
||||||
|
import * as MODALS from 'constants/modal_types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectLanguage, makeSelectClientSetting } from 'redux/selectors/settings';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
|
||||||
import { doSearch } from 'redux/actions/search';
|
|
||||||
import { withRouter } from 'react-router';
|
|
||||||
import { doResolveUris, SETTINGS } from 'lbry-redux';
|
|
||||||
import analytics from 'analytics';
|
|
||||||
import Wunderbar from './view';
|
import Wunderbar from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
|
||||||
language: selectLanguage(state),
|
|
||||||
showMature: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
const perform = (dispatch, ownProps) => ({
|
const perform = (dispatch, ownProps) => ({
|
||||||
doResolveUris: uris => dispatch(doResolveUris(uris)),
|
doOpenMobileSearch: () => dispatch(doOpenModal(MODALS.MOBILE_SEARCH)),
|
||||||
doSearch: (query, options) => dispatch(doSearch(query, options)),
|
|
||||||
navigateToSearchPage: query => {
|
|
||||||
let encodedQuery = encodeURIComponent(query);
|
|
||||||
ownProps.history.push({ pathname: `/$/search`, search: `?q=${encodedQuery}` });
|
|
||||||
analytics.apiLogSearch();
|
|
||||||
},
|
|
||||||
doShowSnackBar: message => dispatch(doToast({ isError: true, message })),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withRouter(connect(select, perform)(Wunderbar));
|
export default connect(null, perform)(Wunderbar);
|
||||||
|
|
|
@ -1,184 +1,21 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { URL, URL_LOCAL, URL_DEV } from 'config';
|
|
||||||
import * as PAGES from 'constants/pages';
|
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Icon from 'component/common/icon';
|
|
||||||
import { isURIValid, normalizeURI, parseURI } from 'lbry-redux';
|
|
||||||
import { Combobox, ComboboxInput, ComboboxPopover, ComboboxList, ComboboxOption } from '@reach/combobox';
|
|
||||||
import '@reach/combobox/styles.css';
|
|
||||||
import useLighthouse from 'effects/use-lighthouse';
|
|
||||||
import { Form } from 'component/common/form';
|
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import WunderbarTopSuggestion from 'component/wunderbarTopSuggestion';
|
import { useIsMobile } from 'effects/use-screensize';
|
||||||
import WunderbarSuggestion from 'component/wunderbarSuggestion';
|
import WunderbarSuggestions from 'component/wunderbarSuggestions';
|
||||||
import { useHistory } from 'react-router';
|
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
|
||||||
import useThrottle from 'effects/use-throttle';
|
|
||||||
|
|
||||||
const WEB_DEV_PREFIX = `${URL_DEV}/`;
|
|
||||||
const WEB_LOCAL_PREFIX = `${URL_LOCAL}/`;
|
|
||||||
const WEB_PROD_PREFIX = `${URL}/`;
|
|
||||||
const SEARCH_PREFIX = `$/${PAGES.SEARCH}q=`;
|
|
||||||
const INVALID_URL_ERROR = "Invalid LBRY URL entered. Only A-Z, a-z, 0-9, and '-' allowed.";
|
|
||||||
|
|
||||||
const L_KEY_CODE = 76;
|
|
||||||
const ESC_KEY_CODE = 27;
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
searchQuery: ?string,
|
doOpenMobileSearch: () => void,
|
||||||
onSearch: string => void,
|
|
||||||
navigateToSearchPage: string => void,
|
|
||||||
doResolveUris: string => void,
|
|
||||||
doShowSnackBar: string => void,
|
|
||||||
showMature: boolean,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function WunderBar(props: Props) {
|
export default function WunderBar(props: Props) {
|
||||||
const { navigateToSearchPage, doShowSnackBar, doResolveUris, showMature } = props;
|
const { doOpenMobileSearch } = props;
|
||||||
const inputRef = React.useRef();
|
const isMobile = useIsMobile();
|
||||||
const {
|
|
||||||
push,
|
|
||||||
location: { search },
|
|
||||||
} = useHistory();
|
|
||||||
const urlParams = new URLSearchParams(search);
|
|
||||||
const queryFromUrl = urlParams.get('q') || '';
|
|
||||||
const [term, setTerm] = React.useState(queryFromUrl);
|
|
||||||
const throttledTerm = useThrottle(term, 500) || '';
|
|
||||||
const { results } = useLighthouse(throttledTerm, showMature);
|
|
||||||
const nameFromQuery = throttledTerm
|
|
||||||
.trim()
|
|
||||||
.replace(/\s+/g, '')
|
|
||||||
.replace(/:/g, '#');
|
|
||||||
const uriFromQuery = `lbry://${nameFromQuery}`;
|
|
||||||
let uriFromQueryIsValid = false;
|
|
||||||
let channelUrlForTopTest;
|
|
||||||
try {
|
|
||||||
const { isChannel } = parseURI(uriFromQuery);
|
|
||||||
uriFromQueryIsValid = true;
|
|
||||||
if (!isChannel) {
|
|
||||||
channelUrlForTopTest = `lbry://@${uriFromQuery}`;
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
const topUrisToTest = [uriFromQuery];
|
return isMobile ? (
|
||||||
if (channelUrlForTopTest) {
|
<Button icon={ICONS.SEARCH} className="wunderbar__mobile-search" onClick={() => doOpenMobileSearch()} />
|
||||||
topUrisToTest.push(uriFromQuery);
|
) : (
|
||||||
}
|
<WunderbarSuggestions />
|
||||||
|
|
||||||
function handleSelect(value) {
|
|
||||||
const includesLbryTvProd = value.includes(WEB_PROD_PREFIX);
|
|
||||||
const includesLbryTvLocal = value.includes(WEB_LOCAL_PREFIX);
|
|
||||||
const includesLbryTvDev = value.includes(WEB_DEV_PREFIX);
|
|
||||||
const wasCopiedFromWeb = includesLbryTvDev || includesLbryTvLocal || includesLbryTvProd;
|
|
||||||
const isLbryUrl = value.startsWith('lbry://');
|
|
||||||
|
|
||||||
if (inputRef.current) {
|
|
||||||
inputRef.current.blur();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wasCopiedFromWeb) {
|
|
||||||
let prefix = WEB_PROD_PREFIX;
|
|
||||||
if (includesLbryTvLocal) prefix = WEB_LOCAL_PREFIX;
|
|
||||||
if (includesLbryTvDev) prefix = WEB_DEV_PREFIX;
|
|
||||||
|
|
||||||
let query = value.slice(prefix.length).replace(/:/g, '#');
|
|
||||||
|
|
||||||
if (query.includes(SEARCH_PREFIX)) {
|
|
||||||
query = query.slice(SEARCH_PREFIX.length);
|
|
||||||
navigateToSearchPage(query);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const lbryUrl = `lbry://${query}`;
|
|
||||||
parseURI(lbryUrl);
|
|
||||||
const formattedLbryUrl = formatLbryUrlForWeb(lbryUrl);
|
|
||||||
push(formattedLbryUrl);
|
|
||||||
|
|
||||||
return;
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLbryUrl) {
|
|
||||||
navigateToSearchPage(value);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
if (isURIValid(value)) {
|
|
||||||
const uri = normalizeURI(value);
|
|
||||||
const normalizedWebUrl = formatLbryUrlForWeb(uri);
|
|
||||||
push(normalizedWebUrl);
|
|
||||||
} else {
|
|
||||||
doShowSnackBar(INVALID_URL_ERROR);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
navigateToSearchPage(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
function handleKeyDown(event) {
|
|
||||||
const { ctrlKey, metaKey, keyCode } = event;
|
|
||||||
|
|
||||||
if (!inputRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputRef.current === document.activeElement && keyCode === ESC_KEY_CODE) {
|
|
||||||
inputRef.current.blur();
|
|
||||||
}
|
|
||||||
|
|
||||||
// @if TARGET='app'
|
|
||||||
const shouldFocus =
|
|
||||||
process.platform === 'darwin' ? keyCode === L_KEY_CODE && metaKey : keyCode === L_KEY_CODE && ctrlKey;
|
|
||||||
if (shouldFocus) {
|
|
||||||
inputRef.current.focus();
|
|
||||||
}
|
|
||||||
// @endif
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
|
||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
||||||
}, [inputRef]);
|
|
||||||
|
|
||||||
const stringifiedResults = JSON.stringify(results);
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (stringifiedResults) {
|
|
||||||
const arrayResults = JSON.parse(stringifiedResults);
|
|
||||||
if (arrayResults && arrayResults.length > 0) {
|
|
||||||
doResolveUris(arrayResults);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [doResolveUris, stringifiedResults]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form className="wunderbar__wrapper" onSubmit={() => handleSelect(term)}>
|
|
||||||
<Combobox className="wunderbar" onSelect={handleSelect}>
|
|
||||||
<Icon icon={ICONS.SEARCH} />
|
|
||||||
<ComboboxInput
|
|
||||||
ref={inputRef}
|
|
||||||
className="wunderbar__input"
|
|
||||||
placeholder={__('Search')}
|
|
||||||
onChange={e => setTerm(e.target.value)}
|
|
||||||
value={term}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{results && results.length > 0 && (
|
|
||||||
<ComboboxPopover portal={false} className="wunderbar__suggestions">
|
|
||||||
<ComboboxList>
|
|
||||||
{uriFromQueryIsValid ? <WunderbarTopSuggestion query={nameFromQuery} /> : null}
|
|
||||||
|
|
||||||
<div className="wunderbar__label">{__('Search Results')}</div>
|
|
||||||
{results.slice(0, 5).map(uri => (
|
|
||||||
<WunderbarSuggestion key={uri} uri={uri} />
|
|
||||||
))}
|
|
||||||
<ComboboxOption value={term} className="wunderbar__more-results">
|
|
||||||
<Button button="link" label={__('View All Results')} />
|
|
||||||
</ComboboxOption>
|
|
||||||
</ComboboxList>
|
|
||||||
</ComboboxPopover>
|
|
||||||
)}
|
|
||||||
</Combobox>
|
|
||||||
</Form>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
29
ui/component/wunderbarSuggestions/index.js
Normal file
29
ui/component/wunderbarSuggestions/index.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import * as MODALS from 'constants/modal_types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectLanguage, makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
|
import { doToast } from 'redux/actions/notifications';
|
||||||
|
import { doSearch } from 'redux/actions/search';
|
||||||
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
|
import { doResolveUris, SETTINGS } from 'lbry-redux';
|
||||||
|
import analytics from 'analytics';
|
||||||
|
import Wunderbar from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
language: selectLanguage(state),
|
||||||
|
showMature: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = (dispatch, ownProps) => ({
|
||||||
|
doResolveUris: uris => dispatch(doResolveUris(uris)),
|
||||||
|
doSearch: (query, options) => dispatch(doSearch(query, options)),
|
||||||
|
navigateToSearchPage: query => {
|
||||||
|
let encodedQuery = encodeURIComponent(query);
|
||||||
|
ownProps.history.push({ pathname: `/$/search`, search: `?q=${encodedQuery}` });
|
||||||
|
analytics.apiLogSearch();
|
||||||
|
},
|
||||||
|
doShowSnackBar: message => dispatch(doToast({ isError: true, message })),
|
||||||
|
doOpenMobileSearch: () => dispatch(doOpenModal(MODALS.MOBILE_SEARCH)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withRouter(connect(select, perform)(Wunderbar));
|
214
ui/component/wunderbarSuggestions/view.jsx
Normal file
214
ui/component/wunderbarSuggestions/view.jsx
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
// @flow
|
||||||
|
import { URL, URL_LOCAL, URL_DEV } from 'config';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import Icon from 'component/common/icon';
|
||||||
|
import { isURIValid, normalizeURI, parseURI } from 'lbry-redux';
|
||||||
|
import { Combobox, ComboboxInput, ComboboxPopover, ComboboxList, ComboboxOption } from '@reach/combobox';
|
||||||
|
import '@reach/combobox/styles.css';
|
||||||
|
import useLighthouse from 'effects/use-lighthouse';
|
||||||
|
import { Form } from 'component/common/form';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import WunderbarTopSuggestion from 'component/wunderbarTopSuggestion';
|
||||||
|
import WunderbarSuggestion from 'component/wunderbarSuggestion';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import { formatLbryUrlForWeb } from 'util/url';
|
||||||
|
import useThrottle from 'effects/use-throttle';
|
||||||
|
import Yrbl from 'component/yrbl';
|
||||||
|
|
||||||
|
const WEB_DEV_PREFIX = `${URL_DEV}/`;
|
||||||
|
const WEB_LOCAL_PREFIX = `${URL_LOCAL}/`;
|
||||||
|
const WEB_PROD_PREFIX = `${URL}/`;
|
||||||
|
const SEARCH_PREFIX = `$/${PAGES.SEARCH}q=`;
|
||||||
|
const INVALID_URL_ERROR = "Invalid LBRY URL entered. Only A-Z, a-z, 0-9, and '-' allowed.";
|
||||||
|
|
||||||
|
const L_KEY_CODE = 76;
|
||||||
|
const ESC_KEY_CODE = 27;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
searchQuery: ?string,
|
||||||
|
onSearch: string => void,
|
||||||
|
navigateToSearchPage: string => void,
|
||||||
|
doResolveUris: string => void,
|
||||||
|
doShowSnackBar: string => void,
|
||||||
|
showMature: boolean,
|
||||||
|
isMobile: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function WunderBarSuggestions(props: Props) {
|
||||||
|
const { navigateToSearchPage, doShowSnackBar, doResolveUris, showMature, isMobile } = props;
|
||||||
|
const inputRef = React.useRef();
|
||||||
|
const {
|
||||||
|
push,
|
||||||
|
location: { search },
|
||||||
|
} = useHistory();
|
||||||
|
const urlParams = new URLSearchParams(search);
|
||||||
|
const queryFromUrl = urlParams.get('q') || '';
|
||||||
|
const [term, setTerm] = React.useState(queryFromUrl);
|
||||||
|
const throttledTerm = useThrottle(term, 500) || '';
|
||||||
|
const searchSize = isMobile ? 20 : 5;
|
||||||
|
const { results, loading } = useLighthouse(throttledTerm, showMature, searchSize);
|
||||||
|
const noResults = throttledTerm && !loading && results && results.length === 0;
|
||||||
|
const nameFromQuery = throttledTerm
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+/g, '')
|
||||||
|
.replace(/:/g, '#');
|
||||||
|
const uriFromQuery = `lbry://${nameFromQuery}`;
|
||||||
|
let uriFromQueryIsValid = false;
|
||||||
|
let channelUrlForTopTest;
|
||||||
|
try {
|
||||||
|
const { isChannel } = parseURI(uriFromQuery);
|
||||||
|
uriFromQueryIsValid = true;
|
||||||
|
if (!isChannel) {
|
||||||
|
channelUrlForTopTest = `lbry://@${uriFromQuery}`;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
const topUrisToTest = [uriFromQuery];
|
||||||
|
if (channelUrlForTopTest) {
|
||||||
|
topUrisToTest.push(uriFromQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelect(value) {
|
||||||
|
const includesLbryTvProd = value.includes(WEB_PROD_PREFIX);
|
||||||
|
const includesLbryTvLocal = value.includes(WEB_LOCAL_PREFIX);
|
||||||
|
const includesLbryTvDev = value.includes(WEB_DEV_PREFIX);
|
||||||
|
const wasCopiedFromWeb = includesLbryTvDev || includesLbryTvLocal || includesLbryTvProd;
|
||||||
|
const isLbryUrl = value.startsWith('lbry://');
|
||||||
|
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasCopiedFromWeb) {
|
||||||
|
let prefix = WEB_PROD_PREFIX;
|
||||||
|
if (includesLbryTvLocal) prefix = WEB_LOCAL_PREFIX;
|
||||||
|
if (includesLbryTvDev) prefix = WEB_DEV_PREFIX;
|
||||||
|
|
||||||
|
let query = value.slice(prefix.length).replace(/:/g, '#');
|
||||||
|
|
||||||
|
if (query.includes(SEARCH_PREFIX)) {
|
||||||
|
query = query.slice(SEARCH_PREFIX.length);
|
||||||
|
navigateToSearchPage(query);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const lbryUrl = `lbry://${query}`;
|
||||||
|
parseURI(lbryUrl);
|
||||||
|
const formattedLbryUrl = formatLbryUrlForWeb(lbryUrl);
|
||||||
|
push(formattedLbryUrl);
|
||||||
|
|
||||||
|
return;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLbryUrl) {
|
||||||
|
navigateToSearchPage(value);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
if (isURIValid(value)) {
|
||||||
|
const uri = normalizeURI(value);
|
||||||
|
const normalizedWebUrl = formatLbryUrlForWeb(uri);
|
||||||
|
push(normalizedWebUrl);
|
||||||
|
} else {
|
||||||
|
doShowSnackBar(INVALID_URL_ERROR);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
navigateToSearchPage(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
function handleKeyDown(event) {
|
||||||
|
const { ctrlKey, metaKey, keyCode } = event;
|
||||||
|
|
||||||
|
if (!inputRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputRef.current === document.activeElement && keyCode === ESC_KEY_CODE) {
|
||||||
|
inputRef.current.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
// @if TARGET='app'
|
||||||
|
const shouldFocus =
|
||||||
|
process.platform === 'darwin' ? keyCode === L_KEY_CODE && metaKey : keyCode === L_KEY_CODE && ctrlKey;
|
||||||
|
if (shouldFocus) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
// @endif
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
}, [inputRef]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isMobile && inputRef.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [inputRef, isMobile]);
|
||||||
|
|
||||||
|
const stringifiedResults = JSON.stringify(results);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (stringifiedResults) {
|
||||||
|
const arrayResults = JSON.parse(stringifiedResults);
|
||||||
|
if (arrayResults && arrayResults.length > 0) {
|
||||||
|
doResolveUris(arrayResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [doResolveUris, stringifiedResults]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form
|
||||||
|
className={classnames('wunderbar__wrapper', { 'wunderbar__wrapper--mobile': isMobile })}
|
||||||
|
onSubmit={() => handleSelect(term)}
|
||||||
|
>
|
||||||
|
<Combobox className="wunderbar" onSelect={handleSelect}>
|
||||||
|
<Icon icon={ICONS.SEARCH} />
|
||||||
|
<ComboboxInput
|
||||||
|
ref={inputRef}
|
||||||
|
className="wunderbar__input"
|
||||||
|
placeholder={__('Search')}
|
||||||
|
onChange={e => setTerm(e.target.value)}
|
||||||
|
value={term}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{results && results.length > 0 && (
|
||||||
|
<ComboboxPopover
|
||||||
|
portal={false}
|
||||||
|
className={classnames('wunderbar__suggestions', { 'wunderbar__suggestions--mobile': isMobile })}
|
||||||
|
>
|
||||||
|
<ComboboxList>
|
||||||
|
{uriFromQueryIsValid ? <WunderbarTopSuggestion query={nameFromQuery} /> : null}
|
||||||
|
|
||||||
|
<div className="wunderbar__label">{__('Search Results')}</div>
|
||||||
|
{results.slice(0, isMobile ? 20 : 5).map(uri => (
|
||||||
|
<WunderbarSuggestion key={uri} uri={uri} />
|
||||||
|
))}
|
||||||
|
<ComboboxOption value={term} className="wunderbar__more-results">
|
||||||
|
<Button button="link" label={__('View All Results')} />
|
||||||
|
</ComboboxOption>
|
||||||
|
</ComboboxList>
|
||||||
|
</ComboboxPopover>
|
||||||
|
)}
|
||||||
|
</Combobox>
|
||||||
|
</Form>
|
||||||
|
{isMobile && !term && (
|
||||||
|
<div className="main--empty">
|
||||||
|
<Yrbl subtitle={__('Search for something...')} alwaysShow />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isMobile && noResults && (
|
||||||
|
<div className="main--empty">
|
||||||
|
<Yrbl type="sad" subtitle={__('No results')} alwaysShow />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ type Props = {
|
||||||
type: string,
|
type: string,
|
||||||
className?: string,
|
className?: string,
|
||||||
actions?: Node,
|
actions?: Node,
|
||||||
|
alwaysShow?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
const yrblTypes = {
|
const yrblTypes = {
|
||||||
|
@ -23,13 +24,19 @@ export default class extends React.PureComponent<Props> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { title, subtitle, type, className, actions } = this.props;
|
const { title, subtitle, type, className, actions, alwaysShow = false } = this.props;
|
||||||
|
|
||||||
const image = yrblTypes[type];
|
const image = yrblTypes[type];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="yrbl__wrap">
|
<div className="yrbl__wrap">
|
||||||
<img alt="Friendly gerbil" className={classnames('yrbl', className)} src={`${image}`} />
|
<img
|
||||||
|
alt="Friendly gerbil"
|
||||||
|
className={classnames('yrbl', className, {
|
||||||
|
'yrbl--always-show': alwaysShow,
|
||||||
|
})}
|
||||||
|
src={`${image}`}
|
||||||
|
/>
|
||||||
<div>
|
<div>
|
||||||
{(title || subtitle) && (
|
{(title || subtitle) && (
|
||||||
<div className="yrbl__content">
|
<div className="yrbl__content">
|
||||||
|
|
|
@ -43,3 +43,4 @@ export const CONFIRM_AGE = 'confirm_age';
|
||||||
export const SYNC_ENABLE = 'SYNC_ENABLE';
|
export const SYNC_ENABLE = 'SYNC_ENABLE';
|
||||||
export const REMOVE_BLOCKED = 'remove_blocked';
|
export const REMOVE_BLOCKED = 'remove_blocked';
|
||||||
export const IMAGE_UPLOAD = 'image_upload';
|
export const IMAGE_UPLOAD = 'image_upload';
|
||||||
|
export const MOBILE_SEARCH = 'mobile_search';
|
||||||
|
|
|
@ -7,7 +7,7 @@ import useThrottle from './use-throttle';
|
||||||
export default function useLighthouse(query: string, showMature?: boolean, size?: number = 5) {
|
export default function useLighthouse(query: string, showMature?: boolean, size?: number = 5) {
|
||||||
const [results, setResults] = React.useState();
|
const [results, setResults] = React.useState();
|
||||||
const [loading, setLoading] = React.useState();
|
const [loading, setLoading] = React.useState();
|
||||||
const queryString = getSearchQueryString(query, { nsfw: showMature, size });
|
const queryString = query ? getSearchQueryString(query, { nsfw: showMature, size }) : '';
|
||||||
const throttledQuery = useThrottle(queryString, 500);
|
const throttledQuery = useThrottle(queryString, 500);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
9
ui/modal/modalMobileSearch/index.js
Normal file
9
ui/modal/modalMobileSearch/index.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doHideModal } from 'redux/actions/app';
|
||||||
|
import ModalMobileSearch from './view';
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
closeModal: () => dispatch(doHideModal()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(null, perform)(ModalMobileSearch);
|
18
ui/modal/modalMobileSearch/view.jsx
Normal file
18
ui/modal/modalMobileSearch/view.jsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { Modal } from 'modal/modal';
|
||||||
|
import WunderbarSuggestions from 'component/wunderbarSuggestions';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
closeModal: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ModalMobileSearch(props: Props) {
|
||||||
|
const { closeModal } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal onAborted={closeModal} isOpen type="card">
|
||||||
|
<WunderbarSuggestions isMobile />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
|
@ -41,6 +41,7 @@ import ModalConfirmAge from 'modal/modalConfirmAge';
|
||||||
import ModalFileSelection from 'modal/modalFileSelection';
|
import ModalFileSelection from 'modal/modalFileSelection';
|
||||||
import ModalSyncEnable from 'modal/modalSyncEnable';
|
import ModalSyncEnable from 'modal/modalSyncEnable';
|
||||||
import ModalImageUpload from 'modal/modalImageUpload';
|
import ModalImageUpload from 'modal/modalImageUpload';
|
||||||
|
import ModalMobileSearch from 'modal/modalMobileSearch';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modal: { id: string, modalProps: {} },
|
modal: { id: string, modalProps: {} },
|
||||||
|
@ -53,6 +54,8 @@ function ModalRouter(props: Props) {
|
||||||
const { modal, error, location, hideModal } = props;
|
const { modal, error, location, hideModal } = props;
|
||||||
const { pathname } = location;
|
const { pathname } = location;
|
||||||
|
|
||||||
|
// return <ModalMobileSearch />;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
hideModal();
|
hideModal();
|
||||||
}, [pathname, hideModal]);
|
}, [pathname, hideModal]);
|
||||||
|
@ -146,6 +149,8 @@ function ModalRouter(props: Props) {
|
||||||
return <ModalImageUpload {...modalProps} />;
|
return <ModalImageUpload {...modalProps} />;
|
||||||
case MODALS.SYNC_ENABLE:
|
case MODALS.SYNC_ENABLE:
|
||||||
return <ModalSyncEnable {...modalProps} />;
|
return <ModalSyncEnable {...modalProps} />;
|
||||||
|
case MODALS.MOBILE_SEARCH:
|
||||||
|
return <ModalMobileSearch {...modalProps} />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,7 +156,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
padding: var(--spacing-m) var(--spacing-s);
|
padding: 0.8rem 0.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,10 +50,6 @@
|
||||||
> .button:only-child {
|
> .button:only-child {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header__buttons {
|
.header__buttons {
|
||||||
|
@ -146,20 +142,6 @@
|
||||||
height: var(--height-button);
|
height: var(--height-button);
|
||||||
width: var(--height-button);
|
width: var(--height-button);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
|
||||||
.button__label {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header__navigation-item--button-mobile {
|
|
||||||
@media (max-width: $breakpoint-small) {
|
|
||||||
.button__label {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header__navigation-dropdown {
|
.header__navigation-dropdown {
|
||||||
|
@ -200,8 +182,12 @@
|
||||||
|
|
||||||
.header__center {
|
.header__center {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: flex-end;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header__auth-title {
|
.header__auth-title {
|
||||||
|
|
|
@ -47,6 +47,11 @@
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button--close {
|
||||||
|
z-index: 10000;
|
||||||
|
margin-top: var(--spacing-s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal--card-internal {
|
.modal--card-internal {
|
||||||
|
|
|
@ -5,6 +5,31 @@
|
||||||
margin-right: var(--spacing-s);
|
margin-right: var(--spacing-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wunderbar__wrapper--mobile {
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
height: var(--header-height);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.wunderbar {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wunderbar__input {
|
||||||
|
font-size: 16px; // https://stackoverflow.com/questions/2989263/disable-auto-zoom-in-input-text-tag-safari-on-iphone
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--color-text);
|
||||||
|
border-radius: 0;
|
||||||
|
margin-right: var(--spacing-l);
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.wunderbar {
|
.wunderbar {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -13,20 +38,20 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
font-size: var(--font-small);
|
font-size: var(--font-small);
|
||||||
height: var(--height-input);
|
height: var(--height-input);
|
||||||
|
padding-left: var(--spacing-s);
|
||||||
@media (max-width: $breakpoint-small) {
|
|
||||||
max-width: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .icon {
|
> .icon {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: var(--spacing-s);
|
left: var(--spacing-m);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
stroke: var(--color-input-placeholder);
|
stroke: var(--color-input-placeholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wunderbar--inline {
|
.wunderbar--inline {
|
||||||
|
@ -73,10 +98,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wunderbar__results {
|
|
||||||
margin-left: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wunderbar__suggestions {
|
.wunderbar__suggestions {
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -92,41 +113,48 @@
|
||||||
margin: 0 var(--spacing-s);
|
margin: 0 var(--spacing-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wunderbar__suggestions--mobile {
|
||||||
|
top: calc(var(--header-height) - var(--spacing-xs));
|
||||||
|
margin-top: var(--spacing-m);
|
||||||
|
padding: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
.wunderbar__top-claim {
|
.wunderbar__top-claim {
|
||||||
margin-bottom: var(--spacing-m);
|
margin-bottom: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wunderbar__label {
|
.wunderbar__label {
|
||||||
margin-bottom: var(--spacing-xs);
|
margin-bottom: var(--spacing-xs);
|
||||||
margin-left: var(--spacing-xs);
|
margin-left: var(--spacing-m);
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
margin-left: var(--spacing-xs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wunderbar__top-separator {
|
.wunderbar__top-separator {
|
||||||
margin: var(--spacing-s) 0;
|
margin: var(--spacing-s) 0;
|
||||||
width: 120%;
|
width: 100%;
|
||||||
transform: translateX(-10%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wunderbar__suggestion {
|
.wunderbar__suggestion {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 3rem;
|
padding: var(--spacing-s) 0;
|
||||||
|
margin-left: var(--spacing-m);
|
||||||
|
|
||||||
.media__thumb {
|
.media__thumb {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
$width: 3rem;
|
$width: 5rem;
|
||||||
@include handleClaimListGifThumbnail($width);
|
@include handleClaimListGifThumbnail($width);
|
||||||
width: $width;
|
width: $width;
|
||||||
height: calc(#{$width} * (9 / 16));
|
height: calc(#{$width} * (9 / 16));
|
||||||
margin-right: var(--spacing-s);
|
margin-right: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: $breakpoint-small) {
|
@media (min-width: $breakpoint-small) {
|
||||||
$width: 5rem;
|
margin-left: var(--spacing-s);
|
||||||
@include handleClaimListGifThumbnail($width);
|
|
||||||
width: $width;
|
|
||||||
height: calc(#{$width} * (9 / 16));
|
|
||||||
margin-right: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,17 +185,25 @@
|
||||||
|
|
||||||
.wunderbar__more-results {
|
.wunderbar__more-results {
|
||||||
margin-top: var(--spacing-xs);
|
margin-top: var(--spacing-xs);
|
||||||
|
margin-left: var(--spacing-m);
|
||||||
|
margin-bottom: var(--spacing-l);
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
margin-left: var(--spacing-s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wunderbar__placeholder-suggestion {
|
.wunderbar__placeholder-suggestion {
|
||||||
padding: var(--spacing-xs);
|
margin-bottom: var(--spacing-s);
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
padding: var(--spacing-s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wunderbar__placeholder-label {
|
.wunderbar__placeholder-label {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
margin-left: var(--spacing-xs);
|
|
||||||
margin-bottom: var(--spacing-m);
|
|
||||||
margin-top: var(--spacing-xs);
|
margin-top: var(--spacing-xs);
|
||||||
@include placeholder;
|
@include placeholder;
|
||||||
}
|
}
|
||||||
|
@ -185,8 +221,25 @@
|
||||||
@include placeholder;
|
@include placeholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-reach-combobox-option] {
|
.wunderbar__mobile-search {
|
||||||
|
@extend .button--alt;
|
||||||
|
@extend .header__navigation-item--icon;
|
||||||
padding: var(--spacing-xs);
|
padding: var(--spacing-xs);
|
||||||
|
|
||||||
|
.button__label {
|
||||||
|
color: var(--color-input-placeholder);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-reach-combobox-option] {
|
||||||
|
padding: 0;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.yrbl__wrap {
|
.yrbl__wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
.yrbl {
|
.yrbl {
|
||||||
display: none;
|
display: none;
|
||||||
|
height: 10rem;
|
||||||
|
|
||||||
@media (min-width: $breakpoint-small) {
|
@media (min-width: $breakpoint-small) {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -29,6 +30,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.yrbl--always-show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.yrbl__content {
|
.yrbl__content {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
--color-tab-text: var(--color-white);
|
--color-tab-text: var(--color-white);
|
||||||
--color-tabs-background: var(--color-card-background);
|
--color-tabs-background: var(--color-card-background);
|
||||||
--color-tab-divider: var(--color-white);
|
--color-tab-divider: var(--color-white);
|
||||||
--color-modal-background: var(--color-header-background);
|
--color-modal-background: var(--color-card-background);
|
||||||
--color-comment-menu: #6a6a6a;
|
--color-comment-menu: #6a6a6a;
|
||||||
--color-comment-menu-hovering: #e0e0e0;
|
--color-comment-menu-hovering: #e0e0e0;
|
||||||
--color-notice: #58563b;
|
--color-notice: #58563b;
|
||||||
|
|
Loading…
Reference in a new issue