UI: simplify and ship layouts via CLI

This commit is contained in:
Fuma Nama
2024-11-08 18:05:15 +08:00
parent 94ea8211b1
commit 0bab8aacdb
13 changed files with 51 additions and 102 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

@@ -4,7 +4,7 @@ import { type HTMLAttributes, type ReactNode, useMemo, useState } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { cn } from '@/utils/cn';
import { isActive } from '@/utils/shared';
import { isActive } from '@/utils/is-active';
import { useSidebar } from '@/contexts/sidebar';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
+5 -4
View File
@@ -14,6 +14,7 @@ export const registry: Registry = {
namespaces: {
'': 'components',
'../utils': 'lib',
'../layouts': 'components',
},
components: [
{
@@ -27,13 +28,13 @@ export const registry: Registry = {
mapImportPath: contextsMap,
},
{
name: 'layout/sidebar',
files: ['layout/sidebar.tsx'],
name: 'layouts/docs',
files: ['../layouts/docs.tsx'],
mapImportPath: contextsMap,
},
{
name: 'layout/dynamic-sidebar',
files: ['layout/dynamic-sidebar.tsx'],
name: 'layouts/home',
files: ['../layouts/home.tsx'],
mapImportPath: contextsMap,
},
{ name: 'accordion', files: ['accordion.tsx'] },
+7
View File
@@ -1,3 +1,4 @@
'use client';
import { createContext, useContext } from 'react';
export interface Translations {
@@ -42,6 +43,12 @@ export const I18nContext = createContext<I18nContextType>({
},
});
export function I18nLabel(props: { label: keyof Translations }): string {
const { text } = useI18n();
return text[props.label];
}
export function useI18n(): I18nContextType {
return useContext(I18nContext);
}
-6
View File
@@ -76,10 +76,4 @@ export function I18nProvider({
);
}
export function I18nLabel(props: { label: keyof Translations }): string {
const { text } = useI18n();
return text[props.label];
}
export { type Translations };
+27 -59
View File
@@ -113,25 +113,6 @@ export function DocsLayout({
tabs = getSidebarTabs(props.tree);
}
const banner = (
<>
<CustomSidebarHeader {...nav} links={links} />
{sidebarBanner}
</>
);
const footer = (
<>
<CustomSidebarFooter
sidebarCollapsible={collapsible}
i18n={i18n}
disableThemeSwitch={props.disableThemeSwitch ?? false}
iconItems={links.filter((v) => v.type === 'icon')}
/>
{sidebarFooter}
</>
);
return (
<TreeContextProvider tree={props.tree}>
<NavProvider transparentMode={transparentMode}>
@@ -164,7 +145,8 @@ export function DocsLayout({
{ enabled: sidebarEnabled, component: sidebarReplace },
<Aside {...sidebar}>
<SidebarHeader>
{banner}
<SidebarHeaderItems {...nav} links={links} />
{sidebarBanner}
{tabs.length > 0 ? (
<RootToggle options={tabs} className="-mx-2" />
) : null}
@@ -173,7 +155,7 @@ export function DocsLayout({
</SearchOnly>
</SidebarHeader>
<SidebarViewport>
<div className="px-4 pt-4 empty:hidden md:hidden">
<div className="px-2 pt-4 empty:hidden md:hidden">
{links
.filter((v) => v.type !== 'icon')
.map((item, i) => (
@@ -182,13 +164,19 @@ export function DocsLayout({
</div>
<SidebarItems components={sidebarComponents} />
</SidebarViewport>
<SidebarFooter>{footer}</SidebarFooter>
<SidebarFooter>
<SidebarFooterItems
sidebarCollapsible={collapsible}
i18n={i18n}
disableThemeSwitch={props.disableThemeSwitch ?? false}
iconItems={links.filter((v) => v.type === 'icon')}
/>
{sidebarFooter}
</SidebarFooter>
</Aside>,
{
...sidebar,
tabs,
banner,
footer,
},
)}
{props.children}
@@ -248,7 +236,7 @@ function renderLinkItem(item: LinkItemType): ReactNode {
if (item.type === 'custom') return item.children;
}
function CustomSidebarHeader({
function SidebarHeaderItems({
links,
...props
}: SharedNavProps & { links: LinkItemType[] }): ReactNode {
@@ -284,7 +272,7 @@ function CustomSidebarHeader({
);
}
function CustomSidebarFooter({
function SidebarFooterItems({
iconItems,
...props
}: {
@@ -293,53 +281,33 @@ function CustomSidebarFooter({
disableThemeSwitch: boolean;
sidebarCollapsible: boolean;
}) {
const iconItem = cn(
buttonVariants({ size: 'icon', color: 'ghost' }),
'text-fd-muted-foreground md:hidden',
);
if (props.i18n) {
return (
<div className="flex flex-row items-center">
{iconItems.map((item, i) => (
<BaseLinkItem
key={i}
item={item}
className={iconItem}
aria-label={item.label}
>
{item.icon}
</BaseLinkItem>
))}
{!props.disableThemeSwitch ? <ThemeToggle /> : null}
<LanguageToggle className="max-md:order-first max-md:me-auto md:ms-auto">
<Languages className="size-5" />
<LanguageToggleText className="md:hidden" />
</LanguageToggle>
{props.sidebarCollapsible ? (
<SidebarCollapseTrigger className="max-md:hidden" />
) : null}
</div>
);
}
return (
<div className="flex flex-row items-center">
{iconItems.map((item, i) => (
<BaseLinkItem
key={i}
item={item}
className={iconItem}
className={cn(
buttonVariants({ size: 'icon', color: 'ghost' }),
'text-fd-muted-foreground md:hidden',
)}
aria-label={item.label}
>
{item.icon}
</BaseLinkItem>
))}
<div role="separator" className="flex-1" />
{props.i18n ? (
<LanguageToggle className="me-1.5">
<Languages className="size-5" />
<LanguageToggleText className="md:hidden" />
</LanguageToggle>
) : null}
{!props.disableThemeSwitch ? (
<ThemeToggle className="p-0 max-md:ms-auto" />
<ThemeToggle className="p-0 md:order-first" />
) : null}
{props.sidebarCollapsible ? (
<SidebarCollapseTrigger className="-me-1.5 ms-auto max-md:hidden" />
<SidebarCollapseTrigger className="-me-1.5 max-md:hidden" />
) : null}
</div>
);
-18
View File
@@ -1,18 +0,0 @@
import {
Sidebar,
SidebarFooter,
SidebarHeader,
SidebarViewport,
} from '@/layouts/docs/sidebar';
export function Layout() {
return (
<>
<Sidebar>
<SidebarHeader></SidebarHeader>
<SidebarViewport></SidebarViewport>
<SidebarFooter />
</Sidebar>
</>
);
}
+1 -1
View File
@@ -19,7 +19,7 @@ import Link, { type LinkProps } from 'fumadocs-core/link';
import { useOnChange } from 'fumadocs-core/utils/use-on-change';
import { cn } from '@/utils/cn';
import { ScrollArea, ScrollViewport } from '@/components/ui/scroll-area';
import { isActive } from '@/utils/shared';
import { isActive } from '@/utils/is-active';
import {
Collapsible,
CollapsibleContent,
+7 -10
View File
@@ -57,7 +57,7 @@ export function HomeLayout({
id="nd-home-layout"
{...props}
className={cn(
'flex flex-1 flex-col pt-[var(--fd-nav-height)] [--fd-nav-height:48px] lg:[--fd-nav-height:56px]',
'flex flex-1 flex-col pt-[var(--fd-nav-height)] [--fd-nav-height:56px]',
props.className,
)}
>
@@ -126,21 +126,18 @@ export function HomeLayout({
/>
))}
<div className="-ms-1.5 flex flex-row items-center gap-1.5 max-sm:mt-2">
{menuItems.filter(isSecondary).map((item, i) => (
<MenuLinkItem key={i} item={item} className="-me-1.5" />
))}
<div role="separator" className="flex-1" />
{i18n ? (
<LanguageToggle className="me-auto">
<LanguageToggle>
<Languages className="size-5" />
<LanguageToggleText />
<ChevronDown className="size-3 text-fd-muted-foreground" />
</LanguageToggle>
) : null}
<div className="flex flex-row items-center empty:hidden">
{menuItems.filter(isSecondary).map((item, i) => (
<MenuLinkItem key={i} item={item} />
))}
</div>
{!disableThemeSwitch ? (
<ThemeToggle className={cn(!i18n && 'ms-auto')} />
) : null}
{!disableThemeSwitch ? <ThemeToggle /> : null}
</div>
</NavigationMenuContent>
</NavigationMenuItem>
+1 -1
View File
@@ -45,7 +45,7 @@ export function Navbar(props: HTMLAttributes<HTMLElement>) {
props.className,
)}
>
<nav className="flex h-12 w-full flex-row items-center gap-6 px-4">
<nav className="flex h-14 w-full flex-row items-center gap-6 px-4 lg:h-12">
{props.children}
</nav>
<NavigationMenuViewport />
+1 -1
View File
@@ -7,7 +7,7 @@ import {
type HTMLAttributes,
type ReactNode,
} from 'react';
import { isActive } from '@/utils/shared';
import { isActive } from '@/utils/is-active';
interface BaseItem {
/**
+1 -1
View File
@@ -30,7 +30,7 @@ import {
} from '@/components/layout/toc-popover';
import { buttonVariants } from '@/components/ui/button';
import { Edit, Text } from 'lucide-react';
import { I18nLabel } from '@/i18n';
import { I18nLabel } from '@/contexts/i18n';
const ClerkTOCItems = dynamic(() => import('@/components/layout/toc-clerk'));