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 hasBackout = Boolean(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 identityChannel;
|
||||
if (myChannels && myChannels.length >= 1) {
|
||||
|
@ -237,7 +237,7 @@ const Header = (props: Props) => {
|
|||
</span>
|
||||
)}
|
||||
<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'
|
||||
label={'LBRY'}
|
||||
// @endif
|
||||
|
@ -269,134 +269,17 @@ const Header = (props: Props) => {
|
|||
|
||||
{!authHeader && <WunderBar />}
|
||||
|
||||
<div className="header__buttons mobile-hidden">
|
||||
{(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"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @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>
|
||||
<HeaderMenuButtons
|
||||
authenticated={authenticated}
|
||||
notificationsEnabled={notificationsEnabled}
|
||||
history={history}
|
||||
handleThemeToggle={handleThemeToggle}
|
||||
currentTheme={currentTheme}
|
||||
channelUrl={channelUrl}
|
||||
openSignOutModal={openSignOutModal}
|
||||
email={email}
|
||||
signOut={signOut}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -408,7 +291,7 @@ const Header = (props: Props) => {
|
|||
button="link"
|
||||
aria-label={__('Your 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()}
|
||||
icon={ICONS.LBC}
|
||||
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);
|
||||
|
|
|
@ -41,7 +41,7 @@ export default function NotificationHeaderButton(props: Props) {
|
|||
onClick={handleMenuClick}
|
||||
aria-label={__('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 />
|
||||
<NotificationBubble />
|
||||
|
|
|
@ -1,26 +1,10 @@
|
|||
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 { withRouter } from 'react-router';
|
||||
import { doResolveUris, SETTINGS } from 'lbry-redux';
|
||||
import analytics from 'analytics';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
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));
|
||||
export default connect(null, perform)(Wunderbar);
|
||||
|
|
|
@ -1,184 +1,21 @@
|
|||
// @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 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';
|
||||
|
||||
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;
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
import WunderbarSuggestions from 'component/wunderbarSuggestions';
|
||||
|
||||
type Props = {
|
||||
searchQuery: ?string,
|
||||
onSearch: string => void,
|
||||
navigateToSearchPage: string => void,
|
||||
doResolveUris: string => void,
|
||||
doShowSnackBar: string => void,
|
||||
showMature: boolean,
|
||||
doOpenMobileSearch: () => void,
|
||||
};
|
||||
|
||||
export default function WunderBar(props: Props) {
|
||||
const { navigateToSearchPage, doShowSnackBar, doResolveUris, showMature } = 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 { 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 { doOpenMobileSearch } = props;
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
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]);
|
||||
|
||||
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>
|
||||
return isMobile ? (
|
||||
<Button icon={ICONS.SEARCH} className="wunderbar__mobile-search" onClick={() => doOpenMobileSearch()} />
|
||||
) : (
|
||||
<WunderbarSuggestions />
|
||||
);
|
||||
}
|
||||
|
|
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,
|
||||
className?: string,
|
||||
actions?: Node,
|
||||
alwaysShow?: boolean,
|
||||
};
|
||||
|
||||
const yrblTypes = {
|
||||
|
@ -23,13 +24,19 @@ export default class extends React.PureComponent<Props> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { title, subtitle, type, className, actions } = this.props;
|
||||
const { title, subtitle, type, className, actions, alwaysShow = false } = this.props;
|
||||
|
||||
const image = yrblTypes[type];
|
||||
|
||||
return (
|
||||
<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>
|
||||
{(title || subtitle) && (
|
||||
<div className="yrbl__content">
|
||||
|
|
|
@ -43,3 +43,4 @@ export const CONFIRM_AGE = 'confirm_age';
|
|||
export const SYNC_ENABLE = 'SYNC_ENABLE';
|
||||
export const REMOVE_BLOCKED = 'remove_blocked';
|
||||
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) {
|
||||
const [results, setResults] = 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);
|
||||
|
||||
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 ModalSyncEnable from 'modal/modalSyncEnable';
|
||||
import ModalImageUpload from 'modal/modalImageUpload';
|
||||
import ModalMobileSearch from 'modal/modalMobileSearch';
|
||||
|
||||
type Props = {
|
||||
modal: { id: string, modalProps: {} },
|
||||
|
@ -53,6 +54,8 @@ function ModalRouter(props: Props) {
|
|||
const { modal, error, location, hideModal } = props;
|
||||
const { pathname } = location;
|
||||
|
||||
// return <ModalMobileSearch />;
|
||||
|
||||
React.useEffect(() => {
|
||||
hideModal();
|
||||
}, [pathname, hideModal]);
|
||||
|
@ -146,6 +149,8 @@ function ModalRouter(props: Props) {
|
|||
return <ModalImageUpload {...modalProps} />;
|
||||
case MODALS.SYNC_ENABLE:
|
||||
return <ModalSyncEnable {...modalProps} />;
|
||||
case MODALS.MOBILE_SEARCH:
|
||||
return <ModalMobileSearch {...modalProps} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@
|
|||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
padding: var(--spacing-m) var(--spacing-s);
|
||||
padding: 0.8rem 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,10 +50,6 @@
|
|||
> .button:only-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header__buttons {
|
||||
|
@ -146,20 +142,6 @@
|
|||
height: 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 {
|
||||
|
@ -200,8 +182,12 @@
|
|||
|
||||
.header__center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.header__auth-title {
|
||||
|
|
|
@ -47,6 +47,11 @@
|
|||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.button--close {
|
||||
z-index: 10000;
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
.modal--card-internal {
|
||||
|
|
|
@ -5,6 +5,31 @@
|
|||
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 {
|
||||
cursor: text;
|
||||
display: flex;
|
||||
|
@ -13,20 +38,20 @@
|
|||
z-index: 1;
|
||||
font-size: var(--font-small);
|
||||
height: var(--height-input);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
}
|
||||
padding-left: var(--spacing-s);
|
||||
|
||||
> .icon {
|
||||
top: 0;
|
||||
left: var(--spacing-s);
|
||||
left: var(--spacing-m);
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
stroke: var(--color-input-placeholder);
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.wunderbar--inline {
|
||||
|
@ -73,10 +98,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.wunderbar__results {
|
||||
margin-left: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.wunderbar__suggestions {
|
||||
z-index: 3;
|
||||
position: absolute;
|
||||
|
@ -92,41 +113,48 @@
|
|||
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 {
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.wunderbar__label {
|
||||
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 {
|
||||
margin: var(--spacing-s) 0;
|
||||
width: 120%;
|
||||
transform: translateX(-10%);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wunderbar__suggestion {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 3rem;
|
||||
padding: var(--spacing-s) 0;
|
||||
margin-left: var(--spacing-m);
|
||||
|
||||
.media__thumb {
|
||||
flex-shrink: 0;
|
||||
$width: 3rem;
|
||||
$width: 5rem;
|
||||
@include handleClaimListGifThumbnail($width);
|
||||
width: $width;
|
||||
height: calc(#{$width} * (9 / 16));
|
||||
margin-right: var(--spacing-s);
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
$width: 5rem;
|
||||
@include handleClaimListGifThumbnail($width);
|
||||
width: $width;
|
||||
height: calc(#{$width} * (9 / 16));
|
||||
margin-right: var(--spacing-xs);
|
||||
}
|
||||
@media (min-width: $breakpoint-small) {
|
||||
margin-left: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,17 +185,25 @@
|
|||
|
||||
.wunderbar__more-results {
|
||||
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 {
|
||||
padding: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-s);
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
padding: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
.wunderbar__placeholder-label {
|
||||
width: 30%;
|
||||
height: 1rem;
|
||||
margin-left: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-m);
|
||||
margin-top: var(--spacing-xs);
|
||||
@include placeholder;
|
||||
}
|
||||
|
@ -185,8 +221,25 @@
|
|||
@include placeholder;
|
||||
}
|
||||
|
||||
[data-reach-combobox-option] {
|
||||
.wunderbar__mobile-search {
|
||||
@extend .button--alt;
|
||||
@extend .header__navigation-item--icon;
|
||||
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);
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.yrbl__wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column-reverse;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
text-align: left;
|
||||
|
@ -21,6 +21,7 @@
|
|||
|
||||
.yrbl {
|
||||
display: none;
|
||||
height: 10rem;
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
display: block;
|
||||
|
@ -29,6 +30,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.yrbl--always-show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.yrbl__content {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
--color-tab-text: var(--color-white);
|
||||
--color-tabs-background: var(--color-card-background);
|
||||
--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-hovering: #e0e0e0;
|
||||
--color-notice: #58563b;
|
||||
|
|
Loading…
Reference in a new issue