make page layout better

This commit is contained in:
Nate Kelley 2025-02-28 17:11:40 -07:00
parent 92fe355f47
commit b883ffc31c
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
13 changed files with 70 additions and 74 deletions

View File

@ -10,17 +10,16 @@ import { BusterListColumn, BusterListRow } from '@/components/ui/list';
import { ChatSelectedOptionPopup } from './ChatItemsSelectedPopup'; import { ChatSelectedOptionPopup } from './ChatItemsSelectedPopup';
import { BusterList, ListEmptyStateWithButton } from '@/components/ui/list'; import { BusterList, ListEmptyStateWithButton } from '@/components/ui/list';
import { useCreateListByDate } from '@/components/ui/list/useCreateListByDate'; import { useCreateListByDate } from '@/components/ui/list/useCreateListByDate';
import { cn } from '@/lib/utils';
export const ChatItemsContainer: React.FC<{ export const ChatItemsContainer: React.FC<{
chats: BusterChatListItem[]; chats: BusterChatListItem[];
className?: string; className?: string;
openNewMetricModal: () => void;
loading: boolean; loading: boolean;
}> = ({ chats = [], className = '', loading, openNewMetricModal }) => { }> = ({ chats = [], className = '', loading }) => {
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]); const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
const renderedDates = useRef<Record<string, string>>({}); const renderedDates = useRef<Record<string, string>>({});
const renderedOwners = useRef<Record<string, React.ReactNode>>({}); const renderedOwners = useRef<Record<string, React.ReactNode>>({});
const tableContainerRef = useRef<HTMLDivElement>(null);
const onSelectChange = useMemoizedFn((selectedRowKeys: string[]) => { const onSelectChange = useMemoizedFn((selectedRowKeys: string[]) => {
setSelectedRowKeys(selectedRowKeys); setSelectedRowKeys(selectedRowKeys);
@ -106,15 +105,13 @@ export const ChatItemsContainer: React.FC<{
); );
return ( return (
<div <>
ref={tableContainerRef}
className={`${className} relative flex h-full flex-col items-center`}>
<BusterList <BusterList
rows={chatsByDate} rows={chatsByDate}
columns={columns} columns={columns}
onSelectChange={onSelectChange} onSelectChange={onSelectChange}
selectedRowKeys={selectedRowKeys} selectedRowKeys={selectedRowKeys}
emptyState={<EmptyState loading={loading} openNewMetricModal={openNewMetricModal} />} emptyState={<EmptyState loading={loading} />}
/> />
<ChatSelectedOptionPopup <ChatSelectedOptionPopup
@ -122,31 +119,27 @@ export const ChatItemsContainer: React.FC<{
onSelectChange={onSelectChange} onSelectChange={onSelectChange}
hasSelected={hasSelected} hasSelected={hasSelected}
/> />
</div> </>
); );
}; };
const EmptyState: React.FC<{ const EmptyState: React.FC<{
loading: boolean; loading: boolean;
openNewMetricModal: () => void; }> = React.memo(({ loading }) => {
}> = React.memo(({ loading, openNewMetricModal }) => {
if (loading) { if (loading) {
return <></>; return <></>;
} }
return <ChatsEmptyState openNewMetricModal={openNewMetricModal} />; return <ChatsEmptyState />;
}); });
EmptyState.displayName = 'EmptyState'; EmptyState.displayName = 'EmptyState';
const ChatsEmptyState: React.FC<{ const ChatsEmptyState: React.FC<{}> = ({}) => {
openNewMetricModal: () => void;
}> = ({ openNewMetricModal }) => {
return ( return (
<ListEmptyStateWithButton <ListEmptyStateWithButton
title="You don't have any chats yet." title="You don't have any chats yet."
description="You don't have any chats. As soon as you do, they will start to appear here." description="You don't have any chats. As soon as you do, they will start to appear here."
buttonText="New chat" buttonText="New chat"
onClick={openNewMetricModal}
/> />
); );
}; };

View File

@ -1,44 +1,15 @@
'use client'; 'use client';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { AppContentPage } from '@/components/ui/layouts/AppContentPage';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import { useMemoizedFn, useMount } from 'ahooks';
import { useBusterChatListByFilter } from '@/context/Chats'; import { useBusterChatListByFilter } from '@/context/Chats';
import { ChatListHeader } from './ChatListHeader';
import { ChatItemsContainer } from './ChatItemsContainer'; import { ChatItemsContainer } from './ChatItemsContainer';
export const ChatListContainer: React.FC<{ export const ChatListContainer: React.FC<{}> = ({}) => {
className?: string;
}> = ({ className = '' }) => {
const onToggleChatsModal = useAppLayoutContextSelector((s) => s.onToggleChatsModal);
const [filters, setFilters] = useState<Parameters<typeof useBusterChatListByFilter>[0]>({ const [filters, setFilters] = useState<Parameters<typeof useBusterChatListByFilter>[0]>({
admin_view: false admin_view: false
}); });
const { list, isFetched } = useBusterChatListByFilter(filters); const { list, isFetched } = useBusterChatListByFilter(filters);
const onSetFilters = useMemoizedFn( return <ChatItemsContainer chats={list} loading={!isFetched} />;
(newFilters: Parameters<typeof useBusterChatListByFilter>[0]) => {
setFilters(newFilters);
}
);
useMount(async () => {
onSetFilters({ admin_view: false });
});
return (
<div className={`${className} flex h-full flex-col`}>
<ChatListHeader />
<AppContentPage>
<ChatItemsContainer
chats={list}
loading={!isFetched}
openNewMetricModal={onToggleChatsModal}
className="flex-col overflow-hidden"
/>
</AppContentPage>
</div>
);
}; };

View File

@ -1,17 +1,10 @@
'use client';
import React from 'react'; import React from 'react';
import { AppContentHeader } from '@/components/ui/layouts/AppContentHeader'; import { Text } from '@/components/ui/typography';
import { Text } from '@/components/ui';
export const ChatListHeader: React.FC<{}> = ({}) => { export const ChatListHeader: React.FC<{}> = ({}) => {
return ( return (
<AppContentHeader> <>
<div className="flex w-full items-center justify-between"> <Text>{'Chats'}</Text>
<div className="flex items-center space-x-2"> </>
<Text>{'Chats'}</Text>
</div>
</div>
</AppContentHeader>
); );
}; };

View File

@ -1,5 +1,11 @@
import { AppPageLayout } from '@/components/ui/layouts/AppPageLayout';
import { ChatListContainer } from './_ChatsListContainer'; import { ChatListContainer } from './_ChatsListContainer';
import { ChatListHeader } from './_ChatsListContainer/ChatListHeader';
export default function ChatsPage() { export default function ChatsPage() {
return <ChatListContainer />; return (
<AppPageLayout headerVariant="list" header={<ChatListHeader />}>
<ChatListContainer />
</AppPageLayout>
);
} }

View File

@ -14,6 +14,9 @@ import { Tooltip } from '@/components/ui/tooltip/Tooltip';
import Link from 'next/link'; import Link from 'next/link';
import { useUserConfigContextSelector } from '@/context/Users'; import { useUserConfigContextSelector } from '@/context/Users';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout'; import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import { SupportModal } from '../modal/SupportModal';
import { InvitePeopleModal } from '../modal/InvitePeopleModal';
import { useMemoizedFn } from 'ahooks';
const topItems: ISidebarList = { const topItems: ISidebarList = {
items: [ items: [
@ -126,7 +129,11 @@ export const SidebarPrimary = React.memo(() => {
}, [isAdmin, favorites, currentRoute]); }, [isAdmin, favorites, currentRoute]);
return ( return (
<Sidebar content={sidebarItems} header={<SidebarPrimaryHeader />} activeItem={currentRoute} /> <>
<Sidebar content={sidebarItems} header={<SidebarPrimaryHeader />} activeItem={currentRoute} />
<GlobalModals />
</>
); );
}); });
@ -148,7 +155,7 @@ const SidebarPrimaryHeader = React.memo(() => {
size="tall" size="tall"
rounding={'large'} rounding={'large'}
prefix={ prefix={
<div className="translate-x-[-1px] translate-y-[-1px]"> <div className="translate-x-[0px] translate-y-[-1px]">
<PencilSquareIcon /> <PencilSquareIcon />
</div> </div>
} }
@ -162,6 +169,25 @@ const SidebarPrimaryHeader = React.memo(() => {
SidebarPrimaryHeader.displayName = 'SidebarPrimaryHeader'; SidebarPrimaryHeader.displayName = 'SidebarPrimaryHeader';
const GlobalModals = React.memo(() => {
const onToggleSupportModal = useAppLayoutContextSelector((s) => s.onToggleSupportModal);
const onToggleInviteModal = useAppLayoutContextSelector((s) => s.onToggleInviteModal);
const openSupportModal = useAppLayoutContextSelector((s) => s.openSupportModal);
const openInviteModal = useAppLayoutContextSelector((s) => s.openInviteModal);
const isAnonymousUser = useUserConfigContextSelector((state) => state.isAnonymousUser);
const onCloseInviteModal = useMemoizedFn(() => onToggleInviteModal(false));
const onCloseSupportModal = useMemoizedFn(() => onToggleSupportModal(false));
if (isAnonymousUser) return null;
return (
<>
<InvitePeopleModal open={openInviteModal} onClose={onCloseInviteModal} />
<SupportModal open={openSupportModal} onClose={onCloseSupportModal} />
</>
);
});
const favoritesDropdown = (favorites: BusterUserFavorite[]): ISidebarGroup => { const favoritesDropdown = (favorites: BusterUserFavorite[]): ISidebarGroup => {
return { return {
label: 'Favorites', label: 'Favorites',

View File

@ -1,6 +1,5 @@
import React, { PropsWithChildren } from 'react'; import React, { PropsWithChildren } from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { cva, type VariantProps } from 'class-variance-authority';
export const AppContentPage: React.FC< export const AppContentPage: React.FC<
PropsWithChildren<{ PropsWithChildren<{
@ -11,7 +10,8 @@ export const AppContentPage: React.FC<
return ( return (
<main <main
className={cn( className={cn(
'bg-background app-content max-h-[100%] overflow-hidden p-0', 'app-content-page',
'bg-background app-content h-full max-h-[100%] overflow-hidden p-0',
scrollable && 'overflow-y-auto', scrollable && 'overflow-y-auto',
className className
)}> )}>

View File

@ -1,5 +1,5 @@
import { cn } from '@/lib/utils';
import React from 'react'; import React from 'react';
import { cn } from '@/lib/utils';
import { AppContentHeader } from './AppContentHeader'; import { AppContentHeader } from './AppContentHeader';
import { AppContentPage } from './AppContentPage'; import { AppContentPage } from './AppContentPage';
@ -18,7 +18,7 @@ export const AppPageLayout: React.FC<
className?: string; className?: string;
headerVariant?: 'default' | 'list'; headerVariant?: 'default' | 'list';
}> }>
> = ({ children, header, scrollable = true, className = '', headerVariant = 'default' }) => { > = ({ children, header, scrollable = false, className = '', headerVariant = 'default' }) => {
return ( return (
<div <div
className={cn( className={cn(

View File

@ -1,5 +1,9 @@
export * from './AppSplitter';
export * from './PreventNavigation'; export * from './PreventNavigation';
export * from './AppContentHeader_Old'; export * from './AppContentHeader_Old';
export * from './AppContentPage'; export * from './AppContentPage';
export * from './ClientRedirect'; export * from './ClientRedirect';
//keepers
// export * from './AppPageLayout';
// export * from './AppSplitter';
// export * from './AppLayout';

View File

@ -1,14 +1,16 @@
import React from 'react'; import React from 'react';
import { AppMaterialIcons, Text, Title } from '@/components/ui'; import { AppMaterialIcons, Text, Title } from '@/components/ui';
import { Button } from 'antd'; import { Button } from '../buttons/Button';
import Link from 'next/link';
export const ListEmptyStateWithButton: React.FC<{ export const ListEmptyStateWithButton: React.FC<{
isAdmin?: boolean; isAdmin?: boolean;
title: string; title: string;
description: string; description: string;
onClick: () => void; onClick?: () => void;
buttonText: string; buttonText: string;
loading?: boolean; loading?: boolean;
linkButton?: string;
}> = React.memo(({ isAdmin = true, title, buttonText, description, onClick, loading = false }) => { }> = React.memo(({ isAdmin = true, title, buttonText, description, onClick, loading = false }) => {
return ( return (
<div className="flex h-full w-full flex-col"> <div className="flex h-full w-full flex-col">
@ -27,8 +29,8 @@ export const ListEmptyStateWithButton: React.FC<{
{isAdmin && ( {isAdmin && (
<Button <Button
type="default" variant="default"
icon={<AppMaterialIcons icon="add" />} prefix={<AppMaterialIcons icon="add" />}
loading={loading} loading={loading}
onClick={onClick}> onClick={onClick}>
{buttonText} {buttonText}

View File

@ -30,7 +30,7 @@ type ParagraphProps = {
} & VariantProps<typeof textColorVariants> & } & VariantProps<typeof textColorVariants> &
VariantProps<typeof paragraphVariants>; VariantProps<typeof paragraphVariants>;
const Paragraph: React.FC<ParagraphProps> = ({ export const Paragraph: React.FC<ParagraphProps> = ({
onClick, onClick,
variant = 'default', variant = 'default',
size = 'base', size = 'base',

View File

@ -33,7 +33,7 @@ type TextProps = {
} & VariantProps<typeof textVariants> & } & VariantProps<typeof textVariants> &
VariantProps<typeof textColorVariants>; VariantProps<typeof textColorVariants>;
const Text: React.FC<TextProps> = ({ export const Text: React.FC<TextProps> = ({
variant = 'default', variant = 'default',
size = 'base', size = 'base',
truncate, truncate,
@ -53,5 +53,3 @@ const Text: React.FC<TextProps> = ({
</TextNode> </TextNode>
); );
}; };
export default Text;

View File

@ -33,7 +33,7 @@ type TitleProps = {
} & VariantProps<typeof titleVariants> & } & VariantProps<typeof titleVariants> &
VariantProps<typeof textColorVariants>; VariantProps<typeof textColorVariants>;
const Title: React.FC<TitleProps> = ({ export const Title: React.FC<TitleProps> = ({
as = 'h1', as = 'h1',
variant = 'default', variant = 'default',
size, size,

View File

@ -0,0 +1,3 @@
export * from './Text';
export * from './Title';
export * from './Paragraph';