Skip to content
Merged
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
16 changes: 16 additions & 0 deletions frontend/src/components/ACLPage/List/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { ControlPanelWrapper } from 'components/common/ControlPanel/ControlPanel
import Search from 'components/common/Search/Search';
import ResourcePageHeading from 'components/common/ResourcePageHeading/ResourcePageHeading';
import BreakableTextCell from 'components/common/NewTable/BreakableTextCell';
import { useQueryPersister } from 'components/common/NewTable/ColumnFilter';

import * as S from './List.styled';

Expand Down Expand Up @@ -82,6 +83,10 @@ const ACList: React.FC = () => {
cell: ({ getValue }) => (
<S.EnumCell>{getValue<string>().toLowerCase()}</S.EnumCell>
),
filterFn: 'arrIncludesSome',
meta: {
filterVariant: 'multi-select',
},
size: 145,
},
{
Expand Down Expand Up @@ -112,6 +117,10 @@ const ACList: React.FC = () => {
</S.PatternCell>
);
},
filterFn: 'includesString',
meta: {
filterVariant: 'text',
},
size: 257,
},
{
Expand All @@ -126,6 +135,10 @@ const ACList: React.FC = () => {
cell: ({ getValue }) => (
<S.EnumCell>{getValue<string>().toLowerCase()}</S.EnumCell>
),
filterFn: 'arrIncludesSome',
meta: {
filterVariant: 'multi-select',
},
size: 121,
},
{
Expand Down Expand Up @@ -165,6 +178,8 @@ const ACList: React.FC = () => {
[rowId]
);

const filterPersister = useQueryPersister(columns);

return (
<S.Container>
<ResourcePageHeading text="Access Control List">
Expand Down Expand Up @@ -193,6 +208,7 @@ const ACList: React.FC = () => {
emptyMessage="No ACL items found"
onRowHover={handleRowHover}
onMouseLeave={() => setRowId('')}
filterPersister={filterPersister}
enableSorting
/>
<ACLFormContext.Provider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export const ColumnFilter = <T,>(props: FilterProps<T>) => {
case 'multi-select': {
return <Variant.MultiSelect column={column} />;
}
case 'text': {
return <Variant.Text column={column} />;
}
default: {
throw Error('Not implemented filter');
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import styled from 'styled-components';

export const Container = styled.div`
display: flex;
height: 24px;
align-items: center;
position: relative;
padding-right: 8px;
`;

export const Positioner = styled.div`
position: absolute;
z-index: 30;
`;

export const Count = styled.span`
padding-left: 4px;
color: ${({ theme }) => theme.table.filter.multiSelect.value.color};
`;

export const FilterIcon = styled.div`
height: 12px;
margin: 2px;
`;

export const ResetIcon = styled.div`
margin: 3px;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, { useState, useRef, ReactNode } from 'react';
import { Column } from '@tanstack/react-table';
import useBoolean from 'lib/hooks/useBoolean';
import Portal from 'components/common/Portal/Portal';
import useClickOutside from 'lib/hooks/useClickOutside';

import FilterIcon from './FilterIcon';
import ClearIcon from './ClearIcon';
import * as S from './FilterContainer.styled';

interface Props<T> {
column: Column<T, unknown>;
hasFilterValue: boolean;
valueComponent?: ReactNode;
filterComponent?: ReactNode;
}

const TOP_PADDING = 8;

export const FilterContainer = <T,>(props: Props<T>) => {
const { column, valueComponent, filterComponent, hasFilterValue } = props;
const { value: opened, toggle } = useBoolean(false);
const [coords, setCoords] = useState<{ right: number; top: number }>({
right: 0,
top: 0,
});

const ref = useRef(null);
useClickOutside(ref, toggle);

const resetFilter = () => column.setFilterValue('');
const onFilterClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
const node = event.target as HTMLElement;
const rect = node.getBoundingClientRect();
setCoords({
right: window.innerWidth - rect.right,
top: rect.bottom + TOP_PADDING,
});
toggle();
};

return (
<S.Container>
<S.FilterIcon onClick={onFilterClick}>
<FilterIcon active={opened || hasFilterValue} />
</S.FilterIcon>

{hasFilterValue && (
<>
{valueComponent}
<S.ResetIcon onClick={resetFilter}>
<ClearIcon />
</S.ResetIcon>
</>
)}

<Portal isOpen={opened}>
<S.Positioner
ref={ref}
style={{
right: coords.right,
top: coords.top,
}}
>
{filterComponent}
</S.Positioner>
</Portal>
</S.Container>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const SelectPanel = styled(ReactMultiSelect)<{
minWidth?: string;
height?: string;
}>`
min-width: 160px;
font-size: 14px;
padding-right: 12px;
.dropdown-container:focus-within {
Expand Down Expand Up @@ -50,7 +51,6 @@ export const SelectPanel = styled(ReactMultiSelect)<{

& > .dropdown-content {
width: fit-content;
min-width: 120px;
right: 0px;
top: 0;
padding-top: 0;
Expand All @@ -67,29 +67,7 @@ export const SelectPanel = styled(ReactMultiSelect)<{
}
`;

export const Container = styled.div`
display: flex;
height: 24px;
align-items: center;
position: relative;
padding-right: 8px;
`;

export const Positioner = styled.div`
position: absolute;
z-index: 30;
`;

export const Count = styled.span`
padding-left: 4px;
color: ${({ theme }) => theme.table.filter.multiSelect.value.color};
`;

export const FilterIcon = styled.div`
height: 12px;
margin: 2px;
`;

export const ResetIcon = styled.div`
margin: 3px;
`;
Original file line number Diff line number Diff line change
@@ -1,44 +1,16 @@
import React, { useState, useRef, useMemo } from 'react';
import React, { useMemo } from 'react';
import { Column } from '@tanstack/react-table';
import useBoolean from 'lib/hooks/useBoolean';
import Portal from 'components/common/Portal/Portal';
import useClickOutside from 'lib/hooks/useClickOutside';
import { FilterContainer } from 'components/common/NewTable/ColumnFilter/ui/FilterContainer/FilterContainer';

import ClearIcon from './ui/ClearIcon';
import SelectPanel from './SelectPanel';
import * as S from './MultiSelect.styled';
import FilterIcon from './ui/FilterIcon';

interface FilterProps<T> {
column: Column<T, unknown>;
}

const TOP_PADDING = 8;
const LEFT_PADDING = 16;

export const MultiSelect = <T,>(props: FilterProps<T>) => {
const { column } = props;
const { value: opened, toggle } = useBoolean(false);
const [coords, setCoords] = useState<{ left: number; top: number }>({
left: 0,
top: 0,
});

const ref = useRef(null);
useClickOutside(ref, toggle);

const resetFilter = () => column.setFilterValue('');
const onFilterClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
const node = event.target as HTMLElement;
const rect = node.getBoundingClientRect();
setCoords({
left: rect.left + LEFT_PADDING,
top: rect.bottom + TOP_PADDING,
});
toggle();
};

const value: string[] = useMemo(() => {
const filterValue = column.getFilterValue();
Expand All @@ -50,31 +22,11 @@ export const MultiSelect = <T,>(props: FilterProps<T>) => {
}, [column.getFilterValue()]);

return (
<S.Container>
<S.FilterIcon onClick={onFilterClick}>
<FilterIcon active={opened || !!value.length} />
</S.FilterIcon>

{!!value.length && (
<>
<S.Count>{value.length}</S.Count>
<S.ResetIcon onClick={resetFilter}>
<ClearIcon />
</S.ResetIcon>
</>
)}

<Portal isOpen={opened}>
<S.Positioner
ref={ref}
style={{
left: coords.left,
top: coords.top,
}}
>
<SelectPanel column={column} />
</S.Positioner>
</Portal>
</S.Container>
<FilterContainer
column={column}
hasFilterValue={value.length > 0}
valueComponent={<S.Count>{value.length}</S.Count>}
filterComponent={<SelectPanel column={column} />}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { Column } from '@tanstack/react-table';
import { FilterContainer } from 'components/common/NewTable/ColumnFilter/ui/FilterContainer/FilterContainer';
import Input from 'components/common/Input/Input';

interface FilterProps<T> {
column: Column<T, unknown>;
}

const TextFilterInput = <T,>(props: FilterProps<T>) => {
const { column } = props;
const value = column.getFilterValue() as string;
return (
<Input
autoFocus
value={value}
onChange={({ target }) => {
column.setFilterValue(target?.value ?? '');
}}
/>
);
};

export const Text = <T,>(props: FilterProps<T>) => {
const { column } = props;

const value = column.getFilterValue() as string | undefined;

return (
<FilterContainer
column={column}
filterComponent={<TextFilterInput column={column} />}
hasFilterValue={!!value?.length}
/>
);
};
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { MultiSelect } from './MultiSelect/MultiSelect';
export { Text } from './Text/Text';
3 changes: 0 additions & 3 deletions frontend/src/components/common/NewTable/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,6 @@ function Table<TData>({
columnResizeMode: 'onChange',
columnResizeDirection: 'ltr',
enableColumnResizing,
debugTable: true,
debugHeaders: true,
debugColumns: true,
state: {
sorting: getSortingFromSearchParams(searchParams),
pagination: getPaginationFromSearchParams(searchParams),
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/tanstack.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { type RowData, type FilterFn } from '@tanstack/react-table';

declare module '@tanstack/react-table' {
interface ColumnMeta<TData extends RowData, TValue> {
filterVariant?: 'multi-select';
filterVariant?: 'multi-select' | 'text';
width?: string;
}

Expand Down
Loading