mirror of
https://github.com/qlik-oss/nebula.js.git
synced 2025-12-20 18:27:09 -05:00
feat(ListBoxHeader): add icon for cyclic dimension and new drilldown icon (#1504)
* feat: add cyclic icon and new drilldown icon * fix: failed unit test * fix: update snapshot * test: add rendering test for cyclic * refactor: data-title-utils.js * test: add unit test * fix: update snapshot * fix: pr comments * fix: failed test * fix: typo * fix: remove unnecessary things
This commit is contained in:
@@ -115,6 +115,10 @@
|
||||
"value": "Drill-down dimension",
|
||||
"comment": "Tooltip for the drill-down icons on the dimension titles in charts. (pde 160210)"
|
||||
},
|
||||
"Listbox.Cyclic": {
|
||||
"value": "Cyclic dimension",
|
||||
"comment": "Tooltip for the cyclic icons on the dimension titles in charts. (pde 160210)"
|
||||
},
|
||||
"Menu.More": {
|
||||
"value": "More",
|
||||
"comment": "Menu option to display list of additional actions to choose from (ffd 20200520)"
|
||||
|
||||
@@ -207,7 +207,6 @@ function ListBoxInline({ options, layout }) {
|
||||
const { wildCardSearch, searchEnabled, autoConfirm = false, layoutOptions = {} } = layout ?? {};
|
||||
const isLocked = layout?.qListObject?.qDimensionInfo?.qLocked;
|
||||
const showSearchIcon = searchEnabled !== false && search === 'toggle' && !isLocked;
|
||||
const isDrillDown = layout?.qListObject?.qDimensionInfo?.qGrouping === 'H';
|
||||
|
||||
const canShowTitle = layout?.title?.length && layout?.showTitle !== false;
|
||||
const showDetachedToolbarOnly = toolbar && !canShowTitle && !isPopover;
|
||||
@@ -257,8 +256,8 @@ function ListBoxInline({ options, layout }) {
|
||||
|
||||
const listBoxHeader = (
|
||||
<ListBoxHeader
|
||||
app={app}
|
||||
showSearchIcon={showSearchIcon}
|
||||
isDrillDown={isDrillDown}
|
||||
onShowSearch={handleShowSearch}
|
||||
isPopover={isPopover}
|
||||
showToolbar={showToolbar}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Button, styled } from '@mui/material';
|
||||
import { ICON_PADDING } from '../../constants';
|
||||
|
||||
const StyledButton = styled(Button)(() => ({
|
||||
borderRadius: 4,
|
||||
width: 24,
|
||||
height: 24,
|
||||
minWidth: 22,
|
||||
padding: 0,
|
||||
}));
|
||||
|
||||
function DimensionIcon({ iconData, translator, iconStyle }) {
|
||||
if (!iconData) return undefined;
|
||||
|
||||
const { icon, tooltip, onClick } = iconData;
|
||||
const Icon = icon;
|
||||
const title = translator.get(tooltip);
|
||||
const isButton = onClick;
|
||||
|
||||
return isButton ? (
|
||||
<StyledButton variant="outlined" onClick={onClick} tabIndex={-1} title={title} size="large" disableRipple>
|
||||
<Icon style={iconStyle} />
|
||||
</StyledButton>
|
||||
) : (
|
||||
<Icon title={title} size="large" style={{ ...iconStyle, padding: `${ICON_PADDING}px` }} />
|
||||
);
|
||||
}
|
||||
export default DimensionIcon;
|
||||
@@ -4,7 +4,6 @@ import { Grid, IconButton } from '@mui/material';
|
||||
import Lock from '@nebula.js/ui/icons/lock';
|
||||
import { unlock } from '@nebula.js/ui/icons/unlock';
|
||||
import SearchIcon from '@nebula.js/ui/icons/search';
|
||||
import DrillDownIcon from '@nebula.js/ui/icons/drill-down';
|
||||
import ActionsToolbar from '../../../ActionsToolbar';
|
||||
import showToolbarDetached from '../../interactions/listbox-show-toolbar-detached';
|
||||
import getListboxActionProps from '../../interactions/listbox-action-props';
|
||||
@@ -12,6 +11,8 @@ import createListboxSelectionToolbar from '../../interactions/listbox-selection-
|
||||
import { BUTTON_ICON_WIDTH, CELL_PADDING_LEFT, HEADER_PADDING_RIGHT, ICON_PADDING } from '../../constants';
|
||||
import hasSelections from '../../assets/has-selections';
|
||||
import { HeaderTitle, StyledGridHeader, UnlockCoverButton, iconStyle } from './ListBoxHeaderComponents';
|
||||
import iconUtils from './icon-utils';
|
||||
import DimensionIcon from './DimensionIcon';
|
||||
|
||||
// ms that needs to pass before the lock button can be toggled again
|
||||
const lockTimeFrameMs = 500;
|
||||
@@ -51,7 +52,6 @@ export default function ListBoxHeader({
|
||||
isRtl,
|
||||
showLock,
|
||||
showSearchIcon,
|
||||
isDrillDown,
|
||||
constraints,
|
||||
onShowSearch,
|
||||
classes,
|
||||
@@ -66,6 +66,7 @@ export default function ListBoxHeader({
|
||||
selections,
|
||||
keyboard,
|
||||
autoConfirm,
|
||||
app,
|
||||
}) {
|
||||
const [isToolbarDetached, setIsToolbarDetached] = useState(showDetachedToolbarOnly);
|
||||
const [isLocked, setLocked] = useState(layout?.qListObject?.qDimensionInfo?.qLocked);
|
||||
@@ -76,10 +77,10 @@ export default function ListBoxHeader({
|
||||
}, [layout?.qListObject?.qDimensionInfo?.qLocked]);
|
||||
|
||||
const titleRef = useRef(null);
|
||||
|
||||
const iconData = iconUtils.createDimensionIconData(layout?.qListObject?.qDimensionInfo, app);
|
||||
const showUnlock = showLock && isLocked;
|
||||
const showLockIcon = !showLock && isLocked; // shows instead of the cover button when field/dim is locked.
|
||||
const showLeftIcon = showSearchIcon || showLockIcon || isDrillDown; // the left-most icon outside of the actions/selections toolbar.
|
||||
const showLeftIcon = showSearchIcon || showLockIcon || iconData; // the left-most icon outside of the actions/selections toolbar.
|
||||
|
||||
const paddingLeft = CELL_PADDING_LEFT - (showLeftIcon ? ICON_PADDING : 0);
|
||||
const paddingRight = isRtl ? CELL_PADDING_LEFT - (showLeftIcon ? ICON_PADDING : 0) : HEADER_PADDING_RIGHT;
|
||||
@@ -88,7 +89,7 @@ export default function ListBoxHeader({
|
||||
const iconsWidth =
|
||||
(showSearchIcon ? BUTTON_ICON_WIDTH : 0) +
|
||||
(showLockIcon ? BUTTON_ICON_WIDTH : 0) +
|
||||
(isDrillDown ? BUTTON_ICON_WIDTH : 0);
|
||||
(iconData ? BUTTON_ICON_WIDTH : 0);
|
||||
|
||||
const toggleLock = getToggleLock({ isLocked, setLocked, settingLockedState, setSettingLockedState, model });
|
||||
|
||||
@@ -203,13 +204,7 @@ export default function ListBoxHeader({
|
||||
{showLeftIcon && (
|
||||
<Grid item container alignItems="center" width={iconsWidth} className="header-action-container">
|
||||
{lockedIconComp || (showSearchIcon && searchIconComp)}
|
||||
{isDrillDown && (
|
||||
<DrillDownIcon
|
||||
tabIndex={-1}
|
||||
title={translator.get('Listbox.DrillDown')}
|
||||
style={{ ...iconStyle, padding: `${ICON_PADDING}px` }}
|
||||
/>
|
||||
)}
|
||||
<DimensionIcon iconData={iconData} iconStyle={iconStyle} translator={translator} />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import DrillDownIcon from '@nebula.js/ui/icons/drill-down';
|
||||
import CyclicIcon from '@nebula.js/ui/icons/reload';
|
||||
|
||||
const dimensionTypes = {
|
||||
single: 'N',
|
||||
drillDown: 'H',
|
||||
cyclic: 'C',
|
||||
};
|
||||
|
||||
const createDimensionIconData = (dimInfo, app) => {
|
||||
switch (dimInfo.qGrouping) {
|
||||
case dimensionTypes.drillDown:
|
||||
return {
|
||||
icon: DrillDownIcon,
|
||||
tooltip: 'Listbox.DrillDown',
|
||||
onClick: undefined,
|
||||
};
|
||||
case dimensionTypes.cyclic:
|
||||
return {
|
||||
icon: CyclicIcon,
|
||||
tooltip: 'Listbox.Cyclic',
|
||||
onClick: () => {
|
||||
app
|
||||
.getDimension(dimInfo.qLibraryId)
|
||||
.then((dimensionModel) => {
|
||||
dimensionModel.stepCycle(1);
|
||||
})
|
||||
.catch(() => null);
|
||||
},
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export default { createDimensionIconData };
|
||||
@@ -0,0 +1,32 @@
|
||||
import DrillDownIcon from '@nebula.js/ui/icons/drill-down';
|
||||
import CyclicIcon from '@nebula.js/ui/icons/reload';
|
||||
import utils from '../ListBoxHeader/icon-utils';
|
||||
|
||||
describe('icon-utils', () => {
|
||||
it('should return no icon data for single dimension', () => {
|
||||
const dimInfo = { qGrouping: 'N' };
|
||||
const result = utils.createDimensionIconData(dimInfo, undefined);
|
||||
expect(result).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should return icon data for drilldown dimension', () => {
|
||||
const dimInfo = { qGrouping: 'H' };
|
||||
const result = utils.createDimensionIconData(dimInfo, undefined);
|
||||
expect(result).toMatchObject({
|
||||
icon: DrillDownIcon,
|
||||
tooltip: 'Listbox.DrillDown',
|
||||
onClick: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return no icon data for cyclic dimension', () => {
|
||||
const dimInfo = { qGrouping: 'C' };
|
||||
const app = {};
|
||||
const result = utils.createDimensionIconData(dimInfo, app);
|
||||
expect(result).toMatchObject({
|
||||
icon: CyclicIcon,
|
||||
tooltip: 'Listbox.Cyclic',
|
||||
onClick: expect.any(Function),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -38,7 +38,7 @@ const model = {
|
||||
function getDefaultProps() {
|
||||
const containerRef = React.createRef();
|
||||
const defaultProps = {
|
||||
layout: { title: 'The title', qListObject: { qDimensionInfo: { qLocked: false } } },
|
||||
layout: { title: 'The title', qListObject: { qDimensionInfo: { qLocked: false, qGrouping: 'N' } } },
|
||||
translator,
|
||||
styles,
|
||||
isRtl: false,
|
||||
@@ -216,7 +216,7 @@ describe('<ListBoxHeader />', () => {
|
||||
});
|
||||
|
||||
test('There should be an unlock cover button on top of the actions toolbar', async () => {
|
||||
const layout = { title: 'The title', qListObject: { qDimensionInfo: { qLocked: true } } };
|
||||
const layout = { title: 'The title', qListObject: { qDimensionInfo: { qLocked: true, qGrouping: 'N' } } };
|
||||
hasSelections.mockReturnValue(false);
|
||||
const testRenderer = await render({
|
||||
layout,
|
||||
|
||||
@@ -6,7 +6,7 @@ const drillDown = (props) => ({
|
||||
{
|
||||
type: 'path',
|
||||
attrs: {
|
||||
d: 'M15.9993744,3 L15.9993744,5 L4.99937445,5 L4.99937445,3 L15.9993744,3 Z M8.99937445,7 L15.9993744,7 L15.9993744,9 L8.99937445,9 L8.99937445,7 Z M11.9993744,11 L15.9993744,11 L15.9993744,13 L11.9993744,13 L11.9993744,11 Z M2.2,11 L7,11 L7,9.00369263 C7,8.89323568 7.08954305,8.80369263 7.2,8.80369263 C7.2549016,8.80369263 7.30738916,8.82626165 7.34515712,8.86610844 L10.1852182,11.8624926 C10.2583124,11.93961 10.258346,12.0604218 10.1852948,12.13758 L7.34523376,15.1373102 C7.2692928,15.2175206 7.14270711,15.2209817 7.06249671,15.1450407 C7.02260076,15.1072683 7,15.0547472 7,14.9998069 L7,12.9043747 C4.79351111,12.9043747 2.8018683,12.9266213 1.02507156,12.9711145 L1.02507252,12.9711526 C0.472939773,12.9849787 0.014139487,12.5485949 0.000313396522,11.9964622 C0.000104473692,11.988119 -1.32268838e-12,11.9797736 -1.3231638e-12,11.9714278 L-1.83320026e-12,3 L2,3 L2,10.8 C2,10.9104569 2.08954305,11 2.2,11 Z',
|
||||
d: 'M7 4h9v1H7zm2 4h7v1H9zm2 4h5v1h-5zM1 4v3.526a3.5 3.5 0 0 0 3.5 3.5h2.31L5.22 9.402l.715-.7L8.7 11.527 5.935 14.35l-.714-.7 1.59-1.624H4.5a4.5 4.5 0 0 1-4.5-4.5V4z',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
15
packages/ui/icons/reload.js
Normal file
15
packages/ui/icons/reload.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import SvgIcon from './SvgIcon';
|
||||
|
||||
const reload = (props) => ({
|
||||
...props,
|
||||
shapes: [
|
||||
{
|
||||
type: 'path',
|
||||
attrs: {
|
||||
d: 'M1 8a7 7 0 1 1 10 6.326V11h-1v5h5v-1h-3.124A8 8 0 1 0 8 16v-1a7 7 0 0 1-7-7Z',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
export default (props) => SvgIcon(reload(props));
|
||||
export { reload };
|
||||
55
test/rendering/listbox/__fixtures__/cyclic.js
Normal file
55
test/rendering/listbox/__fixtures__/cyclic.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const fixture = {
|
||||
getLayout: () => ({
|
||||
title: 'Field title with truncated text that is very long',
|
||||
qInfo: {
|
||||
qId: 'qId',
|
||||
},
|
||||
visualization: 'listbox',
|
||||
qListObject: {
|
||||
qDimensionInfo: {
|
||||
qGrouping: 'C',
|
||||
},
|
||||
qSize: {
|
||||
qcy: 2,
|
||||
},
|
||||
qInitialDataFetch: [{ qLeft: 0, qWidth: 0, qTop: 0, qHeight: 0 }],
|
||||
},
|
||||
qSelectionInfo: {
|
||||
qInSelections: false,
|
||||
},
|
||||
layoutOptions: {
|
||||
layoutOrder: 'row',
|
||||
},
|
||||
}),
|
||||
getListObjectData: () => [
|
||||
{
|
||||
qMatrix: [
|
||||
[
|
||||
{
|
||||
qText: 'A',
|
||||
qNum: 'NaN',
|
||||
qElemNumber: 0,
|
||||
qState: 'O',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
qText: 'B',
|
||||
qNum: 'NaN',
|
||||
qElemNumber: 1,
|
||||
qState: 'O',
|
||||
},
|
||||
],
|
||||
],
|
||||
qTails: [],
|
||||
qArea: {
|
||||
qLeft: 0,
|
||||
qTop: 0,
|
||||
qWidth: 1,
|
||||
qHeight: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default fixture;
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.5 KiB |
Reference in New Issue
Block a user