mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Spinner: Change spinner icon when prefers-reduced-motion
is set (#87641)
* change spinner icon when prefers-reduced-motion is set * update mock * remove outdated comment * fix matchMedia mocks * update spinner aria label
This commit is contained in:
parent
58e554b67f
commit
9bf3adabd8
@ -6,6 +6,7 @@ import { GrafanaTheme2 } from '@grafana/data';
|
|||||||
|
|
||||||
import { useStyles2 } from '../../themes';
|
import { useStyles2 } from '../../themes';
|
||||||
import { IconSize, isIconSize } from '../../types';
|
import { IconSize, isIconSize } from '../../types';
|
||||||
|
import { t } from '../../utils/i18n';
|
||||||
import { Icon } from '../Icon/Icon';
|
import { Icon } from '../Icon/Icon';
|
||||||
import { getIconRoot, getIconSubDir } from '../Icon/utils';
|
import { getIconRoot, getIconSubDir } from '../Icon/utils';
|
||||||
|
|
||||||
@ -38,6 +39,8 @@ export const Spinner = ({
|
|||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const deprecatedStyles = useStyles2(getDeprecatedStyles, size);
|
const deprecatedStyles = useStyles2(getDeprecatedStyles, size);
|
||||||
|
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
const iconName = prefersReducedMotion ? 'hourglass' : 'spinner';
|
||||||
|
|
||||||
// this entire if statement is handling the deprecated size prop
|
// this entire if statement is handling the deprecated size prop
|
||||||
// TODO remove once we fully remove the deprecated type
|
// TODO remove once we fully remove the deprecated type
|
||||||
@ -80,7 +83,17 @@ export const Spinner = ({
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon className={cx('fa-spin', iconClassName)} name="spinner" size={size} aria-label="loading spinner" />
|
<Icon
|
||||||
|
className={cx(
|
||||||
|
{
|
||||||
|
'fa-spin': !prefersReducedMotion,
|
||||||
|
},
|
||||||
|
iconClassName
|
||||||
|
)}
|
||||||
|
name={iconName}
|
||||||
|
size={size}
|
||||||
|
aria-label={t('grafana-ui.spinner.aria-label', 'Loading')}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -30,11 +30,14 @@ const renderWithProvider = ({ initialState }: { initialState?: Partial<appTypes.
|
|||||||
|
|
||||||
describe('OrganisationSwitcher', () => {
|
describe('OrganisationSwitcher', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(window.matchMedia as jest.Mock).mockImplementation(() => ({
|
jest.spyOn(window, 'matchMedia').mockImplementation(
|
||||||
addEventListener: jest.fn(),
|
() =>
|
||||||
removeEventListener: jest.fn(),
|
({
|
||||||
matches: true,
|
addEventListener: jest.fn(),
|
||||||
}));
|
removeEventListener: jest.fn(),
|
||||||
|
matches: true,
|
||||||
|
}) as unknown as MediaQueryList
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only render if more than one organisations', () => {
|
it('should only render if more than one organisations', () => {
|
||||||
@ -80,11 +83,14 @@ describe('OrganisationSwitcher', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render a picker in mobile screen', () => {
|
it('should render a picker in mobile screen', () => {
|
||||||
(window.matchMedia as jest.Mock).mockImplementation(() => ({
|
jest.spyOn(window, 'matchMedia').mockImplementation(
|
||||||
addEventListener: jest.fn(),
|
() =>
|
||||||
removeEventListener: jest.fn(),
|
({
|
||||||
matches: false,
|
addEventListener: jest.fn(),
|
||||||
}));
|
removeEventListener: jest.fn(),
|
||||||
|
matches: false,
|
||||||
|
}) as unknown as MediaQueryList
|
||||||
|
);
|
||||||
renderWithProvider({
|
renderWithProvider({
|
||||||
initialState: {
|
initialState: {
|
||||||
organization: {
|
organization: {
|
||||||
|
@ -14,11 +14,14 @@ const renderComponent = (options?: { props: TopSearchBarSectionProps }) => {
|
|||||||
|
|
||||||
describe('TopSearchBarSection', () => {
|
describe('TopSearchBarSection', () => {
|
||||||
it('should use a wrapper on non mobile screen', () => {
|
it('should use a wrapper on non mobile screen', () => {
|
||||||
(window.matchMedia as jest.Mock).mockImplementation(() => ({
|
jest.spyOn(window, 'matchMedia').mockImplementation(
|
||||||
addEventListener: jest.fn(),
|
() =>
|
||||||
removeEventListener: jest.fn(),
|
({
|
||||||
matches: true,
|
addEventListener: jest.fn(),
|
||||||
}));
|
removeEventListener: jest.fn(),
|
||||||
|
matches: true,
|
||||||
|
}) as unknown as MediaQueryList
|
||||||
|
);
|
||||||
|
|
||||||
const component = renderComponent();
|
const component = renderComponent();
|
||||||
|
|
||||||
@ -27,11 +30,14 @@ describe('TopSearchBarSection', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not use a wrapper on mobile screen', () => {
|
it('should not use a wrapper on mobile screen', () => {
|
||||||
(window.matchMedia as jest.Mock).mockImplementation(() => ({
|
jest.spyOn(window, 'matchMedia').mockImplementation(
|
||||||
addEventListener: jest.fn(),
|
() =>
|
||||||
removeEventListener: jest.fn(),
|
({
|
||||||
matches: false,
|
addEventListener: jest.fn(),
|
||||||
}));
|
removeEventListener: jest.fn(),
|
||||||
|
matches: false,
|
||||||
|
}) as unknown as MediaQueryList
|
||||||
|
);
|
||||||
|
|
||||||
const component = renderComponent();
|
const component = renderComponent();
|
||||||
|
|
||||||
|
@ -618,6 +618,9 @@
|
|||||||
"select": {
|
"select": {
|
||||||
"no-options-label": "No options found",
|
"no-options-label": "No options found",
|
||||||
"placeholder": "Choose"
|
"placeholder": "Choose"
|
||||||
|
},
|
||||||
|
"spinner": {
|
||||||
|
"aria-label": "Loading"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"graph": {
|
"graph": {
|
||||||
|
@ -618,6 +618,9 @@
|
|||||||
"select": {
|
"select": {
|
||||||
"no-options-label": "Ńő őpŧįőʼnş ƒőūʼnđ",
|
"no-options-label": "Ńő őpŧįőʼnş ƒőūʼnđ",
|
||||||
"placeholder": "Cĥőőşę"
|
"placeholder": "Cĥőőşę"
|
||||||
|
},
|
||||||
|
"spinner": {
|
||||||
|
"aria-label": "Ŀőäđįʼnģ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"graph": {
|
"graph": {
|
||||||
|
@ -34,19 +34,15 @@ global.grafanaBootData = {
|
|||||||
navTree: [],
|
navTree: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
window.matchMedia = (query) => ({
|
||||||
Object.defineProperty(global, 'matchMedia', {
|
matches: false,
|
||||||
writable: true,
|
media: query,
|
||||||
value: jest.fn().mockImplementation((query) => ({
|
onchange: null,
|
||||||
matches: false,
|
addListener: jest.fn(), // Deprecated
|
||||||
media: query,
|
removeListener: jest.fn(), // Deprecated
|
||||||
onchange: null,
|
addEventListener: jest.fn(),
|
||||||
addListener: jest.fn(), // deprecated
|
removeEventListener: jest.fn(),
|
||||||
removeListener: jest.fn(), // deprecated
|
dispatchEvent: jest.fn(),
|
||||||
addEventListener: jest.fn(),
|
|
||||||
removeEventListener: jest.fn(),
|
|
||||||
dispatchEvent: jest.fn(),
|
|
||||||
})),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
angular.module('grafana', ['ngRoute']);
|
angular.module('grafana', ['ngRoute']);
|
||||||
|
Loading…
Reference in New Issue
Block a user