diff --git a/frontend/src/component/project/ProjectList/ProjectList.tsx b/frontend/src/component/project/ProjectList/ProjectList.tsx index 7dbc97234d..91402773a2 100644 --- a/frontend/src/component/project/ProjectList/ProjectList.tsx +++ b/frontend/src/component/project/ProjectList/ProjectList.tsx @@ -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 = () => { - setState({ - sortBy: sortBy as typeof state.sortBy, - }) - } - /> + <> + {projectListViewToggleEnabled && + !isOss() && ( + + setState({ view }) + } + /> + )} + + setState({ + sortBy: sortBy as typeof state.sortBy, + }) + } + /> + } > My projects diff --git a/frontend/src/component/project/ProjectList/ProjectsListHeader/ProjectsListHeader.tsx b/frontend/src/component/project/ProjectList/ProjectsListHeader/ProjectsListHeader.tsx index 2ed0c0f197..c24992ab7b 100644 --- a/frontend/src/component/project/ProjectList/ProjectsListHeader/ProjectsListHeader.tsx +++ b/frontend/src/component/project/ProjectList/ProjectsListHeader/ProjectsListHeader.tsx @@ -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 = ({ children, helpText, @@ -36,7 +43,7 @@ export const ProjectsListHeader: FC = ({ {children} - {actions} + {actions} ); }; diff --git a/frontend/src/component/project/ProjectList/ProjectsListSort/ProjectsListSort.tsx b/frontend/src/component/project/ProjectList/ProjectsListSort/ProjectsListSort.tsx index 05fa4f1405..8e6054f8b0 100644 --- a/frontend/src/component/project/ProjectList/ProjectsListSort/ProjectsListSort.tsx +++ b/frontend/src/component/project/ProjectList/ProjectsListSort/ProjectsListSort.tsx @@ -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 = ({ setSortBy, }) => { return ( - - - - - + + + ); }; diff --git a/frontend/src/component/project/ProjectList/ProjectsListViewToggle/ProjectsListViewToggle.tsx b/frontend/src/component/project/ProjectList/ProjectsListViewToggle/ProjectsListViewToggle.tsx new file mode 100644 index 0000000000..09ffbeab52 --- /dev/null +++ b/frontend/src/component/project/ProjectList/ProjectsListViewToggle/ProjectsListViewToggle.tsx @@ -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 ( + + setView(nextView)}> + {nextView === 'list' ? ( + + ) : ( + + )} + + + ); +}; diff --git a/frontend/src/component/project/ProjectList/hooks/useProjectsListState.ts b/frontend/src/component/project/ProjectList/hooks/useProjectsListState.ts index 178377f06b..db44b6b6d1 100644 --- a/frontend/src/component/project/ProjectList/hooks/useProjectsListState.ts +++ b/frontend/src/component/project/ProjectList/hooks/useProjectsListState.ts @@ -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(['cards', 'list']), + 'cards', + ) as QueryParamConfig, } as const; export const useProjectsListState = () => diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index 4c83aa5108..ca48030981 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -95,6 +95,7 @@ export type UiFlags = { reportUnknownFlags?: boolean; lifecycleGraphs?: boolean; addConfiguration?: boolean; + projectListViewToggle?: boolean; }; export interface IVersionInfo { diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index a743b56cc9..c033e042ba 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -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 = { diff --git a/src/server-dev.ts b/src/server-dev.ts index b6b2a125d4..d72e15d94c 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -61,6 +61,7 @@ process.nextTick(async () => { timestampsInChangeRequestTimeline: true, lifecycleGraphs: true, addConfiguration: true, + projectListViewToggle: true, }, }, authentication: {