mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-19 17:52:45 +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:
parent
91f138349e
commit
bd5a8539c0
@ -19,6 +19,8 @@ import { ProjectArchiveLink } from './ProjectArchiveLink/ProjectArchiveLink.tsx'
|
|||||||
import { ProjectsListHeader } from './ProjectsListHeader/ProjectsListHeader.tsx';
|
import { ProjectsListHeader } from './ProjectsListHeader/ProjectsListHeader.tsx';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { TablePlaceholder } from 'component/common/Table/index.ts';
|
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 }) => ({
|
const StyledApiError = styled(ApiError)(({ theme }) => ({
|
||||||
maxWidth: '500px',
|
maxWidth: '500px',
|
||||||
@ -34,6 +36,7 @@ const StyledContainer = styled('div')(({ theme }) => ({
|
|||||||
export const ProjectList = () => {
|
export const ProjectList = () => {
|
||||||
const { projects, loading, error, refetch } = useProjects();
|
const { projects, loading, error, refetch } = useProjects();
|
||||||
const { isOss } = useUiConfig();
|
const { isOss } = useUiConfig();
|
||||||
|
const projectListViewToggleEnabled = useUiFlag('projectListViewToggle');
|
||||||
|
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
@ -126,14 +129,25 @@ export const ProjectList = () => {
|
|||||||
<ProjectsListHeader
|
<ProjectsListHeader
|
||||||
helpText='Favorite projects, projects you own, and projects you are a member of'
|
helpText='Favorite projects, projects you own, and projects you are a member of'
|
||||||
actions={
|
actions={
|
||||||
<ProjectsListSort
|
<>
|
||||||
sortBy={state.sortBy}
|
{projectListViewToggleEnabled &&
|
||||||
setSortBy={(sortBy) =>
|
!isOss() && (
|
||||||
setState({
|
<ProjectsListViewToggle
|
||||||
sortBy: sortBy as typeof state.sortBy,
|
view={state.view}
|
||||||
})
|
setView={(view) =>
|
||||||
}
|
setState({ view })
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ProjectsListSort
|
||||||
|
sortBy={state.sortBy}
|
||||||
|
setSortBy={(sortBy) =>
|
||||||
|
setState({
|
||||||
|
sortBy: sortBy as typeof state.sortBy,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
My projects
|
My projects
|
||||||
|
@ -25,6 +25,13 @@ const StyledHeaderTitle = styled('div')(({ theme }) => ({
|
|||||||
flexGrow: 0,
|
flexGrow: 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledHeaderActions = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
export const ProjectsListHeader: FC<ProjectsListHeaderProps> = ({
|
export const ProjectsListHeader: FC<ProjectsListHeaderProps> = ({
|
||||||
children,
|
children,
|
||||||
helpText,
|
helpText,
|
||||||
@ -36,7 +43,7 @@ export const ProjectsListHeader: FC<ProjectsListHeaderProps> = ({
|
|||||||
{children}
|
{children}
|
||||||
<HelpIcon tooltip={helpText} />
|
<HelpIcon tooltip={helpText} />
|
||||||
</StyledHeaderTitle>
|
</StyledHeaderTitle>
|
||||||
{actions}
|
<StyledHeaderActions>{actions}</StyledHeaderActions>
|
||||||
</StyledHeaderContainer>
|
</StyledHeaderContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,12 +2,6 @@ import type { FC } from 'react';
|
|||||||
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
|
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
const StyledWrapper = styled('div')(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
flex: 1,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledContainer = styled('div')(() => ({
|
const StyledContainer = styled('div')(() => ({
|
||||||
maxWidth: '200px',
|
maxWidth: '200px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -35,16 +29,14 @@ export const ProjectsListSort: FC<ProjectsListSortProps> = ({
|
|||||||
setSortBy,
|
setSortBy,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledContainer>
|
||||||
<StyledContainer>
|
<GeneralSelect
|
||||||
<GeneralSelect
|
fullWidth
|
||||||
fullWidth
|
label='Sort by'
|
||||||
label='Sort by'
|
onChange={setSortBy}
|
||||||
onChange={setSortBy}
|
options={options}
|
||||||
options={options}
|
value={sortBy || options[0].key}
|
||||||
value={sortBy || options[0].key}
|
/>
|
||||||
/>
|
</StyledContainer>
|
||||||
</StyledContainer>
|
|
||||||
</StyledWrapper>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
@ -7,6 +7,8 @@ import {
|
|||||||
} from 'use-query-params';
|
} from 'use-query-params';
|
||||||
import { sortKeys } from '../ProjectsListSort/ProjectsListSort.jsx';
|
import { sortKeys } from '../ProjectsListSort/ProjectsListSort.jsx';
|
||||||
|
|
||||||
|
export type ProjectsListView = 'cards' | 'list';
|
||||||
|
|
||||||
const stateConfig = {
|
const stateConfig = {
|
||||||
query: StringParam,
|
query: StringParam,
|
||||||
sortBy: withDefault(
|
sortBy: withDefault(
|
||||||
@ -14,6 +16,10 @@ const stateConfig = {
|
|||||||
sortKeys[0],
|
sortKeys[0],
|
||||||
) as QueryParamConfig<(typeof sortKeys)[number] | null | undefined>,
|
) as QueryParamConfig<(typeof sortKeys)[number] | null | undefined>,
|
||||||
create: StringParam,
|
create: StringParam,
|
||||||
|
view: withDefault(
|
||||||
|
createEnumParam<ProjectsListView>(['cards', 'list']),
|
||||||
|
'cards',
|
||||||
|
) as QueryParamConfig<ProjectsListView>,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const useProjectsListState = () =>
|
export const useProjectsListState = () =>
|
||||||
|
@ -95,6 +95,7 @@ export type UiFlags = {
|
|||||||
reportUnknownFlags?: boolean;
|
reportUnknownFlags?: boolean;
|
||||||
lifecycleGraphs?: boolean;
|
lifecycleGraphs?: boolean;
|
||||||
addConfiguration?: boolean;
|
addConfiguration?: boolean;
|
||||||
|
projectListViewToggle?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IVersionInfo {
|
export interface IVersionInfo {
|
||||||
|
@ -65,7 +65,8 @@ export type IFlagKey =
|
|||||||
| 'timestampsInChangeRequestTimeline'
|
| 'timestampsInChangeRequestTimeline'
|
||||||
| 'lifecycleGraphs'
|
| 'lifecycleGraphs'
|
||||||
| 'githubAuth'
|
| 'githubAuth'
|
||||||
| 'addConfiguration';
|
| 'addConfiguration'
|
||||||
|
| 'projectListViewToggle';
|
||||||
|
|
||||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||||
|
|
||||||
@ -305,6 +306,10 @@ const flags: IFlags = {
|
|||||||
process.env.UNLEASH_EXPERIMENTAL_ADD_CONFIGURATION,
|
process.env.UNLEASH_EXPERIMENTAL_ADD_CONFIGURATION,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
projectListViewToggle: parseEnvVarBoolean(
|
||||||
|
process.env.UNLEASH_EXPERIMENTAL_PROJECT_LIST_VIEW_TOGGLE,
|
||||||
|
false,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||||
|
@ -61,6 +61,7 @@ process.nextTick(async () => {
|
|||||||
timestampsInChangeRequestTimeline: true,
|
timestampsInChangeRequestTimeline: true,
|
||||||
lifecycleGraphs: true,
|
lifecycleGraphs: true,
|
||||||
addConfiguration: true,
|
addConfiguration: true,
|
||||||
|
projectListViewToggle: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authentication: {
|
authentication: {
|
||||||
|
Loading…
Reference in New Issue
Block a user