Skip to content

Commit

Permalink
layout update
Browse files Browse the repository at this point in the history
  • Loading branch information
traviskuhl committed Jun 3, 2024
1 parent 0a3cbc5 commit 9d428ea
Show file tree
Hide file tree
Showing 19 changed files with 275 additions and 269 deletions.
2 changes: 1 addition & 1 deletion apps/docs/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Analytics} from '@vercel/analytics/react';
import {Providers} from '@/app/providers';
import {Layout} from '@/components/Layout';

import '@elwood/ui/style.css';
import '@elwood/ui/dist/style.css';
import '@/styles/tailwind.css';

const inter = Inter({
Expand Down
57 changes: 57 additions & 0 deletions packages/react/src/components/layouts/content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type {PropsWithChildren, ReactNode} from 'react';
import {Spinner, cn} from '@elwood/ui';

export interface ContentLayoutProps {
headerLeft?: ReactNode;
headerRight?: ReactNode;
mainProps?: React.HTMLProps<HTMLDivElement>;
mainClassName?: string;
rail?: ReactNode;
loading?: boolean;
largeTitle?: ReactNode;
}

export function ContentLayout(
props: PropsWithChildren<ContentLayoutProps>,
): JSX.Element {
const showHeader = props.headerLeft ?? props.headerRight ?? props.largeTitle;
const headerLeft = props.largeTitle ? (
<h1 className="text-3xl font-extrabold">{props.largeTitle}</h1>
) : (
props.headerLeft
);

const bodyClass = cn(
'flex-grow flex-nowrap overflow-y-auto overflow-x-hidden min-h-0 pb-4',
props.mainClassName,
);

return (
<>
<div className="flex flex-col min-h-0 h-full w-full">
{showHeader ? (
<header className="flex items-center justify-between px-8 pt-4">
<div>{headerLeft}</div>
<div>{props.headerRight}</div>
</header>
) : null}
<div className="flex-grow flex flex-row flex-nowrap min-h-0 px-8 pt-4">
<div {...props.mainProps} className={bodyClass}>
{props.children}
{props.loading ? (
<div className="w-full h-full flex items-center justify-center">
<Spinner />
</div>
) : null}
</div>
</div>
</div>

{props.rail ? (
<div className="ml-0 w-1/4 min-w-[300px] flex flex-col border rounded m-4">
{props.rail}
</div>
) : null}
</>
);
}
4 changes: 1 addition & 3 deletions packages/react/src/components/layouts/main.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type {Meta, StoryObj} from '@storybook/react';

import {Button} from '@elwood/ui';

import {MainLayout as Component} from './main';

const meta: Meta<typeof Component> = {
Expand All @@ -18,7 +16,7 @@ type Story = StoryObj<typeof Component>;
*/
export const Primary: Story = {
render: () => (
<Component sidebar={<div>sidebar</div>}>
<Component>
<div>child</div>
</Component>
),
Expand Down
9 changes: 0 additions & 9 deletions packages/react/src/components/layouts/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import {Spinner} from '@elwood/ui';

export interface MainLayoutProps {
header?: ReactNode;
sidebar?: ReactNode;
sidebarFooter?: ReactNode;
loading?: boolean;
}

Expand All @@ -17,13 +15,6 @@ export function MainLayout(
<div className="w-full h-full grid grid-rows-[60px_auto] ">
<div className="border-b h-full bg-black">{props.header}</div>
<div className="flex flex-row min-h-0">
<div className="bg-sidebar border-r w-[300px] flex-shrink-0 flex flex-col justify-between">
<div className="flex-grow overflow-auto px-6">{props.sidebar}</div>
{props.sidebarFooter ? (
<footer className="flex flex-col">{props.sidebarFooter}</footer>
) : null}
</div>

{children ? (
children
) : (
Expand Down
53 changes: 10 additions & 43 deletions packages/react/src/components/layouts/page.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,24 @@
import type {PropsWithChildren, ReactNode} from 'react';
import {Spinner, cn} from '@elwood/ui';

export interface PageLayoutProps {
headerLeft?: ReactNode;
headerRight?: ReactNode;
mainProps?: React.HTMLProps<HTMLDivElement>;
mainClassName?: string;
rail?: ReactNode;
loading?: boolean;
largeTitle?: ReactNode;
sidebar?: ReactNode;
sidebarFooter?: ReactNode;
}

export function PageLayout(
props: PropsWithChildren<PageLayoutProps>,
): JSX.Element {
const showHeader = props.headerLeft ?? props.headerRight ?? props.largeTitle;
const headerLeft = props.largeTitle ? (
<h1 className="text-3xl font-extrabold">{props.largeTitle}</h1>
) : (
props.headerLeft
);

const bodyClass = cn(
'flex-grow flex-nowrap overflow-y-auto overflow-x-hidden min-h-0 pb-4',
props.mainClassName,
);

return (
<>
<div className="flex flex-col min-h-0 h-full w-full">
{showHeader ? (
<header className="flex items-center justify-between px-8 pt-4">
<div>{headerLeft}</div>
<div>{props.headerRight}</div>
</header>
) : null}
<div className="flex-grow flex flex-row flex-nowrap min-h-0 px-8 pt-4">
<div {...props.mainProps} className={bodyClass}>
{props.children}
{props.loading ? (
<div className="w-full h-full flex items-center justify-center">
<Spinner />
</div>
) : null}
</div>
</div>
</div>

{props.rail ? (
<div className="ml-0 w-1/4 min-w-[300px] flex flex-col border rounded m-4">
{props.rail}
{props.sidebar && (
<div className="bg-sidebar border-r w-[300px] flex-shrink-0 flex flex-col justify-between">
<div className="flex-grow overflow-auto px-6">{props.sidebar}</div>
{props.sidebarFooter ? (
<footer className="flex flex-col">{props.sidebarFooter}</footer>
) : null}
</div>
) : null}
)}
{props.children}
</>
);
}
142 changes: 88 additions & 54 deletions packages/react/src/hooks/ui/use-main-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,50 @@
import {useState, type PropsWithChildren, useMemo, useEffect} from 'react';
import {useDebounce} from 'react-use';
import {
FolderIcon,
FileIcon,
useTheme,
Button,
BookMarkedIcon,
SparklesIcon,
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@elwood/ui';
type PropsWithChildren,
useState,
useMemo,
useEffect,
createContext,
useContext,
ReactNode,
useReducer,
Reducer,
useCallback,
} from 'react';
import {useDebounce} from 'react-use';
import {FolderIcon, FileIcon, useTheme} from '@elwood/ui';

import {useProviderContext} from '@/hooks/use-provider-context';
import {MainLayout, type MainLayoutProps} from '@/components/layouts/main';
import {type MainLayoutProps} from '@/components/layouts/main';

import {Link} from '@/components/link';
import {Header, HeaderProps} from '@/components/header/header';
import {type HeaderProps} from '@/components/header/header';
import {HeaderSearch, HeaderSearchProps} from '@/components/header/search';
import {HeaderUserMenu} from '@/components/header/user-menu';

import {useSearch} from '@/data/search/use-search';
import {useSidebarFooter} from './use-sidebar-footer';
import {useCurrentMember} from '../use-current-member';
import {useAssistant} from './use-assistant';
import {Json} from '@elwood/common';

type MainLayoutContextValue = {
setTitle(title: ReactNode): void;
};

export type MainLayoutState = {
contextValue: MainLayoutContextValue;
title: ReactNode;
workspaceName: ReactNode;
search: ReactNode;
assistant: ReactNode;
userMenu: ReactNode;
};

const MainLayoutContext = createContext<MainLayoutContextValue>({
setTitle: () => {},
});

export const MainLayoutProvider = MainLayoutContext.Provider;

export type UseMainLayoutInput = PropsWithChildren<
MainLayoutProps & {
Expand All @@ -39,12 +55,37 @@ export type UseMainLayoutInput = PropsWithChildren<

export function useMainLayout(
input: PropsWithChildren<UseMainLayoutInput> = {},
): JSX.Element {
): MainLayoutState {
const {workspaceName, avatarUrl, onLogout} = useProviderContext();
const currentMember = useCurrentMember();
const sidebarFooter = useSidebarFooter();
const theme = useTheme();

const [contextValue, dispatch] = useReducer<
Reducer<
{
title: MainLayoutState['title'];
},
{type: 'SET_TITLE'; value: MainLayoutState['title']}
>
>(
(state, action) => {
switch (action.type) {
case 'SET_TITLE':
if (state.title === action.value) {
return state;
}

return {
...state,
title: action.value,
};
}
},
{
title: null,
},
);

// Search
const [searchValue, setSearchValue] = useState('');
const [searchDebounceValue, setSearchDebounceValue] = useState('');
Expand Down Expand Up @@ -104,39 +145,32 @@ export function useMainLayout(
};
}, []);

return (
<MainLayout
header={
<Header
workspaceName={<Link href="/">{workspaceName}</Link>}
title={input.title}
search={search}
actions={
<>
<Drawer direction="right" shouldScaleBackground={false}>
<DrawerTrigger asChild={true}>
<Button type="button" size="sm" variant="outline-muted">
<SparklesIcon className="size-4" />
</Button>
</DrawerTrigger>
<DrawerContent className="border-l p-4">
{assistant}
</DrawerContent>
</Drawer>

<Button href="/bookmarks" size="sm" variant="outline-muted">
<BookMarkedIcon className="size-4" />
</Button>
{userMenu}
</>
}
/>
}
sidebarFooter={sidebarFooter}
sidebar={input.sidebar}>
{input.children}
</MainLayout>
);
return {
contextValue: {
setTitle(nextTitle) {
dispatch({
type: 'SET_TITLE',
value: nextTitle,
});
},
},
title: contextValue.title,
workspaceName,
search,
assistant,
userMenu,
};
}

export function useSetMainLayoutTitle(
title: MainLayoutState['title'],
deps: Json[] = [],
) {
const ctx = useContext(MainLayoutContext);

useEffect(() => {
ctx.setTitle(title);
}, deps);
}

function mapSearchResults(
Expand Down
Loading

0 comments on commit 9d428ea

Please sign in to comment.