mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
refactor: refactor personal dashboard front end code pt1 (#8440)
This is the first step in refactoring the front end code for personal dashboards. At this point: - extract `useDashboardState` to its own file - extract my flags to its own file - Rename `Grid.tsx` to `SharedComponents.tsx` as it contains more than just the grid.
This commit is contained in:
parent
39fb1b5db5
commit
9d49070cee
@ -9,7 +9,7 @@ import {
|
||||
EmptyGridItem,
|
||||
ProjectGrid,
|
||||
GridItem,
|
||||
} from './Grid';
|
||||
} from './SharedComponents';
|
||||
|
||||
const PaddedEmptyGridItem = styled(EmptyGridItem)(({ theme }) => ({
|
||||
padding: theme.spacing(4),
|
||||
|
180
frontend/src/component/personalDashboard/MyFlags.tsx
Normal file
180
frontend/src/component/personalDashboard/MyFlags.tsx
Normal file
@ -0,0 +1,180 @@
|
||||
import { type FC, useEffect, useRef } from 'react';
|
||||
import {
|
||||
ContentGridContainer,
|
||||
FlagGrid,
|
||||
ListItemBox,
|
||||
SpacedGridItem,
|
||||
StyledCardTitle,
|
||||
StyledList,
|
||||
listItemStyle,
|
||||
} from './SharedComponents';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
|
||||
import {
|
||||
Alert,
|
||||
IconButton,
|
||||
Link,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
Typography,
|
||||
styled,
|
||||
} from '@mui/material';
|
||||
import LinkIcon from '@mui/icons-material/ArrowForward';
|
||||
import React from 'react';
|
||||
import type { PersonalDashboardSchemaFlagsItem } from 'openapi';
|
||||
|
||||
const NoActiveFlagsInfo = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexFlow: 'column',
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const FlagListItem: FC<{
|
||||
flag: { name: string; project: string; type: string };
|
||||
selected: boolean;
|
||||
onClick: () => void;
|
||||
}> = ({ flag, selected, onClick }) => {
|
||||
const activeFlagRef = useRef<HTMLLIElement>(null);
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
|
||||
useEffect(() => {
|
||||
if (activeFlagRef.current) {
|
||||
activeFlagRef.current.scrollIntoView({
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
const IconComponent = getFeatureTypeIcons(flag.type);
|
||||
const flagLink = `projects/${flag.project}/features/${flag.name}`;
|
||||
return (
|
||||
<ListItem
|
||||
key={flag.name}
|
||||
disablePadding={true}
|
||||
sx={{ mb: 1 }}
|
||||
ref={selected ? activeFlagRef : null}
|
||||
>
|
||||
<ListItemButton
|
||||
sx={listItemStyle}
|
||||
selected={selected}
|
||||
onClick={onClick}
|
||||
>
|
||||
<ListItemBox>
|
||||
<IconComponent color='primary' />
|
||||
<StyledCardTitle>{flag.name}</StyledCardTitle>
|
||||
<IconButton
|
||||
component={Link}
|
||||
href={flagLink}
|
||||
onClick={() => {
|
||||
trackEvent('personal-dashboard', {
|
||||
props: {
|
||||
eventType: `Go to flag from list`,
|
||||
},
|
||||
});
|
||||
}}
|
||||
size='small'
|
||||
sx={{ ml: 'auto' }}
|
||||
>
|
||||
<LinkIcon titleAccess={flagLink} />
|
||||
</IconButton>
|
||||
</ListItemBox>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
type FlagData =
|
||||
| {
|
||||
state: 'flags';
|
||||
flags: PersonalDashboardSchemaFlagsItem[];
|
||||
activeFlag?: PersonalDashboardSchemaFlagsItem;
|
||||
}
|
||||
| {
|
||||
state: 'no flags';
|
||||
};
|
||||
|
||||
type Props = {
|
||||
hasProjects: boolean;
|
||||
flagData: FlagData;
|
||||
setActiveFlag: (flag: PersonalDashboardSchemaFlagsItem) => void;
|
||||
refetchDashboard: () => void;
|
||||
};
|
||||
|
||||
export const MyFlags: FC<Props> = ({
|
||||
hasProjects,
|
||||
flagData,
|
||||
setActiveFlag,
|
||||
refetchDashboard,
|
||||
}) => {
|
||||
return (
|
||||
<ContentGridContainer>
|
||||
<FlagGrid>
|
||||
<SpacedGridItem gridArea='flags'>
|
||||
{flagData.state === 'flags' ? (
|
||||
<StyledList
|
||||
disablePadding={true}
|
||||
sx={{
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{flagData.flags.map((flag) => (
|
||||
<FlagListItem
|
||||
key={flag.name}
|
||||
flag={flag}
|
||||
selected={
|
||||
flag.name === flagData.activeFlag?.name
|
||||
}
|
||||
onClick={() => setActiveFlag(flag)}
|
||||
/>
|
||||
))}
|
||||
</StyledList>
|
||||
) : hasProjects ? (
|
||||
<NoActiveFlagsInfo>
|
||||
<Typography>
|
||||
You have not created or favorited any feature
|
||||
flags. Once you do, they will show up here.
|
||||
</Typography>
|
||||
<Typography>
|
||||
To create a new flag, go to one of your
|
||||
projects.
|
||||
</Typography>
|
||||
</NoActiveFlagsInfo>
|
||||
) : (
|
||||
<Alert severity='info'>
|
||||
You need to create or join a project to be able to
|
||||
add a flag, or you must be given the rights by your
|
||||
admin to add feature flags.
|
||||
</Alert>
|
||||
)}
|
||||
</SpacedGridItem>
|
||||
|
||||
<SpacedGridItem gridArea='chart'>
|
||||
{flagData.state === 'flags' && flagData.activeFlag ? (
|
||||
<FlagMetricsChart
|
||||
flag={flagData.activeFlag}
|
||||
onArchive={refetchDashboard}
|
||||
/>
|
||||
) : (
|
||||
<PlaceholderFlagMetricsChart
|
||||
label={
|
||||
'Metrics for your feature flags will be shown here'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</SpacedGridItem>
|
||||
</FlagGrid>
|
||||
</ContentGridContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const FlagMetricsChart = React.lazy(() =>
|
||||
import('./FlagMetricsChart').then((module) => ({
|
||||
default: module.FlagMetricsChart,
|
||||
})),
|
||||
);
|
||||
const PlaceholderFlagMetricsChart = React.lazy(() =>
|
||||
import('./FlagMetricsChart').then((module) => ({
|
||||
default: module.PlaceholderFlagMetricsChartWithWrapper,
|
||||
})),
|
||||
);
|
@ -13,7 +13,6 @@ import { ConnectSDK, CreateFlag, ExistingFlag } from './ConnectSDK';
|
||||
import { LatestProjectEvents } from './LatestProjectEvents';
|
||||
import { RoleAndOwnerInfo } from './RoleAndOwnerInfo';
|
||||
import { forwardRef, useEffect, useRef, type FC } from 'react';
|
||||
import { StyledCardTitle } from './PersonalDashboard';
|
||||
import type {
|
||||
PersonalDashboardProjectDetailsSchema,
|
||||
PersonalDashboardSchemaAdminsItem,
|
||||
@ -28,7 +27,8 @@ import {
|
||||
GridItem,
|
||||
SpacedGridItem,
|
||||
StyledList,
|
||||
} from './Grid';
|
||||
StyledCardTitle,
|
||||
} from './SharedComponents';
|
||||
import { ContactAdmins, DataError } from './ProjectDetailsError';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
|
||||
|
@ -3,201 +3,23 @@ import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Alert,
|
||||
Button,
|
||||
IconButton,
|
||||
Link,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
styled,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import React, { type FC, useEffect, useRef } from 'react';
|
||||
import LinkIcon from '@mui/icons-material/ArrowForward';
|
||||
import { WelcomeDialog } from './WelcomeDialog';
|
||||
import { useLocalStorageState } from 'hooks/useLocalStorageState';
|
||||
import { usePersonalDashboard } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboard';
|
||||
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
|
||||
import type {
|
||||
PersonalDashboardSchemaFlagsItem,
|
||||
PersonalDashboardSchemaProjectsItem,
|
||||
} from '../../openapi';
|
||||
import { usePersonalDashboardProjectDetails } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboardProjectDetails';
|
||||
import useLoading from '../../hooks/useLoading';
|
||||
import { MyProjects } from './MyProjects';
|
||||
import {
|
||||
ContentGridContainer,
|
||||
FlagGrid,
|
||||
ListItemBox,
|
||||
listItemStyle,
|
||||
SpacedGridItem,
|
||||
StyledList,
|
||||
} from './Grid';
|
||||
import { ContentGridNoProjects } from './ContentGridNoProjects';
|
||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import useSplashApi from 'hooks/api/actions/useSplashApi/useSplashApi';
|
||||
import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash';
|
||||
|
||||
export const StyledCardTitle = styled('div')<{ lines?: number }>(
|
||||
({ theme, lines = 2 }) => ({
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
lineClamp: `${lines}`,
|
||||
WebkitLineClamp: lines,
|
||||
lineHeight: '1.2',
|
||||
display: '-webkit-box',
|
||||
boxOrient: 'vertical',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
alignItems: 'flex-start',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
wordBreak: 'break-word',
|
||||
}),
|
||||
);
|
||||
const FlagListItem: FC<{
|
||||
flag: { name: string; project: string; type: string };
|
||||
selected: boolean;
|
||||
onClick: () => void;
|
||||
}> = ({ flag, selected, onClick }) => {
|
||||
const activeFlagRef = useRef<HTMLLIElement>(null);
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
|
||||
useEffect(() => {
|
||||
if (activeFlagRef.current) {
|
||||
activeFlagRef.current.scrollIntoView({
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
const IconComponent = getFeatureTypeIcons(flag.type);
|
||||
const flagLink = `projects/${flag.project}/features/${flag.name}`;
|
||||
return (
|
||||
<ListItem
|
||||
key={flag.name}
|
||||
disablePadding={true}
|
||||
sx={{ mb: 1 }}
|
||||
ref={selected ? activeFlagRef : null}
|
||||
>
|
||||
<ListItemButton
|
||||
sx={listItemStyle}
|
||||
selected={selected}
|
||||
onClick={onClick}
|
||||
>
|
||||
<ListItemBox>
|
||||
<IconComponent color='primary' />
|
||||
<StyledCardTitle>{flag.name}</StyledCardTitle>
|
||||
<IconButton
|
||||
component={Link}
|
||||
href={flagLink}
|
||||
onClick={() => {
|
||||
trackEvent('personal-dashboard', {
|
||||
props: {
|
||||
eventType: `Go to flag from list`,
|
||||
},
|
||||
});
|
||||
}}
|
||||
size='small'
|
||||
sx={{ ml: 'auto' }}
|
||||
>
|
||||
<LinkIcon titleAccess={flagLink} />
|
||||
</IconButton>
|
||||
</ListItemBox>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
// todo: move into own file
|
||||
const useDashboardState = (
|
||||
projects: PersonalDashboardSchemaProjectsItem[],
|
||||
flags: PersonalDashboardSchemaFlagsItem[],
|
||||
) => {
|
||||
type State = {
|
||||
activeProject: string | undefined;
|
||||
activeFlag: PersonalDashboardSchemaFlagsItem | undefined;
|
||||
expandProjects: boolean;
|
||||
expandFlags: boolean;
|
||||
};
|
||||
|
||||
const defaultState: State = {
|
||||
activeProject: undefined,
|
||||
activeFlag: undefined,
|
||||
expandProjects: true,
|
||||
expandFlags: true,
|
||||
};
|
||||
|
||||
const [state, setState] = useLocalStorageState<State>(
|
||||
'personal-dashboard:v1',
|
||||
defaultState,
|
||||
);
|
||||
|
||||
const updateState = (newState: Partial<State>) =>
|
||||
setState({ ...defaultState, ...state, ...newState });
|
||||
|
||||
useEffect(() => {
|
||||
const updates: Partial<State> = {};
|
||||
const setDefaultFlag =
|
||||
flags.length &&
|
||||
(!state.activeFlag ||
|
||||
!flags.some((flag) => flag.name === state.activeFlag?.name));
|
||||
|
||||
if (setDefaultFlag) {
|
||||
updates.activeFlag = flags[0];
|
||||
}
|
||||
|
||||
const setDefaultProject =
|
||||
projects.length &&
|
||||
(!state.activeProject ||
|
||||
!projects.some(
|
||||
(project) => project.id === state.activeProject,
|
||||
));
|
||||
|
||||
if (setDefaultProject) {
|
||||
updates.activeProject = projects[0].id;
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length) {
|
||||
updateState(updates);
|
||||
}
|
||||
}, [
|
||||
JSON.stringify(projects, null, 2),
|
||||
JSON.stringify(flags, null, 2),
|
||||
JSON.stringify(state, null, 2),
|
||||
]);
|
||||
|
||||
const { activeFlag, activeProject } = state;
|
||||
|
||||
const setActiveFlag = (flag: PersonalDashboardSchemaFlagsItem) => {
|
||||
updateState({
|
||||
activeFlag: flag,
|
||||
});
|
||||
};
|
||||
|
||||
const setActiveProject = (projectId: string) => {
|
||||
updateState({
|
||||
activeProject: projectId,
|
||||
});
|
||||
};
|
||||
|
||||
const toggleSectionState = (section: 'flags' | 'projects') => {
|
||||
const property = section === 'flags' ? 'expandFlags' : 'expandProjects';
|
||||
updateState({
|
||||
[property]: !(state[property] ?? true),
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
activeFlag,
|
||||
setActiveFlag,
|
||||
activeProject,
|
||||
setActiveProject,
|
||||
expandFlags: state.expandFlags ?? true,
|
||||
expandProjects: state.expandProjects ?? true,
|
||||
toggleSectionState,
|
||||
};
|
||||
};
|
||||
import { useDashboardState } from './useDashboardState';
|
||||
import { MyFlags } from './MyFlags';
|
||||
|
||||
const WelcomeSection = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -258,12 +80,6 @@ const MainContent = styled('div')(({ theme }) => ({
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const NoActiveFlagsInfo = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexFlow: 'column',
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
export const PersonalDashboard = () => {
|
||||
const { user } = useAuthUser();
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
@ -385,70 +201,20 @@ export const PersonalDashboard = () => {
|
||||
</Typography>
|
||||
</StyledAccordionSummary>
|
||||
<StyledAccordionDetails>
|
||||
<ContentGridContainer>
|
||||
<FlagGrid>
|
||||
<SpacedGridItem gridArea='flags'>
|
||||
{personalDashboard &&
|
||||
personalDashboard.flags.length > 0 ? (
|
||||
<StyledList
|
||||
disablePadding={true}
|
||||
sx={{
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{personalDashboard.flags.map((flag) => (
|
||||
<FlagListItem
|
||||
key={flag.name}
|
||||
flag={flag}
|
||||
selected={
|
||||
flag.name ===
|
||||
activeFlag?.name
|
||||
}
|
||||
onClick={() =>
|
||||
setActiveFlag(flag)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</StyledList>
|
||||
) : activeProject ? (
|
||||
<NoActiveFlagsInfo>
|
||||
<Typography>
|
||||
You have not created or favorited
|
||||
any feature flags. Once you do, they
|
||||
will show up here.
|
||||
</Typography>
|
||||
<Typography>
|
||||
To create a new flag, go to one of
|
||||
your projects.
|
||||
</Typography>
|
||||
</NoActiveFlagsInfo>
|
||||
) : (
|
||||
<Alert severity='info'>
|
||||
You need to create or join a project to
|
||||
be able to add a flag, or you must be
|
||||
given the rights by your admin to add
|
||||
feature flags.
|
||||
</Alert>
|
||||
)}
|
||||
</SpacedGridItem>
|
||||
|
||||
<SpacedGridItem gridArea='chart'>
|
||||
{activeFlag ? (
|
||||
<FlagMetricsChart
|
||||
flag={activeFlag}
|
||||
onArchive={refetchDashboard}
|
||||
/>
|
||||
) : (
|
||||
<PlaceholderFlagMetricsChart
|
||||
label={
|
||||
'Metrics for your feature flags will be shown here'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</SpacedGridItem>
|
||||
</FlagGrid>
|
||||
</ContentGridContainer>
|
||||
<MyFlags
|
||||
hasProjects={projects?.length > 0}
|
||||
flagData={
|
||||
personalDashboard && personalDashboard.flags.length
|
||||
? {
|
||||
state: 'flags' as const,
|
||||
activeFlag,
|
||||
flags: personalDashboard.flags,
|
||||
}
|
||||
: { state: 'no flags' as const }
|
||||
}
|
||||
setActiveFlag={setActiveFlag}
|
||||
refetchDashboard={refetchDashboard}
|
||||
/>
|
||||
</StyledAccordionDetails>
|
||||
</SectionAccordion>
|
||||
<WelcomeDialog
|
||||
@ -461,14 +227,3 @@ export const PersonalDashboard = () => {
|
||||
</MainContent>
|
||||
);
|
||||
};
|
||||
|
||||
const FlagMetricsChart = React.lazy(() =>
|
||||
import('./FlagMetricsChart').then((module) => ({
|
||||
default: module.FlagMetricsChart,
|
||||
})),
|
||||
);
|
||||
const PlaceholderFlagMetricsChart = React.lazy(() =>
|
||||
import('./FlagMetricsChart').then((module) => ({
|
||||
default: module.PlaceholderFlagMetricsChartWithWrapper,
|
||||
})),
|
||||
);
|
||||
|
@ -58,7 +58,7 @@ export const FlagGrid = styled(ContentGrid)(
|
||||
);
|
||||
|
||||
export const GridItem = styled('div', {
|
||||
shouldForwardProp: (prop) => !['gridArea', 'sx'].includes(prop.toString()),
|
||||
shouldForwardProp: (prop) => !['gridArea'].includes(prop.toString()),
|
||||
})<{ gridArea: string }>(({ theme, gridArea }) => ({
|
||||
padding: theme.spacing(2, 4),
|
||||
maxHeight: '100%',
|
||||
@ -113,3 +113,20 @@ export const StyledList = styled(List)(({ theme }) => ({
|
||||
maxHeight: '100%',
|
||||
})({ theme }),
|
||||
}));
|
||||
|
||||
export const StyledCardTitle = styled('div')<{ lines?: number }>(
|
||||
({ theme, lines = 2 }) => ({
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
lineClamp: `${lines}`,
|
||||
WebkitLineClamp: lines,
|
||||
lineHeight: '1.2',
|
||||
display: '-webkit-box',
|
||||
boxOrient: 'vertical',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
alignItems: 'flex-start',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
wordBreak: 'break-word',
|
||||
}),
|
||||
);
|
@ -0,0 +1,95 @@
|
||||
import { useLocalStorageState } from 'hooks/useLocalStorageState';
|
||||
import type {
|
||||
PersonalDashboardSchemaFlagsItem,
|
||||
PersonalDashboardSchemaProjectsItem,
|
||||
} from 'openapi';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const useDashboardState = (
|
||||
projects: PersonalDashboardSchemaProjectsItem[],
|
||||
flags: PersonalDashboardSchemaFlagsItem[],
|
||||
) => {
|
||||
type State = {
|
||||
activeProject: string | undefined;
|
||||
activeFlag: PersonalDashboardSchemaFlagsItem | undefined;
|
||||
expandProjects: boolean;
|
||||
expandFlags: boolean;
|
||||
};
|
||||
|
||||
const defaultState: State = {
|
||||
activeProject: undefined,
|
||||
activeFlag: undefined,
|
||||
expandProjects: true,
|
||||
expandFlags: true,
|
||||
};
|
||||
|
||||
const [state, setState] = useLocalStorageState<State>(
|
||||
'personal-dashboard:v1',
|
||||
defaultState,
|
||||
);
|
||||
|
||||
const updateState = (newState: Partial<State>) =>
|
||||
setState({ ...defaultState, ...state, ...newState });
|
||||
|
||||
useEffect(() => {
|
||||
const updates: Partial<State> = {};
|
||||
const setDefaultFlag =
|
||||
flags.length &&
|
||||
(!state.activeFlag ||
|
||||
!flags.some((flag) => flag.name === state.activeFlag?.name));
|
||||
|
||||
if (setDefaultFlag) {
|
||||
updates.activeFlag = flags[0];
|
||||
}
|
||||
|
||||
const setDefaultProject =
|
||||
projects.length &&
|
||||
(!state.activeProject ||
|
||||
!projects.some(
|
||||
(project) => project.id === state.activeProject,
|
||||
));
|
||||
|
||||
if (setDefaultProject) {
|
||||
updates.activeProject = projects[0].id;
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length) {
|
||||
updateState(updates);
|
||||
}
|
||||
}, [
|
||||
JSON.stringify(projects),
|
||||
JSON.stringify(flags),
|
||||
JSON.stringify(state),
|
||||
]);
|
||||
|
||||
const { activeFlag, activeProject } = state;
|
||||
|
||||
const setActiveFlag = (flag: PersonalDashboardSchemaFlagsItem) => {
|
||||
updateState({
|
||||
activeFlag: flag,
|
||||
});
|
||||
};
|
||||
|
||||
const setActiveProject = (projectId: string) => {
|
||||
updateState({
|
||||
activeProject: projectId,
|
||||
});
|
||||
};
|
||||
|
||||
const toggleSectionState = (section: 'flags' | 'projects') => {
|
||||
const property = section === 'flags' ? 'expandFlags' : 'expandProjects';
|
||||
updateState({
|
||||
[property]: !(state[property] ?? true),
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
activeFlag,
|
||||
setActiveFlag,
|
||||
activeProject,
|
||||
setActiveProject,
|
||||
expandFlags: state.expandFlags ?? true,
|
||||
expandProjects: state.expandProjects ?? true,
|
||||
toggleSectionState,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user