Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions apps/site/components/withSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useClientContext, useScrollToElement } from '#site/hooks/client';
import { useSiteNavigation } from '#site/hooks/generic';
import { useRouter, usePathname } from '#site/navigation.mjs';

import type { NavigationKeys } from '#site/types';
import type { FormattedMessage, NavigationKeys } from '#site/types';
import type { RichTranslationValues } from 'next-intl';
import type { FC } from 'react';

Expand All @@ -18,6 +18,27 @@ type WithSidebarProps = {
context?: Record<string, RichTranslationValues>;
};

type MappedItem = {
label: FormattedMessage;
link: string;
target?: string;
items?: Array<[string, MappedItem]>;
};

type SidebarMappedEntry = {
label: FormattedMessage;
link: string;
target?: string;
items?: Array<SidebarMappedEntry>;
};

const mapItem = ([, item]: [string, MappedItem]): SidebarMappedEntry => ({
label: item.label,
link: item.link,
target: item.target,
items: item.items ? item.items.map(mapItem) : [],
});

const WithSidebar: FC<WithSidebarProps> = ({ navKeys, context, ...props }) => {
const { getSideNavigation } = useSiteNavigation();
const pathname = usePathname()!;
Expand All @@ -34,9 +55,9 @@ const WithSidebar: FC<WithSidebarProps> = ({ navKeys, context, ...props }) => {
// If there's only a single navigation key, use its sub-items
// as our navigation.
(navKeys.length === 1 ? sideNavigation[0][1].items : sideNavigation).map(
([, { label, items }]) => ({
([, { label, items }]: [string, MappedItem]) => ({
groupName: label,
items: items.map(([, item]) => item),
items: items ? items.map(mapItem) : [],
})
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,51 @@
text-neutral-800
dark:text-neutral-600;
}

.subGroup {
@apply flex
w-full
flex-col
gap-1;
}

.summary {
@apply flex
cursor-pointer
items-center
justify-between
rounded-md
px-2
py-1
text-sm
font-semibold
text-neutral-800
select-none
hover:bg-neutral-100
dark:text-neutral-200
hover:dark:bg-neutral-900;

list-style: none;

&::-webkit-details-marker {
display: none;
}
}

.subGroup[open] > .summary {
@apply text-green-600
dark:text-green-400;
}

.subItemList {
@apply mt-1
ml-2
flex
flex-col
gap-1
border-l
border-neutral-200
pl-2
dark:border-neutral-800;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,27 @@ export const EmptyGroup: Story = {
},
};

export const NestedGroup: Story = {
args: {
groupName: 'Nested Group',
pathname: '/nested/folder-b/leaf-2',
items: [
{ label: 'Flat Item', link: '/nested/flat' },
{
label: 'Folder A',
link: '/nested/folder-a',
items: [{ label: 'Leaf A.1', link: '/nested/folder-a/leaf-1' }],
},
{
label: 'Folder B',
link: '/nested/folder-b',
items: [
{ label: 'Leaf B.1', link: '/nested/folder-b/leaf-1' },
{ label: 'Leaf B.2 (Active)', link: '/nested/folder-b/leaf-2' },
],
},
],
},
};

export default { component: SidebarGroup } as Meta;
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,73 @@ import type { ComponentProps, FC } from 'react';

import styles from './index.module.css';

type SidebarItemType = Omit<
ComponentProps<typeof SidebarItem>,
'as' | 'pathname'
> & {
items?: Array<SidebarItemType>;
};

type SidebarGroupProps = {
groupName: FormattedMessage;
items: Array<Omit<ComponentProps<typeof SidebarItem>, 'as' | 'pathname'>>;
items: Array<SidebarItemType>;
as?: LinkLike;
pathname?: string;
className: string;
className?: string;
};

const hasActivePath = (
items: Array<SidebarItemType>,
pathname?: string
): boolean => {
return items.some(
item =>
item.link === pathname ||
(item.items && hasActivePath(item.items, pathname))
);
};

const renderItems = (
items: Array<SidebarItemType>,
props: { as?: LinkLike },
pathname?: string
) => {
return items.map(({ label, link, items: subItems }) => {
if (subItems && subItems.length > 0) {
const isOpen = link === pathname || hasActivePath(subItems, pathname);
return (
<li key={link}>
<details className={styles.subGroup} open={isOpen}>
<summary className={styles.summary}>{label}</summary>
<ul className={styles.subItemList}>
{renderItems(subItems, props, pathname)}
</ul>
</details>
</li>
);
}
return (
<SidebarItem
key={link}
label={label}
link={link}
pathname={pathname}
{...props}
/>
);
});
};

const SidebarGroup: FC<SidebarGroupProps> = ({
groupName,
items,
className,
pathname,
...props
}) => (
<section className={classNames(styles.group, className)}>
<label className={styles.groupName}>{groupName}</label>
<ul className={styles.itemList}>
{items.map(({ label, link }) => (
<SidebarItem key={link} label={label} link={link} {...props} />
))}
</ul>
<ul className={styles.itemList}>{renderItems(items, props, pathname)}</ul>
</section>
);

Expand Down
20 changes: 18 additions & 2 deletions packages/ui-components/src/Containers/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,25 @@ import { forwardRef } from 'react';
import WithNoScriptSelect from '#ui/Common/Select/NoScriptSelect';
import SidebarGroup from '#ui/Containers/Sidebar/SidebarGroup';

import type { LinkLike } from '#ui/types';
import type { FormattedMessage, LinkLike } from '#ui/types';
import type { ComponentProps, PropsWithChildren } from 'react';

import styles from './index.module.css';

type SidebarItemType = {
label: FormattedMessage;
link: string;
items?: Array<SidebarItemType>;
};

const flattenItems = (
items: Array<SidebarItemType>
): Array<SidebarItemType> => {
return items.flatMap((item: SidebarItemType) =>
item.items && item.items.length ? flattenItems(item.items) : [item]
);
};

type SidebarProps = {
groups: Array<
Pick<ComponentProps<typeof SidebarGroup>, 'items' | 'groupName'>
Expand All @@ -23,7 +37,9 @@ const SideBar = forwardRef<HTMLElement, PropsWithChildren<SidebarProps>>(
({ groups, pathname, title, onSelect, as, children, placeholder }, ref) => {
const selectItems = groups.map(({ items, groupName }) => ({
label: groupName,
items: items.map(({ label, link }) => ({ value: link, label })),
items: flattenItems(items as Array<SidebarItemType>).map(
({ label, link }) => ({ value: link, label })
),
}));

const currentItem = selectItems
Expand Down