mirror of
https://github.com/mastodon/mastodon.git
synced 2025-07-15 08:48:15 +00:00
Merge branch 'main' into mute-prefill
This commit is contained in:
commit
1b0122631e
|
@ -11,6 +11,7 @@ const meta = {
|
||||||
compact: false,
|
compact: false,
|
||||||
dangerous: false,
|
dangerous: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
loading: false,
|
||||||
onClick: fn(),
|
onClick: fn(),
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
|
@ -41,16 +42,6 @@ const buttonTest: Story['play'] = async ({ args, canvas, userEvent }) => {
|
||||||
await expect(args.onClick).toHaveBeenCalled();
|
await expect(args.onClick).toHaveBeenCalled();
|
||||||
};
|
};
|
||||||
|
|
||||||
const disabledButtonTest: Story['play'] = async ({
|
|
||||||
args,
|
|
||||||
canvas,
|
|
||||||
userEvent,
|
|
||||||
}) => {
|
|
||||||
const button = await canvas.findByRole('button');
|
|
||||||
await userEvent.click(button);
|
|
||||||
await expect(args.onClick).not.toHaveBeenCalled();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Primary: Story = {
|
export const Primary: Story = {
|
||||||
args: {
|
args: {
|
||||||
children: 'Primary button',
|
children: 'Primary button',
|
||||||
|
@ -82,6 +73,18 @@ export const Dangerous: Story = {
|
||||||
play: buttonTest,
|
play: buttonTest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const disabledButtonTest: Story['play'] = async ({
|
||||||
|
args,
|
||||||
|
canvas,
|
||||||
|
userEvent,
|
||||||
|
}) => {
|
||||||
|
const button = await canvas.findByRole('button');
|
||||||
|
await userEvent.click(button);
|
||||||
|
// Disabled controls can't be focused
|
||||||
|
await expect(button).not.toHaveFocus();
|
||||||
|
await expect(args.onClick).not.toHaveBeenCalled();
|
||||||
|
};
|
||||||
|
|
||||||
export const PrimaryDisabled: Story = {
|
export const PrimaryDisabled: Story = {
|
||||||
args: {
|
args: {
|
||||||
...Primary.args,
|
...Primary.args,
|
||||||
|
@ -97,3 +100,24 @@ export const SecondaryDisabled: Story = {
|
||||||
},
|
},
|
||||||
play: disabledButtonTest,
|
play: disabledButtonTest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadingButtonTest: Story['play'] = async ({
|
||||||
|
args,
|
||||||
|
canvas,
|
||||||
|
userEvent,
|
||||||
|
}) => {
|
||||||
|
const button = await canvas.findByRole('button', {
|
||||||
|
name: 'Primary button Loading…',
|
||||||
|
});
|
||||||
|
await userEvent.click(button);
|
||||||
|
await expect(button).toHaveFocus();
|
||||||
|
await expect(args.onClick).not.toHaveBeenCalled();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Loading: Story = {
|
||||||
|
args: {
|
||||||
|
...Primary.args,
|
||||||
|
loading: true,
|
||||||
|
},
|
||||||
|
play: loadingButtonTest,
|
||||||
|
};
|
||||||
|
|
|
@ -3,12 +3,15 @@ import { useCallback } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
|
|
||||||
interface BaseProps
|
interface BaseProps
|
||||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
||||||
block?: boolean;
|
block?: boolean;
|
||||||
secondary?: boolean;
|
secondary?: boolean;
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
dangerous?: boolean;
|
dangerous?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PropsChildren extends PropsWithChildren<BaseProps> {
|
interface PropsChildren extends PropsWithChildren<BaseProps> {
|
||||||
|
@ -34,6 +37,7 @@ export const Button: React.FC<Props> = ({
|
||||||
secondary,
|
secondary,
|
||||||
compact,
|
compact,
|
||||||
dangerous,
|
dangerous,
|
||||||
|
loading,
|
||||||
className,
|
className,
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
|
@ -42,13 +46,18 @@ export const Button: React.FC<Props> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const handleClick = useCallback<React.MouseEventHandler<HTMLButtonElement>>(
|
const handleClick = useCallback<React.MouseEventHandler<HTMLButtonElement>>(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (!disabled && onClick) {
|
if (disabled || loading) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (onClick) {
|
||||||
onClick(e);
|
onClick(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[disabled, onClick],
|
[disabled, loading, onClick],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const label = text ?? children;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={classNames('button', className, {
|
className={classNames('button', className, {
|
||||||
|
@ -56,14 +65,27 @@ export const Button: React.FC<Props> = ({
|
||||||
'button--compact': compact,
|
'button--compact': compact,
|
||||||
'button--block': block,
|
'button--block': block,
|
||||||
'button--dangerous': dangerous,
|
'button--dangerous': dangerous,
|
||||||
|
loading,
|
||||||
})}
|
})}
|
||||||
disabled={disabled}
|
// Disabled buttons can't have focus, so we don't really
|
||||||
|
// disable the button during loading
|
||||||
|
disabled={disabled && !loading}
|
||||||
|
aria-disabled={loading}
|
||||||
|
// If the loading prop is used, announce label changes
|
||||||
|
aria-live={loading !== undefined ? 'polite' : undefined}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
title={title}
|
title={title}
|
||||||
type={type}
|
type={type}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{text ?? children}
|
{loading ? (
|
||||||
|
<>
|
||||||
|
<span className='button__label-wrapper'>{label}</span>
|
||||||
|
<LoadingIndicator role='none' />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
label
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,14 +13,13 @@ interface Props extends React.SVGProps<SVGSVGElement> {
|
||||||
children?: never;
|
children?: never;
|
||||||
id: string;
|
id: string;
|
||||||
icon: IconProp;
|
icon: IconProp;
|
||||||
title?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Icon: React.FC<Props> = ({
|
export const Icon: React.FC<Props> = ({
|
||||||
id,
|
id,
|
||||||
icon: IconComponent,
|
icon: IconComponent,
|
||||||
className,
|
className,
|
||||||
title: titleProp,
|
'aria-label': ariaLabel,
|
||||||
...other
|
...other
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
@ -34,18 +33,19 @@ export const Icon: React.FC<Props> = ({
|
||||||
IconComponent = CheckBoxOutlineBlankIcon;
|
IconComponent = CheckBoxOutlineBlankIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ariaHidden = titleProp ? undefined : true;
|
const ariaHidden = ariaLabel ? undefined : true;
|
||||||
const role = !ariaHidden ? 'img' : undefined;
|
const role = !ariaHidden ? 'img' : undefined;
|
||||||
|
|
||||||
// Set the title to an empty string to remove the built-in SVG one if any
|
// Set the title to an empty string to remove the built-in SVG one if any
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
const title = titleProp || '';
|
const title = ariaLabel || '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconComponent
|
<IconComponent
|
||||||
className={classNames('icon', `icon-${id}`, className)}
|
className={classNames('icon', `icon-${id}`, className)}
|
||||||
title={title}
|
title={title}
|
||||||
aria-hidden={ariaHidden}
|
aria-hidden={ariaHidden}
|
||||||
|
aria-label={ariaLabel}
|
||||||
role={role}
|
role={role}
|
||||||
{...other}
|
{...other}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,15 +6,34 @@ const messages = defineMessages({
|
||||||
loading: { id: 'loading_indicator.label', defaultMessage: 'Loading…' },
|
loading: { id: 'loading_indicator.label', defaultMessage: 'Loading…' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const LoadingIndicator: React.FC = () => {
|
interface LoadingIndicatorProps {
|
||||||
|
/**
|
||||||
|
* Use role='none' to opt out of the current default role 'progressbar'
|
||||||
|
* and aria attributes which we should re-visit to check if they're appropriate.
|
||||||
|
* In Firefox the aria-label is not applied, instead an implied value of `50` is
|
||||||
|
* used as the label.
|
||||||
|
*/
|
||||||
|
role?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||||
|
role = 'progressbar',
|
||||||
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const a11yProps =
|
||||||
|
role === 'progressbar'
|
||||||
|
? ({
|
||||||
|
role,
|
||||||
|
'aria-busy': true,
|
||||||
|
'aria-live': 'polite',
|
||||||
|
} as const)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='loading-indicator'
|
className='loading-indicator'
|
||||||
role='progressbar'
|
{...a11yProps}
|
||||||
aria-busy
|
|
||||||
aria-live='polite'
|
|
||||||
aria-label={intl.formatMessage(messages.loading)}
|
aria-label={intl.formatMessage(messages.loading)}
|
||||||
>
|
>
|
||||||
<CircularProgress size={50} strokeWidth={6} />
|
<CircularProgress size={50} strokeWidth={6} />
|
||||||
|
|
|
@ -318,7 +318,7 @@ const PollOption: React.FC<PollOptionProps> = (props) => {
|
||||||
id='check'
|
id='check'
|
||||||
icon={CheckIcon}
|
icon={CheckIcon}
|
||||||
className='poll__voted__mark'
|
className='poll__voted__mark'
|
||||||
title={intl.formatMessage(messages.voted)}
|
aria-label={intl.formatMessage(messages.voted)}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -58,7 +58,7 @@ export const VisibilityIcon: React.FC<{ visibility: StatusVisibility }> = ({
|
||||||
<Icon
|
<Icon
|
||||||
id={visibilityIcon.icon}
|
id={visibilityIcon.icon}
|
||||||
icon={visibilityIcon.iconComponent}
|
icon={visibilityIcon.iconComponent}
|
||||||
title={visibilityIcon.text}
|
aria-label={visibilityIcon.text}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -768,7 +768,7 @@ export const AccountHeader: React.FC<{
|
||||||
<Icon
|
<Icon
|
||||||
id='lock'
|
id='lock'
|
||||||
icon={LockIcon}
|
icon={LockIcon}
|
||||||
title={intl.formatMessage(messages.account_locked)}
|
aria-label={intl.formatMessage(messages.account_locked)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,10 @@ import { length } from 'stringz';
|
||||||
|
|
||||||
import { missingAltTextModal } from 'mastodon/initial_state';
|
import { missingAltTextModal } from 'mastodon/initial_state';
|
||||||
|
|
||||||
import AutosuggestInput from '../../../components/autosuggest_input';
|
import AutosuggestInput from 'mastodon/components/autosuggest_input';
|
||||||
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
import AutosuggestTextarea from 'mastodon/components/autosuggest_textarea';
|
||||||
import { Button } from '../../../components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
|
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
|
||||||
import PollButtonContainer from '../containers/poll_button_container';
|
import PollButtonContainer from '../containers/poll_button_container';
|
||||||
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
||||||
|
@ -225,9 +226,8 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, onPaste, autoFocus, withoutNavigation, maxChars } = this.props;
|
const { intl, onPaste, autoFocus, withoutNavigation, maxChars, isSubmitting } = this.props;
|
||||||
const { highlighted } = this.state;
|
const { highlighted } = this.state;
|
||||||
const disabled = this.props.isSubmitting;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className='compose-form' onSubmit={this.handleSubmit}>
|
<form className='compose-form' onSubmit={this.handleSubmit}>
|
||||||
|
@ -246,7 +246,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
<AutosuggestInput
|
<AutosuggestInput
|
||||||
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
||||||
value={this.props.spoilerText}
|
value={this.props.spoilerText}
|
||||||
disabled={disabled}
|
disabled={isSubmitting}
|
||||||
onChange={this.handleChangeSpoilerText}
|
onChange={this.handleChangeSpoilerText}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
ref={this.setSpoilerText}
|
ref={this.setSpoilerText}
|
||||||
|
@ -268,7 +268,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
<AutosuggestTextarea
|
<AutosuggestTextarea
|
||||||
ref={this.textareaRef}
|
ref={this.textareaRef}
|
||||||
placeholder={intl.formatMessage(messages.placeholder)}
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
disabled={disabled}
|
disabled={isSubmitting}
|
||||||
value={this.props.text}
|
value={this.props.text}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
suggestions={this.props.suggestions}
|
suggestions={this.props.suggestions}
|
||||||
|
@ -305,9 +305,15 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
<Button
|
<Button
|
||||||
type='submit'
|
type='submit'
|
||||||
compact
|
compact
|
||||||
text={intl.formatMessage(this.props.isEditing ? messages.saveChanges : (this.props.isInReply ? messages.reply : messages.publish))}
|
|
||||||
disabled={!this.canSubmit()}
|
disabled={!this.canSubmit()}
|
||||||
/>
|
loading={isSubmitting}
|
||||||
|
>
|
||||||
|
{intl.formatMessage(
|
||||||
|
this.props.isEditing ?
|
||||||
|
messages.saveChanges :
|
||||||
|
(this.props.isInReply ? messages.reply : messages.publish)
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
|
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
|
||||||
|
clearSearch: { id: 'search.clear', defaultMessage: 'Clear search' },
|
||||||
placeholderSignedIn: {
|
placeholderSignedIn: {
|
||||||
id: 'search.search_or_paste',
|
id: 'search.search_or_paste',
|
||||||
defaultMessage: 'Search or paste URL',
|
defaultMessage: 'Search or paste URL',
|
||||||
|
@ -50,6 +51,34 @@ const unfocus = () => {
|
||||||
document.querySelector('.ui')?.parentElement?.focus();
|
document.querySelector('.ui')?.parentElement?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ClearButton: React.FC<{
|
||||||
|
onClick: () => void;
|
||||||
|
hasValue: boolean;
|
||||||
|
}> = ({ onClick, hasValue }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames('search__icon-wrapper', { 'has-value': hasValue })}
|
||||||
|
>
|
||||||
|
<Icon id='search' icon={SearchIcon} className='search__icon' />
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={onClick}
|
||||||
|
className='search__icon search__icon--clear-button'
|
||||||
|
tabIndex={hasValue ? undefined : -1}
|
||||||
|
aria-hidden={!hasValue}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
id='times-circle'
|
||||||
|
icon={CancelIcon}
|
||||||
|
aria-label={intl.formatMessage(messages.clearSearch)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface SearchOption {
|
interface SearchOption {
|
||||||
key: string;
|
key: string;
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
|
@ -380,6 +409,7 @@ export const Search: React.FC<{
|
||||||
setValue('');
|
setValue('');
|
||||||
setQuickActions([]);
|
setQuickActions([]);
|
||||||
setSelectedOption(-1);
|
setSelectedOption(-1);
|
||||||
|
unfocus();
|
||||||
}, [setValue, setQuickActions, setSelectedOption]);
|
}, [setValue, setQuickActions, setSelectedOption]);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
|
@ -474,19 +504,7 @@ export const Search: React.FC<{
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button type='button' className='search__icon' onClick={handleClear}>
|
<ClearButton hasValue={hasValue} onClick={handleClear} />
|
||||||
<Icon
|
|
||||||
id='search'
|
|
||||||
icon={SearchIcon}
|
|
||||||
className={hasValue ? '' : 'active'}
|
|
||||||
/>
|
|
||||||
<Icon
|
|
||||||
id='times-circle'
|
|
||||||
icon={CancelIcon}
|
|
||||||
className={hasValue ? 'active' : ''}
|
|
||||||
aria-label={intl.formatMessage(messages.placeholder)}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className='search__popout'>
|
<div className='search__popout'>
|
||||||
{!hasValue && (
|
{!hasValue && (
|
||||||
|
|
|
@ -804,6 +804,7 @@
|
||||||
"report_notification.categories.violation": "Rule violation",
|
"report_notification.categories.violation": "Rule violation",
|
||||||
"report_notification.categories.violation_sentence": "rule violation",
|
"report_notification.categories.violation_sentence": "rule violation",
|
||||||
"report_notification.open": "Open report",
|
"report_notification.open": "Open report",
|
||||||
|
"search.clear": "Clear search",
|
||||||
"search.no_recent_searches": "No recent searches",
|
"search.no_recent_searches": "No recent searches",
|
||||||
"search.placeholder": "Search",
|
"search.placeholder": "Search",
|
||||||
"search.quick_action.account_search": "Profiles matching {x}",
|
"search.quick_action.account_search": "Profiles matching {x}",
|
||||||
|
|
|
@ -249,6 +249,21 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
cursor: wait;
|
||||||
|
|
||||||
|
.button__label-wrapper {
|
||||||
|
// Hide the label only visually, so that
|
||||||
|
// it keeps its layout and accessibility
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-indicator {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
@ -4645,14 +4660,20 @@ a.status-card {
|
||||||
.icon-button .loading-indicator {
|
.icon-button .loading-indicator {
|
||||||
position: static;
|
position: static;
|
||||||
transform: none;
|
transform: none;
|
||||||
|
color: inherit;
|
||||||
|
|
||||||
.circular-progress {
|
.circular-progress {
|
||||||
color: $primary-text-color;
|
color: inherit;
|
||||||
width: 22px;
|
width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button--compact .loading-indicator .circular-progress {
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-button .loading-indicator .circular-progress {
|
.icon-button .loading-indicator .circular-progress {
|
||||||
color: lighten($ui-base-color, 26%);
|
color: lighten($ui-base-color, 26%);
|
||||||
width: 12px;
|
width: 12px;
|
||||||
|
@ -5649,18 +5670,47 @@ a.status-card {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search__icon {
|
.search__icon-wrapper {
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
padding: 0;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px + 2px;
|
top: 14px;
|
||||||
cursor: default;
|
display: grid;
|
||||||
pointer-events: none;
|
|
||||||
margin-inline-start: 16px - 2px;
|
margin-inline-start: 16px - 2px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.has-value) {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search__icon {
|
||||||
|
grid-area: 1 / 1;
|
||||||
|
transition: all 100ms linear;
|
||||||
|
transition-property: transform, opacity;
|
||||||
|
color: $darker-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search__icon.icon-search {
|
||||||
|
.has-value & {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search__icon--clear-button {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 100%;
|
||||||
|
|
||||||
&::-moz-focus-inner {
|
&::-moz-focus-inner {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
@ -5670,39 +5720,14 @@ a.status-card {
|
||||||
outline: 0 !important;
|
outline: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
&:focus-visible {
|
||||||
position: absolute;
|
box-shadow: 0 0 0 2px $ui-button-focus-outline-color;
|
||||||
top: 0;
|
}
|
||||||
inset-inline-start: 0;
|
|
||||||
|
&[aria-hidden='true'] {
|
||||||
|
pointer-events: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: all 100ms linear;
|
transform: rotate(-90deg);
|
||||||
transition-property: transform, opacity;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
color: $darker-text-color;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
pointer-events: auto;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-search {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
pointer-events: none;
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-times-circle {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user