Merge pull request #1650 from lbryio/ux-fixes

UX changes
This commit is contained in:
Sean Yesmunt 2018-06-20 18:36:11 -04:00 committed by GitHub
commit 1ead6a3be7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 243 additions and 244 deletions

View file

@ -60,7 +60,7 @@ class IconComponent extends React.PureComponent<Props> {
} }
const inner = <Icon size={size} className="icon" color={color} />; const inner = <Icon size={size} className="icon" color={color} />;
return tooltip ? ( return tooltipText ? (
<Tooltip icon body={tooltipText} direction={tooltip}> <Tooltip icon body={tooltipText} direction={tooltip}>
{inner} {inner}
</Tooltip> </Tooltip>

View file

@ -5,10 +5,10 @@ import classnames from 'classnames';
type Props = { type Props = {
body: string, body: string,
label?: string, label?: string,
children: ?React.Node, children?: React.Node,
icon: ?boolean, icon?: boolean,
direction: string, direction: string,
onFormField?: boolean, onComponent?: boolean, // extra padding to account for button/form field size
}; };
class ToolTip extends React.PureComponent<Props> { class ToolTip extends React.PureComponent<Props> {
@ -17,9 +17,11 @@ class ToolTip extends React.PureComponent<Props> {
}; };
render() { render() {
const { children, label, body, icon, direction, onFormField } = this.props; const { children, label, body, icon, direction, onComponent } = this.props;
const tooltipContent = children || label; const tooltipContent = children || label;
const bodyLength = body.length;
const isShortDescription = bodyLength < 30;
return ( return (
<span <span
@ -30,11 +32,17 @@ class ToolTip extends React.PureComponent<Props> {
'tooltip--right': direction === 'right', 'tooltip--right': direction === 'right',
'tooltip--bottom': direction === 'bottom', 'tooltip--bottom': direction === 'bottom',
'tooltip--left': direction === 'left', 'tooltip--left': direction === 'left',
'tooltip--on-formfield': onFormField, 'tooltip--on-component': onComponent,
})} })}
> >
{tooltipContent} {tooltipContent}
<span className="tooltip__body">{body}</span> <span
className={classnames('tooltip__body', {
'tooltip__body--short': isShortDescription,
})}
>
{body}
</span>
</span> </span>
); );
} }

View file

@ -1,9 +1,9 @@
// @flow // @flow
import React from 'react'; import * as React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import { MODALS } from 'lbry-redux'; import { MODALS } from 'lbry-redux';
import classnames from 'classnames';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
import Tooltip from 'component/common/tooltip';
type FileInfo = { type FileInfo = {
claim_id: string, claim_id: string,
@ -15,34 +15,35 @@ type Props = {
openModal: ({ id: string }, { uri: string }) => void, openModal: ({ id: string }, { uri: string }) => void,
claimIsMine: boolean, claimIsMine: boolean,
fileInfo: FileInfo, fileInfo: FileInfo,
vertical?: boolean, // should the buttons be stacked vertically?
}; };
class FileActions extends React.PureComponent<Props> { class FileActions extends React.PureComponent<Props> {
render() { render() {
const { fileInfo, uri, openModal, claimIsMine, vertical, claimId } = this.props; const { fileInfo, uri, openModal, claimIsMine, claimId } = this.props;
const showDelete = fileInfo && Object.keys(fileInfo).length > 0; const showDelete = fileInfo && Object.keys(fileInfo).length > 0;
return ( return (
<section className={classnames('card__actions', { 'card__actions--vertical': vertical })}> <React.Fragment>
{showDelete && ( {showDelete && (
<Button <Tooltip onComponent body={__('Delete this file')}>
button="alt" <Button
icon={icons.TRASH} button="alt"
iconColor="red" icon={icons.TRASH}
label={__('Delete')} description={__('Delete')}
onClick={() => openModal({ id: MODALS.CONFIRM_FILE_REMOVE }, { uri })} onClick={() => openModal({ id: MODALS.CONFIRM_FILE_REMOVE }, { uri })}
/> />
</Tooltip>
)} )}
{!claimIsMine && ( {!claimIsMine && (
<Button <Tooltip onComponent body={__('Report content')}>
button="alt" <Button
icon={icons.REPORT} button="alt"
href={`https://lbry.io/dmca?claim_id=${claimId}`} icon={icons.REPORT}
label={__('Report content')} href={`https://lbry.io/dmca?claim_id=${claimId}`}
/> />
</Tooltip>
)} )}
</section> </React.Fragment>
); );
} }
} }

View file

@ -2,6 +2,7 @@
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
import ToolTip from 'component/common/tooltip';
type Props = { type Props = {
uri: string, uri: string,
@ -59,7 +60,7 @@ class FileDownloadLink extends React.PureComponent<Props> {
if (loading || downloading) { if (loading || downloading) {
const progress = const progress =
fileInfo && fileInfo.written_bytes fileInfo && fileInfo.written_bytes
? fileInfo.written_bytes / fileInfo.total_bytes * 100 ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100
: 0; : 0;
const label = fileInfo const label = fileInfo
? __('Downloading: ') + progress.toFixed(0) + __('% complete') ? __('Downloading: ') + progress.toFixed(0) + __('% complete')
@ -72,25 +73,22 @@ class FileDownloadLink extends React.PureComponent<Props> {
} }
return ( return (
<Button <ToolTip onComponent body={__('Download')}>
button="alt" <Button
label={__('Download')} button="alt"
icon={icons.DOWNLOAD} icon={icons.DOWNLOAD}
iconColor="purple" iconColor="purple"
onClick={() => { onClick={() => {
purchaseUri(uri); purchaseUri(uri);
}} }}
/> />
</ToolTip>
); );
} else if (fileInfo && fileInfo.download_path) { } else if (fileInfo && fileInfo.download_path) {
return ( return (
<Button <ToolTip onComponent body={__('Open file')}>
button="alt" <Button button="alt" iconColor="purple" icon={icons.LOCAL} onClick={() => openFile()} />
iconColor="purple" </ToolTip>
label={__('Open File')}
icon={icons.OPEN}
onClick={() => openFile()}
/>
); );
} }

View file

@ -1,10 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { makeSelectSearchUris, selectIsSearching, selectSearchDownloadUris } from 'lbry-redux';
doSearch,
makeSelectSearchUris,
selectIsSearching,
selectSearchDownloadUris,
} from 'lbry-redux';
import FileListSearch from './view'; import FileListSearch from './view';
const select = (state, props) => ({ const select = (state, props) => ({
@ -13,8 +8,9 @@ const select = (state, props) => ({
isSearching: selectIsSearching(state), isSearching: selectIsSearching(state),
}); });
const perform = dispatch => ({ const perform = () => ({});
search: search => dispatch(doSearch(search)),
});
export default connect(select, perform)(FileListSearch); export default connect(
select,
perform
)(FileListSearch);

View file

@ -3,14 +3,10 @@ import React from 'react';
import FileTile from 'component/fileTile'; import FileTile from 'component/fileTile';
import ChannelTile from 'component/channelTile'; import ChannelTile from 'component/channelTile';
import { parseURI } from 'lbry-redux'; import { parseURI } from 'lbry-redux';
import debounce from 'util/debounce';
const SEARCH_DEBOUNCE_TIME = 800;
const NoResults = () => <div className="file-tile">{__('No results')}</div>; const NoResults = () => <div className="file-tile">{__('No results')}</div>;
type Props = { type Props = {
search: string => void,
query: string, query: string,
isSearching: boolean, isSearching: boolean,
uris: ?Array<string>, uris: ?Array<string>,
@ -18,27 +14,6 @@ type Props = {
}; };
class FileListSearch extends React.PureComponent<Props> { class FileListSearch extends React.PureComponent<Props> {
constructor(props: Props) {
super(props);
this.debouncedSearch = debounce(this.props.search, SEARCH_DEBOUNCE_TIME);
}
componentDidMount() {
const { search, query } = this.props;
search(query);
}
componentWillReceiveProps(nextProps: Props) {
const { query: nextQuery } = nextProps;
const { query: currentQuerry } = this.props;
if (nextQuery !== currentQuerry) {
this.debouncedSearch(nextQuery);
}
}
debouncedSearch: string => void;
render() { render() {
const { uris, query, downloadUris, isSearching } = this.props; const { uris, query, downloadUris, isSearching } = this.props;

View file

@ -131,16 +131,14 @@ class ActiveShapeShift extends React.PureComponent<Props> {
href={`https://shapeshift.io/#/status/${shiftOrderId}`} href={`https://shapeshift.io/#/status/${shiftOrderId}`}
/> />
)} )}
{shiftState === statuses.NO_DEPOSITS &&
shiftReturnAddress && (
<div className="shapeshift__actions-help">
<span className="help">
If the transaction doesn't go through, ShapeShift will return your {shiftCoinType}{' '}
back to {shiftReturnAddress}
</span>
</div>
)}
</div> </div>
{shiftState === statuses.NO_DEPOSITS &&
shiftReturnAddress && (
<div className="help">
If the transaction doesn't go through, ShapeShift will return your {shiftCoinType}{' '}
back to {shiftReturnAddress}
</div>
)}
</div> </div>
); );
} }

View file

@ -8,6 +8,7 @@ type Props = {
errorMessage: ?string, errorMessage: ?string,
email: string, email: string,
isPending: boolean, isPending: boolean,
onModal?: boolean,
verifyUserEmail: (string, string) => void, verifyUserEmail: (string, string) => void,
verifyUserEmailFailure: string => void, verifyUserEmailFailure: string => void,
resendVerificationEmail: string => void, resendVerificationEmail: string => void,
@ -50,7 +51,7 @@ class UserEmailVerify extends React.PureComponent<Props, State> {
} }
render() { render() {
const { cancelButton, errorMessage, email, isPending } = this.props; const { cancelButton, errorMessage, email, isPending, onModal } = this.props;
return ( return (
<Form onSubmit={this.handleSubmit}> <Form onSubmit={this.handleSubmit}>
@ -77,12 +78,23 @@ class UserEmailVerify extends React.PureComponent<Props, State> {
<div className="card__actions"> <div className="card__actions">
<Submit label={__('Verify')} disabled={isPending} /> <Submit label={__('Verify')} disabled={isPending} />
{cancelButton} {cancelButton}
<Button {!onModal && (
button="link" <Button
label={__('Resend verification email')} button="link"
onClick={this.handleResendVerificationEmail} label={__('Resend verification email')}
/> onClick={this.handleResendVerificationEmail}
/>
)}
</div> </div>
{onModal && (
<div className="card__actions help">
<Button
button="link"
label={__('Resend verification email')}
onClick={this.handleResendVerificationEmail}
/>
</div>
)}
</Form> </Form>
); );
} }

View file

@ -66,9 +66,11 @@ class WalletAddress extends React.PureComponent<Props> {
/> />
</div> </div>
<div className="card__content"> {showQR && (
{showQR && <QRCode value={receiveAddress} paddingTop />} <div className="card__content">
</div> <QRCode value={receiveAddress} paddingTop />
</div>
)}
<div className="card__content"> <div className="card__content">
<div className="help"> <div className="help">

View file

@ -3,8 +3,6 @@ import {
selectSearchState as selectSearch, selectSearchState as selectSearch,
selectWunderBarAddress, selectWunderBarAddress,
doUpdateSearchQuery, doUpdateSearchQuery,
doNotify,
MODALS,
doFocusSearchInput, doFocusSearchInput,
doBlurSearchInput, doBlurSearchInput,
doSearch, doSearch,
@ -28,7 +26,7 @@ const select = state => {
const perform = dispatch => ({ const perform = dispatch => ({
onSearch: query => { onSearch: query => {
dispatch(doSearch(query)); dispatch(doSearch(query, 30)); // Hard coding this for now until https://github.com/lbryio/lbry-app/pull/1639 is merged
dispatch(doNavigate(`/search`, { query })); dispatch(doNavigate(`/search`, { query }));
}, },
onSubmit: (uri, extraParams) => dispatch(doNavigate('/show', { uri, ...extraParams })), onSubmit: (uri, extraParams) => dispatch(doNavigate('/show', { uri, ...extraParams })),
@ -37,4 +35,7 @@ const perform = dispatch => ({
doBlur: () => dispatch(doBlurSearchInput()), doBlur: () => dispatch(doBlurSearchInput()),
}); });
export default connect(select, perform)(Wunderbar); export default connect(
select,
perform
)(Wunderbar);

View file

@ -1,7 +1,7 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { normalizeURI } from 'lbry-redux'; import { normalizeURI, SEARCH_TYPES } from 'lbry-redux';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import { parseQueryParams } from 'util/query_params'; import { parseQueryParams } from 'util/query_params';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
@ -29,7 +29,7 @@ class WunderBar extends React.PureComponent<Props> {
getSuggestionIcon = (type: string) => { getSuggestionIcon = (type: string) => {
switch (type) { switch (type) {
case 'file': case 'file':
return icons.COMPASS; return icons.LOCAL;
case 'channel': case 'channel':
return icons.AT_SIGN; return icons.AT_SIGN;
default: default:
@ -109,7 +109,7 @@ class WunderBar extends React.PureComponent<Props> {
placeholder="Enter LBRY URL here or search for videos, music, games and more" placeholder="Enter LBRY URL here or search for videos, music, games and more"
/> />
)} )}
renderItem={({ value, type, shorthand }, isHighlighted) => ( renderItem={({ value, type }, isHighlighted) => (
<div <div
key={value} key={value}
className={classnames('wunderbar__suggestion', { className={classnames('wunderbar__suggestion', {
@ -117,11 +117,13 @@ class WunderBar extends React.PureComponent<Props> {
})} })}
> >
<Icon icon={this.getSuggestionIcon(type)} /> <Icon icon={this.getSuggestionIcon(type)} />
<span className="wunderbar__suggestion-label">{shorthand || value}</span> <span className="wunderbar__suggestion-label">{value}</span>
{(true || isHighlighted) && ( {isHighlighted && (
<span className="wunderbar__suggestion-label--action"> <span className="wunderbar__suggestion-label--action">
{'- '} {'- '}
{type === 'search' ? 'Search' : value} {type === SEARCH_TYPES.SEARCH && __('Search')}
{type === SEARCH_TYPES.CHANNEL && __('View channel')}
{type === SEARCH_TYPES.FILE && __('View file')}
</span> </span>
)} )}
</div> </div>

View file

@ -20,7 +20,7 @@ class ModalEmailCollection extends React.PureComponent<Props> {
if (user && !user.has_verified_email && !email) { if (user && !user.has_verified_email && !email) {
return <UserEmailNew cancelButton={cancelButton} />; return <UserEmailNew cancelButton={cancelButton} />;
} else if (user && !user.has_verified_email) { } else if (user && !user.has_verified_email) {
return <UserEmailVerify cancelButton={cancelButton} />; return <UserEmailVerify onModal cancelButton={cancelButton} />;
} }
return closeModal(); return closeModal();

View file

@ -167,10 +167,10 @@ class FilePage extends React.Component<Props> {
<div className="card__title-identity--file"> <div className="card__title-identity--file">
<h1 className="card__title card__title--file">{title}</h1> <h1 className="card__title card__title--file">{title}</h1>
<div className="card__title-identity-icons"> <div className="card__title-identity-icons">
<FilePrice uri={normalizeURI(uri)} />
{isRewardContent && ( {isRewardContent && (
<Icon iconColor="red" tooltip="bottom" icon={icons.FEATURED} /> <Icon iconColor="red" tooltip="bottom" icon={icons.FEATURED} />
)} )}
<FilePrice uri={normalizeURI(uri)} />
</div> </div>
</div> </div>
<span className="card__subtitle card__subtitle--file"> <span className="card__subtitle card__subtitle--file">
@ -180,60 +180,55 @@ class FilePage extends React.Component<Props> {
{metadata.nsfw && <div>NSFW</div>} {metadata.nsfw && <div>NSFW</div>}
<div className="card__channel-info"> <div className="card__channel-info">
<UriIndicator uri={uri} link /> <UriIndicator uri={uri} link />
<div className="card__actions card__actions--no-margin"> </div>
{claimIsMine ? ( <div className="card__actions card__actions--between">
<Button {(claimIsMine || subscriptionUri || speechSharable) && (
button="primary" <div className="card__actions">
icon={icons.EDIT} {claimIsMine ? (
label={__('Edit')} <Button
onClick={() => { button="primary"
prepareEdit(claim, editUri); icon={icons.EDIT}
navigate('/publish'); label={__('Edit')}
}} onClick={() => {
/> prepareEdit(claim, editUri);
) : ( navigate('/publish');
<SubscribeButton uri={subscriptionUri} channelName={channelName} /> }}
)} />
) : (
<SubscribeButton uri={subscriptionUri} channelName={channelName} />
)}
{!claimIsMine && (
<Button
button="alt"
icon="Send"
label={__('Enjoy this? Send a tip')}
onClick={() => openModal({ id: MODALS.SEND_TIP }, { uri })}
/>
)}
{speechSharable && (
<ViewOnWebButton claimId={claim.claim_id} claimName={claim.name} />
)}
</div>
)}
<div className="card__actions">
<FileDownloadLink uri={uri} />
<FileActions uri={uri} claimId={claim.claim_id} />
</div> </div>
</div> </div>
{(!claimIsMine || speechSharable) && ( <FormRow padded>
<div className="card__actions card__actions--end"> <ToolTip onComponent body={__('Automatically download and play free content.')}>
{!claimIsMine && ( <FormField
<Button useToggle
button="alt" name="autoplay"
icon="Send" type="checkbox"
label={__('Enjoy this? Send a tip')} postfix={__('Autoplay')}
onClick={() => openModal({ id: MODALS.SEND_TIP }, { uri })} checked={autoplay}
/> onChange={this.onAutoplayChange}
)} />
{speechSharable && ( </ToolTip>
<ViewOnWebButton claimId={claim.claim_id} claimName={claim.name} />
)}
</div>
)}
<FormRow alignRight padded>
<FormField
useToggle
name="autoplay"
type="checkbox"
checked={autoplay}
onChange={this.onAutoplayChange}
postfix={
<ToolTip
onFormField
label={__('Autoplay')}
body={__('Automatically download and play free content.')}
/>
}
/>
</FormRow> </FormRow>
</div> </div>
<div className="card__content">
<FileDownloadLink uri={uri} />
<FileActions uri={uri} claimId={claim.claim_id} />
</div>
<div className="card__content--extra-padding"> <div className="card__content--extra-padding">
<FileDetails uri={uri} /> <FileDetails uri={uri} />
</div> </div>

View file

@ -1,12 +1,11 @@
import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectIsSearching, selectSearchValue, doUpdateSearchQuery } from 'lbry-redux'; import { selectIsSearching, doUpdateSearchQuery, makeSelectCurrentParam } from 'lbry-redux';
import { doNavigate } from 'redux/actions/navigation'; import { doNavigate } from 'redux/actions/navigation';
import SearchPage from './view'; import SearchPage from './view';
const select = state => ({ const select = state => ({
isSearching: selectIsSearching(state), isSearching: selectIsSearching(state),
query: selectSearchValue(state), query: makeSelectCurrentParam('query')(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
@ -14,4 +13,7 @@ const perform = dispatch => ({
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)), updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)),
}); });
export default connect(select, perform)(SearchPage); export default connect(
select,
perform
)(SearchPage);

View file

@ -8,31 +8,11 @@ import Page from 'component/page';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
const MODAL_ANIMATION_TIME = 250;
type Props = { type Props = {
query: ?string, query: ?string,
}; };
class SearchPage extends React.PureComponent<Props> { class SearchPage extends React.PureComponent<Props> {
constructor() {
super();
this.input = null;
}
componentDidMount() {
// Wait for the modal to animate down before focusing
// without this there is an issue with scroll the page down
setTimeout(() => {
if (this.input) {
this.input.focus();
}
}, MODAL_ANIMATION_TIME);
}
input: ?HTMLInputElement;
render() { render() {
const { query } = this.props; const { query } = this.props;
return ( return (

View file

@ -190,15 +190,7 @@ p {
.main--contained { .main--contained {
max-width: 1000px; max-width: 1000px;
margin: 0 80px - $spacing-width; margin: auto;
@media only screen and (min-width: $medium-breakpoint) {
margin: 0 120px - $spacing-width;
}
@media only screen and (min-width: $large-breakpoint) {
margin: 0 200px - $spacing-width;
}
} }
.main--no-padding { .main--no-padding {

View file

@ -93,9 +93,10 @@
.card__title-identity--file { .card__title-identity--file {
display: flex; display: flex;
align-items: center; align-items: center;
.credit-amount, justify-content: space-between;
.icon { .icon {
margin: $spacing-vertical * 1/3; margin: 0 $spacing-vertical * 1/3;
} }
} }
@ -113,9 +114,9 @@
.card__title--small { .card__title--small {
font-size: 14px; font-size: 14px;
line-height: 18px; line-height: 18px;
padding-top: 20px; padding-top: $spacing-vertical / 3;
@media only screen and (min-width: $medium-breakpoint) { @media only screen and (min-width: $large-breakpoint) {
font-size: 16px; font-size: 16px;
} }
} }
@ -198,7 +199,7 @@
.card__subtext-title { .card__subtext-title {
color: var(--text-color); color: var(--text-color);
font-size: calc(var(--font-size-subtext-multiple) * 1.5em); font-size: calc(var(--font-size-subtext-multiple) * 1.2em);
&:not(:first-of-type) { &:not(:first-of-type) {
margin-top: $spacing-vertical * 3/2; margin-top: $spacing-vertical * 3/2;
@ -220,8 +221,12 @@
margin-top: $spacing-vertical * 2/3; margin-top: $spacing-vertical * 2/3;
display: flex; display: flex;
&:not(.card__actions--vertical) .btn:nth-child(n + 2) { &:not(.card__actions--vertical) {
margin-left: $spacing-vertical / 3; .btn:nth-child(n + 2),
// For buttons wrapped in a tooltip
.tooltip:nth-child(n + 2) {
margin-left: $spacing-vertical / 3;
}
} }
} }
@ -258,6 +263,11 @@
justify-content: flex-end; justify-content: flex-end;
} }
.card__actions--between {
justify-content: space-between;
flex-wrap: wrap;
}
/* /*
.card-row is used on the discover page .card-row is used on the discover page
It is a list of cards that extend past the right edge of the screen It is a list of cards that extend past the right edge of the screen

View file

@ -1,3 +1,4 @@
.file-download { .file-download {
font-size: 0.8em; font-size: 0.8em;
align-self: center;
} }

View file

@ -53,6 +53,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-items: flex-start; justify-items: flex-start;
align-items: center;
font-family: 'metropolis-medium'; font-family: 'metropolis-medium';
&:not(:first-of-type) { &:not(:first-of-type) {
@ -74,6 +75,9 @@
.wunderbar__suggestion-label--action { .wunderbar__suggestion-label--action {
margin-left: $spacing-vertical * 1/3; margin-left: $spacing-vertical * 1/3;
white-space: nowrap; white-space: nowrap;
font-family: 'metropolis-medium';
font-size: 12px;
line-height: 0.1; // to vertically align because the font size is smaller
} }
.wunderbar__active-suggestion { .wunderbar__active-suggestion {

View file

@ -13,43 +13,57 @@
} }
} }
.tooltip--on-formfield {
padding: 0;
}
.tooltip--icon { .tooltip--icon {
margin-top: 5px; margin-top: 5px;
} }
/* Tooltip text */ /* Tooltip text */
.tooltip .tooltip__body { .tooltip {
background-color: var(--tooltip-bg); .tooltip__body {
font-family: 'metropolis-medium'; background-color: var(--tooltip-bg);
font-size: 12px; font-family: 'metropolis-medium';
color: var(--tooltip-color); font-size: 12px;
border-radius: 8px; color: var(--tooltip-color);
position: absolute; border-radius: 8px;
z-index: 1; position: absolute;
width: 200px; z-index: 1;
text-align: center; width: 200px;
white-space: pre-wrap; text-align: center;
padding: $spacing-vertical * 1/3; white-space: pre-wrap;
visibility: hidden; padding: $spacing-vertical * 1/3;
visibility: hidden;
}
.tooltip__body--short {
width: 130px;
}
.tooltip__body::after {
content: ' ';
width: 0;
height: 0;
position: absolute;
border-width: 5px;
border-style: solid;
}
&.tooltip--on-component {
.tooltip__body {
margin-top: 10px;
}
}
} }
.tooltip .tooltip__body::after { .tooltip--top {
content: ' '; .tooltip__body {
width: 0; bottom: 100%;
height: 0; left: 50%;
position: absolute; margin-left: -100px;
border-width: 5px;
border-style: solid;
}
.tooltip--top .tooltip__body { &.tooltip__body--short {
bottom: 100%; margin-left: -65px;
left: 50%; }
margin-left: -100px; }
&::after { &::after {
top: 100%; top: 100%;
@ -59,28 +73,36 @@
} }
} }
.tooltip--right .tooltip__body { .tooltip--right {
margin-top: -5px; .tooltip__body {
margin-left: 10px;
&::after {
top: 17px;
right: 100%; /* To the left of the tooltip */
margin-top: -5px; margin-top: -5px;
border-color: transparent var(--tooltip-bg) transparent transparent; margin-left: 10px;
&::after {
top: 17px;
right: 100%; /* To the left of the tooltip */
margin-top: -5px;
border-color: transparent var(--tooltip-bg) transparent transparent;
}
} }
} }
.tooltip--bottom .tooltip__body { .tooltip--bottom {
top: 90%; .tooltip__body {
left: 50%; top: 90%;
margin-left: -100px;
&::after {
bottom: 100%;
left: 50%; left: 50%;
margin-left: -5px; margin-left: -100px;
border-color: transparent transparent var(--tooltip-bg) transparent;
&.tooltip__body--short {
margin-left: -65px;
}
&::after {
bottom: 100%;
left: 50%;
margin-left: -5px;
border-color: transparent transparent var(--tooltip-bg) transparent;
}
} }
} }