Skip to content

Commit

Permalink
UI: Improve sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
fuma-nama committed Jan 26, 2025
1 parent 581f4a5 commit b91aa83
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/tiny-maps-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'fumadocs-ui': patch
---

Redesign sidebar
2 changes: 1 addition & 1 deletion packages/openapi/src/ui/components/method-label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
import type { HTMLAttributes } from 'react';
import { cn } from 'fumadocs-ui/components/api';

const variants = cva('text-xs font-mono font-medium', {
const variants = cva('font-mono font-medium', {
variants: {
color: {
green: 'text-green-600 dark:text-green-400',
Expand Down
4 changes: 2 additions & 2 deletions packages/openapi/src/ui/components/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const SelectTrigger = forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'flex h-10 items-center w-full rounded-md border px-3 py-2 text-start text-sm text-fd-foreground hover:bg-fd-accent focus:outline-none focus:ring-2 focus:ring-fd-ring disabled:cursor-not-allowed disabled:opacity-50',
'flex h-10 items-center w-full rounded-md border px-3 py-2 text-start text-[13px] text-fd-foreground hover:bg-fd-accent focus:outline-none focus:ring-2 focus:ring-fd-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
Expand Down Expand Up @@ -101,7 +101,7 @@ const SelectItem = forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
'flex select-none flex-row items-center rounded-md py-1.5 pe-2 ps-6 text-sm outline-none focus:bg-fd-accent focus:text-fd-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'flex select-none flex-row items-center rounded-md py-1.5 pe-2 ps-6 text-[13px] outline-none focus:bg-fd-accent focus:text-fd-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
Expand Down
4 changes: 2 additions & 2 deletions packages/openapi/src/ui/playground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ function Route({
<div
{...props}
className={cn(
'flex flex-row items-center gap-0.5 overflow-auto text-nowrap text-xs',
'flex flex-row items-center gap-0.5 overflow-auto text-nowrap',
props.className,
)}
>
Expand Down Expand Up @@ -326,7 +326,7 @@ function FormHeader({
isLoading: boolean;
}) {
return (
<div className="flex flex-row items-center gap-2">
<div className="flex flex-row items-center gap-2 text-sm">
<MethodLabel>{method}</MethodLabel>
<Route route={route} className="flex-1" />
<button
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi/src/ui/scalar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function ScalarPlayground({
mounted ? `${resolvedTheme}-mode` : null,
)}
>
<MethodLabel>{method}</MethodLabel>
<MethodLabel className="text-xs">{method}</MethodLabel>
<code className="flex-1 overflow-auto text-nowrap text-[13px] text-fd-muted-foreground">
{path}
</code>
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi/src/ui/server-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function ServerSelect(props: HTMLAttributes<HTMLDivElement>) {
{servers.map((item) => (
<SelectItem key={item.url} value={item.url}>
{item.url}
<p className="text-start text-xs text-fd-muted-foreground">
<p className="text-start text-fd-muted-foreground">
{item.description}
</p>
</SelectItem>
Expand Down
94 changes: 75 additions & 19 deletions packages/ui/src/layouts/docs/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ interface InternalContext {
}

const itemVariants = cva(
'flex flex-row items-center gap-2 rounded-md p-2 text-start text-fd-muted-foreground [overflow-wrap:anywhere] md:py-1.5 [&_svg]:size-4 [&_svg]:shrink-0',
'relative flex flex-row items-center gap-2 rounded-md p-2 text-start text-fd-muted-foreground [overflow-wrap:anywhere] md:py-1.5 [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
active: {
Expand Down Expand Up @@ -221,11 +221,18 @@ export function SidebarViewport(props: ScrollAreaProps) {
);
}

export function SidebarSeparator(props: HTMLAttributes<HTMLParagraphElement>) {
export function SidebarSeparator({
level = 1,
...props
}: HTMLAttributes<HTMLParagraphElement> & { level?: number }) {
return (
<p
{...props}
className={cn('mb-2 px-2 text-sm font-medium', props.className)}
style={{
paddingInlineStart: getOffset(level),
...props.style,
}}
>
{props.children}
</p>
Expand All @@ -234,8 +241,10 @@ export function SidebarSeparator(props: HTMLAttributes<HTMLParagraphElement>) {

export function SidebarItem({
icon,
level = 1,
...props
}: LinkProps & {
level?: number;
icon?: ReactNode;
}) {
const pathname = usePathname();
Expand All @@ -249,7 +258,12 @@ export function SidebarItem({
data-active={active}
className={cn(itemVariants({ active }), props.className)}
prefetch={prefetch}
style={{
paddingInlineStart: getOffset(level),
...props.style,
}}
>
<Border depth={level} active={active} />
{icon ?? (props.external ? <ExternalLink /> : null)}
{props.children}
</Link>
Expand Down Expand Up @@ -279,14 +293,22 @@ export function SidebarFolder({
);
}

export function SidebarFolderTrigger(props: CollapsibleTriggerProps) {
export function SidebarFolderTrigger({
level = 1,
...props
}: CollapsibleTriggerProps & { level?: number }) {
const { open } = useFolderContext();

return (
<CollapsibleTrigger
{...props}
className={cn(itemVariants({ active: false }), 'w-full')}
style={{
paddingInlineStart: getOffset(level),
...props.style,
}}
>
<Border depth={level} />
{props.children}
<ChevronDown
data-icon
Expand All @@ -296,7 +318,10 @@ export function SidebarFolderTrigger(props: CollapsibleTriggerProps) {
);
}

export function SidebarFolderLink(props: LinkProps) {
export function SidebarFolderLink({
level = 1,
...props
}: LinkProps & { level?: number }) {
const { open, setOpen } = useFolderContext();
const { prefetch } = useInternalContext();

Expand All @@ -318,7 +343,12 @@ export function SidebarFolderLink(props: LinkProps) {
}
}}
prefetch={prefetch}
style={{
paddingInlineStart: getOffset(level),
...props.style,
}}
>
<Border depth={level} active={active} />
{props.children}
<ChevronDown
data-icon
Expand All @@ -329,13 +359,7 @@ export function SidebarFolderLink(props: LinkProps) {
}

export function SidebarFolderContent(props: CollapsibleContentProps) {
return (
<CollapsibleContent {...props}>
<div className="ms-3 border-s py-1.5 ps-1.5 md:ms-2">
{props.children}
</div>
</CollapsibleContent>
);
return <CollapsibleContent {...props}>{props.children}</CollapsibleContent>;
}

export function SidebarCollapseTrigger(
Expand Down Expand Up @@ -395,11 +419,16 @@ export function SidebarPageTree(props: {
level: number,
): ReactNode[] {
return items.map((item, i) => {
const id = `${item.type}_${i.toString()}`;
const id = `${item.type}_${i}`;

if (item.type === 'separator') {
if (Separator) return <Separator key={id} item={item} />;
return (
<SidebarSeparator key={id} className={cn(i !== 0 && 'mt-8')}>
<SidebarSeparator
key={id}
className={cn(i !== 0 && 'mt-8')}
level={level}
>
{item.name}
</SidebarSeparator>
);
Expand Down Expand Up @@ -428,6 +457,7 @@ export function SidebarPageTree(props: {
href={item.url}
external={item.external}
icon={item.icon}
level={level}
>
{item.name}
</SidebarItem>
Expand All @@ -441,12 +471,11 @@ export function SidebarPageTree(props: {

function PageTreeFolder({
item,
children,
level,
}: {
...props
}: HTMLAttributes<HTMLElement> & {
item: PageTree.Folder;
level: number;
children: ReactNode;
}) {
const { defaultOpenLevel } = useInternalContext();
const path = useTreePath();
Expand All @@ -458,17 +487,44 @@ function PageTreeFolder({
}
>
{item.index ? (
<SidebarFolderLink href={item.index.url} external={item.index.external}>
<SidebarFolderLink
href={item.index.url}
external={item.index.external}
level={level}
{...props}
>
{item.icon}
{item.name}
</SidebarFolderLink>
) : (
<SidebarFolderTrigger>
<SidebarFolderTrigger level={level} {...props}>
{item.icon}
{item.name}
</SidebarFolderTrigger>
)}
<SidebarFolderContent>{children}</SidebarFolderContent>
<SidebarFolderContent className="relative">
{props.children}
</SidebarFolderContent>
</SidebarFolder>
);
}

function getOffset(level: number) {
return `calc(var(--spacing) * ${(level - 1) * 4 + 2})`;
}

function Border({ depth, active }: { depth: number; active?: boolean }) {
if (depth <= 1) return null;

return (
<div
className={cn(
'absolute w-px inset-y-0 bg-fd-border z-[2]',
active && 'bg-fd-primary',
)}
style={{
insetInlineStart: `calc(${getOffset(1)} + 2px)`,
}}
/>
);
}

0 comments on commit b91aa83

Please sign in to comment.