Skip to content

Commit

Permalink
Sidebar fixes (#279)
Browse files Browse the repository at this point in the history
  • Loading branch information
dan-lee authored Oct 17, 2024
1 parent 9c01fde commit 7cfd90e
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 90 deletions.
1 change: 0 additions & 1 deletion packages/zudoku/src/app/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@
.CollapsibleContent {
--easing: cubic-bezier(0.4, 0, 0.2, 1);
--slide-offset: -0.75rem;
@apply overflow-hidden;
}
.CollapsibleContent[data-state="open"] {
animation: slideDown 300ms var(--easing);
Expand Down
36 changes: 8 additions & 28 deletions packages/zudoku/src/config/validators/InputSidebarSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { z } from "zod";

type IconNames = keyof typeof dynamicIconImports;

const BadgeSchema = z.object({
label: z.string(),
color: z.enum(["green", "blue", "yellow", "red", "purple", "indigo", "gray"]),
placement: z.enum(["start", "end"]).optional(),
});

export const BaseInputSidebarItemCategoryLinkDocSchema = z.object({
type: z.literal("doc"),
id: z.string(),
Expand All @@ -23,20 +29,7 @@ export const BaseInputSidebarItemDocSchema = z.object({
id: z.string(),
icon: z.custom<IconNames>().optional(),
label: z.string().optional(),
badge: z
.object({
label: z.string(),
color: z.enum([
"green",
"blue",
"yellow",
"red",
"purple",
"indigo",
"gray",
]),
})
.optional(),
badge: BadgeSchema.optional(),
});

export type BaseInputSidebarItemDoc = z.infer<
Expand All @@ -53,20 +46,7 @@ export const InputSidebarItemLinkSchema = z.object({
label: z.string(),
href: z.string(),
description: z.string().optional(),
badge: z
.object({
label: z.string(),
color: z.enum([
"green",
"blue",
"yellow",
"red",
"purple",
"indigo",
"gray",
]),
})
.optional(),
badge: BadgeSchema.optional(),
});

export type InputSidebarItemLink = z.infer<typeof InputSidebarItemLinkSchema>;
Expand Down
17 changes: 7 additions & 10 deletions packages/zudoku/src/config/validators/SidebarSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export type SidebarItem =
| SidebarItemLink
| SidebarItemCategory;

const extractTitleFromContent = (content: string) =>
const extractTitleFromContent = (content: string): string | undefined =>
content.match(/^\s*#\s(.*)$/m)?.at(1);

export const resolveSidebar = async (
Expand Down Expand Up @@ -68,14 +68,12 @@ export const resolveSidebar = async (

const { data, content } = matter(file);
const label =
data.sidebar_label ?? data.title ?? extractTitleFromContent(content);
const icon = data.sidebar_icon;
data.sidebar_label ??
data.title ??
extractTitleFromContent(content) ??
id;

if (typeof label !== "string") {
throw new Error(
`Error determining title for document '${id}'. Check that the document has a H1 header or title frontmatter.`,
);
}
const icon = data.sidebar_icon;

return {
type: "doc",
Expand Down Expand Up @@ -117,9 +115,8 @@ export const resolveSidebar = async (
}

const doc = await resolveDoc(item.id, categoryLabel);
const label = item.label ?? doc.label;

return { ...item, label, categoryLabel };
return { ...doc, ...item };
};

const resolveSidebarItem = async (
Expand Down
60 changes: 34 additions & 26 deletions packages/zudoku/src/lib/components/navigation/SidebarCategory.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Collapsible from "@radix-ui/react-collapsible";
import { ChevronRightIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { NavLink } from "react-router-dom";
import { NavLink, useMatch } from "react-router-dom";
import type { SidebarItemCategory } from "../../../config/validators/SidebarSchema.js";
import { cn } from "../../util/cn.js";
import { joinPath } from "../../util/joinPath.js";
Expand All @@ -26,6 +26,7 @@ export const SidebarCategory = ({
!isCollapsible || !isCollapsed || isCategoryOpen,
);
const [open, setOpen] = useState(isDefaultOpen);
const isActive = useMatch(joinPath(topNavItem?.id, category.link?.id));

useEffect(() => {
// this is triggered when an item from the sidebar is clicked
Expand Down Expand Up @@ -56,46 +57,54 @@ export const SidebarCategory = ({

return (
<Collapsible.Root
className={cn("flex flex-col", level === 0 && "-mx-[--padding-nav-item]")}
className="flex flex-col"
defaultOpen={isDefaultOpen}
open={open}
onOpenChange={() => setOpen(true)}
>
<Collapsible.Trigger className="group" asChild disabled={!isCollapsible}>
<div
className={cn(
"text-start",
navigationListItem({ isActive: false, isTopLevel: level === 0 }),
isCollapsible
? "cursor-pointer"
: "cursor-default hover:bg-transparent",
)}
onClick={() => setHasInteracted(true)}
className={navigationListItem({
isActive: false,
isTopLevel: level === 0,
className: [
"text-start",
isCollapsible
? "cursor-pointer"
: "cursor-default hover:bg-transparent",
],
})}
>
{category.icon && (
<category.icon
size={16}
className="align-[-0.125em] -translate-x-1"
className={cn(
"align-[-0.125em] -translate-x-1",
isActive && "text-primary",
)}
/>
)}
{category.link?.type === "doc" ? (
<NavLink
to={joinPath(topNavItem?.id, category.link.id)}
className="flex-1"
onClick={() => setHasInteracted(true)}
onClick={() => {
// if it is the current path and closed then open it because there's no path change to trigger the open
if (isActive && !open) {
setOpen(true);
}
}}
>
{({ isActive }) => (
<div
className={cn(
"flex items-center gap-2 justify-between w-full",
isActive
? "text-primary font-medium"
: "text-foreground/80",
)}
>
<div className="truncate">{category.label}</div>
{ToggleButton}
</div>
)}
<div
className={cn(
"flex items-center gap-2 justify-between w-full",
isActive ? "text-primary" : "text-foreground/80",
)}
>
<div className="truncate">{category.label}</div>
{ToggleButton}
</div>
</NavLink>
) : (
<div className="flex items-center justify-between w-full">
Expand All @@ -109,10 +118,9 @@ export const SidebarCategory = ({
className={cn(
// CollapsibleContent class is used to animate and it should only be applied when the user has triggered the toggle
hasInteracted && "CollapsibleContent",
"ms-[calc(var(--padding-nav-item)*1.125)]",
)}
>
<ul className="mt-1 border-l ps-2">
<ul className="mt-1 border-l ms-0.5">
{category.items.map((item) => (
<SidebarItem
key={
Expand Down
33 changes: 16 additions & 17 deletions packages/zudoku/src/lib/components/navigation/SidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ExternalLinkIcon } from "lucide-react";
import { NavLink, useSearchParams } from "react-router-dom";

import type { SidebarItem as SidebarItemType } from "../../../config/validators/SidebarSchema.js";
import { cn } from "../../util/cn.js";
import { joinPath } from "../../util/joinPath.js";
import { AnchorLink } from "../AnchorLink.js";
import { useViewportAnchor } from "../context/ViewportAnchorContext.js";
Expand All @@ -16,7 +15,8 @@ export const navigationListItem = cva(
{
variants: {
isTopLevel: {
true: "font-semibold",
true: "font-medium -mx-[--padding-nav-item]",
false: "-mr-[--padding-nav-item] ml-[--padding-nav-item]",
},
isActive: {
true: "text-primary font-medium",
Expand All @@ -27,6 +27,9 @@ export const navigationListItem = cva(
false: "",
},
},
defaultVariants: {
isActive: false,
},
},
);

Expand Down Expand Up @@ -58,7 +61,7 @@ export const SidebarItem = ({
{item.icon && <item.icon size={16} className="align-[-0.125em]" />}
{item.badge ? (
<>
<span className="truncate" title={item.label}>
<span className="truncate flex-1" title={item.label}>
{item.label}
</span>
<SidebarBadge {...item.badge} />
Expand All @@ -73,13 +76,11 @@ export const SidebarItem = ({
<AnchorLink
to={{ hash: item.href, search: searchParams.toString() }}
{...{ [DATA_ANCHOR_ATTR]: item.href.slice(1) }}
className={cn(
"flex gap-2.5 justify-between",
level === 0 && "-mx-[--padding-nav-item]",
navigationListItem({
isActive: item.href.slice(1) === activeAnchor,
}),
)}
className={navigationListItem({
isActive: item.href.slice(1) === activeAnchor,
isTopLevel: level === 0,
className: item.badge?.placement !== "start" && "justify-between",
})}
>
{item.badge ? (
<>
Expand All @@ -94,7 +95,9 @@ export const SidebarItem = ({
</AnchorLink>
) : !item.href.startsWith("http") ? (
<NavLink
className={cn("flex gap-2.5 justify-between", navigationListItem())}
className={navigationListItem({
className: item.badge?.placement !== "start" && "justify-between",
})}
to={item.href}
>
{item.badge ? (
Expand All @@ -110,19 +113,15 @@ export const SidebarItem = ({
</NavLink>
) : (
<a
className={cn(
navigationListItem({ isTopLevel: level === 0 }),
"block",
)}
className={navigationListItem({ isTopLevel: level === 0 })}
href={item.href}
target="_blank"
rel="noopener noreferrer"
>
<span className="whitespace-normal">{item.label}</span>
{/* This prevents that the icon would be positioned in its own line if the text fills a line entirely */}
<span className="whitespace-nowrap">
&nbsp;
<ExternalLinkIcon className="inline ml-1" size={12} />
<ExternalLinkIcon className="inline -translate-y-0.5" size={12} />
</span>
</a>
);
Expand Down
18 changes: 10 additions & 8 deletions packages/zudoku/src/lib/plugins/markdown/MdxPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,16 @@ export const MdxPage = ({
"max-w-full xl:w-full xl:max-w-prose flex-1 flex-shrink pt-[--padding-content-top] pb-[--padding-content-bottom]",
)}
>
<header>
{category && <CategoryHeading>{category}</CategoryHeading>}
{title && (
<Heading level={1} id={slugify(title)}>
{title}
</Heading>
)}
</header>
{(category || title) && (
<header>
{category && <CategoryHeading>{category}</CategoryHeading>}
{title && (
<Heading level={1} id={slugify(title)}>
{title}
</Heading>
)}
</header>
)}
<MdxComponent
components={{ ...useMDXComponents(), ...MarkdownHeadings }}
/>
Expand Down

0 comments on commit 7cfd90e

Please sign in to comment.