Skip to content

Commit

Permalink
feat: ListView components (#1919)
Browse files Browse the repository at this point in the history
- ListView components
- Smart item tooltip handling (only show when text is overflowing)
- Split out some shared util code from Picker
- Wrapped Text, View, and Heading in forwardRef

**Testing**
This can be tested using this PR:
deephaven/deephaven-plugins#408 . To make
TypeScript happy, you can install DHC as an alpha via:

```sh
npm run update-dh-packages -- --scope=@deephaven/js-plugin-ui -- 0.72.1-alpha-list-view.38
```

BREAKING CHANGE: `LIST_VIEW_ROW_HEIGHT` number constant replaced with
dictionary `LIST_VIEW_ROW_HEIGHTS`
  • Loading branch information
bmingles authored Apr 19, 2024
1 parent 01c2a06 commit b63ab18
Show file tree
Hide file tree
Showing 42 changed files with 1,934 additions and 190 deletions.
15 changes: 15 additions & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ Object.defineProperty(window, 'ResizeObserver', {
},
});

Object.defineProperty(window, 'DOMRect', {
value: function (x: number = 0, y: number = 0, width = 0, height = 0) {
return TestUtils.createMockProxy<DOMRect>({
x,
y,
width,
height,
top: y,
bottom: y + height,
left: x,
right: x + width,
});
},
});

Object.defineProperty(window, 'TextDecoder', {
value: TextDecoder,
});
Expand Down
123 changes: 123 additions & 0 deletions packages/code-studio/src/styleguide/ListViews.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React, { useCallback, useState } from 'react';
import {
Grid,
Icon,
Item,
ListView,
ItemKey,
Text,
} from '@deephaven/components';
import { vsAccount, vsPerson } from '@deephaven/icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { generateNormalizedItems, sampleSectionIdAndClasses } from './utils';

// Generate enough items to require scrolling
const itemsSimple = [...generateNormalizedItems(52)];

function AccountIllustration(): JSX.Element {
return (
// Images in ListView items require a slot of 'image' or 'illustration' to
// be set in order to be positioned correctly:
// https://github.com/adobe/react-spectrum/blob/784737effd44b9d5e2b1316e690da44555eafd7e/packages/%40react-spectrum/list/src/ListViewItem.tsx#L266-L267
<Icon slot="illustration">
<FontAwesomeIcon icon={vsAccount} />
</Icon>
);
}

export function ListViews(): JSX.Element {
const [selectedKeys, setSelectedKeys] = useState<'all' | Iterable<ItemKey>>(
[]
);

const onChange = useCallback((keys: 'all' | Iterable<ItemKey>): void => {
setSelectedKeys(keys);
}, []);

return (
// eslint-disable-next-line react/jsx-props-no-spreading
<div {...sampleSectionIdAndClasses('list-views')}>
<h2 className="ui-title">List View</h2>

<Grid columnGap={14} height="size-6000">
<Text>Single Child</Text>
<ListView
density="compact"
gridRow="2"
aria-label="Single Child"
selectionMode="multiple"
>
<Item>Aaa</Item>
</ListView>

<label>Icons</label>
<ListView
gridRow="2"
aria-label="Icon"
density="compact"
selectionMode="multiple"
>
<Item textValue="Item with icon A">
<AccountIllustration />
<Text>Item with icon A</Text>
</Item>
<Item textValue="Item with icon B">
<AccountIllustration />
<Text>Item with icon B</Text>
</Item>
<Item textValue="Item with icon C">
<AccountIllustration />
<Text>Item with icon C</Text>
</Item>
<Item textValue="Item with icon D">
<AccountIllustration />
<Text>Item with icon D with overflowing content</Text>
</Item>
</ListView>

<label>Mixed Children Types</label>
<ListView
gridRow="2"
aria-label="Mixed Children Types"
density="compact"
maxWidth="size-2400"
selectionMode="multiple"
defaultSelectedKeys={[999, 444]}
>
{/* 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>Item Aaa</Item>
<Item>Item Bbb</Item>
<Item textValue="Complex Ccc">
<Icon slot="image">
<FontAwesomeIcon icon={vsPerson} />
</Icon>
<Text>Complex Ccc with text that should be truncated</Text>
</Item>
</ListView>

<label>Controlled</label>
<ListView
gridRow="2"
aria-label="Controlled"
selectionMode="multiple"
selectedKeys={selectedKeys}
onChange={onChange}
>
{itemsSimple}
</ListView>
</Grid>
</div>
);
}

export default ListViews;
11 changes: 3 additions & 8 deletions packages/code-studio/src/styleguide/Pickers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,10 @@ import {
import { vsPerson } from '@deephaven/icons';
import { Icon } from '@adobe/react-spectrum';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { sampleSectionIdAndClasses } from './utils';
import { generateNormalizedItems, sampleSectionIdAndClasses } from './utils';

// Generate enough items to require scrolling
const items = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
.split('')
.map((key, i) => ({
key,
item: { key: (i + 1) * 100, content: `${key}${key}${key}` },
}));
const items = [...generateNormalizedItems(52)];

function PersonIcon(): JSX.Element {
return (
Expand All @@ -29,7 +24,7 @@ function PersonIcon(): JSX.Element {
}

export function Pickers(): JSX.Element {
const [selectedKey, setSelectedKey] = useState<ItemKey>();
const [selectedKey, setSelectedKey] = useState<ItemKey | null>(null);

const onChange = useCallback((key: ItemKey): void => {
setSelectedKey(key);
Expand Down
26 changes: 26 additions & 0 deletions packages/code-studio/src/styleguide/StyleGuide.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,37 @@ import StyleGuide from './StyleGuide';
window.HTMLElement.prototype.scroll = jest.fn();
window.HTMLElement.prototype.scrollIntoView = jest.fn();

/**
* Mock a dimension property of a ListView element.
*/
function mockListViewDimension(propName: keyof HTMLElement, value: number) {
jest
.spyOn(window.HTMLElement.prototype, propName, 'get')
.mockImplementation(function getDimension() {
const isSpectrumListView =
this instanceof HTMLElement &&
this.className.includes('_react-spectrum-ListView');

// For non ListView, just return zero which is the default value anyway.
return isSpectrumListView === true ? value : 0;
});
}

describe('<StyleGuide /> mounts', () => {
test('h1 text of StyleGuide renders', () => {
// Provide a non-null array to ThemeProvider to tell it to initialize
const customThemes: ThemeData[] = [];

// React Spectrum `useVirtualizerItem` depends on `scrollWidth` and `scrollHeight`.
// Mocking these to avoid React "Maximum update depth exceeded" errors.
// https://github.com/adobe/react-spectrum/blob/0b2a838b36ad6d86eee13abaf68b7e4d2b4ada6c/packages/%40react-aria/virtualizer/src/useVirtualizerItem.ts#L49C3-L49C60
// From preview docs: https://reactspectrum.blob.core.windows.net/reactspectrum/726a5e8f0ed50fc8d98e39c74bd6dfeb3660fbdf/docs/react-spectrum/testing.html#virtualized-components
// The virtualizer will now think it has a visible area of 1000px x 1000px and that the items within it are 40px x 40px
mockListViewDimension('clientWidth', 1000);
mockListViewDimension('clientHeight', 1000);
mockListViewDimension('scrollHeight', 40);
mockListViewDimension('scrollWidth', 40);

expect(() =>
render(
<ApiContext.Provider value={dh}>
Expand Down
2 changes: 2 additions & 0 deletions packages/code-studio/src/styleguide/StyleGuide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { GoldenLayout } from './GoldenLayout';
import { RandomAreaPlotAnimation } from './RandomAreaPlotAnimation';
import SpectrumComparison from './SpectrumComparison';
import Pickers from './Pickers';
import ListViews from './ListViews';

const stickyProps = {
position: 'sticky',
Expand Down Expand Up @@ -109,6 +110,7 @@ function StyleGuide(): React.ReactElement {
<Buttons />
<Progress />
<Inputs />
<ListViews />
<Pickers />
<ItemListInputs />
<DraggableLists />
Expand Down
Loading

0 comments on commit b63ab18

Please sign in to comment.