Created a breadcrumb for left nav menu sub items (#6762)
Closes https://github.com/twentyhq/twenty/issues/6484 <img width="270" alt="image" src="https://github.com/user-attachments/assets/3cfd7a5a-5239-4998-87f7-a9b45e3b5229">
This commit is contained in:
@ -4,7 +4,7 @@ import { IconSearch, IconSettings } from 'twenty-ui';
|
|||||||
|
|
||||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||||
import { Favorites } from '@/favorites/components/Favorites';
|
import { Favorites } from '@/favorites/components/Favorites';
|
||||||
import { ObjectMetadataNavItems } from '@/object-metadata/components/ObjectMetadataNavItems';
|
import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems';
|
||||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||||
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
||||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||||
@ -41,8 +41,8 @@ export const MainNavigationDrawerItems = () => {
|
|||||||
|
|
||||||
<Favorites />
|
<Favorites />
|
||||||
|
|
||||||
<ObjectMetadataNavItems isRemote={false} />
|
<NavigationDrawerSectionForObjectMetadataItems isRemote={false} />
|
||||||
<ObjectMetadataNavItems isRemote={true} />
|
<NavigationDrawerSectionForObjectMetadataItems isRemote={true} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { AnimatePresence, motion } from 'framer-motion';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined, useIcons } from 'twenty-ui';
|
import { isDefined, useIcons } from 'twenty-ui';
|
||||||
|
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
|
import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
|
||||||
import { ObjectMetadataNavItemsSkeletonLoader } from '@/object-metadata/components/ObjectMetadataNavItemsSkeletonLoader';
|
import { NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader';
|
||||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||||
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
||||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||||
@ -15,9 +14,9 @@ import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/compo
|
|||||||
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
||||||
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
|
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
|
||||||
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
|
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
|
||||||
|
import { getNavigationSubItemState } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemState';
|
||||||
import { View } from '@/views/types/View';
|
import { View } from '@/views/types/View';
|
||||||
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
|
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
|
||||||
import { Theme, useTheme } from '@emotion/react';
|
|
||||||
|
|
||||||
const ORDERED_STANDARD_OBJECTS = [
|
const ORDERED_STANDARD_OBJECTS = [
|
||||||
'person',
|
'person',
|
||||||
@ -27,20 +26,11 @@ const ORDERED_STANDARD_OBJECTS = [
|
|||||||
'note',
|
'note',
|
||||||
];
|
];
|
||||||
|
|
||||||
const navItemsAnimationVariants = (theme: Theme) => ({
|
export const NavigationDrawerSectionForObjectMetadataItems = ({
|
||||||
hidden: {
|
isRemote,
|
||||||
height: 0,
|
}: {
|
||||||
opacity: 0,
|
isRemote: boolean;
|
||||||
marginTop: 0,
|
}) => {
|
||||||
},
|
|
||||||
visible: {
|
|
||||||
height: 'auto',
|
|
||||||
opacity: 1,
|
|
||||||
marginTop: theme.spacing(1),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
|
|
||||||
const currentUser = useRecoilValue(currentUserState);
|
const currentUser = useRecoilValue(currentUserState);
|
||||||
|
|
||||||
const { toggleNavigationSection, isNavigationSectionOpenState } =
|
const { toggleNavigationSection, isNavigationSectionOpenState } =
|
||||||
@ -57,13 +47,13 @@ export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
|
|||||||
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
|
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
|
||||||
const loading = useIsPrefetchLoading();
|
const loading = useIsPrefetchLoading();
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
const { getLastVisitedViewIdFromObjectMetadataItemId } = useLastVisitedView();
|
const { getLastVisitedViewIdFromObjectMetadataItemId } = useLastVisitedView();
|
||||||
|
|
||||||
if (loading && isDefined(currentUser)) {
|
if (loading && isDefined(currentUser)) {
|
||||||
return <ObjectMetadataNavItemsSkeletonLoader />;
|
return <NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: refactor this by splitting into separate components
|
||||||
return (
|
return (
|
||||||
filteredActiveObjectMetadataItems.length > 0 && (
|
filteredActiveObjectMetadataItems.length > 0 && (
|
||||||
<NavigationDrawerSection>
|
<NavigationDrawerSection>
|
||||||
@ -121,6 +111,17 @@ export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
|
|||||||
currentPath === `/objects/${objectMetadataItem.namePlural}` &&
|
currentPath === `/objects/${objectMetadataItem.namePlural}` &&
|
||||||
objectMetadataViews.length > 1;
|
objectMetadataViews.length > 1;
|
||||||
|
|
||||||
|
const sortedObjectMetadataViews = [...objectMetadataViews].sort(
|
||||||
|
(viewA, viewB) =>
|
||||||
|
viewA.key === 'INDEX' ? -1 : viewA.position - viewB.position,
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedSubItemIndex = sortedObjectMetadataViews.findIndex(
|
||||||
|
(view) => viewId === view.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const subItemArrayLength = sortedObjectMetadataViews.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={objectMetadataItem.id}>
|
<div key={objectMetadataItem.id}>
|
||||||
<NavigationDrawerItem
|
<NavigationDrawerItem
|
||||||
@ -132,33 +133,21 @@ export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
|
|||||||
currentPath === `/objects/${objectMetadataItem.namePlural}`
|
currentPath === `/objects/${objectMetadataItem.namePlural}`
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<AnimatePresence>
|
{shouldSubItemsBeDisplayed &&
|
||||||
{shouldSubItemsBeDisplayed && (
|
sortedObjectMetadataViews.map((view, index) => (
|
||||||
<motion.div
|
<NavigationDrawerSubItem
|
||||||
initial="hidden"
|
label={view.name}
|
||||||
animate="visible"
|
to={`/objects/${objectMetadataItem.namePlural}?view=${view.id}`}
|
||||||
exit="hidden"
|
active={viewId === view.id}
|
||||||
variants={navItemsAnimationVariants(theme)}
|
subItemState={getNavigationSubItemState({
|
||||||
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
index,
|
||||||
>
|
arrayLength: subItemArrayLength,
|
||||||
{objectMetadataViews
|
selectedIndex: selectedSubItemIndex,
|
||||||
.sort((viewA, viewB) =>
|
})}
|
||||||
viewA.key === 'INDEX'
|
Icon={getIcon(view.icon)}
|
||||||
? -1
|
key={view.id}
|
||||||
: viewA.position - viewB.position,
|
/>
|
||||||
)
|
))}
|
||||||
.map((view) => (
|
|
||||||
<NavigationDrawerSubItem
|
|
||||||
label={view.name}
|
|
||||||
to={`/objects/${objectMetadataItem.namePlural}?view=${view.id}`}
|
|
||||||
active={viewId === view.id}
|
|
||||||
Icon={getIcon(view.icon)}
|
|
||||||
key={view.id}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||||
|
|
||||||
|
const StyledSkeletonColumn = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
height: 76px;
|
||||||
|
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader: React.FC =
|
||||||
|
() => {
|
||||||
|
const theme = useTheme();
|
||||||
|
return (
|
||||||
|
<SkeletonTheme
|
||||||
|
baseColor={theme.background.tertiary}
|
||||||
|
highlightColor={theme.background.transparent.light}
|
||||||
|
borderRadius={4}
|
||||||
|
>
|
||||||
|
<StyledSkeletonColumn>
|
||||||
|
<Skeleton width={196} height={16} />
|
||||||
|
<Skeleton width={196} height={16} />
|
||||||
|
<Skeleton width={196} height={16} />
|
||||||
|
</StyledSkeletonColumn>
|
||||||
|
</SkeletonTheme>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
const StyledSkeletonColumn = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
|
||||||
height: 76px;
|
|
||||||
padding-left: ${({ theme }) => theme.spacing(1)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const ObjectMetadataNavItemsSkeletonLoader: React.FC = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<SkeletonTheme
|
|
||||||
baseColor={theme.background.tertiary}
|
|
||||||
highlightColor={theme.background.transparent.light}
|
|
||||||
borderRadius={4}
|
|
||||||
>
|
|
||||||
<StyledSkeletonColumn>
|
|
||||||
<Skeleton width={196} height={16} />
|
|
||||||
<Skeleton width={196} height={16} />
|
|
||||||
<Skeleton width={196} height={16} />
|
|
||||||
</StyledSkeletonColumn>
|
|
||||||
</SkeletonTheme>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -9,12 +9,12 @@ import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadat
|
|||||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
|
||||||
|
import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems';
|
||||||
import { PrefetchLoadedDecorator } from '~/testing/decorators/PrefetchLoadedDecorator';
|
import { PrefetchLoadedDecorator } from '~/testing/decorators/PrefetchLoadedDecorator';
|
||||||
import { ObjectMetadataNavItems } from '../ObjectMetadataNavItems';
|
|
||||||
|
|
||||||
const meta: Meta<typeof ObjectMetadataNavItems> = {
|
const meta: Meta<typeof NavigationDrawerSectionForObjectMetadataItems> = {
|
||||||
title: 'Modules/ObjectMetadata/ObjectMetadataNavItems',
|
title: 'Modules/ObjectMetadata/NavigationDrawerSectionForObjectMetadataItems',
|
||||||
component: ObjectMetadataNavItems,
|
component: NavigationDrawerSectionForObjectMetadataItems,
|
||||||
decorators: [
|
decorators: [
|
||||||
IconsProviderDecorator,
|
IconsProviderDecorator,
|
||||||
ObjectMetadataItemsDecorator,
|
ObjectMetadataItemsDecorator,
|
||||||
@ -29,7 +29,7 @@ const meta: Meta<typeof ObjectMetadataNavItems> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof ObjectMetadataNavItems>;
|
type Story = StoryObj<typeof NavigationDrawerSectionForObjectMetadataItems>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
play: async () => {
|
play: async () => {
|
||||||
|
|||||||
@ -63,7 +63,7 @@ export const useHandleToggleTrashColumnFilter = ({
|
|||||||
upsertCombinedViewFilter(newFilter);
|
upsertCombinedViewFilter(newFilter);
|
||||||
}, [
|
}, [
|
||||||
columnDefinitions,
|
columnDefinitions,
|
||||||
objectMetadataItem.fields,
|
objectMetadataItem,
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
upsertCombinedViewFilter,
|
upsertCombinedViewFilter,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -6,32 +6,38 @@ import {
|
|||||||
NavigationDrawerItem,
|
NavigationDrawerItem,
|
||||||
NavigationDrawerItemProps,
|
NavigationDrawerItemProps,
|
||||||
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||||
|
import { NavigationDrawerSubItemState } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerSubItemState';
|
||||||
|
|
||||||
type SettingsNavigationDrawerItemProps = Pick<
|
type SettingsNavigationDrawerItemProps = Pick<
|
||||||
NavigationDrawerItemProps,
|
NavigationDrawerItemProps,
|
||||||
'Icon' | 'label' | 'level' | 'soon'
|
'Icon' | 'label' | 'indentationLevel' | 'soon'
|
||||||
> & {
|
> & {
|
||||||
matchSubPages?: boolean;
|
matchSubPages?: boolean;
|
||||||
path: SettingsPath;
|
path: SettingsPath;
|
||||||
|
subItemState?: NavigationDrawerSubItemState;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsNavigationDrawerItem = ({
|
export const SettingsNavigationDrawerItem = ({
|
||||||
Icon,
|
Icon,
|
||||||
label,
|
label,
|
||||||
level,
|
indentationLevel,
|
||||||
matchSubPages = false,
|
matchSubPages = false,
|
||||||
path,
|
path,
|
||||||
soon,
|
soon,
|
||||||
|
subItemState,
|
||||||
}: SettingsNavigationDrawerItemProps) => {
|
}: SettingsNavigationDrawerItemProps) => {
|
||||||
const href = getSettingsPagePath(path);
|
const href = getSettingsPagePath(path);
|
||||||
|
const pathName = useResolvedPath(href).pathname;
|
||||||
|
|
||||||
const isActive = !!useMatch({
|
const isActive = !!useMatch({
|
||||||
path: useResolvedPath(href).pathname,
|
path: pathName,
|
||||||
end: !matchSubPages,
|
end: !matchSubPages,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationDrawerItem
|
<NavigationDrawerItem
|
||||||
level={level}
|
indentationLevel={indentationLevel}
|
||||||
|
subItemState={subItemState}
|
||||||
label={label}
|
label={label}
|
||||||
to={href}
|
to={href}
|
||||||
Icon={Icon}
|
Icon={Icon}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
IconCalendarEvent,
|
IconCalendarEvent,
|
||||||
IconCode,
|
IconCode,
|
||||||
IconColorSwatch,
|
IconColorSwatch,
|
||||||
|
IconComponent,
|
||||||
IconCurrencyDollar,
|
IconCurrencyDollar,
|
||||||
IconDoorEnter,
|
IconDoorEnter,
|
||||||
IconFunction,
|
IconFunction,
|
||||||
@ -19,12 +20,26 @@ import {
|
|||||||
import { useAuth } from '@/auth/hooks/useAuth';
|
import { useAuth } from '@/auth/hooks/useAuth';
|
||||||
import { billingState } from '@/client-config/states/billingState';
|
import { billingState } from '@/client-config/states/billingState';
|
||||||
import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem';
|
import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem';
|
||||||
|
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
import {
|
||||||
|
NavigationDrawerItem,
|
||||||
|
NavigationDrawerItemIndentationLevel,
|
||||||
|
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||||
import { NavigationDrawerItemGroup } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemGroup';
|
import { NavigationDrawerItemGroup } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemGroup';
|
||||||
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
||||||
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
||||||
|
import { getNavigationSubItemState } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemState';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
|
import { matchPath, resolvePath, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
type SettingsNavigationItem = {
|
||||||
|
label: string;
|
||||||
|
path: SettingsPath;
|
||||||
|
Icon: IconComponent;
|
||||||
|
matchSubPages?: boolean;
|
||||||
|
indentationLevel?: NavigationDrawerItemIndentationLevel;
|
||||||
|
};
|
||||||
|
|
||||||
export const SettingsNavigationDrawerItems = () => {
|
export const SettingsNavigationDrawerItems = () => {
|
||||||
const { signOut } = useAuth();
|
const { signOut } = useAuth();
|
||||||
@ -38,6 +53,39 @@ export const SettingsNavigationDrawerItems = () => {
|
|||||||
const isBillingPageEnabled =
|
const isBillingPageEnabled =
|
||||||
billing?.isBillingEnabled && !isFreeAccessEnabled;
|
billing?.isBillingEnabled && !isFreeAccessEnabled;
|
||||||
|
|
||||||
|
// TODO: Refactor this part to only have arrays of navigation items
|
||||||
|
const currentPathName = useLocation().pathname;
|
||||||
|
|
||||||
|
const accountSubSettings: SettingsNavigationItem[] = [
|
||||||
|
{
|
||||||
|
label: 'Emails',
|
||||||
|
path: SettingsPath.AccountsEmails,
|
||||||
|
Icon: IconMail,
|
||||||
|
matchSubPages: true,
|
||||||
|
indentationLevel: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Calendars',
|
||||||
|
path: SettingsPath.AccountsCalendars,
|
||||||
|
Icon: IconCalendarEvent,
|
||||||
|
matchSubPages: true,
|
||||||
|
indentationLevel: 2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const selectedIndex = accountSubSettings.findIndex((accountSubSetting) => {
|
||||||
|
const href = getSettingsPagePath(accountSubSetting.path);
|
||||||
|
const pathName = resolvePath(href).pathname;
|
||||||
|
|
||||||
|
return matchPath(
|
||||||
|
{
|
||||||
|
path: pathName,
|
||||||
|
end: !accountSubSetting.matchSubPages,
|
||||||
|
},
|
||||||
|
currentPathName,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NavigationDrawerSection>
|
<NavigationDrawerSection>
|
||||||
@ -58,23 +106,21 @@ export const SettingsNavigationDrawerItems = () => {
|
|||||||
path={SettingsPath.Accounts}
|
path={SettingsPath.Accounts}
|
||||||
Icon={IconAt}
|
Icon={IconAt}
|
||||||
/>
|
/>
|
||||||
<SettingsNavigationDrawerItem
|
{accountSubSettings.map((navigationItem, index) => (
|
||||||
level={2}
|
<SettingsNavigationDrawerItem
|
||||||
label="Emails"
|
label={navigationItem.label}
|
||||||
path={SettingsPath.AccountsEmails}
|
path={navigationItem.path}
|
||||||
Icon={IconMail}
|
Icon={navigationItem.Icon}
|
||||||
matchSubPages
|
indentationLevel={navigationItem.indentationLevel}
|
||||||
/>
|
subItemState={getNavigationSubItemState({
|
||||||
<SettingsNavigationDrawerItem
|
arrayLength: accountSubSettings.length,
|
||||||
level={2}
|
index,
|
||||||
label="Calendars"
|
selectedIndex,
|
||||||
path={SettingsPath.AccountsCalendars}
|
})}
|
||||||
Icon={IconCalendarEvent}
|
/>
|
||||||
matchSubPages
|
))}
|
||||||
/>
|
|
||||||
</NavigationDrawerItemGroup>
|
</NavigationDrawerItemGroup>
|
||||||
</NavigationDrawerSection>
|
</NavigationDrawerSection>
|
||||||
|
|
||||||
<NavigationDrawerSection>
|
<NavigationDrawerSection>
|
||||||
<NavigationDrawerSectionTitle label="Workspace" />
|
<NavigationDrawerSectionTitle label="Workspace" />
|
||||||
<SettingsNavigationDrawerItem
|
<SettingsNavigationDrawerItem
|
||||||
@ -125,7 +171,6 @@ export const SettingsNavigationDrawerItems = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</NavigationDrawerSection>
|
</NavigationDrawerSection>
|
||||||
|
|
||||||
<NavigationDrawerSection>
|
<NavigationDrawerSection>
|
||||||
<NavigationDrawerSectionTitle label="Other" />
|
<NavigationDrawerSectionTitle label="Other" />
|
||||||
<SettingsNavigationDrawerItem
|
<SettingsNavigationDrawerItem
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { NavigationDrawerItemBreadcrumb } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemBreadcrumb';
|
||||||
|
import { NavigationDrawerSubItemState } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerSubItemState';
|
||||||
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
|
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import isPropValid from '@emotion/is-prop-valid';
|
import isPropValid from '@emotion/is-prop-valid';
|
||||||
@ -9,10 +11,15 @@ import { useSetRecoilState } from 'recoil';
|
|||||||
import { IconComponent, MOBILE_VIEWPORT, Pill } from 'twenty-ui';
|
import { IconComponent, MOBILE_VIEWPORT, Pill } from 'twenty-ui';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
const DEFAULT_INDENTATION_LEVEL = 1;
|
||||||
|
|
||||||
|
export type NavigationDrawerItemIndentationLevel = 1 | 2;
|
||||||
|
|
||||||
export type NavigationDrawerItemProps = {
|
export type NavigationDrawerItemProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
label: string;
|
label: string;
|
||||||
level?: 1 | 2;
|
indentationLevel?: NavigationDrawerItemIndentationLevel;
|
||||||
|
subItemState?: NavigationDrawerSubItemState;
|
||||||
to?: string;
|
to?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
@ -23,13 +30,10 @@ export type NavigationDrawerItemProps = {
|
|||||||
keyboard?: string[];
|
keyboard?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyledItemProps = {
|
type StyledItemProps = Pick<
|
||||||
active?: boolean;
|
NavigationDrawerItemProps,
|
||||||
danger?: boolean;
|
'active' | 'danger' | 'indentationLevel' | 'soon' | 'to'
|
||||||
level: 1 | 2;
|
>;
|
||||||
soon?: boolean;
|
|
||||||
to?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledItem = styled('div', {
|
const StyledItem = styled('div', {
|
||||||
shouldForwardProp: (prop) =>
|
shouldForwardProp: (prop) =>
|
||||||
@ -59,13 +63,17 @@ const StyledItem = styled('div', {
|
|||||||
font-family: 'Inter';
|
font-family: 'Inter';
|
||||||
font-size: ${({ theme }) => theme.font.size.md};
|
font-size: ${({ theme }) => theme.font.size.md};
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
margin-left: ${({ level, theme }) => theme.spacing((level - 1) * 4)};
|
|
||||||
padding-bottom: ${({ theme }) => theme.spacing(1)};
|
padding-bottom: ${({ theme }) => theme.spacing(1)};
|
||||||
padding-left: ${({ theme }) => theme.spacing(1)};
|
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
padding-right: ${({ theme }) => theme.spacing(1)};
|
||||||
padding-top: ${({ theme }) => theme.spacing(1)};
|
padding-top: ${({ theme }) => theme.spacing(1)};
|
||||||
pointer-events: ${(props) => (props.soon ? 'none' : 'auto')};
|
|
||||||
|
|
||||||
|
margin-top: ${({ indentationLevel }) =>
|
||||||
|
indentationLevel === 2 ? '2px' : '0'};
|
||||||
|
|
||||||
|
pointer-events: ${(props) => (props.soon ? 'none' : 'auto')};
|
||||||
|
width: 100%;
|
||||||
:hover {
|
:hover {
|
||||||
background: ${({ theme }) => theme.background.transparent.light};
|
background: ${({ theme }) => theme.background.transparent.light};
|
||||||
color: ${(props) =>
|
color: ${(props) =>
|
||||||
@ -116,10 +124,16 @@ const StyledKeyBoardShortcut = styled.div`
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledNavigationDrawerItemContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
export const NavigationDrawerItem = ({
|
export const NavigationDrawerItem = ({
|
||||||
className,
|
className,
|
||||||
label,
|
label,
|
||||||
level = 1,
|
indentationLevel = DEFAULT_INDENTATION_LEVEL,
|
||||||
Icon,
|
Icon,
|
||||||
to,
|
to,
|
||||||
onClick,
|
onClick,
|
||||||
@ -128,6 +142,7 @@ export const NavigationDrawerItem = ({
|
|||||||
soon,
|
soon,
|
||||||
count,
|
count,
|
||||||
keyboard,
|
keyboard,
|
||||||
|
subItemState,
|
||||||
}: NavigationDrawerItemProps) => {
|
}: NavigationDrawerItemProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
@ -136,6 +151,8 @@ export const NavigationDrawerItem = ({
|
|||||||
isNavigationDrawerOpenState,
|
isNavigationDrawerOpenState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const showBreadcrumb = indentationLevel === 2;
|
||||||
|
|
||||||
const handleItemClick = () => {
|
const handleItemClick = () => {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
setIsNavigationDrawerOpen(false);
|
setIsNavigationDrawerOpen(false);
|
||||||
@ -152,26 +169,33 @@ export const NavigationDrawerItem = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledItem
|
<StyledNavigationDrawerItemContainer>
|
||||||
className={className}
|
<StyledItem
|
||||||
level={level}
|
className={className}
|
||||||
onClick={handleItemClick}
|
onClick={handleItemClick}
|
||||||
active={active}
|
active={active}
|
||||||
aria-selected={active}
|
aria-selected={active}
|
||||||
danger={danger}
|
danger={danger}
|
||||||
soon={soon}
|
soon={soon}
|
||||||
as={to ? Link : 'div'}
|
as={to ? Link : 'div'}
|
||||||
to={to ? to : undefined}
|
to={to ? to : undefined}
|
||||||
>
|
indentationLevel={indentationLevel}
|
||||||
{Icon && <Icon size={theme.icon.size.md} stroke={theme.icon.stroke.md} />}
|
>
|
||||||
<StyledItemLabel>{label}</StyledItemLabel>
|
{showBreadcrumb && (
|
||||||
{soon && <Pill label="Soon" />}
|
<NavigationDrawerItemBreadcrumb state={subItemState} />
|
||||||
{!!count && <StyledItemCount>{count}</StyledItemCount>}
|
)}
|
||||||
{keyboard && (
|
{Icon && (
|
||||||
<StyledKeyBoardShortcut className="keyboard-shortcuts">
|
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.md} />
|
||||||
{keyboard}
|
)}
|
||||||
</StyledKeyBoardShortcut>
|
<StyledItemLabel>{label}</StyledItemLabel>
|
||||||
)}
|
{soon && <Pill label="Soon" />}
|
||||||
</StyledItem>
|
{!!count && <StyledItemCount>{count}</StyledItemCount>}
|
||||||
|
{keyboard && (
|
||||||
|
<StyledKeyBoardShortcut className="keyboard-shortcuts">
|
||||||
|
{keyboard}
|
||||||
|
</StyledKeyBoardShortcut>
|
||||||
|
)}
|
||||||
|
</StyledItem>
|
||||||
|
</StyledNavigationDrawerItemContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,79 @@
|
|||||||
|
import { NavigationDrawerSubItemState } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerSubItemState';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
export type NavigationDrawerItemBreadcrumbProps = {
|
||||||
|
state?: NavigationDrawerSubItemState;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledNavigationDrawerItemBreadcrumbContainer = styled.div`
|
||||||
|
margin-left: 7.5px;
|
||||||
|
|
||||||
|
height: 28px;
|
||||||
|
width: 9px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledGapVerticalLine = styled.div<{ darker: boolean }>`
|
||||||
|
background: ${({ theme, darker }) =>
|
||||||
|
darker ? theme.font.color.tertiary : theme.border.color.strong};
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
|
||||||
|
height: 2px;
|
||||||
|
width: 1px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledSecondaryFullVerticalBar = styled.div<{ darker: boolean }>`
|
||||||
|
background: ${({ theme, darker }) =>
|
||||||
|
darker ? theme.font.color.tertiary : theme.border.color.strong};
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
top: -17px;
|
||||||
|
height: 28px;
|
||||||
|
width: 1px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledRoundedProtrusion = styled.div<{ darker: boolean }>`
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
|
||||||
|
border: 1px solid
|
||||||
|
${({ theme, darker }) =>
|
||||||
|
darker ? theme.font.color.tertiary : theme.border.color.strong};
|
||||||
|
|
||||||
|
${({ darker }) => (darker ? 'z-index: 1;' : '')}
|
||||||
|
|
||||||
|
border-top: none;
|
||||||
|
border-right: none;
|
||||||
|
height: 14px;
|
||||||
|
width: 8px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const NavigationDrawerItemBreadcrumb = ({
|
||||||
|
state,
|
||||||
|
}: NavigationDrawerItemBreadcrumbProps) => {
|
||||||
|
const showVerticalBar =
|
||||||
|
state !== 'last-not-selected' && state !== 'last-selected';
|
||||||
|
|
||||||
|
const verticalBarShouldBeDarker = state === 'intermediate-before-selected';
|
||||||
|
|
||||||
|
const protrusionShouldBeDarker =
|
||||||
|
state === 'intermediate-selected' || state === 'last-selected';
|
||||||
|
|
||||||
|
const gapShouldBeDarker =
|
||||||
|
state === 'intermediate-before-selected' ||
|
||||||
|
state === 'intermediate-selected' ||
|
||||||
|
state === 'last-selected';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledNavigationDrawerItemBreadcrumbContainer>
|
||||||
|
<StyledGapVerticalLine darker={gapShouldBeDarker} />
|
||||||
|
<StyledRoundedProtrusion darker={protrusionShouldBeDarker} />
|
||||||
|
{showVerticalBar && (
|
||||||
|
<StyledSecondaryFullVerticalBar darker={verticalBarShouldBeDarker} />
|
||||||
|
)}
|
||||||
|
</StyledNavigationDrawerItemBreadcrumbContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -3,7 +3,6 @@ import styled from '@emotion/styled';
|
|||||||
const StyledGroup = styled.div`
|
const StyledGroup = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: ${({ theme }) => theme.spacing(0.5)};
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { StyledGroup as NavigationDrawerItemGroup };
|
export { StyledGroup as NavigationDrawerItemGroup };
|
||||||
|
|||||||
@ -2,21 +2,12 @@ import {
|
|||||||
NavigationDrawerItem,
|
NavigationDrawerItem,
|
||||||
NavigationDrawerItemProps,
|
NavigationDrawerItemProps,
|
||||||
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
const StyledItem = styled.div`
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(0.5)};
|
|
||||||
}
|
|
||||||
margin-left: ${({ theme }) => theme.spacing(4)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
type NavigationDrawerSubItemProps = NavigationDrawerItemProps;
|
type NavigationDrawerSubItemProps = NavigationDrawerItemProps;
|
||||||
|
|
||||||
export const NavigationDrawerSubItem = ({
|
export const NavigationDrawerSubItem = ({
|
||||||
className,
|
className,
|
||||||
label,
|
label,
|
||||||
level = 1,
|
|
||||||
Icon,
|
Icon,
|
||||||
to,
|
to,
|
||||||
onClick,
|
onClick,
|
||||||
@ -25,22 +16,22 @@ export const NavigationDrawerSubItem = ({
|
|||||||
soon,
|
soon,
|
||||||
count,
|
count,
|
||||||
keyboard,
|
keyboard,
|
||||||
|
subItemState,
|
||||||
}: NavigationDrawerSubItemProps) => {
|
}: NavigationDrawerSubItemProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledItem>
|
<NavigationDrawerItem
|
||||||
<NavigationDrawerItem
|
className={className}
|
||||||
className={className}
|
label={label}
|
||||||
label={label}
|
indentationLevel={2}
|
||||||
level={level}
|
subItemState={subItemState}
|
||||||
Icon={Icon}
|
Icon={Icon}
|
||||||
to={to}
|
to={to}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
active={active}
|
active={active}
|
||||||
danger={danger}
|
danger={danger}
|
||||||
soon={soon}
|
soon={soon}
|
||||||
count={count}
|
count={count}
|
||||||
keyboard={keyboard}
|
keyboard={keyboard}
|
||||||
/>
|
/>
|
||||||
</StyledItem>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { GithubVersionLink } from '@/ui/navigation/link/components/GithubVersion
|
|||||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
|
|
||||||
|
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
|
||||||
import { NavigationDrawer } from '../NavigationDrawer';
|
import { NavigationDrawer } from '../NavigationDrawer';
|
||||||
import { NavigationDrawerItem } from '../NavigationDrawerItem';
|
import { NavigationDrawerItem } from '../NavigationDrawerItem';
|
||||||
import { NavigationDrawerItemGroup } from '../NavigationDrawerItemGroup';
|
import { NavigationDrawerItemGroup } from '../NavigationDrawerItemGroup';
|
||||||
@ -108,17 +109,17 @@ export const Submenu: Story = {
|
|||||||
to={getSettingsPagePath(SettingsPath.Accounts)}
|
to={getSettingsPagePath(SettingsPath.Accounts)}
|
||||||
Icon={IconAt}
|
Icon={IconAt}
|
||||||
/>
|
/>
|
||||||
<NavigationDrawerItem
|
<NavigationDrawerSubItem
|
||||||
level={2}
|
|
||||||
label="Emails"
|
label="Emails"
|
||||||
to={getSettingsPagePath(SettingsPath.AccountsEmails)}
|
to={getSettingsPagePath(SettingsPath.AccountsEmails)}
|
||||||
Icon={IconMail}
|
Icon={IconMail}
|
||||||
|
subItemState="intermediate-before-selected"
|
||||||
/>
|
/>
|
||||||
<NavigationDrawerItem
|
<NavigationDrawerSubItem
|
||||||
level={2}
|
|
||||||
label="Calendar"
|
label="Calendar"
|
||||||
to={getSettingsPagePath(SettingsPath.AccountsCalendars)}
|
to={getSettingsPagePath(SettingsPath.AccountsCalendars)}
|
||||||
Icon={IconCalendarEvent}
|
Icon={IconCalendarEvent}
|
||||||
|
subItemState="last-selected"
|
||||||
/>
|
/>
|
||||||
</NavigationDrawerItemGroup>
|
</NavigationDrawerItemGroup>
|
||||||
</NavigationDrawerSection>
|
</NavigationDrawerSection>
|
||||||
|
|||||||
@ -37,6 +37,115 @@ export const Default: Story = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Breadcrumb: Story = {
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<StyledContainer>
|
||||||
|
<h1>Breadcrumb</h1>
|
||||||
|
<Story
|
||||||
|
args={{
|
||||||
|
indentationLevel: 1,
|
||||||
|
label: 'Search',
|
||||||
|
Icon: IconSearch,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Story
|
||||||
|
args={{
|
||||||
|
indentationLevel: 2,
|
||||||
|
subItemState: 'intermediate-before-selected',
|
||||||
|
label: 'First not selected',
|
||||||
|
Icon: IconSearch,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Story
|
||||||
|
args={{
|
||||||
|
indentationLevel: 2,
|
||||||
|
subItemState: 'intermediate-before-selected',
|
||||||
|
label: 'Before selected',
|
||||||
|
Icon: IconSearch,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Story
|
||||||
|
args={{
|
||||||
|
indentationLevel: 2,
|
||||||
|
subItemState: 'intermediate-selected',
|
||||||
|
label: 'Selected',
|
||||||
|
Icon: IconSearch,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Story
|
||||||
|
args={{
|
||||||
|
indentationLevel: 2,
|
||||||
|
subItemState: 'intermediate-after-selected',
|
||||||
|
label: 'After selected',
|
||||||
|
Icon: IconSearch,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Story
|
||||||
|
args={{
|
||||||
|
indentationLevel: 2,
|
||||||
|
subItemState: 'last-not-selected',
|
||||||
|
label: 'Last not selected',
|
||||||
|
Icon: IconSearch,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</StyledContainer>
|
||||||
|
),
|
||||||
|
ComponentWithRouterDecorator,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BreadcrumbCatalog: CatalogStory<
|
||||||
|
Story,
|
||||||
|
typeof NavigationDrawerItem
|
||||||
|
> = {
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<StyledContainer>
|
||||||
|
<Story />
|
||||||
|
</StyledContainer>
|
||||||
|
),
|
||||||
|
CatalogDecorator,
|
||||||
|
MemoryRouterDecorator,
|
||||||
|
],
|
||||||
|
args: {
|
||||||
|
indentationLevel: 2,
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'] },
|
||||||
|
catalog: {
|
||||||
|
dimensions: [
|
||||||
|
{
|
||||||
|
name: 'subItemState',
|
||||||
|
values: [
|
||||||
|
'Intermediate before selected',
|
||||||
|
'Intermediate selected',
|
||||||
|
'Intermediate after selected',
|
||||||
|
'Last not selected',
|
||||||
|
'Last selected',
|
||||||
|
],
|
||||||
|
props: (state: string) => {
|
||||||
|
switch (state) {
|
||||||
|
case 'Intermediate before selected':
|
||||||
|
return { subItemState: 'intermediate-before-selected' };
|
||||||
|
case 'Intermediate selected':
|
||||||
|
return { subItemState: 'intermediate-selected' };
|
||||||
|
case 'Intermediate after selected':
|
||||||
|
return { subItemState: 'intermediate-after-selected' };
|
||||||
|
case 'Last not selected':
|
||||||
|
return { subItemState: 'last-not-selected' };
|
||||||
|
case 'Last selected':
|
||||||
|
return { subItemState: 'last-selected' };
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown state: ${state}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const Catalog: CatalogStory<Story, typeof NavigationDrawerItem> = {
|
export const Catalog: CatalogStory<Story, typeof NavigationDrawerItem> = {
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
export type NavigationDrawerSubItemState =
|
||||||
|
| 'intermediate-before-selected'
|
||||||
|
| 'intermediate-selected'
|
||||||
|
| 'intermediate-after-selected'
|
||||||
|
| 'last-selected'
|
||||||
|
| 'last-not-selected';
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { NavigationDrawerSubItemState } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerSubItemState';
|
||||||
|
|
||||||
|
export const getNavigationSubItemState = ({
|
||||||
|
index,
|
||||||
|
arrayLength,
|
||||||
|
selectedIndex,
|
||||||
|
}: {
|
||||||
|
index: number;
|
||||||
|
arrayLength: number;
|
||||||
|
selectedIndex: number;
|
||||||
|
}): NavigationDrawerSubItemState => {
|
||||||
|
const thereIsOnlyOneItem = arrayLength === 1;
|
||||||
|
|
||||||
|
const itsTheLastItem = index === arrayLength - 1;
|
||||||
|
|
||||||
|
const itsTheSelectedItem = index === selectedIndex;
|
||||||
|
|
||||||
|
const itsBeforeTheSelectedItem = index < selectedIndex;
|
||||||
|
|
||||||
|
if (thereIsOnlyOneItem || itsTheLastItem) {
|
||||||
|
if (itsTheSelectedItem) {
|
||||||
|
return 'last-selected';
|
||||||
|
} else {
|
||||||
|
return 'last-not-selected';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (itsTheSelectedItem) {
|
||||||
|
return 'intermediate-selected';
|
||||||
|
} else if (itsBeforeTheSelectedItem) {
|
||||||
|
return 'intermediate-before-selected';
|
||||||
|
} else {
|
||||||
|
return 'intermediate-after-selected';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user