diff --git a/packages/code-studio/src/styleguide/ListViews.tsx b/packages/code-studio/src/styleguide/ListViews.tsx
index 40b24e034d..e651580721 100644
--- a/packages/code-studio/src/styleguide/ListViews.tsx
+++ b/packages/code-studio/src/styleguide/ListViews.tsx
@@ -1,4 +1,5 @@
-import React, { useCallback, useState } from 'react';
+import React, { ReactNode, useCallback, useState } from 'react';
+import type { StyleProps } from '@react-types/shared';
import {
Grid,
Icon,
@@ -7,6 +8,8 @@ import {
ListViewNormalized,
ItemKey,
Text,
+ Flex,
+ Checkbox,
} from '@deephaven/components';
import { vsAccount, vsPerson } from '@deephaven/icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -14,6 +17,10 @@ import { generateNormalizedItems, sampleSectionIdAndClasses } from './utils';
// Generate enough items to require scrolling
const itemsSimple = [...generateNormalizedItems(52)];
+const itemsWithIcons = [...generateNormalizedItems(52, { icons: true })];
+const itemsWithIconsAndDescriptions = [
+ ...generateNormalizedItems(52, { icons: true, descriptions: true }),
+];
function AccountIllustration(): JSX.Element {
return (
@@ -26,11 +33,29 @@ function AccountIllustration(): JSX.Element {
);
}
+interface LabeledProps extends StyleProps {
+ label: string;
+ children: ReactNode;
+}
+
+function LabeledFlexColumn({ label, children, ...styleProps }: LabeledProps) {
+ return (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+ {label}
+ {children}
+
+ );
+}
+
export function ListViews(): JSX.Element {
const [selectedKeys, setSelectedKeys] = useState<'all' | Iterable>(
[]
);
+ const [showIcons, setShowIcons] = useState(true);
+ const [showDescriptions, setShowDescriptions] = useState(true);
+
const onChange = useCallback((keys: 'all' | Iterable): void => {
setSelectedKeys(keys);
}, []);
@@ -40,81 +65,137 @@ export function ListViews(): JSX.Element {
List View
-
- Single Child
-
- - Aaa
-
-
-
-
- -
-
- Item with icon A
-
- -
-
- Item with icon B
-
- -
-
- Item with icon C
-
- -
-
- Item with icon D with overflowing content
-
-
-
-
-
- {/* eslint-disable react/jsx-curly-brace-presence */}
- {'String 1'}
- {'String 2'}
- {'String 3'}
- {''}
- {'Some really long text that should get truncated'}
- {/* eslint-enable react/jsx-curly-brace-presence */}
- {444}
- {999}
- {true}
- {false}
- - Item Aaa
- - Item Bbb
- -
-
-
-
- Complex Ccc with text that should be truncated
-
-
-
-
-
+
+
+
+ - Aaa
+
+
+
+
+
+ -
+
+ Item with icon A
+
+ -
+
+ Item with icon B
+
+ -
+
+ Item with icon C
+
+ -
+
+ Item with icon D with overflowing content
+
+ -
+
+ Item with icon E
+
+
+
+
+
+
+ {/* eslint-disable react/jsx-curly-brace-presence */}
+ {'String 1'}
+ {'String 2'}
+ {'String 3'}
+ {''}
+ {'Some really long text that should get truncated'}
+ {/* eslint-enable react/jsx-curly-brace-presence */}
+ {444}
+ {999}
+ {true}
+ {false}
+ - Item Aaa
+ - Item Bbb
+ -
+ Item with Description
+ Description
+
+ -
+
+
+
+ Complex Ccc with text that should be truncated
+
+ -
+
+
+
+ Complex Ccc with text that should be truncated
+ Description
+
+
+
+
+
+ setShowIcons(e.currentTarget.checked)}
+ >
+ Show Ions
+
+ setShowDescriptions(e.currentTarget.checked)}
+ >
+ Show Descriptions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
diff --git a/packages/code-studio/src/styleguide/Pickers.tsx b/packages/code-studio/src/styleguide/Pickers.tsx
index f6a035a6fc..10ecc711ed 100644
--- a/packages/code-studio/src/styleguide/Pickers.tsx
+++ b/packages/code-studio/src/styleguide/Pickers.tsx
@@ -7,6 +7,7 @@ import {
Section,
Text,
PickerNormalized,
+ Checkbox,
} from '@deephaven/components';
import { vsPerson } from '@deephaven/icons';
import { Icon } from '@adobe/react-spectrum';
@@ -21,6 +22,7 @@ import {
// Generate enough items to require scrolling
const items = [...generateNormalizedItems(52)];
+const itemsWithIcons = [...generateNormalizedItems(52, { icons: true })];
const itemElementsA = [...generateItemElements(0, 51)];
const itemElementsB = [...generateItemElements(52, 103)];
const itemElementsC = [...generateItemElements(104, 155)];
@@ -63,6 +65,8 @@ function PersonIcon(): JSX.Element {
export function Pickers(): JSX.Element {
const [selectedKey, setSelectedKey] = useState(null);
+ const [showIcons, setShowIcons] = useState(true);
+
const getInitialScrollPosition = useCallback(
async () =>
getPositionOfSelectedItem({
@@ -83,60 +87,72 @@ export function Pickers(): JSX.Element {
Pickers
-
-
- - Aaa
-
+
+
+
+ - Aaa
+
+
+
+ {mixedItemsWithIconsNoDescriptions}
+
-
- {mixedItemsWithIconsNoDescriptions}
-
+
+ {/* eslint-disable react/jsx-curly-brace-presence */}
+ {'String 1'}
+ {'String 2'}
+ {'String 3'}
+
+ - Item Aaa
+ - Item Bbb
+ -
+
+ Complex Ccc
+
+
+
+ - Item Ddd
+ - Item Eee
+ -
+
+ Complex Fff
+
+ -
+
+ Label
+ Description
+
+ -
+
+ Label that causes overflow
+ Description that causes overflow
+
+
+
+
+
+
+
+
+
-
- {/* eslint-disable react/jsx-curly-brace-presence */}
- {'String 1'}
- {'String 2'}
- {'String 3'}
-
- - Item Aaa
- - Item Bbb
- -
-
- Complex Ccc
-
-
-
- - Item Ddd
- - Item Eee
- -
-
- Complex Fff
-
- -
-
- Label
- Description
-
- -
-
- Label that causes overflow
- Description that causes overflow
-
-
-
-
-
-
-
-
+ setShowIcons(e.currentTarget.checked)}
+ >
+ Show Ions
+
-
+
+
+
);
diff --git a/packages/code-studio/src/styleguide/utils.ts b/packages/code-studio/src/styleguide/utils.ts
index 8707ce5826..e05e16fac4 100644
--- a/packages/code-studio/src/styleguide/utils.ts
+++ b/packages/code-studio/src/styleguide/utils.ts
@@ -1,6 +1,7 @@
-import cl from 'classnames';
import { createElement, useCallback, useState } from 'react';
+import cl from 'classnames';
import { Item, ItemElement, NormalizedItem } from '@deephaven/components';
+import { dh as dhIcons } from '@deephaven/icons';
export const HIDE_FROM_E2E_TESTS_CLASS = 'hide-from-e2e-tests';
export const SAMPLE_SECTION_CLASS = 'sample-section';
@@ -39,11 +40,14 @@ export function* generateItemElements(
* @param count The number of items to generate
*/
export function* generateNormalizedItems(
- count: number
+ count: number,
+ include: { descriptions?: boolean; icons?: boolean } = {}
): Generator {
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const len = letters.length;
+ const iconKeys = Object.keys(dhIcons);
+
for (let i = 0; i < count; i += 1) {
const charI = i % len;
let suffix = String(Math.floor(i / len));
@@ -52,7 +56,14 @@ export function* generateNormalizedItems(
}
const letter = letters[charI];
const key = `${letter}${suffix}`;
- const content = `${letter}${letter}${letter}${suffix}`;
+
+ const icon =
+ include.icons === true ? iconKeys[i % iconKeys.length] : undefined;
+
+ const description =
+ include.descriptions === true ? `Description ${key}` : undefined;
+
+ const content = icon ?? `${letter}${letter}${letter}${suffix}`;
yield {
key,
@@ -60,6 +71,8 @@ export function* generateNormalizedItems(
key: (i + 1) * 100,
content,
textValue: content,
+ description,
+ icon,
},
};
}
diff --git a/packages/components/src/spectrum/listView/ListViewNormalized.tsx b/packages/components/src/spectrum/listView/ListViewNormalized.tsx
index 80a0277ee7..0c3a33ed45 100644
--- a/packages/components/src/spectrum/listView/ListViewNormalized.tsx
+++ b/packages/components/src/spectrum/listView/ListViewNormalized.tsx
@@ -1,4 +1,5 @@
import { useMemo } from 'react';
+import cl from 'classnames';
import {
NormalizedItem,
normalizeTooltipOptions,
@@ -11,6 +12,8 @@ import { ListViewWrapper } from './ListViewWrapper';
export interface ListViewNormalizedProps
extends Omit {
normalizedItems: NormalizedItem[];
+ showItemDescriptions: boolean;
+ showItemIcons: boolean;
}
export function ListViewNormalized({
@@ -19,6 +22,9 @@ export function ListViewNormalized({
selectedKeys,
defaultSelectedKeys,
disabledKeys,
+ showItemDescriptions,
+ showItemIcons,
+ UNSAFE_className,
onChange,
onSelectionChange,
...props
@@ -28,7 +34,17 @@ export function ListViewNormalized({
[tooltip]
);
- const renderNormalizedItem = useRenderNormalizedItem(tooltipOptions);
+ const renderNormalizedItem = useRenderNormalizedItem({
+ itemIconSlot: 'illustration',
+ showItemDescriptions,
+ showItemIcons,
+ tooltipOptions,
+ });
+
+ // Spectrum doesn't re-render if only the `renderNormalizedItems` function
+ // changes, so we create a key from its dependencies that can be used to force
+ // re-render.
+ const forceRerenderKey = `${showItemIcons}-${showItemDescriptions}-${tooltipOptions?.placement}`;
const {
selectedStringKeys,
@@ -47,6 +63,8 @@ export function ListViewNormalized({
{
normalizedItems: (NormalizedItem | NormalizedSection)[];
+ showItemIcons: boolean;
getInitialScrollPosition?: () => Promise;
onScroll?: (event: Event) => void;
}
@@ -35,6 +36,7 @@ export function PickerNormalized({
selectedKey,
defaultSelectedKey,
disabledKeys,
+ showItemIcons,
UNSAFE_className,
getInitialScrollPosition,
onChange,
@@ -48,7 +50,20 @@ export function PickerNormalized({
[tooltip]
);
- const renderNormalizedItem = useRenderNormalizedItem(tooltipOptions);
+ const renderNormalizedItem = useRenderNormalizedItem({
+ itemIconSlot: 'icon',
+ // Descriptions introduce variable item heights which throws off calculation
+ // of initial scroll position. For now not going to implement description
+ // support in Picker.
+ showItemDescriptions: false,
+ showItemIcons,
+ tooltipOptions,
+ });
+
+ // Spectrum doesn't re-render if only the `renderNormalizedItems` function
+ // changes, so we create a key from its dependencies that can be used to force
+ // re-render.
+ const forceRerenderKey = `${showItemIcons}-${tooltipOptions?.placement}`;
const { ref: scrollRef, onOpenChange: onOpenChangeInternal } =
usePickerScrollOnOpen({
@@ -77,6 +92,7 @@ export function PickerNormalized({
}
UNSAFE_className={cl(
'dh-picker',
diff --git a/packages/components/src/spectrum/utils/itemUtils.ts b/packages/components/src/spectrum/utils/itemUtils.ts
index 9eac80deb3..3682440939 100644
--- a/packages/components/src/spectrum/utils/itemUtils.ts
+++ b/packages/components/src/spectrum/utils/itemUtils.ts
@@ -29,6 +29,11 @@ export type SectionElement = ReactElement>;
export type ItemElementOrPrimitive = number | string | boolean | ItemElement;
export type ItemOrSection = ItemElementOrPrimitive | SectionElement;
+// Picker uses `icon` slot. ListView can use `image` or `illustration` slots.
+// https://github.com/adobe/react-spectrum/blob/main/packages/%40react-spectrum/picker/src/Picker.tsx#L194
+// https://github.com/adobe/react-spectrum/blob/main/packages/%40react-spectrum/list/src/ListViewItem.tsx#L266-L267
+export type ItemIconSlot = 'icon' | 'image' | 'illustration';
+
/**
* Augment the Spectrum selection key type to include boolean values.
* Spectrum collection components already supports this, but the built in types
@@ -48,6 +53,8 @@ export type ItemSelectionChangeHandler = (key: ItemKey) => void;
export interface NormalizedItemData {
key?: ItemKey;
content: ReactNode;
+ description?: ReactNode;
+ icon?: ReactNode;
textValue: string | undefined;
}
diff --git a/packages/components/src/spectrum/utils/itemWrapperUtils.tsx b/packages/components/src/spectrum/utils/itemWrapperUtils.tsx
index 47d8ae75bf..efed25cbeb 100644
--- a/packages/components/src/spectrum/utils/itemWrapperUtils.tsx
+++ b/packages/components/src/spectrum/utils/itemWrapperUtils.tsx
@@ -1,17 +1,49 @@
-import { cloneElement, ReactElement } from 'react';
+import { cloneElement, ReactElement, ReactNode } from 'react';
import { Item } from '@adobe/react-spectrum';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { dh as dhIcons } from '@deephaven/icons';
import { isElementOfType } from '@deephaven/react-hooks';
+import { NON_BREAKING_SPACE } from '@deephaven/utils';
import {
isItemElement,
isSectionElement,
ItemElement,
+ ItemIconSlot,
ItemOrSection,
ITEM_EMPTY_STRING_TEXT_VALUE,
SectionElement,
TooltipOptions,
} from './itemUtils';
import { ItemProps } from '../shared';
-import ItemContent from '../ItemContent';
+import { ItemContent } from '../ItemContent';
+import { Icon } from '../icons';
+import { Text } from '../Text';
+
+/**
+ * If the given content is a string, wrap it in an Icon component. Otherwise,
+ * return the original content. If the key is not found in the dhIcons object,
+ * the vsBlank icon will be used.
+ * @param maybeIconKey The content to wrap
+ * @param slot The slot to use for the Icon component
+ * @returns The wrapped content or original content if not a string
+ */
+export function wrapIcon(
+ maybeIconKey: ReactNode,
+ slot: ItemIconSlot
+): ReactNode {
+ // eslint-disable-next-line no-param-reassign
+ maybeIconKey = maybeIconKey ?? '';
+
+ if (typeof maybeIconKey !== 'string') {
+ return maybeIconKey;
+ }
+
+ return (
+
+
+
+ );
+}
/**
* Ensure all primitive children are wrapped in `Item` elements and that all
@@ -88,4 +120,26 @@ export function wrapItemChildren(
});
}
-export default wrapItemChildren;
+/**
+ * If the given content is a primitive type, wrap it in a Text component.
+ * @param content The content to wrap
+ * @param slot The slot to use for the Text component
+ * @returns The wrapped content or original content if not a primitive type
+ */
+export function wrapPrimitiveWithText(
+ content?: ReactNode,
+ slot?: string
+): ReactNode {
+ // eslint-disable-next-line no-param-reassign
+ content = content ?? '';
+
+ if (['string', 'boolean', 'number'].includes(typeof content)) {
+ return (
+
+ {content === '' ? NON_BREAKING_SPACE : String(content)}
+
+ );
+ }
+
+ return content;
+}
diff --git a/packages/components/src/spectrum/utils/useRenderNormalizedItem.tsx b/packages/components/src/spectrum/utils/useRenderNormalizedItem.tsx
index 1f199653d7..025417e119 100644
--- a/packages/components/src/spectrum/utils/useRenderNormalizedItem.tsx
+++ b/packages/components/src/spectrum/utils/useRenderNormalizedItem.tsx
@@ -3,10 +3,19 @@ import { ItemContent } from '../ItemContent';
import { Item } from '../shared';
import {
getItemKey,
+ ItemIconSlot,
ITEM_EMPTY_STRING_TEXT_VALUE,
NormalizedItem,
TooltipOptions,
} from './itemUtils';
+import { wrapIcon, wrapPrimitiveWithText } from './itemWrapperUtils';
+
+export interface UseRenderNormalizedItemOptions {
+ itemIconSlot: ItemIconSlot;
+ showItemDescriptions: boolean;
+ showItemIcons: boolean;
+ tooltipOptions: TooltipOptions | null;
+}
/**
* Returns a render function that can be used to render a normalized item in
@@ -14,15 +23,28 @@ import {
* @param tooltipOptions Tooltip options to use when rendering the item
* @returns Render function for normalized items
*/
-export function useRenderNormalizedItem(
- tooltipOptions: TooltipOptions | null
-): (normalizedItem: NormalizedItem) => JSX.Element {
+export function useRenderNormalizedItem({
+ itemIconSlot,
+ showItemDescriptions,
+ showItemIcons,
+ tooltipOptions,
+}: UseRenderNormalizedItemOptions): (
+ normalizedItem: NormalizedItem
+) => JSX.Element {
return useCallback(
(normalizedItem: NormalizedItem) => {
const key = getItemKey(normalizedItem);
- const content = normalizedItem.item?.content ?? '';
+ const content = wrapPrimitiveWithText(normalizedItem.item?.content);
const textValue = normalizedItem.item?.textValue ?? '';
+ const description = showItemDescriptions
+ ? wrapPrimitiveWithText(normalizedItem.item?.description, 'description')
+ : null;
+
+ const icon = showItemIcons
+ ? wrapIcon(normalizedItem.item?.icon, itemIconSlot)
+ : null;
+
return (
-
- {content}
+
+ {icon}
+ {content}
+ {description}
+
);
},
- [tooltipOptions]
+ [itemIconSlot, showItemDescriptions, showItemIcons, tooltipOptions]
);
}
diff --git a/packages/jsapi-components/src/spectrum/ListView.tsx b/packages/jsapi-components/src/spectrum/ListView.tsx
index 994fed84e8..b48848eefc 100644
--- a/packages/jsapi-components/src/spectrum/ListView.tsx
+++ b/packages/jsapi-components/src/spectrum/ListView.tsx
@@ -6,7 +6,10 @@ import {
} from '@deephaven/components';
import { dh as DhType } from '@deephaven/jsapi-types';
import { Settings } from '@deephaven/jsapi-utils';
-import { LIST_VIEW_ROW_HEIGHTS } from '@deephaven/utils';
+import {
+ LIST_VIEW_ROW_HEIGHTS,
+ LIST_VIEW_ROW_HEIGHTS_WITH_DESCRIPTIONS,
+} from '@deephaven/utils';
import useFormatter from '../useFormatter';
import useViewportData from '../useViewportData';
import { useItemRowDeserializer } from './utils';
@@ -18,7 +21,11 @@ export interface ListViewProps extends ListViewNormalizedProps {
/* The column of values to display as primary text. Defaults to the `keyColumn` value. */
labelColumn?: string;
- // TODO #1890 : descriptionColumn, iconColumn
+ /* The column of values to display as descriptions. */
+ descriptionColumn?: string;
+
+ /* The column of values to map to icons. */
+ iconColumn?: string;
settings?: Settings;
}
@@ -27,16 +34,24 @@ export function ListView({
table,
keyColumn: keyColumnName,
labelColumn: labelColumnName,
+ descriptionColumn: descriptionColumnName,
+ iconColumn: iconColumnName,
settings,
...props
}: ListViewProps): JSX.Element {
const { scale } = useSpectrumThemeProvider();
- const itemHeight = LIST_VIEW_ROW_HEIGHTS[props.density ?? 'regular'][scale];
+ const itemHeight = (
+ descriptionColumnName == null
+ ? LIST_VIEW_ROW_HEIGHTS
+ : LIST_VIEW_ROW_HEIGHTS_WITH_DESCRIPTIONS
+ )[props.density ?? 'regular'][scale];
const { getFormattedString: formatValue } = useFormatter(settings);
const deserializeRow = useItemRowDeserializer({
table,
+ descriptionColumnName,
+ iconColumnName,
keyColumnName,
labelColumnName,
formatValue,
@@ -57,6 +72,8 @@ export function ListView({
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
normalizedItems={viewportData.items}
+ showItemDescriptions={descriptionColumnName != null}
+ showItemIcons={iconColumnName != null}
onScroll={onScroll}
/>
);
diff --git a/packages/jsapi-components/src/spectrum/Picker.tsx b/packages/jsapi-components/src/spectrum/Picker.tsx
index 838aa08e7a..7eff83737c 100644
--- a/packages/jsapi-components/src/spectrum/Picker.tsx
+++ b/packages/jsapi-components/src/spectrum/Picker.tsx
@@ -28,7 +28,11 @@ export interface PickerProps extends Omit {
/* The column of values to display as primary text. Defaults to the `keyColumn` value. */
labelColumn?: string;
- // TODO #1890 : descriptionColumn, iconColumn
+ /* The column of values to display as descriptions. */
+ descriptionColumn?: string;
+
+ /* The column of values to map to icons. */
+ iconColumn?: string;
settings?: Settings;
}
@@ -37,6 +41,7 @@ export function Picker({
table,
keyColumn: keyColumnName,
labelColumn: labelColumnName,
+ iconColumn: iconColumnName,
settings,
onChange,
onSelectionChange,
@@ -61,6 +66,7 @@ export function Picker({
const deserializeRow = useItemRowDeserializer({
table,
+ iconColumnName,
keyColumnName,
labelColumnName,
formatValue,
@@ -140,6 +146,7 @@ export function Picker({
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
normalizedItems={normalizedItems}
+ showItemIcons={iconColumnName != null}
getInitialScrollPosition={getInitialScrollPosition}
onChange={onSelectionChangeInternal}
onScroll={onScroll}
diff --git a/packages/jsapi-components/src/spectrum/utils/useItemRowDeserializer.ts b/packages/jsapi-components/src/spectrum/utils/useItemRowDeserializer.ts
index c09526d10e..d168547277 100644
--- a/packages/jsapi-components/src/spectrum/utils/useItemRowDeserializer.ts
+++ b/packages/jsapi-components/src/spectrum/utils/useItemRowDeserializer.ts
@@ -22,6 +22,8 @@ function defaultFormatValue(value: unknown, _columnType: string): string {
/**
* Returns a function that deserializes a row into a normalized item data object.
* @param table The table to get the key and label columns from
+ * @param descriptionColumnName The name of the column to use for description data
+ * @param iconColumnName The name of the column to use for icon data
* @param keyColumnName The name of the column to use for key data
* @param labelColumnName The name of the column to use for label data
* @param formatValue Optional function to format the label value
@@ -29,11 +31,15 @@ function defaultFormatValue(value: unknown, _columnType: string): string {
*/
export function useItemRowDeserializer({
table,
+ descriptionColumnName,
+ iconColumnName,
keyColumnName,
labelColumnName,
formatValue = defaultFormatValue,
}: {
table: dh.Table;
+ descriptionColumnName?: string;
+ iconColumnName?: string;
keyColumnName?: string;
labelColumnName?: string;
formatValue?: (value: unknown, columnType: string) => string;
@@ -48,18 +54,40 @@ export function useItemRowDeserializer({
[keyColumn, labelColumnName, table]
);
+ const descriptionColumn = useMemo(
+ () =>
+ descriptionColumnName == null
+ ? null
+ : table.findColumn(descriptionColumnName),
+ [descriptionColumnName, table]
+ );
+
+ const iconColumn = useMemo(
+ () => (iconColumnName == null ? null : table.findColumn(iconColumnName)),
+ [iconColumnName, table]
+ );
+
const deserializeRow = useCallback(
(row: dh.Row): NormalizedItemData => {
const key = defaultFormatKey(row.get(keyColumn));
const content = formatValue(row.get(labelColumn), labelColumn.type);
+ const description =
+ descriptionColumn == null
+ ? undefined
+ : formatValue(row.get(descriptionColumn), descriptionColumn.type);
+
+ const icon = iconColumn == null ? undefined : row.get(iconColumn);
+
return {
key,
content,
textValue: content,
+ description,
+ icon,
};
},
- [formatValue, keyColumn, labelColumn]
+ [descriptionColumn, formatValue, iconColumn, keyColumn, labelColumn]
);
return deserializeRow;
diff --git a/packages/utils/src/UIConstants.ts b/packages/utils/src/UIConstants.ts
index 2a5d7b962a..98ce05e307 100644
--- a/packages/utils/src/UIConstants.ts
+++ b/packages/utils/src/UIConstants.ts
@@ -31,3 +31,18 @@ export const LIST_VIEW_ROW_HEIGHTS = {
large: 60,
},
} as const;
+
+export const LIST_VIEW_ROW_HEIGHTS_WITH_DESCRIPTIONS = {
+ compact: {
+ medium: 48,
+ large: 59,
+ },
+ regular: {
+ medium: 54,
+ large: 67,
+ },
+ spacious: {
+ medium: 56,
+ large: 69,
+ },
+} as const;