Skip to content

Commit 9a23bb7

Browse files
committed
add search to column list, styling
1 parent d4ad2cf commit 9a23bb7

11 files changed

Lines changed: 448 additions & 209 deletions

File tree

vuu-ui/packages/vuu-shell/src/shell-layout-templates/context-panel/ContextPanel.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
}
1717

1818
.vuuView.vuuContextPanel-inner {
19-
background-color: var(--salt-container-primary-background);
19+
background-color: inherit;
2020
box-shadow: var(--vuu-side-panel-shadow, none);
2121
display: flex;
2222
flex-direction: column;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from "react";
2+
// TODO try and get TS path alias working to avoid relative paths like this
3+
import {
4+
DefaultColumnList,
5+
ManyColumnList,
6+
ManyColumnListWithSearch,
7+
ManyColumnListRemoveOnly,
8+
} from "../../../../../showcase/src/examples/TableExtras/TableSettings.examples";
9+
10+
describe("ColumnList", () => {
11+
describe("DefaultColumnList", () => {
12+
it("THEN expected list is rendered", () => {
13+
cy.mount(<DefaultColumnList />);
14+
cy.findByRole("listbox").should("be.visible");
15+
});
16+
});
17+
describe("ColumnList with 200+ columns", () => {
18+
it("THEN expected list is rendered", () => {
19+
cy.mount(<ManyColumnList />);
20+
cy.findByRole("listbox").should("be.visible");
21+
});
22+
23+
describe("WHEN configured wuth removeOnly", () => {
24+
it("THEN expected list is rendered", () => {
25+
cy.mount(<ManyColumnListRemoveOnly />);
26+
cy.findByRole("listbox").should("be.visible");
27+
});
28+
});
29+
});
30+
describe("ColumnList with 200+ columns, with search", () => {
31+
it("THEN expected list is rendered", () => {
32+
cy.mount(<ManyColumnListWithSearch />);
33+
cy.findByRole("search").should("be.visible");
34+
cy.findByRole("listbox").should("be.visible");
35+
});
36+
});
37+
});

vuu-ui/packages/vuu-table-extras/src/column-list/ColumnList.css

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@
55
--vuuListItem-padding: 0;
66
display: flex;
77
flex-direction: column;
8+
padding: 0 var(--salt-spacing-100);
89

10+
.saltListBox {
11+
background-color: inherit;
12+
}
913

1014
.saltOption {
1115
align-items: center;
12-
background-color: var(--salt-container-primary-background);
16+
background-color: inherit;
1317
border-bottom: var(--vuuColumnListItem-border, solid) 1px
1418
var(--salt-separable-tertiary-borderColor);
1519
gap: var(--salt-spacing-200);
16-
padding: var(--salt-spacing-100) 0;
17-
20+
height: calc(var(--salt-size-base) + var(--salt-spacing-200));
1821
.vuuIcon {
1922
--vuu-icon-color: var(--salt-content-secondary-foreground);
2023
}
@@ -38,16 +41,19 @@
3841
}
3942

4043
.vuuColumnList-search {
41-
padding: var(--salt-spacing-300);
44+
--saltInput-paddingLeft: var(--salt-spacing-300);
45+
padding: var(--salt-spacing-200) var(--salt-spacing-300) var(--salt-spacing-300) var(--salt-spacing-300);
4246
}
4347

4448
.vuuColumnList-header {
49+
align-items: center;
4550
border-top: solid 2px var(--vuu-color-gray-30);
46-
flex: 0 0 40px;
51+
display: flex;
4752
font-size: 14px;
4853
font-weight: 600;
54+
flex: 0 0 var(--vuuColumnList-headerHeight, 32px);
4955
padding-left: var(--salt-spacing-400);
50-
padding-top: 24px;
56+
5157
}
5258

5359
.vuuColumnList-colHeadings {

vuu-ui/packages/vuu-table-extras/src/column-list/ColumnList.tsx

Lines changed: 20 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { Icon, IconButton } from "@vuu-ui/vuu-ui-controls";
1616
import {
1717
DragDropProvider,
1818
getColumnLabel,
19-
queryClosest,
2019
reorderColumnItems,
2120
useSortable,
2221
} from "@vuu-ui/vuu-utils";
@@ -25,18 +24,16 @@ import {
2524
HTMLAttributes,
2625
MouseEventHandler,
2726
RefCallback,
28-
SyntheticEvent,
2927
useCallback,
3028
useMemo,
3129
useRef,
3230
} from "react";
33-
import { ColumnItem } from "../table-column-settings/useTableSettings";
31+
import { ColumnItem, useColumnList } from "./useColumnList";
3432

35-
import columnList from "./ColumnList.css";
36-
import { useColumnList } from "../table-column-settings/useColumnList";
33+
import cssColumnList from "./ColumnList.css";
3734

38-
const classBase = "vuuColumnList";
39-
const classBaseListItem = "vuuColumnListItem";
35+
export const classBase = "vuuColumnList";
36+
export const classBaseListItem = "vuuColumnListItem";
4037

4138
const searchIcon = <span data-icon="search" />;
4239
const NO_SELECTION: string[] = [] as const;
@@ -154,50 +151,29 @@ export const ColumnList = ({
154151
const targetWindow = useWindow();
155152
useComponentCssInjection({
156153
testId: "vuu-column-list",
157-
css: columnList,
154+
css: cssColumnList,
158155
window: targetWindow,
159156
});
160157
const listRef = useRef<HTMLDivElement>(null);
161-
const [permissions, hideOnly] = useMemo(
162-
() => [
163-
{
164-
allowHideColumns,
165-
allowRemoveColumns,
166-
allowReorderColumns,
167-
},
168-
allowHideColumns && !allowRemoveColumns,
169-
],
158+
const permissions = useMemo(
159+
() => ({
160+
allowHideColumns,
161+
allowRemoveColumns,
162+
allowReorderColumns,
163+
}),
170164
[allowHideColumns, allowRemoveColumns, allowReorderColumns],
171165
);
172166

173167
const {
174-
onChange: onSearchInputChange,
168+
onChangeListItem,
169+
onChangeSearchInput,
175170
searchState,
176171
visibleColumnItems,
177-
} = useColumnList({ columnItems });
178-
179-
const handleChange = useCallback(
180-
({ target }: SyntheticEvent) => {
181-
const input = target as HTMLInputElement;
182-
const listItem = queryClosest(target, `.${classBaseListItem}`);
183-
if (listItem) {
184-
const {
185-
dataset: { name },
186-
} = listItem;
187-
if (name) {
188-
const saltCheckbox = queryClosest(target, `.${classBase}-checkBox`);
189-
const saltSwitch = queryClosest(target, `.${classBase}-switch`);
190-
191-
if (saltCheckbox && !hideOnly) {
192-
onChange(name, "subscribed", input.checked);
193-
} else if (saltSwitch || hideOnly) {
194-
onChange(name, "hidden", input.checked === false);
195-
}
196-
}
197-
}
198-
},
199-
[hideOnly, onChange],
200-
);
172+
} = useColumnList({
173+
columnItems,
174+
permissions,
175+
onChange,
176+
});
201177

202178
const handleClick = useCallback<MouseEventHandler>(
203179
(evt) => {
@@ -246,7 +222,7 @@ export const ColumnList = ({
246222
placeholder="Find column"
247223
ref={searchCallbackRef}
248224
value={searchState.searchText}
249-
onChange={onSearchInputChange}
225+
onChange={onChangeSearchInput}
250226
/>
251227
</form>
252228
) : null}
@@ -265,7 +241,7 @@ export const ColumnList = ({
265241
item={columnItem}
266242
index={index}
267243
key={columnItem.name}
268-
onChange={handleChange}
244+
onChange={onChangeListItem}
269245
onClick={handleClick}
270246
permissions={permissions}
271247
value={columnItem}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import {
2+
ColumnDescriptor,
3+
ColumnListPermissions,
4+
} from "@vuu-ui/vuu-table-types";
5+
import {
6+
FormEventHandler,
7+
SyntheticEvent,
8+
useCallback,
9+
useMemo,
10+
useRef,
11+
useState,
12+
} from "react";
13+
import { classBase, classBaseListItem, ColumnListProps } from "./ColumnList";
14+
import { queryClosest } from "@vuu-ui/vuu-utils";
15+
16+
export interface ColumnSearchProps
17+
extends Pick<ColumnListProps, "columnItems" | "onChange"> {
18+
permissions: ColumnListPermissions;
19+
}
20+
21+
export type ColumnItem = Pick<
22+
ColumnDescriptor,
23+
"hidden" | "label" | "name" | "serverDataType"
24+
> & {
25+
isCalculated: boolean;
26+
subscribed: boolean;
27+
};
28+
29+
export const useColumnList = ({
30+
columnItems,
31+
onChange,
32+
permissions: { allowHideColumns, allowRemoveColumns },
33+
}: ColumnSearchProps) => {
34+
const [searchState, setSearchState] = useState<{
35+
searchText: string;
36+
}>({ searchText: "" });
37+
38+
const visibleColumnsRef = useRef<ColumnItem[] | undefined>(undefined);
39+
40+
const hideOnly = useMemo(
41+
() => allowHideColumns && !allowRemoveColumns,
42+
[allowHideColumns, allowRemoveColumns],
43+
);
44+
45+
useMemo(() => {
46+
if (searchState.searchText) {
47+
visibleColumnsRef.current = columnItems.filter(
48+
(item) => item.name.indexOf(searchState.searchText) !== -1,
49+
);
50+
}
51+
}, [columnItems, searchState.searchText]);
52+
53+
const handleChangeSearchInput = useCallback<FormEventHandler>(
54+
(evt) => {
55+
const { value } = evt.target as HTMLInputElement;
56+
if (value) {
57+
visibleColumnsRef.current = columnItems.filter(
58+
(item) => item.name.indexOf(value) !== -1,
59+
);
60+
} else {
61+
visibleColumnsRef.current = undefined;
62+
}
63+
setSearchState({
64+
searchText: value,
65+
});
66+
},
67+
[columnItems],
68+
);
69+
70+
const handleChangeListItem = useCallback(
71+
({ target }: SyntheticEvent) => {
72+
const input = target as HTMLInputElement;
73+
const listItem = queryClosest(target, `.${classBaseListItem}`);
74+
if (listItem) {
75+
const {
76+
dataset: { name },
77+
} = listItem;
78+
if (name) {
79+
const saltCheckbox = queryClosest(target, `.${classBase}-checkBox`);
80+
const saltSwitch = queryClosest(target, `.${classBase}-switch`);
81+
82+
if (saltCheckbox && !hideOnly) {
83+
onChange(name, "subscribed", input.checked);
84+
} else if (saltSwitch || hideOnly) {
85+
onChange(name, "hidden", input.checked === false);
86+
}
87+
}
88+
}
89+
},
90+
[hideOnly, onChange],
91+
);
92+
93+
return {
94+
onChangeSearchInput: handleChangeSearchInput,
95+
onChangeListItem: handleChangeListItem,
96+
searchState,
97+
visibleColumnItems: visibleColumnsRef.current ?? columnItems,
98+
};
99+
};

vuu-ui/packages/vuu-table-extras/src/table-column-settings/TableSettingsPanel.css

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.vuuTableSettingsPanel {
2+
--vuuScrollable-size: 6px;
23
--vuu-svg-text-strikethrough: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 24"><path d="M28.3333 9.33334C28.8867 9.33334 29.3333 8.88668 29.3333 8.33334C29.3333 7.78001 28.8867 7.33334 28.3333 7.33334H20.26L22.26 9.33334H23.48L23.1133 10.1867L24.5067 11.58L25.4733 9.33334H28.3333ZM27.6267 16.5867L18.7467 7.70668C18.4867 7.44668 18.0667 7.44668 17.8067 7.70668C17.5467 7.96668 17.5467 8.38668 17.8067 8.64668L21.98 12.82L20.88 15.38C20.62 15.9933 21.0667 16.6667 21.7267 16.6667C22.0933 16.6667 22.4267 16.4467 22.5733 16.1067L23.38 14.22L26.68 17.52C26.94 17.78 27.36 17.78 27.62 17.52C27.8867 17.2667 27.8867 16.8467 27.6267 16.5867Z"/></svg>');
34
--vuu-svg-text-Tt: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 24"><path d="M17.6667 7.66666C17.6667 8.21999 18.1133 8.66666 18.6667 8.66666H21V15.6667C21 16.22 21.4467 16.6667 22 16.6667C22.5533 16.6667 23 16.22 23 15.6667V8.66666H25.3333C25.8867 8.66666 26.3333 8.21999 26.3333 7.66666C26.3333 7.11332 25.8867 6.66666 25.3333 6.66666H18.6667C18.1133 6.66666 17.6667 7.11332 17.6667 7.66666ZM29.3333 9.99999H25.3333C24.78 9.99999 24.3333 10.4467 24.3333 11C24.3333 11.5533 24.78 12 25.3333 12H26.3333V15.6667C26.3333 16.22 26.78 16.6667 27.3333 16.6667C27.8867 16.6667 28.3333 16.22 28.3333 15.6667V12H29.3333C29.8867 12 30.3333 11.5533 30.3333 11C30.3333 10.4467 29.8867 9.99999 29.3333 9.99999Z" /></svg>');
45
--vuu-svg-text-T: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 24"><path d="M19.3333 7.66666C19.3333 8.21999 19.78 8.66666 20.3333 8.66666H23V15.6667C23 16.22 23.4467 16.6667 24 16.6667C24.5533 16.6667 25 16.22 25 15.6667V8.66666H27.6667C28.22 8.66666 28.6667 8.21999 28.6667 7.66666C28.6667 7.11332 28.22 6.66666 27.6667 6.66666H20.3333C19.78 6.66666 19.3333 7.11332 19.3333 7.66666Z"/></svg>');
@@ -11,7 +12,7 @@
1112
gap: 24px;
1213
height: 100%;
1314
max-width: 254px;
14-
padding: 24px 2px 0 2px;
15+
padding: 12px 2px var(--salt-spacing-200) 2px;
1516
}
1617

1718

@@ -41,10 +42,16 @@
4142
padding-top: 24px;
4243
}
4344

44-
.vuuTableSettingsPanel .vuuColumnList {
45+
.vuuTableSettingsPanel-columnListContainer {
46+
4547
flex-grow: 1;
4648
flex-shrink: 1;
4749
flex-basis: 0;
50+
position: relative;
51+
overflow: auto;
52+
.vuuColumnList {
53+
height: 100%;
54+
}
4855
}
4956

5057
.vuuTableSettingsPanel-calculatedButtonbar {

vuu-ui/packages/vuu-table-extras/src/table-column-settings/TableSettingsPanel.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { ColumnList } from "../column-list";
1515
import { useTableSettings } from "./useTableSettings";
1616
import { Icon } from "@vuu-ui/vuu-ui-controls";
1717
import { VuuInput } from "@vuu-ui/vuu-ui-controls";
18+
import cx from "clsx";
1819

1920
import tableSettingsPanelCss from "./TableSettingsPanel.css";
2021

@@ -147,13 +148,15 @@ export const TableSettingsPanel = ({
147148
</FormField>
148149
) : null}
149150

150-
<ColumnList
151-
columnItems={columnItems}
152-
permissions={columnListPermissions}
153-
onChange={onColumnChange}
154-
onNavigateToColumn={onNavigateToColumn}
155-
onReorderColumnItems={onReorderColumnItems}
156-
/>
151+
<div className={cx(`${classBase}-columnListContainer`, "vuuScrollable")}>
152+
<ColumnList
153+
columnItems={columnItems}
154+
permissions={columnListPermissions}
155+
onChange={onColumnChange}
156+
onNavigateToColumn={onNavigateToColumn}
157+
onReorderColumnItems={onReorderColumnItems}
158+
/>
159+
</div>
157160

158161
{allowCalculatedColumns ? (
159162
<div className={`${classBase}-calculatedButtonbar`}>

vuu-ui/packages/vuu-table-extras/src/table-column-settings/useColumnList.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)