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",
|
"value": "Drill-down dimension",
|
||||||
"comment": "Tooltip for the drill-down icons on the dimension titles in charts. (pde 160210)"
|
"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": {
|
"Menu.More": {
|
||||||
"value": "More",
|
"value": "More",
|
||||||
"comment": "Menu option to display list of additional actions to choose from (ffd 20200520)"
|
"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 { wildCardSearch, searchEnabled, autoConfirm = false, layoutOptions = {} } = layout ?? {};
|
||||||
const isLocked = layout?.qListObject?.qDimensionInfo?.qLocked;
|
const isLocked = layout?.qListObject?.qDimensionInfo?.qLocked;
|
||||||
const showSearchIcon = searchEnabled !== false && search === 'toggle' && !isLocked;
|
const showSearchIcon = searchEnabled !== false && search === 'toggle' && !isLocked;
|
||||||
const isDrillDown = layout?.qListObject?.qDimensionInfo?.qGrouping === 'H';
|
|
||||||
|
|
||||||
const canShowTitle = layout?.title?.length && layout?.showTitle !== false;
|
const canShowTitle = layout?.title?.length && layout?.showTitle !== false;
|
||||||
const showDetachedToolbarOnly = toolbar && !canShowTitle && !isPopover;
|
const showDetachedToolbarOnly = toolbar && !canShowTitle && !isPopover;
|
||||||
@@ -257,8 +256,8 @@ function ListBoxInline({ options, layout }) {
|
|||||||
|
|
||||||
const listBoxHeader = (
|
const listBoxHeader = (
|
||||||
<ListBoxHeader
|
<ListBoxHeader
|
||||||
|
app={app}
|
||||||
showSearchIcon={showSearchIcon}
|
showSearchIcon={showSearchIcon}
|
||||||
isDrillDown={isDrillDown}
|
|
||||||
onShowSearch={handleShowSearch}
|
onShowSearch={handleShowSearch}
|
||||||
isPopover={isPopover}
|
isPopover={isPopover}
|
||||||
showToolbar={showToolbar}
|
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 Lock from '@nebula.js/ui/icons/lock';
|
||||||
import { unlock } from '@nebula.js/ui/icons/unlock';
|
import { unlock } from '@nebula.js/ui/icons/unlock';
|
||||||
import SearchIcon from '@nebula.js/ui/icons/search';
|
import SearchIcon from '@nebula.js/ui/icons/search';
|
||||||
import DrillDownIcon from '@nebula.js/ui/icons/drill-down';
|
|
||||||
import ActionsToolbar from '../../../ActionsToolbar';
|
import ActionsToolbar from '../../../ActionsToolbar';
|
||||||
import showToolbarDetached from '../../interactions/listbox-show-toolbar-detached';
|
import showToolbarDetached from '../../interactions/listbox-show-toolbar-detached';
|
||||||
import getListboxActionProps from '../../interactions/listbox-action-props';
|
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 { BUTTON_ICON_WIDTH, CELL_PADDING_LEFT, HEADER_PADDING_RIGHT, ICON_PADDING } from '../../constants';
|
||||||
import hasSelections from '../../assets/has-selections';
|
import hasSelections from '../../assets/has-selections';
|
||||||
import { HeaderTitle, StyledGridHeader, UnlockCoverButton, iconStyle } from './ListBoxHeaderComponents';
|
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
|
// ms that needs to pass before the lock button can be toggled again
|
||||||
const lockTimeFrameMs = 500;
|
const lockTimeFrameMs = 500;
|
||||||
@@ -51,7 +52,6 @@ export default function ListBoxHeader({
|
|||||||
isRtl,
|
isRtl,
|
||||||
showLock,
|
showLock,
|
||||||
showSearchIcon,
|
showSearchIcon,
|
||||||
isDrillDown,
|
|
||||||
constraints,
|
constraints,
|
||||||
onShowSearch,
|
onShowSearch,
|
||||||
classes,
|
classes,
|
||||||
@@ -66,6 +66,7 @@ export default function ListBoxHeader({
|
|||||||
selections,
|
selections,
|
||||||
keyboard,
|
keyboard,
|
||||||
autoConfirm,
|
autoConfirm,
|
||||||
|
app,
|
||||||
}) {
|
}) {
|
||||||
const [isToolbarDetached, setIsToolbarDetached] = useState(showDetachedToolbarOnly);
|
const [isToolbarDetached, setIsToolbarDetached] = useState(showDetachedToolbarOnly);
|
||||||
const [isLocked, setLocked] = useState(layout?.qListObject?.qDimensionInfo?.qLocked);
|
const [isLocked, setLocked] = useState(layout?.qListObject?.qDimensionInfo?.qLocked);
|
||||||
@@ -76,10 +77,10 @@ export default function ListBoxHeader({
|
|||||||
}, [layout?.qListObject?.qDimensionInfo?.qLocked]);
|
}, [layout?.qListObject?.qDimensionInfo?.qLocked]);
|
||||||
|
|
||||||
const titleRef = useRef(null);
|
const titleRef = useRef(null);
|
||||||
|
const iconData = iconUtils.createDimensionIconData(layout?.qListObject?.qDimensionInfo, app);
|
||||||
const showUnlock = showLock && isLocked;
|
const showUnlock = showLock && isLocked;
|
||||||
const showLockIcon = !showLock && isLocked; // shows instead of the cover button when field/dim is locked.
|
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 paddingLeft = CELL_PADDING_LEFT - (showLeftIcon ? ICON_PADDING : 0);
|
||||||
const paddingRight = isRtl ? CELL_PADDING_LEFT - (showLeftIcon ? ICON_PADDING : 0) : HEADER_PADDING_RIGHT;
|
const paddingRight = isRtl ? CELL_PADDING_LEFT - (showLeftIcon ? ICON_PADDING : 0) : HEADER_PADDING_RIGHT;
|
||||||
@@ -88,7 +89,7 @@ export default function ListBoxHeader({
|
|||||||
const iconsWidth =
|
const iconsWidth =
|
||||||
(showSearchIcon ? BUTTON_ICON_WIDTH : 0) +
|
(showSearchIcon ? BUTTON_ICON_WIDTH : 0) +
|
||||||
(showLockIcon ? 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 });
|
const toggleLock = getToggleLock({ isLocked, setLocked, settingLockedState, setSettingLockedState, model });
|
||||||
|
|
||||||
@@ -203,13 +204,7 @@ export default function ListBoxHeader({
|
|||||||
{showLeftIcon && (
|
{showLeftIcon && (
|
||||||
<Grid item container alignItems="center" width={iconsWidth} className="header-action-container">
|
<Grid item container alignItems="center" width={iconsWidth} className="header-action-container">
|
||||||
{lockedIconComp || (showSearchIcon && searchIconComp)}
|
{lockedIconComp || (showSearchIcon && searchIconComp)}
|
||||||
{isDrillDown && (
|
<DimensionIcon iconData={iconData} iconStyle={iconStyle} translator={translator} />
|
||||||
<DrillDownIcon
|
|
||||||
tabIndex={-1}
|
|
||||||
title={translator.get('Listbox.DrillDown')}
|
|
||||||
style={{ ...iconStyle, padding: `${ICON_PADDING}px` }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
<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() {
|
function getDefaultProps() {
|
||||||
const containerRef = React.createRef();
|
const containerRef = React.createRef();
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
layout: { title: 'The title', qListObject: { qDimensionInfo: { qLocked: false } } },
|
layout: { title: 'The title', qListObject: { qDimensionInfo: { qLocked: false, qGrouping: 'N' } } },
|
||||||
translator,
|
translator,
|
||||||
styles,
|
styles,
|
||||||
isRtl: false,
|
isRtl: false,
|
||||||
@@ -216,7 +216,7 @@ describe('<ListBoxHeader />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('There should be an unlock cover button on top of the actions toolbar', async () => {
|
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);
|
hasSelections.mockReturnValue(false);
|
||||||
const testRenderer = await render({
|
const testRenderer = await render({
|
||||||
layout,
|
layout,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const drillDown = (props) => ({
|
|||||||
{
|
{
|
||||||
type: 'path',
|
type: 'path',
|
||||||
attrs: {
|
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