Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { joinClassNames } from './helpers/operations';
import RowColRoot from './components/ListBoxRoot';
import FieldWithRanges from './components/FieldWithRanges';
import Field from './components/Field';
import Image from './components/Image';
import Histogram from './components/Histogram';
import Frequency from './components/Frequency';
import ItemGrid from './components/ItemGrid';
Expand Down Expand Up @@ -57,6 +58,8 @@ function RowColumn({ index, rowIndex, columnIndex, style, data }) {
contentFontStyle,
styles,
fillHeight,
representation,
attrExprIndex = {},
} = data;

const { dense = false, dataLayout = 'singleColumn', layoutOrder } = layoutOptions;
Expand Down Expand Up @@ -149,6 +152,27 @@ function RowColumn({ index, rowIndex, columnIndex, style, data }) {
const isGridCol = dataLayout === 'grid' && layoutOrder === 'column';

const label = cell?.qText ?? '';

// Image representation. In 'url' mode the image src comes from the imageUrl attribute
// expression and the field value is the alt text; in 'label' mode (default) the field
// value is the image src and the imageLabel expression provides the alt text.
const isImage = representation?.type === 'image';
let imageSrc;
let imageAlt;
if (isImage) {
const imageSetting = representation?.imageSetting ?? 'label';
const urlExprValue = cell?.qAttrExps?.qValues?.[attrExprIndex.imageUrl]?.qText;
const labelExprValue = cell?.qAttrExps?.qValues?.[attrExprIndex.imageLabel]?.qText;

if (imageSetting === 'url') {
imageSrc = urlExprValue ?? label;
imageAlt = label;
} else {
imageSrc = label;
imageAlt = labelExprValue ?? label;
}
}

// Search highlights. Split up labelText span into several and add the highlighted class to matching sub-strings.

let labels;
Expand Down Expand Up @@ -246,7 +270,9 @@ function RowColumn({ index, rowIndex, columnIndex, style, data }) {
/>
)}
<Grid style={cellStyle} className={joinClassNames([classes.cell, classes.selectedCell])} title={`${label}`}>
{labels ? (
{isImage ? (
<Image representation={representation} src={imageSrc} label={imageAlt} />
) : labels ? (
<FieldWithRanges
onChange={onChange}
labels={labels}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React from 'react';

const resolveImagePosition = (imagePosition) => {
switch (imagePosition) {
case 'topLeft':
return {
horizontal: 'flex-start',
vertical: 'flex-start',
};
case 'centerLeft':
return {
horizontal: 'center',
vertical: 'flex-start',
};
case 'bottomLeft':
return {
horizontal: 'flex-end',
vertical: 'flex-start',
};
case 'topCenter':
return {
horizontal: 'flex-start',
vertical: 'center',
};
case 'centerCenter':
return {
horizontal: 'center',
vertical: 'center',
};
case 'bottomCenter':
return {
horizontal: 'flex-end',
vertical: 'center',
};
case 'topRight':
return {
horizontal: 'flex-start',
vertical: 'flex-end',
};
case 'centerRight':
return {
horizontal: 'center',
vertical: 'flex-end',
};
case 'bottomRight':
return {
horizontal: 'flex-end',
vertical: 'flex-end',
};
default:
return {
horizontal: 'flex-start',
vertical: 'flex-start',
};
}
};

const getImageWidth = (imageSize) => {
switch (imageSize) {
case 'fitHeight':
return 'auto';
case 'originalSize':
return 'fit-content';
case 'fill':
case 'alwaysFit':
case 'fitWidth':
default:
return '100%';
}
};

const getObjectPosition = (resolvedImagePosition) => {
let verticalPos = 'center';
let horizontalPos = 'center';

if (resolvedImagePosition?.vertical === 'flex-start') {
verticalPos = 'top';
} else if (resolvedImagePosition?.vertical === 'flex-end') {
verticalPos = 'bottom';
}

if (resolvedImagePosition?.horizontal === 'flex-start') {
horizontalPos = 'left';
} else if (resolvedImagePosition?.horizontal === 'flex-end') {
horizontalPos = 'right';
}

return `${horizontalPos} ${verticalPos}`;
};

const getObjectFit = (imageSize) => {
switch (imageSize) {
case 'alwaysFit':
return 'contain';
case 'fill':
return 'fill';
case 'fitHeight':
case 'fitWidth':
return 'cover';
case 'originalSize':
return 'none';
default:
return undefined;
}
};

function Image({ representation, src, label }) {
const { imageSize, imagePosition } = representation;
const isFitHeight = imageSize === 'fitHeight';
const resolvedImagePosition = resolveImagePosition(imagePosition);
const maxImageHeight = '200px';

const imgNode = (
<img
src={src}
alt={label}
style={{
width: getImageWidth(imageSize),
height: '100%',
maxHeight: maxImageHeight,
objectFit: getObjectFit(imageSize),
objectPosition: getObjectPosition(resolvedImagePosition),
overflow: 'hidden',
}}
/>
);

return (
<div
data-key="image-horizontal-container"
style={{
width: '100%',
height: isFitHeight ? maxImageHeight : '100%',
overflow: 'hidden',
display: 'flex',
justifyContent: isFitHeight ? resolvedImagePosition?.horizontal : undefined,
}}
>
{imgNode}
</div>
);
}

export default Image;
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ exports[`grid-list-components should create a Grid component 1`] = `
"selectAll": undefined,
"setScrollPosition": undefined,
},
"attrExprIndex": {},
"checkboxes": false,
"column": undefined,
"columnCount": 2,
Expand All @@ -38,6 +39,7 @@ exports[`grid-list-components should create a Grid component 1`] = `
"layoutOrder": "column",
},
"pages": "pages",
"representation": undefined,
"rowCount": 200,
"showGray": true,
"showSearch": undefined,
Expand Down Expand Up @@ -91,6 +93,7 @@ exports[`grid-list-components should create a List component 1`] = `
"selectAll": undefined,
"setScrollPosition": undefined,
},
"attrExprIndex": {},
"checkboxes": false,
"column": false,
"dataOffset": undefined,
Expand All @@ -111,6 +114,7 @@ exports[`grid-list-components should create a List component 1`] = `
},
"listCount": undefined,
"pages": "pages",
"representation": undefined,
"showGray": true,
"showSearch": undefined,
"showTick": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ export default function getListBoxComponents({
const { layoutOptions = {} } = layout || {};
const { columnWidth, listHeight, itemHeight, rowCount, columnCount } = sizes || {};

// Representation settings and attribute-expression index map from dimension info, used for both List and Grid.
const dimensionInfo = layout?.qListObject?.qDimensionInfo;
const representation = dimensionInfo?.representation;
const attrExprIndex = (dimensionInfo?.qAttrExprInfo || []).reduce((acc, expr, i) => {
acc[expr.id] = i;
return acc;
}, {});

const itemWidth = layoutOptions.dataLayout === 'grid' ? columnWidth : width;
const showTick = itemWidth > REMOVE_TICK_LIMIT;

Expand Down Expand Up @@ -73,6 +81,8 @@ export default function getListBoxComponents({
isSingleSelect,
textAlign,
sizes,
representation,
attrExprIndex,
actions: {
select,
confirm: () => selections?.confirm.call(selections),
Expand Down
11 changes: 11 additions & 0 deletions apis/nucleus/src/components/listbox/default-properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@
* @property {boolean} [frequencyEnabled=false] Show frequency count. also requires qListObjectDef.qFrequencyMode to be set
*/

/**
* Representation settings for the dimension values, set on `qListObjectDef.qDef.representation`.
* When `type` is `'image'` the values are rendered as images; the image URL is resolved from the
* `imageUrl` attribute expression (added to `qListObjectDef.qDef.qAttributeExpressions`), falling
* back to the field value.
* @interface Representation
* @property {('text'|'image')} [type='text'] How the dimension values are presented.
* @property {('alwaysFit'|'fitWidth'|'fitHeight'|'fill')} [imageSize='fitHeight'] Image sizing mode. Only used when type is 'image'.
* @property {string} [imagePosition='topCenter'] Image position within the cell. Only used when type is 'image' and imageSize is not 'fill'.
*/

/**
* @name ListboxProperties
* @type object
Expand Down
Loading