1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-17 13:46:47 +02:00

feat: update search placement on flags overview screen (#9854)

- search has a new place, closer to filters
- filters are adjusted to wrap properly on small screens
This commit is contained in:
Tymoteusz Czech 2025-04-30 09:25:45 +02:00 committed by GitHub
parent 58a01d0c47
commit 6c5fa4c8a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 85 additions and 52 deletions

View File

@ -1,5 +1,5 @@
import { Box, Chip, styled } from '@mui/material'; import { Box, Chip, styled } from '@mui/material';
import type { FC } from 'react'; import type { FC, ReactNode } from 'react';
import type { FilterItemParamHolder } from '../../../filter/Filters/Filters'; import type { FilterItemParamHolder } from '../../../filter/Filters/Filters';
import type { LifecycleStage } from '../../FeatureView/FeatureOverview/FeatureLifecycle/LifecycleStage'; import type { LifecycleStage } from '../../FeatureView/FeatureOverview/FeatureLifecycle/LifecycleStage';
import { useLifecycleCount } from 'hooks/api/getters/useLifecycleCount/useLifecycleCount'; import { useLifecycleCount } from 'hooks/api/getters/useLifecycleCount/useLifecycleCount';
@ -30,13 +30,22 @@ interface ILifecycleFiltersProps {
state: FilterItemParamHolder; state: FilterItemParamHolder;
onChange: (value: FilterItemParamHolder) => void; onChange: (value: FilterItemParamHolder) => void;
total?: number; total?: number;
children?: ReactNode;
} }
const Wrapper = styled(Box)(({ theme }) => ({ const Wrapper = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
padding: theme.spacing(1.5, 3, 0, 3),
minHeight: theme.spacing(7),
gap: theme.spacing(2),
}));
const StyledContainer = styled(Box)(({ theme }) => ({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
flexWrap: 'wrap',
gap: theme.spacing(1), gap: theme.spacing(1),
padding: theme.spacing(2, 3, 0, 3),
})); }));
const lifecycleOptions: { const lifecycleOptions: {
@ -74,43 +83,49 @@ export const LifecycleFilters: FC<ILifecycleFiltersProps> = ({
state, state,
onChange, onChange,
total, total,
children,
}) => { }) => {
const { lifecycleCount } = useLifecycleCount(); const { lifecycleCount } = useLifecycleCount();
const current = state.lifecycle?.values ?? []; const current = state.lifecycle?.values ?? [];
return ( return (
<Wrapper> <Wrapper>
{lifecycleOptions.map(({ label, value }) => { <StyledContainer>
const isActive = {lifecycleOptions.map(({ label, value }) => {
value === null ? !state.lifecycle : current.includes(value); const isActive =
const count = getStageCount(value, lifecycleCount);
const dynamicLabel =
isActive && Number.isInteger(total)
? `${label} (${total === count ? total : `${total} of ${count}`})`
: `${label}${count !== undefined ? ` (${count})` : ''}`;
const handleClick = () =>
onChange(
value === null value === null
? { lifecycle: null } ? !state.lifecycle
: { : current.includes(value);
lifecycle: { const count = getStageCount(value, lifecycleCount);
operator: 'IS', const dynamicLabel =
values: [value], isActive && Number.isInteger(total)
}, ? `${label} (${total === count ? total : `${total} of ${count}`})`
}, : `${label}${count !== undefined ? ` (${count})` : ''}`;
);
return ( const handleClick = () =>
<StyledChip onChange(
key={label} value === null
label={dynamicLabel} ? { lifecycle: null }
variant='outlined' : {
isActive={isActive} lifecycle: {
onClick={handleClick} operator: 'IS',
/> values: [value],
); },
})} },
);
return (
<StyledChip
key={label}
label={dynamicLabel}
variant='outlined'
isActive={isActive}
onClick={handleClick}
/>
);
})}
</StyledContainer>
{children}
</Wrapper> </Wrapper>
); );
}; };

View File

@ -155,7 +155,7 @@ export const FeatureToggleListTable: FC = () => {
onTagClick, onTagClick,
onFlagTypeClick, onFlagTypeClick,
), ),
meta: { width: '50%' }, meta: { width: '40%' },
}), }),
columnHelper.accessor('createdAt', { columnHelper.accessor('createdAt', {
header: 'Created', header: 'Created',
@ -181,7 +181,7 @@ export const FeatureToggleListTable: FC = () => {
data-loading data-loading
/> />
), ),
enableSorting: false, // FIXME: enable sorting by lifecycle enableSorting: false,
size: 50, size: 50,
meta: { width: '1%' }, meta: { width: '1%' },
}), }),
@ -192,6 +192,7 @@ export const FeatureToggleListTable: FC = () => {
<StatusCell {...original} /> <StatusCell {...original} />
), ),
enableSorting: false, enableSorting: false,
size: 350,
}), }),
columnHelper.accessor('project', { columnHelper.accessor('project', {
header: 'Project', header: 'Project',
@ -402,23 +403,19 @@ export const FeatureToggleListTable: FC = () => {
} }
actions={ actions={
<> <>
<ConditionallyRender {!flagsReleaseManagementUIEnabled &&
condition={!isSmallScreen} !isSmallScreen ? (
show={ <>
<> <Search
<Search placeholder='Search'
placeholder='Search' expandable
expandable initialValue={tableState.query || ''}
initialValue={ onChange={setSearchValue}
tableState.query || '' id='globalFeatureFlags'
} />
onChange={setSearchValue} <PageHeader.Divider />
id='globalFeatureFlags' </>
/> ) : null}
<PageHeader.Divider />
</>
}
/>
<Link <Link
component={RouterLink} component={RouterLink}
to='/archive' to='/archive'
@ -449,7 +446,9 @@ export const FeatureToggleListTable: FC = () => {
} }
> >
<ConditionallyRender <ConditionallyRender
condition={isSmallScreen} condition={
isSmallScreen && !flagsReleaseManagementUIEnabled
}
show={ show={
<Search <Search
initialValue={tableState.query || ''} initialValue={tableState.query || ''}
@ -466,17 +465,36 @@ export const FeatureToggleListTable: FC = () => {
state={filterState} state={filterState}
onChange={setTableState} onChange={setTableState}
total={loading ? undefined : total} total={loading ? undefined : total}
/> >
{!isSmallScreen ? (
<Search
placeholder='Search'
initialValue={tableState.query || ''}
onChange={setSearchValue}
id='globalFeatureFlags'
/>
) : null}
</LifecycleFilters>
) : null} ) : null}
<FeatureToggleFilters <FeatureToggleFilters
onChange={setTableState} onChange={setTableState}
state={filterState} state={filterState}
/> />
{isSmallScreen ? (
<Box sx={(theme) => ({ padding: theme.spacing(0, 3, 3) })}>
<Search
initialValue={tableState.query || ''}
onChange={setSearchValue}
id='globalFeatureFlags'
/>
</Box>
) : null}
<SearchHighlightProvider value={tableState.query || ''}> <SearchHighlightProvider value={tableState.query || ''}>
<div ref={bodyLoadingRef}> <div ref={bodyLoadingRef}>
<PaginatedTable tableInstance={table} totalItems={total} /> <PaginatedTable tableInstance={table} totalItems={total} />
</div> </div>
</SearchHighlightProvider> </SearchHighlightProvider>
<ConditionallyRender <ConditionallyRender
condition={rows.length === 0} condition={rows.length === 0}
show={ show={