1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +02:00

chore: add project list view toggle with respective flag (#10452)

https://linear.app/unleash/issue/2-3746/add-project-list-view-toggle-with-respective-flag

Adds a project list view toggle hidden behind a feature flag:
`projectListViewToggle`.

This is already part of the persistent project list page state.

Even though the view mode switching logic is in place, this isn't really
doing anything else. We'll leave the actual visual changes (tables) for
a follow up PR.

<img width="1412" height="406" alt="image"
src="https://github.com/user-attachments/assets/793d0bd9-9874-4630-98b4-0ee364f50241"
/>
This commit is contained in:
Nuno Góis 2025-08-04 08:53:04 +01:00 committed by GitHub
parent 91f138349e
commit bd5a8539c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 81 additions and 27 deletions

View File

@ -19,6 +19,8 @@ import { ProjectArchiveLink } from './ProjectArchiveLink/ProjectArchiveLink.tsx'
import { ProjectsListHeader } from './ProjectsListHeader/ProjectsListHeader.tsx';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { TablePlaceholder } from 'component/common/Table/index.ts';
import { useUiFlag } from 'hooks/useUiFlag.ts';
import { ProjectsListViewToggle } from './ProjectsListViewToggle/ProjectsListViewToggle.tsx';
const StyledApiError = styled(ApiError)(({ theme }) => ({
maxWidth: '500px',
@ -34,6 +36,7 @@ const StyledContainer = styled('div')(({ theme }) => ({
export const ProjectList = () => {
const { projects, loading, error, refetch } = useProjects();
const { isOss } = useUiConfig();
const projectListViewToggleEnabled = useUiFlag('projectListViewToggle');
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
@ -126,14 +129,25 @@ export const ProjectList = () => {
<ProjectsListHeader
helpText='Favorite projects, projects you own, and projects you are a member of'
actions={
<ProjectsListSort
sortBy={state.sortBy}
setSortBy={(sortBy) =>
setState({
sortBy: sortBy as typeof state.sortBy,
})
}
/>
<>
{projectListViewToggleEnabled &&
!isOss() && (
<ProjectsListViewToggle
view={state.view}
setView={(view) =>
setState({ view })
}
/>
)}
<ProjectsListSort
sortBy={state.sortBy}
setSortBy={(sortBy) =>
setState({
sortBy: sortBy as typeof state.sortBy,
})
}
/>
</>
}
>
My projects

View File

@ -25,6 +25,13 @@ const StyledHeaderTitle = styled('div')(({ theme }) => ({
flexGrow: 0,
}));
const StyledHeaderActions = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
marginLeft: 'auto',
gap: theme.spacing(2),
}));
export const ProjectsListHeader: FC<ProjectsListHeaderProps> = ({
children,
helpText,
@ -36,7 +43,7 @@ export const ProjectsListHeader: FC<ProjectsListHeaderProps> = ({
{children}
<HelpIcon tooltip={helpText} />
</StyledHeaderTitle>
{actions}
<StyledHeaderActions>{actions}</StyledHeaderActions>
</StyledHeaderContainer>
);
};

View File

@ -2,12 +2,6 @@ import type { FC } from 'react';
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
import { styled } from '@mui/material';
const StyledWrapper = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'flex-end',
flex: 1,
}));
const StyledContainer = styled('div')(() => ({
maxWidth: '200px',
width: '100%',
@ -35,16 +29,14 @@ export const ProjectsListSort: FC<ProjectsListSortProps> = ({
setSortBy,
}) => {
return (
<StyledWrapper>
<StyledContainer>
<GeneralSelect
fullWidth
label='Sort by'
onChange={setSortBy}
options={options}
value={sortBy || options[0].key}
/>
</StyledContainer>
</StyledWrapper>
<StyledContainer>
<GeneralSelect
fullWidth
label='Sort by'
onChange={setSortBy}
options={options}
value={sortBy || options[0].key}
/>
</StyledContainer>
);
};

View File

@ -0,0 +1,28 @@
import { IconButton, Tooltip } from '@mui/material';
import type { ProjectsListView } from '../hooks/useProjectsListState.js';
import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
import GridViewIcon from '@mui/icons-material/GridView';
type ProjectsListViewToggleProps = {
view: ProjectsListView;
setView: (view: ProjectsListView) => void;
};
export const ProjectsListViewToggle = ({
view,
setView,
}: ProjectsListViewToggleProps) => {
const nextView = view === 'list' ? 'cards' : 'list';
return (
<Tooltip title={`Switch to ${nextView} view`} arrow>
<IconButton size='small' onClick={() => setView(nextView)}>
{nextView === 'list' ? (
<FormatListBulletedIcon />
) : (
<GridViewIcon />
)}
</IconButton>
</Tooltip>
);
};

View File

@ -7,6 +7,8 @@ import {
} from 'use-query-params';
import { sortKeys } from '../ProjectsListSort/ProjectsListSort.jsx';
export type ProjectsListView = 'cards' | 'list';
const stateConfig = {
query: StringParam,
sortBy: withDefault(
@ -14,6 +16,10 @@ const stateConfig = {
sortKeys[0],
) as QueryParamConfig<(typeof sortKeys)[number] | null | undefined>,
create: StringParam,
view: withDefault(
createEnumParam<ProjectsListView>(['cards', 'list']),
'cards',
) as QueryParamConfig<ProjectsListView>,
} as const;
export const useProjectsListState = () =>

View File

@ -95,6 +95,7 @@ export type UiFlags = {
reportUnknownFlags?: boolean;
lifecycleGraphs?: boolean;
addConfiguration?: boolean;
projectListViewToggle?: boolean;
};
export interface IVersionInfo {

View File

@ -65,7 +65,8 @@ export type IFlagKey =
| 'timestampsInChangeRequestTimeline'
| 'lifecycleGraphs'
| 'githubAuth'
| 'addConfiguration';
| 'addConfiguration'
| 'projectListViewToggle';
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
@ -305,6 +306,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_ADD_CONFIGURATION,
false,
),
projectListViewToggle: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_PROJECT_LIST_VIEW_TOGGLE,
false,
),
};
export const defaultExperimentalOptions: IExperimentalOptions = {

View File

@ -61,6 +61,7 @@ process.nextTick(async () => {
timestampsInChangeRequestTimeline: true,
lifecycleGraphs: true,
addConfiguration: true,
projectListViewToggle: true,
},
},
authentication: {