mirror of
https://github.com/qlik-oss/nebula.js.git
synced 2025-12-19 17:58:43 -05:00
feat: support screen reader in gridless sheet (#1756)
* feat: support screen reader on sheet * feat: support screen reader * fix: add hidden screen reader * fix: failed test case * fix: change to currentId * fix: remove unrelated change * fix: change to cell id * fix: comments * fix: add no title * fix: failed test cases * refactor: comments * fix: remove ?
This commit is contained in:
@@ -234,5 +234,145 @@
|
|||||||
"Visualization.Invalid.Measure": {
|
"Visualization.Invalid.Measure": {
|
||||||
"value": "Invalid measure",
|
"value": "Invalid measure",
|
||||||
"comment": "Status text shown when a measure is not valid"
|
"comment": "Status text shown when a measure is not valid"
|
||||||
|
},
|
||||||
|
"Object.AutoChart": {
|
||||||
|
"value": "Autochart",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.BarChart": {
|
||||||
|
"value": "Bar chart",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.ComboChart": {
|
||||||
|
"value": "Combo chart",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.Container": {
|
||||||
|
"value": "Container",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.DistributionPlot": {
|
||||||
|
"value": "Distribution plot",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.BoxPlot": {
|
||||||
|
"value": "Box plot",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.FilterPane": {
|
||||||
|
"value": "Filter pane",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.Gauge": {
|
||||||
|
"value": "Gauge",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.Histogram": {
|
||||||
|
"value": "Histogram",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.Kpi": {
|
||||||
|
"value": "KPI",
|
||||||
|
"comment": "Do not use more than a total of 19 characters when you translate this string."
|
||||||
|
},
|
||||||
|
"Object.LineChart": {
|
||||||
|
"value": "Line chart",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.Listbox": {
|
||||||
|
"value": "List box",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.PieChart": {
|
||||||
|
"value": "Pie chart",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.PivotTable": {
|
||||||
|
"value": "Pivot table",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.Map": {
|
||||||
|
"value": "Map",
|
||||||
|
"comment": "Visualization for geographical data"
|
||||||
|
},
|
||||||
|
"Object.ScatterPlot": {
|
||||||
|
"value": "Scatter plot",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.StraightTable": {
|
||||||
|
"value": "Straight table",
|
||||||
|
"comment": "Visualization in the library."
|
||||||
|
},
|
||||||
|
"Object.TextImage": {
|
||||||
|
"value": "Text & image",
|
||||||
|
"comment": "Visualization in the library. Renamed from Utility object to Text & image"
|
||||||
|
},
|
||||||
|
"Object.Treemap": {
|
||||||
|
"value": "Treemap",
|
||||||
|
"comment": ""
|
||||||
|
},
|
||||||
|
"Object.WaterfallChart":{
|
||||||
|
"value": "Waterfall chart",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.MekkoChart": {
|
||||||
|
"value": "Mekko chart",
|
||||||
|
"comment": "Visualization for?"
|
||||||
|
},
|
||||||
|
"Object.ActionButton": {
|
||||||
|
"value": "Button",
|
||||||
|
"comment": "Button for selections and navigation"
|
||||||
|
},
|
||||||
|
"Object.NavMenu": {
|
||||||
|
"value": "Navigation menu",
|
||||||
|
"comment": "A navigation menu for navigating sheets."
|
||||||
|
},
|
||||||
|
"Object.BulletChart": {
|
||||||
|
"value": "Bullet chart",
|
||||||
|
"comment": "Visualization for values using bars and ranges"
|
||||||
|
},
|
||||||
|
"Object.NlgChart": {
|
||||||
|
"value": "NL insights",
|
||||||
|
"comment": "Visualization in the library."
|
||||||
|
},
|
||||||
|
"Object.TabContainer": {
|
||||||
|
"value": "Tab container",
|
||||||
|
"comment": "Visualization in the library."
|
||||||
|
},
|
||||||
|
"Object.Table": {
|
||||||
|
"value": "Table",
|
||||||
|
"comment": "Visualization in the library"
|
||||||
|
},
|
||||||
|
"Object.Table.Deprecated": {
|
||||||
|
"value": "Table ",
|
||||||
|
"comment": "Visualization in the library that will be deprecated."
|
||||||
|
},
|
||||||
|
"Object.Text": {
|
||||||
|
"value": "Text",
|
||||||
|
"comment": "A chart used to display text, measures, add tables with rows and columns, etc."
|
||||||
|
},
|
||||||
|
"Object.LayoutContainer": {
|
||||||
|
"value": "Layout container",
|
||||||
|
"comment": "Visualization in the library."
|
||||||
|
},
|
||||||
|
"Object.GridChart": {
|
||||||
|
"value": "Grid chart",
|
||||||
|
"comment": "A chart used to present data in a matrix."
|
||||||
|
},
|
||||||
|
"Object.FunnelChart": {
|
||||||
|
"value": "Funnel chart",
|
||||||
|
"comment": "A chart often used to represent stages of a process."
|
||||||
|
},
|
||||||
|
"Object.SankeyChart": {
|
||||||
|
"value": "Sankey chart",
|
||||||
|
"comment": "A chart that shows the flow from a set of values to another."
|
||||||
|
},
|
||||||
|
"Object.RadarChart": {
|
||||||
|
"value": "Radar chart",
|
||||||
|
"comment": "A chart that displays multivariate data stacked at an axis with the same central point."
|
||||||
|
},
|
||||||
|
"Accessibility.Object.NoTitle": {
|
||||||
|
"value": "No title",
|
||||||
|
"comment": "Screen reader text for objects without a title"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import eventmixin from '../selections/event-mixin';
|
|||||||
import useStyling from '../hooks/useStyling';
|
import useStyling from '../hooks/useStyling';
|
||||||
import RenderError from '../utils/render-error';
|
import RenderError from '../utils/render-error';
|
||||||
import getPadding from '../utils/cell-padding';
|
import getPadding from '../utils/cell-padding';
|
||||||
|
import translationKeys from '../utils/extension-translation-keys';
|
||||||
|
import hiddenScreenReaderText from '../utils/style/screen-reader';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @interface
|
* @interface
|
||||||
@@ -583,6 +585,7 @@ const Cell = forwardRef(
|
|||||||
snPlugins={snPlugins}
|
snPlugins={snPlugins}
|
||||||
layout={layout}
|
layout={layout}
|
||||||
appLayout={appLayout}
|
appLayout={appLayout}
|
||||||
|
cellId={currentId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -607,7 +610,8 @@ const Cell = forwardRef(
|
|||||||
translator,
|
translator,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const translationKey = translationKeys.get(layout?.visualization);
|
||||||
|
const translation = translator.get(translationKey);
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
style={{
|
style={{
|
||||||
@@ -644,7 +648,15 @@ const Cell = forwardRef(
|
|||||||
...(useOldCellPadding ? { padding: theme.spacing(1) } : {}),
|
...(useOldCellPadding ? { padding: theme.spacing(1) } : {}),
|
||||||
...(state.longRunningQuery ? { opacity: '0.3' } : {}),
|
...(state.longRunningQuery ? { opacity: '0.3' } : {}),
|
||||||
}}
|
}}
|
||||||
|
aria-labelledby={`${currentId}_title ${currentId}_type ${currentId}_content`}
|
||||||
>
|
>
|
||||||
|
{layout && (
|
||||||
|
<div
|
||||||
|
id={`${currentId}_type`}
|
||||||
|
style={hiddenScreenReaderText}
|
||||||
|
aria-label={translation ?? layout.visualization}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{cellNode && layout && state.sn && (
|
{cellNode && layout && state.sn && (
|
||||||
<Header
|
<Header
|
||||||
layout={layout}
|
layout={layout}
|
||||||
@@ -654,6 +666,8 @@ const Cell = forwardRef(
|
|||||||
focusHandler={focusHandler.current}
|
focusHandler={focusHandler.current}
|
||||||
titleStyles={titleStyles}
|
titleStyles={titleStyles}
|
||||||
isRtl={isRtl}
|
isRtl={isRtl}
|
||||||
|
id={currentId}
|
||||||
|
translator={translator}
|
||||||
>
|
>
|
||||||
|
|
||||||
</Header>
|
</Header>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { styled } from '@mui/material/styles';
|
|||||||
|
|
||||||
import { Grid, Tooltip, Typography } from '@mui/material';
|
import { Grid, Tooltip, Typography } from '@mui/material';
|
||||||
import ActionsToolbar from './ActionsToolbar';
|
import ActionsToolbar from './ActionsToolbar';
|
||||||
|
import hiddenScreenReaderText from '../utils/style/screen-reader';
|
||||||
|
|
||||||
const PREFIX = 'Header';
|
const PREFIX = 'Header';
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ const CellSubTitle = {
|
|||||||
className: 'njs-cell-sub-title',
|
className: 'njs-cell-sub-title',
|
||||||
};
|
};
|
||||||
|
|
||||||
function Header({ layout, sn, anchorEl, hovering, focusHandler, titleStyles = {}, isRtl }) {
|
function Header({ id, layout, sn, anchorEl, hovering, focusHandler, titleStyles = {}, isRtl, translator }) {
|
||||||
const showTitle = layout.showTitles && !!layout.title;
|
const showTitle = layout.showTitles && !!layout.title;
|
||||||
const showSubtitle = layout.showTitles && !!layout.subtitle;
|
const showSubtitle = layout.showTitles && !!layout.subtitle;
|
||||||
const showInSelectionActions = layout.qSelectionInfo && layout.qSelectionInfo.qInSelections;
|
const showInSelectionActions = layout.qSelectionInfo && layout.qSelectionInfo.qInSelections;
|
||||||
@@ -76,17 +77,28 @@ function Header({ layout, sn, anchorEl, hovering, focusHandler, titleStyles = {}
|
|||||||
isRtl={isRtl}
|
isRtl={isRtl}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledGrid item container wrap="nowrap" className={cls.join(' ')}>
|
<StyledGrid item container wrap="nowrap" className={cls.join(' ')}>
|
||||||
<Grid item zeroMinWidth xs dir={isRtl ? 'rtl' : 'ltr'}>
|
<Grid item zeroMinWidth xs dir={isRtl ? 'rtl' : 'ltr'}>
|
||||||
<Grid container wrap="nowrap" direction="column">
|
<Grid container wrap="nowrap" direction="column">
|
||||||
{showTitle && (
|
{showTitle ? (
|
||||||
<Tooltip title={layout.title}>
|
<Tooltip title={layout.title}>
|
||||||
<Typography variant="h6" noWrap className={CellTitle.className} style={titleStyles.main}>
|
<Typography
|
||||||
|
id={`${id}_title`}
|
||||||
|
variant="h6"
|
||||||
|
noWrap
|
||||||
|
className={CellTitle.className}
|
||||||
|
style={titleStyles.main}
|
||||||
|
>
|
||||||
{layout.title}
|
{layout.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
id={`${id}_title`}
|
||||||
|
style={hiddenScreenReaderText}
|
||||||
|
aria-label={translator.get('Accessibility.Object.NoTitle')}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{showSubtitle && (
|
{showSubtitle && (
|
||||||
<Tooltip title={layout.subtitle}>
|
<Tooltip title={layout.subtitle}>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const VizElement = {
|
|||||||
className: 'njs-viz',
|
className: 'njs-viz',
|
||||||
};
|
};
|
||||||
|
|
||||||
function Supernova({ sn, snOptions: options, snPlugins: plugins, layout, appLayout, halo }) {
|
function Supernova({ sn, snOptions: options, snPlugins: plugins, layout, appLayout, halo, cellId }) {
|
||||||
const { component } = sn;
|
const { component } = sn;
|
||||||
|
|
||||||
const { theme: themeName, language, constraints, interactions, keyboardNavigation } = useContext(InstanceContext);
|
const { theme: themeName, language, constraints, interactions, keyboardNavigation } = useContext(InstanceContext);
|
||||||
@@ -135,7 +135,7 @@ function Supernova({ sn, snOptions: options, snPlugins: plugins, layout, appLayo
|
|||||||
}}
|
}}
|
||||||
className={VizElement.className}
|
className={VizElement.className}
|
||||||
>
|
>
|
||||||
<div ref={snRef} style={{ position: 'absolute', width: '100%', height: '100%' }} />
|
<div ref={snRef} id={`${cellId}_content`} style={{ position: 'absolute', width: '100%', height: '100%' }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ describe('<Cell />', () => {
|
|||||||
Header = jest.fn().mockImplementation(() => 'Header');
|
Header = jest.fn().mockImplementation(() => 'Header');
|
||||||
InstanceContext = React.createContext();
|
InstanceContext = React.createContext();
|
||||||
appLayout = { foo: 'app-layout' };
|
appLayout = { foo: 'app-layout' };
|
||||||
layout = { qSelectionInfo: {}, visualization: '' };
|
layout = { qSelectionInfo: {}, visualization: '', qInfo: { qId: 'id' } };
|
||||||
layoutState = { validating: true, canCancel: false, canRetry: false };
|
layoutState = { validating: true, canCancel: false, canRetry: false };
|
||||||
longrunning = { cancel: jest.fn(), retry: jest.fn() };
|
longrunning = { cancel: jest.fn(), retry: jest.fn() };
|
||||||
useLayout = jest.fn().mockReturnValue([layout, layoutState, longrunning]);
|
useLayout = jest.fn().mockReturnValue([layout, layoutState, longrunning]);
|
||||||
|
|||||||
@@ -23,9 +23,14 @@ describe('<Header />', () => {
|
|||||||
jest.spyOn(useRectModule, 'default').mockImplementation(() => [() => {}, rect]);
|
jest.spyOn(useRectModule, 'default').mockImplementation(() => [() => {}, rect]);
|
||||||
ActionsToolbarModule.default = ActionsToolbar;
|
ActionsToolbarModule.default = ActionsToolbar;
|
||||||
|
|
||||||
render = async (layout = {}, sn = { component: {}, selectionToolbar: {} }, focusHandler = {}) => {
|
render = async (
|
||||||
|
layout = {},
|
||||||
|
sn = { component: {}, selectionToolbar: {} },
|
||||||
|
focusHandler = {},
|
||||||
|
translator = { get: (s) => s }
|
||||||
|
) => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
renderer = create(<Header layout={layout} sn={sn} focusHandler={focusHandler} />);
|
renderer = create(<Header layout={layout} sn={sn} focusHandler={focusHandler} translator={translator} />);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -40,7 +45,7 @@ describe('<Header />', () => {
|
|||||||
expect(types).toHaveLength(0);
|
expect(types).toHaveLength(0);
|
||||||
});
|
});
|
||||||
test('should render title', async () => {
|
test('should render title', async () => {
|
||||||
await render({ showTitles: true, title: 'foo' });
|
await render({ showTitles: true, title: 'foo', qInfo: { qId: 'id' } });
|
||||||
const types = renderer.root.findAllByType(Typography);
|
const types = renderer.root.findAllByType(Typography);
|
||||||
expect(types).toHaveLength(1);
|
expect(types).toHaveLength(1);
|
||||||
expect(types[0].props.children).toBe('foo');
|
expect(types[0].props.children).toBe('foo');
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ describe('<Supernova />', () => {
|
|||||||
sn = { component: {} },
|
sn = { component: {} },
|
||||||
snOptions = {},
|
snOptions = {},
|
||||||
snPlugins = [],
|
snPlugins = [],
|
||||||
layout = {},
|
layout = { qInfo: { qId: 'id' } },
|
||||||
appLayout = {},
|
appLayout = {},
|
||||||
halo = {},
|
halo = {},
|
||||||
rendererOptions,
|
rendererOptions,
|
||||||
@@ -30,6 +30,7 @@ describe('<Supernova />', () => {
|
|||||||
layout={layout}
|
layout={layout}
|
||||||
appLayout={appLayout}
|
appLayout={appLayout}
|
||||||
halo={halo}
|
halo={halo}
|
||||||
|
cellId="id"
|
||||||
/>,
|
/>,
|
||||||
rendererOptions || null
|
rendererOptions || null
|
||||||
);
|
);
|
||||||
@@ -47,9 +48,10 @@ describe('<Supernova />', () => {
|
|||||||
},
|
},
|
||||||
snOptions: {},
|
snOptions: {},
|
||||||
snPlugins: [],
|
snPlugins: [],
|
||||||
layout: {},
|
layout: { qInfo: { qId: 'id' } },
|
||||||
appLayout: {},
|
appLayout: {},
|
||||||
halo: {},
|
halo: {},
|
||||||
|
cellId: 'id',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
test('should mount', async () => {
|
test('should mount', async () => {
|
||||||
@@ -65,6 +67,7 @@ describe('<Supernova />', () => {
|
|||||||
logicalSize,
|
logicalSize,
|
||||||
component,
|
component,
|
||||||
},
|
},
|
||||||
|
layout: { qInfo: { qId: 'id' } },
|
||||||
rendererOptions: {
|
rendererOptions: {
|
||||||
createNodeMock: () => ({
|
createNodeMock: () => ({
|
||||||
style: {},
|
style: {},
|
||||||
@@ -100,7 +103,7 @@ describe('<Supernova />', () => {
|
|||||||
},
|
},
|
||||||
snOptions,
|
snOptions,
|
||||||
snPlugins: [],
|
snPlugins: [],
|
||||||
layout: 'layout',
|
layout: { qInfo: { qId: 'id' } },
|
||||||
appLayout: { qLocaleInfo: 'loc' },
|
appLayout: { qLocaleInfo: 'loc' },
|
||||||
halo: { public: { theme: 'theme', nebbie: 'embedAPI' }, app: { session: {} } },
|
halo: { public: { theme: 'theme', nebbie: 'embedAPI' }, app: { session: {} } },
|
||||||
rendererOptions: {
|
rendererOptions: {
|
||||||
@@ -115,7 +118,7 @@ describe('<Supernova />', () => {
|
|||||||
expect(await initialRender).toBe(true);
|
expect(await initialRender).toBe(true);
|
||||||
expect(component.render).toHaveBeenCalledTimes(1);
|
expect(component.render).toHaveBeenCalledTimes(1);
|
||||||
expect(component.render.mock.calls[0][0]).toEqual({
|
expect(component.render.mock.calls[0][0]).toEqual({
|
||||||
layout: 'layout',
|
layout: { qInfo: { qId: 'id' } },
|
||||||
options: snOptions,
|
options: snOptions,
|
||||||
plugins: [],
|
plugins: [],
|
||||||
embed: 'embedAPI',
|
embed: 'embedAPI',
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ const model = {
|
|||||||
function getDefaultProps() {
|
function getDefaultProps() {
|
||||||
const containerRef = React.createRef();
|
const containerRef = React.createRef();
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
layout: { title: 'The title', qListObject: { qDimensionInfo: { qLocked: false, qGrouping: 'N' } } },
|
layout: {
|
||||||
|
title: 'The title',
|
||||||
|
qListObject: { qDimensionInfo: { qLocked: false, qGrouping: 'N' } },
|
||||||
|
},
|
||||||
translator,
|
translator,
|
||||||
styles,
|
styles,
|
||||||
isRtl: false,
|
isRtl: false,
|
||||||
|
|||||||
40
apis/nucleus/src/utils/extension-translation-keys.js
Normal file
40
apis/nucleus/src/utils/extension-translation-keys.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const translationKeys = new Map();
|
||||||
|
const extensions = [
|
||||||
|
['auto-chart', 'Object.AutoChart'],
|
||||||
|
['dummy-chart', 'Dummy'],
|
||||||
|
['barchart', 'Object.BarChart'],
|
||||||
|
['combochart', 'Object.ComboChart'],
|
||||||
|
['container', 'Object.Container'],
|
||||||
|
['distributionplot', 'Object.DistributionPlot'],
|
||||||
|
['boxplot', 'Object.BoxPlot'],
|
||||||
|
['filterpane', 'Object.FilterPane'],
|
||||||
|
['gauge', 'Object.Gauge'],
|
||||||
|
['histogram', 'Object.Histogram'],
|
||||||
|
['kpi', 'Object.Kpi'],
|
||||||
|
['linechart', 'Object.LineChart'],
|
||||||
|
['listbox', 'Object.Listbox'],
|
||||||
|
['piechart', 'Object.PieChart'],
|
||||||
|
['pivot-table', 'Object.PivotTable'],
|
||||||
|
['map', 'Object.Map'],
|
||||||
|
['scatterplot', 'Object.ScatterPlot'],
|
||||||
|
['sn-table', 'Object.StraightTable'],
|
||||||
|
['text-image', 'Object.TextImage'],
|
||||||
|
['treemap', 'Object.Treemap'],
|
||||||
|
['waterfallchart', 'Object.WaterfallChart'],
|
||||||
|
['mekkochart', 'Object.MekkoChart'],
|
||||||
|
['action-button', 'Object.ActionButton'],
|
||||||
|
['sn-nav-menu', 'Object.NavMenu'],
|
||||||
|
['bulletchart', 'Object.BulletChart'],
|
||||||
|
['sn-nlg-chart', 'Object.NlgChart'],
|
||||||
|
['sn-analysis-autochart', 'Common.AnalysisTypes'],
|
||||||
|
['sn-tabbed-container', 'Object.TabContainer'],
|
||||||
|
['qlik-sankey-chart-ext', 'Object.SankeyChart'],
|
||||||
|
['qlik-radar-chart', 'Object.RadarChart'],
|
||||||
|
['qlik-funnel-chart-ext', 'Object.FunnelChart'],
|
||||||
|
['sn-grid-chart', 'Object.GridChart'],
|
||||||
|
['sn-layout-container', 'Object.LayoutContainer'],
|
||||||
|
];
|
||||||
|
extensions.forEach(([key, value]) => {
|
||||||
|
translationKeys.set(key, value);
|
||||||
|
});
|
||||||
|
export default translationKeys;
|
||||||
5
apis/nucleus/src/utils/style/screen-reader.js
Normal file
5
apis/nucleus/src/utils/style/screen-reader.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const hiddenScreenReaderText = {
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
};
|
||||||
|
export default hiddenScreenReaderText;
|
||||||
Reference in New Issue
Block a user