mirror of
https://github.com/qlik-oss/nebula.js.git
synced 2025-12-25 01:04:14 -05:00
refactor(listbox): add showGray option to disable gray colors (#815)
* fix: trim space when parsing engine URLs * refactor: add showGray option to show/hide grey * docs: update specs * refactor: fetch start * refactor: fetch start * docs: spec
This commit is contained in:
@@ -66,8 +66,10 @@ export default function ListBox({
|
||||
rangeSelect = true,
|
||||
checkboxes = false,
|
||||
update = undefined,
|
||||
fetchStart = undefined,
|
||||
dense = false,
|
||||
keyboard = {},
|
||||
showGray = true,
|
||||
selectDisabled = () => false,
|
||||
}) {
|
||||
const [layout] = useLayout(model);
|
||||
@@ -133,7 +135,7 @@ export default function ListBox({
|
||||
local.current.timeout = setTimeout(
|
||||
() => {
|
||||
const sorted = local.current.queue.slice(-2).sort((a, b) => a.start - b.start);
|
||||
model
|
||||
const reqPromise = model
|
||||
.getListObjectData(
|
||||
'/qListObjectDef',
|
||||
sorted.map((s) => ({
|
||||
@@ -150,6 +152,7 @@ export default function ListBox({
|
||||
setIsLoadingData(false);
|
||||
resolve();
|
||||
});
|
||||
fetchStart && fetchStart(reqPromise);
|
||||
},
|
||||
isScrolling ? scrollTimeout : 0
|
||||
);
|
||||
@@ -236,6 +239,7 @@ export default function ListBox({
|
||||
frequencyMax,
|
||||
histogram,
|
||||
keyboard,
|
||||
showGray,
|
||||
}}
|
||||
itemSize={itemSize}
|
||||
onItemsRendered={onItemsRendered}
|
||||
|
||||
@@ -51,11 +51,11 @@ const useStyles = makeStyles((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const getIcon = (styles, excluded = false, alternative = false) => (
|
||||
const getIcon = (styles, showGray = true, excluded = false, alternative = false) => (
|
||||
<span className={styles.cbIcon}>
|
||||
{(excluded || alternative) && (
|
||||
<span
|
||||
className={[excluded && styles.cbIconExcluded, alternative && styles.cbIconAlternative]
|
||||
className={[showGray && excluded && styles.cbIconExcluded, showGray && alternative && styles.cbIconAlternative]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
/>
|
||||
@@ -63,7 +63,7 @@ const getIcon = (styles, excluded = false, alternative = false) => (
|
||||
</span>
|
||||
);
|
||||
|
||||
export default function ListboxCheckbox({ checked, label, dense, excluded, alternative }) {
|
||||
export default function ListboxCheckbox({ checked, label, dense, excluded, alternative, showGray = true }) {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
@@ -74,7 +74,7 @@ export default function ListboxCheckbox({ checked, label, dense, excluded, alter
|
||||
className={[styles.checkbox, dense && styles.dense].filter(Boolean).join(' ')}
|
||||
inputProps={{ 'aria-labelledby': label }}
|
||||
name={label}
|
||||
icon={getIcon(styles, excluded, alternative)}
|
||||
icon={getIcon(styles, showGray, excluded, alternative)}
|
||||
checkedIcon={<span className={styles.cbIconChecked} />}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -42,8 +42,10 @@ export default function ListBoxInline({ app, fieldIdentifier, stateName = '$', o
|
||||
sessionModel = undefined,
|
||||
selectionsApi = undefined,
|
||||
update = undefined,
|
||||
fetchStart = undefined,
|
||||
dense = false,
|
||||
selectDisabled = () => false,
|
||||
showGray = true,
|
||||
} = options;
|
||||
let { frequencyMode, histogram = false } = options;
|
||||
|
||||
@@ -321,9 +323,11 @@ export default function ListBoxInline({ app, fieldIdentifier, stateName = '$', o
|
||||
height={height}
|
||||
width={width}
|
||||
update={update}
|
||||
fetchStart={fetchStart}
|
||||
dense={dense}
|
||||
selectDisabled={selectDisabled}
|
||||
keyboard={keyboard}
|
||||
showGray={showGray}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
||||
@@ -181,6 +181,7 @@ function RowColumn({ index, style, data, column = false }) {
|
||||
frequencyMax = '',
|
||||
histogram = false,
|
||||
keyboard,
|
||||
showGray = true,
|
||||
} = data;
|
||||
|
||||
const handleKeyDownCallback = useCallback(getFieldKeyboardNavigation(actions), [actions]);
|
||||
@@ -219,12 +220,12 @@ function RowColumn({ index, style, data, column = false }) {
|
||||
const clazzArr = [column ? classes.column : classes.row];
|
||||
if (!checkboxes) {
|
||||
if (cell.qState === 'XS') {
|
||||
clazzArr.push(classes.XS);
|
||||
clazzArr.push(showGray ? classes.XS : classes.S);
|
||||
} else if (cell.qState === 'S' || cell.qState === 'L') {
|
||||
clazzArr.push(classes.S);
|
||||
} else if (isAlternative(cell)) {
|
||||
} else if (showGray && isAlternative(cell)) {
|
||||
clazzArr.push(classes.A);
|
||||
} else if (isExcluded(cell)) {
|
||||
} else if (showGray && isExcluded(cell)) {
|
||||
clazzArr.push(classes.X);
|
||||
}
|
||||
}
|
||||
@@ -248,7 +249,7 @@ function RowColumn({ index, style, data, column = false }) {
|
||||
classes.labelText,
|
||||
highlighted && classes.highlighted,
|
||||
dense && classes.labelDense,
|
||||
excludedOrAlternative() && classes.excludedTextWithCheckbox,
|
||||
showGray && excludedOrAlternative() && classes.excludedTextWithCheckbox,
|
||||
])}
|
||||
color={color}
|
||||
>
|
||||
@@ -271,6 +272,7 @@ function RowColumn({ index, style, data, column = false }) {
|
||||
dense={dense}
|
||||
excluded={isExcluded(cell)}
|
||||
alternative={isAlternative(cell)}
|
||||
showGray={showGray}
|
||||
/>
|
||||
);
|
||||
const rb = <ListBoxRadioButton label={lbl} checked={isSelected} dense={dense} />;
|
||||
@@ -384,7 +386,7 @@ function RowColumn({ index, style, data, column = false }) {
|
||||
className={joinClassNames([
|
||||
dense && classes.labelDense,
|
||||
classes.labelText,
|
||||
excludedOrAlternative() && classes.excludedTextWithCheckbox,
|
||||
showGray && excludedOrAlternative() && classes.excludedTextWithCheckbox,
|
||||
])}
|
||||
>
|
||||
{getFrequencyText()}
|
||||
|
||||
@@ -63,10 +63,24 @@ describe('<ListBoxCheckbox />', () => {
|
||||
expect(cb.props.icon.props.children.props.className).to.equal('cbIconAlternative');
|
||||
});
|
||||
|
||||
it('should not render checkbox filled with alternative gray when showGray is false', async () => {
|
||||
const testRenderer = await render(<ListBoxCheckbox alternative showGray={false} label="filled with gray" />);
|
||||
const cb = testRenderer.root.findByType(Checkbox);
|
||||
expect(cb.props.className).to.equal('checkbox');
|
||||
expect(cb.props.icon.props.children.props.className).to.equal('');
|
||||
});
|
||||
|
||||
it('should render checkbox filled with excluded gray', async () => {
|
||||
const testRenderer = await render(<ListBoxCheckbox excluded label="filled with gray" />);
|
||||
const cb = testRenderer.root.findByType(Checkbox);
|
||||
expect(cb.props.className).to.equal('checkbox');
|
||||
expect(cb.props.icon.props.children.props.className).to.equal('cbIconExcluded');
|
||||
});
|
||||
|
||||
it('should not render checkbox filled with excluded gray when showGray is false', async () => {
|
||||
const testRenderer = await render(<ListBoxCheckbox excluded showGray={false} label="filled with gray" />);
|
||||
const cb = testRenderer.root.findByType(Checkbox);
|
||||
expect(cb.props.className).to.equal('checkbox');
|
||||
expect(cb.props.icon.props.children.props.className).to.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('<ListboxInline />', () => {
|
||||
[require.resolve('../../../hooks/useSessionModel'), () => useSessionModel],
|
||||
[require.resolve('../../../hooks/useLayout'), () => () => [layout]],
|
||||
[require.resolve('../../ActionsToolbar'), () => ActionsToolbar],
|
||||
[require.resolve('../ListBox'), () => <div className="TheListBox" />],
|
||||
[require.resolve('../ListBox'), () => <div className="theListBox" />],
|
||||
[require.resolve('../ListBoxSearch'), () => ListBoxSearch],
|
||||
[
|
||||
require.resolve('../listbox-keyboard-navigation'),
|
||||
@@ -134,6 +134,7 @@ describe('<ListboxInline />', () => {
|
||||
sessionModel: undefined,
|
||||
selectionsApi: undefined,
|
||||
update: undefined,
|
||||
fetchStart: 'fetchStart',
|
||||
};
|
||||
|
||||
theme.spacing.returns('padding');
|
||||
|
||||
@@ -300,6 +300,44 @@ describe('<ListBoxRowColumn />', () => {
|
||||
await testRenderer.unmount();
|
||||
});
|
||||
|
||||
it('should not add alternative class for A when showGray is false', async () => {
|
||||
const index = 0;
|
||||
const style = {};
|
||||
const data = {
|
||||
onMouseDown: sandbox.spy(),
|
||||
onMouseUp: sandbox.spy(),
|
||||
onMouseEnter: sandbox.spy(),
|
||||
onClick: sandbox.spy(),
|
||||
keyboard,
|
||||
actions,
|
||||
showGray: false,
|
||||
pages: [
|
||||
{
|
||||
qArea: {
|
||||
qLeft: 0,
|
||||
qTop: 0,
|
||||
qWidth: 0,
|
||||
qHeight: 100,
|
||||
},
|
||||
qMatrix: [
|
||||
[
|
||||
{
|
||||
qState: 'A',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const testRenderer = await render(
|
||||
<ListBoxRowColumn index={index} style={style} data={data} column={rowCol === 'column'} />
|
||||
);
|
||||
const testInstance = testRenderer.root;
|
||||
const type = testInstance.findByType(Grid);
|
||||
expect(type.props.className).not.to.include('alternative');
|
||||
await testRenderer.unmount();
|
||||
});
|
||||
|
||||
it('should set excluded - qState X', async () => {
|
||||
const index = 0;
|
||||
const style = {};
|
||||
@@ -337,6 +375,44 @@ describe('<ListBoxRowColumn />', () => {
|
||||
await testRenderer.unmount();
|
||||
});
|
||||
|
||||
it('should not add excluded class for qState X when showGray is false', async () => {
|
||||
const index = 0;
|
||||
const style = {};
|
||||
const data = {
|
||||
onMouseDown: sandbox.spy(),
|
||||
onMouseUp: sandbox.spy(),
|
||||
onMouseEnter: sandbox.spy(),
|
||||
onClick: sandbox.spy(),
|
||||
keyboard,
|
||||
actions,
|
||||
showGray: false,
|
||||
pages: [
|
||||
{
|
||||
qArea: {
|
||||
qLeft: 0,
|
||||
qTop: 0,
|
||||
qWidth: 0,
|
||||
qHeight: 100,
|
||||
},
|
||||
qMatrix: [
|
||||
[
|
||||
{
|
||||
qState: 'X',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const testRenderer = await render(
|
||||
<ListBoxRowColumn index={index} style={style} data={data} column={rowCol === 'column'} />
|
||||
);
|
||||
const testInstance = testRenderer.root;
|
||||
const type = testInstance.findByType(Grid);
|
||||
expect(type.props.className).not.to.include('excluded');
|
||||
await testRenderer.unmount();
|
||||
});
|
||||
|
||||
it('should set excluded-selected - qState XS', async () => {
|
||||
const index = 0;
|
||||
const style = {};
|
||||
@@ -374,6 +450,44 @@ describe('<ListBoxRowColumn />', () => {
|
||||
await testRenderer.unmount();
|
||||
});
|
||||
|
||||
it('should not add excluded-selected class when showGray is false', async () => {
|
||||
const index = 0;
|
||||
const style = {};
|
||||
const data = {
|
||||
onMouseDown: sandbox.spy(),
|
||||
onMouseUp: sandbox.spy(),
|
||||
onMouseEnter: sandbox.spy(),
|
||||
onClick: sandbox.spy(),
|
||||
keyboard,
|
||||
actions,
|
||||
showGray: false,
|
||||
pages: [
|
||||
{
|
||||
qArea: {
|
||||
qLeft: 0,
|
||||
qTop: 0,
|
||||
qWidth: 0,
|
||||
qHeight: 100,
|
||||
},
|
||||
qMatrix: [
|
||||
[
|
||||
{
|
||||
qState: 'XS',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const testRenderer = await render(
|
||||
<ListBoxRowColumn index={index} style={style} data={data} column={rowCol === 'column'} />
|
||||
);
|
||||
const testInstance = testRenderer.root;
|
||||
const type = testInstance.findByType(Grid);
|
||||
expect(type.props.className).not.to.include('excluded-selected');
|
||||
await testRenderer.unmount();
|
||||
});
|
||||
|
||||
it('should set excluded - qState XL', async () => {
|
||||
const index = 0;
|
||||
const style = {};
|
||||
|
||||
@@ -13,11 +13,16 @@ describe('<Listbox />', () => {
|
||||
let pages;
|
||||
let selectDisabled;
|
||||
let FixedSizeList;
|
||||
let fetchStart;
|
||||
let useCallbackStub;
|
||||
let setTimeoutStub;
|
||||
let useSelectionsInteractions;
|
||||
|
||||
before(() => {
|
||||
sandbox = sinon.createSandbox({ useFakeTimers: true });
|
||||
|
||||
setTimeoutStub = sandbox.stub();
|
||||
|
||||
global.document = 'document';
|
||||
|
||||
layout = {
|
||||
@@ -31,6 +36,9 @@ describe('<Listbox />', () => {
|
||||
|
||||
useSelectionsInteractions = sandbox.stub();
|
||||
|
||||
fetchStart = sandbox.stub();
|
||||
useCallbackStub = sandbox.stub();
|
||||
|
||||
FixedSizeList = sandbox.stub().callsFake((funcArgs) => {
|
||||
const { children } = funcArgs;
|
||||
const RowOrColumn = children;
|
||||
@@ -41,6 +49,13 @@ describe('<Listbox />', () => {
|
||||
[
|
||||
[require.resolve('react-window'), () => ({ FixedSizeList })],
|
||||
[require.resolve('../../../hooks/useLayout'), () => () => [layout]],
|
||||
[
|
||||
require.resolve('react'),
|
||||
() => ({
|
||||
...React,
|
||||
useCallback: useCallbackStub,
|
||||
}),
|
||||
],
|
||||
[require.resolve('../useSelectionsInteractions'), () => useSelectionsInteractions],
|
||||
[
|
||||
require.resolve('react-window-infinite-loader'),
|
||||
@@ -64,6 +79,8 @@ describe('<Listbox />', () => {
|
||||
beforeEach(() => {
|
||||
pages = [{ qArea: { qTop: 1, qHeight: 100 } }];
|
||||
|
||||
global.window = { setTimeout: setTimeoutStub };
|
||||
|
||||
selectDisabled = () => false;
|
||||
|
||||
useSelectionsInteractions.returns({
|
||||
@@ -89,6 +106,7 @@ describe('<Listbox />', () => {
|
||||
rangeSelect: false,
|
||||
checkboxes: false,
|
||||
selectDisabled,
|
||||
fetchStart,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -102,18 +120,21 @@ describe('<Listbox />', () => {
|
||||
|
||||
describe('Check rendering with different options', () => {
|
||||
before(() => {
|
||||
render = async () => {
|
||||
render = async (overrides = {}) => {
|
||||
const mergedArgs = { ...args, ...overrides };
|
||||
await act(async () => {
|
||||
renderer = create(
|
||||
<ListBox
|
||||
selections={args.selections}
|
||||
direction={args.direction}
|
||||
height={args.height}
|
||||
width={args.width}
|
||||
rangeSelect={args.rangeSelect}
|
||||
listLayout={args.listLayout}
|
||||
update={args.update}
|
||||
checkboxes={args.checkboxes}
|
||||
selections={mergedArgs.selections}
|
||||
direction={mergedArgs.direction}
|
||||
height={mergedArgs.height}
|
||||
width={mergedArgs.width}
|
||||
rangeSelect={mergedArgs.rangeSelect}
|
||||
listLayout={mergedArgs.listLayout}
|
||||
update={mergedArgs.update}
|
||||
checkboxes={mergedArgs.checkboxes}
|
||||
selectDisabled={mergedArgs.selectDisabled}
|
||||
fetchStart={mergedArgs.fetchStart}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -174,6 +195,26 @@ describe('<Listbox />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call fetchStart unless fetching data', async () => {
|
||||
sandbox
|
||||
.stub(React, 'useRef')
|
||||
.onFirstCall()
|
||||
.returns({
|
||||
loaderRef: {
|
||||
current: {
|
||||
_listRef: { state: { isScrolling: false } },
|
||||
},
|
||||
},
|
||||
})
|
||||
.callsFake((inp) => ({ current: inp }));
|
||||
|
||||
await render();
|
||||
expect(fetchStart).not.called;
|
||||
expect(setTimeoutStub).not.called;
|
||||
const loadMoreItems = useCallbackStub.args[1][0];
|
||||
expect(loadMoreItems).to.be.a('function');
|
||||
});
|
||||
|
||||
it('should call with checkboxes true', async () => {
|
||||
args.checkboxes = true;
|
||||
await render();
|
||||
|
||||
@@ -344,6 +344,10 @@ function nuked(configuration = {}) {
|
||||
* @typedef { boolean | 'toggle' } SearchMode
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {function(promise)} PromiseFunction A callback function which receives a request promise as the first argument.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {function(function)} ReceiverFunction A callback function which receives another function as input.
|
||||
*/
|
||||
@@ -372,10 +376,12 @@ function nuked(configuration = {}) {
|
||||
* @param {boolean=} [options.rangeSelect=true] Enable range selection
|
||||
* @param {boolean=} [options.dense=false] Reduces padding and text size
|
||||
* @param {boolean=} [options.stateName="$"] Sets the state to make selections in
|
||||
* @param {boolean=} [options.showGray=true] Render fields or checkboxes in shades of gray instead of white when their state is excluded or alternative.
|
||||
* @param {object=} [options.properties={}] Properties object to extend default properties with
|
||||
* @param {object} [options.sessionModel] Use a custom sessionModel.
|
||||
* @param {object} [options.selectionsApi] Use a custom selectionsApi to customize how values are selected.
|
||||
* @param {function():boolean} [options.selectDisabled=] Define a function which tells when selections are disabled (true) or enabled (false). By default, always returns false.
|
||||
* @param {PromiseFunction} [options.fetchStart] A function called when the Listbox starts fetching data. Receives the fetch request promise as an argument.
|
||||
* @param {ReceiverFunction} [options.update] A function which receives an update function which upon call will trigger a data fetch.
|
||||
* @since 1.1.0
|
||||
* @instance
|
||||
|
||||
@@ -803,6 +803,18 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"PromiseFunction": {
|
||||
"description": "A callback function which receives a request promise as the first argument.",
|
||||
"kind": "alias",
|
||||
"items": {
|
||||
"kind": "function",
|
||||
"params": [
|
||||
{
|
||||
"type": "promise"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ReceiverFunction": {
|
||||
"description": "A callback function which receives another function as input.",
|
||||
"kind": "alias",
|
||||
@@ -914,6 +926,12 @@
|
||||
"defaultValue": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"showGray": {
|
||||
"description": "Render fields or checkboxes in shades of gray instead of white when their state is excluded or alternative.",
|
||||
"optional": true,
|
||||
"defaultValue": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"properties": {
|
||||
"description": "Properties object to extend default properties with",
|
||||
"optional": true,
|
||||
@@ -939,6 +957,11 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"fetchStart": {
|
||||
"description": "A function called when the Listbox starts fetching data. Receives the fetch request promise as an argument.",
|
||||
"optional": true,
|
||||
"type": "#/definitions/PromiseFunction"
|
||||
},
|
||||
"update": {
|
||||
"description": "A function which receives an update function which upon call will trigger a data fetch.",
|
||||
"optional": true,
|
||||
|
||||
7
apis/stardust/types/index.d.ts
vendored
7
apis/stardust/types/index.d.ts
vendored
@@ -239,6 +239,11 @@ declare namespace stardust {
|
||||
|
||||
type SearchMode = boolean | "toggle";
|
||||
|
||||
/**
|
||||
* A callback function which receives a request promise as the first argument.
|
||||
*/
|
||||
type PromiseFunction = ($: promise)=>void;
|
||||
|
||||
/**
|
||||
* A callback function which receives another function as input.
|
||||
*/
|
||||
@@ -265,6 +270,7 @@ declare namespace stardust {
|
||||
rangeSelect?: boolean;
|
||||
dense?: boolean;
|
||||
stateName?: boolean;
|
||||
showGray?: boolean;
|
||||
properties?: object;
|
||||
sessionModel?: object;
|
||||
selectionsApi?: object;
|
||||
@@ -272,6 +278,7 @@ declare namespace stardust {
|
||||
* Define a function which tells when selections are disabled (true) or enabled (false). By default, always returns false.
|
||||
*/
|
||||
"selectDisabled="?(): boolean;
|
||||
fetchStart?: stardust.PromiseFunction;
|
||||
update?: stardust.ReceiverFunction;
|
||||
}): void;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user