home page

This commit is contained in:
Nate Kelley 2025-03-03 14:25:51 -07:00
parent 386875aa6d
commit ac932fefee
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
13 changed files with 153 additions and 28 deletions

View File

@ -1,5 +1,11 @@
import { AppPageLayout } from '@/components/ui';
import React from 'react';
import { HomePageController, HomePageHeader } from '@/controllers/HomePage';
export default function HomePage() {
return <div>HomePage TODO:</div>;
return (
<AppPageLayout header={<HomePageHeader />}>
<HomePageController />
</AppPageLayout>
);
}

View File

@ -150,12 +150,12 @@ const SidebarPrimaryHeader = React.memo(() => {
</Link>
</Tooltip>
<Tooltip title="Start a chat">
<Link href={createBusterRoute({ route: BusterRoutes.SETTINGS_PROFILE })}>
<Link href={createBusterRoute({ route: BusterRoutes.APP_HOME })}>
<Button
size="tall"
rounding={'large'}
prefix={
<div className="translate-x-[0px] translate-y-[-1px]">
<div className="flex items-center justify-center">
<PencilSquareIcon />
</div>
}

View File

@ -7,7 +7,7 @@ import { CircleSpinnerLoader } from '../loaders/CircleSpinnerLoader';
export const buttonTypeClasses = {
default:
'bg-background border hover:bg-item-hover disabled:bg-disabled disabled:text-gray-light active:bg-item-active data-[selected=true]:bg-item-select',
black: 'bg-black text-white hover:bg-foreground-hover disabled:bg-black/30',
black: 'bg-black text-white hover:bg-foreground-hover disabled:bg-black/60',
primary:
'bg-primary text-white hover:bg-primary-light active:bg-primary-dark data-[selected=true]:bg-primary-dark',
ghost:
@ -36,7 +36,7 @@ const sizeVariants = {
};
export const buttonVariants = cva(
'inline-flex items-center overflow-hidden text-base justify-center gap-[5px] shadow rounded transition-all duration-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:cursor-not-allowed data-[loading=true]:cursor-progress',
'inline-flex items-center overflow-hidden text-base justify-center gap-[5px] shadow rounded transition-all duration-300 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:cursor-not-allowed data-[loading=true]:cursor-progress',
{
variants: {
variant: buttonTypeClasses,

View File

@ -41,7 +41,16 @@ export interface InputTextAreaProps
export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextAreaProps>(
(
{ className, variant = 'default', autoResize, style, rows = 1, rounding = 'default', ...props },
{
className,
variant = 'default',
autoResize,
style,
rows = 1,
rounding = 'default',
onPressMetaEnter,
...props
},
ref
) => {
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
@ -84,7 +93,7 @@ export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextArea
const adjustHeight = useMemoizedFn(() => {
const textarea = textareaRef.current;
if (!textarea || !autoResize) return;
if (!textarea || !autoResize || textarea.value === '') return;
const minHeight = calculateMinHeight();
if (!minHeight) return;
@ -93,14 +102,14 @@ export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextArea
const computedStyle = window.getComputedStyle(textarea);
const lineHeight =
parseFloat(computedStyle.lineHeight) || parseFloat(computedStyle.fontSize) * 1.2;
parseFloat(computedStyle.lineHeight) || parseFloat(computedStyle.fontSize) * 1.3;
const { top, bottom } = getPaddingValues();
const maxHeight = autoResize.maxRows
? autoResize.maxRows * lineHeight + top + bottom
: Infinity;
const scrollHeight = Math.max(textarea.scrollHeight, minHeight);
const newHeight = Math.min(scrollHeight, maxHeight) + 3;
const newHeight = Math.min(scrollHeight, maxHeight);
textarea.style.height = `${newHeight}px`;
textarea.style.overflowY = scrollHeight > maxHeight ? 'auto' : 'hidden';
@ -110,6 +119,14 @@ export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextArea
requestAnimationFrame(adjustHeight);
});
const handleKeyDown = useMemoizedFn((e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
onPressMetaEnter?.(e);
}
props.onKeyDown?.(e);
});
useEffect(() => {
const textarea = textareaRef.current;
if (!textarea || !autoResize) return;
@ -138,10 +155,11 @@ export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextArea
inputTextAreaVariants({ variant }),
textAreaVariants({ rounding }),
'px-2.5 py-2.5',
autoResize && 'resize-none',
autoResize && 'resize-none!',
className
)}
rows={autoResize ? 1 : rows}
onKeyDown={handleKeyDown}
{...props}
/>
);

View File

@ -8,12 +8,12 @@ import { ShapeSquare } from '../icons/NucleoIconFilled';
import { useMemoizedFn } from 'ahooks';
const inputTextAreaButtonVariants = cva(
'relative flex w-full items-center overflow-hidden rounded border border-border transition-all duration-200',
'relative flex w-full items-center overflow-hidden rounded-xl border border-border transition-all duration-200',
{
variants: {
variant: {
default:
'has-[textarea:hover]:border-foreground has-[textarea:focus]:border-foreground has-[textarea:disabled]:border-border'
'has-[textarea:hover]:border-foreground shadow has-[textarea:focus]:border-foreground has-[textarea:disabled]:border-border'
}
}
}
@ -25,6 +25,7 @@ export interface InputTextAreaButtonProps extends Omit<InputTextAreaProps, 'vari
loading?: boolean;
onSubmit: (text: string) => void;
variant?: 'default';
disabledSubmit?: boolean;
}
export const InputTextAreaButton: React.FC<InputTextAreaButtonProps> = ({
@ -36,6 +37,7 @@ export const InputTextAreaButton: React.FC<InputTextAreaButtonProps> = ({
loading = false,
onSubmit,
variant = 'default',
disabledSubmit,
...props
}) => {
const textRef = useRef<HTMLTextAreaElement>(null);
@ -52,12 +54,20 @@ export const InputTextAreaButton: React.FC<InputTextAreaButtonProps> = ({
});
return (
<div className={cn(inputTextAreaButtonVariants(), loading && 'border-border!', className)}>
<div
className={cn(
inputTextAreaButtonVariants({ variant }),
loading && 'border-border!',
className
)}>
<InputTextArea
ref={textRef}
disabled={disabled || loading}
variant="ghost"
className={cn('w-full pr-10 align-middle leading-[1.2]', loading && '!cursor-default')}
className={cn(
'leading-1.3 w-full px-5! py-4! pr-10 align-middle',
loading && '!cursor-default'
)}
autoResize={autoResize}
rounding="xl"
onPressMetaEnter={onPressMetaEnter}
@ -66,7 +76,7 @@ export const InputTextAreaButton: React.FC<InputTextAreaButtonProps> = ({
<div className="absolute right-2 bottom-2">
<SubmitButton
disabled={disabled}
disabled={disabled || disabledSubmit}
loading={loading}
sendIcon={sendIcon}
loadingIcon={loadingIcon}

View File

@ -11,7 +11,7 @@ export const AppPageLayoutContent: React.FC<
<main
className={cn(
'app-content-page',
'bg-background app-content h-full max-h-[100%] overflow-hidden p-0',
'bg-page-background app-content h-full max-h-[100%] overflow-hidden p-0',
scrollable && 'overflow-y-auto',
className
)}>

View File

@ -2,14 +2,17 @@ import { cn } from '@/lib/utils';
import React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
const headerVariants = cva('bg-background flex max-h-[38px] min-h-[38px] items-center border-b', {
variants: {
variant: {
default: 'px-4.5',
list: 'px-7.5'
const headerVariants = cva(
'bg-page-background flex max-h-[38px] min-h-[38px] items-center justify-between gap-x-2.5 border-b',
{
variants: {
variant: {
default: 'px-4.5',
list: 'px-7.5'
}
}
}
});
);
export const AppPageLayoutHeader: React.FC<
React.PropsWithChildren<

View File

@ -15,7 +15,7 @@ const paragraphVariants = cva('', {
lineHeight: {
none: 'leading-[1]!',
sm: 'leading-[1.2]!',
base: 'leading-[1.3]!',
base: 'leading-1.3!',
md: 'leading-[1.4]!',
lg: 'leading-[1.5]!'
}

View File

@ -6,11 +6,11 @@ import { textColorVariants } from './variants';
const titleVariants = cva('', {
variants: {
size: {
h1: 'text-3xl',
h2: 'text-2xl',
h3: 'text-xl',
h4: 'text-lg',
h5: 'text-md'
h1: 'text-4xl font-normal! ',
h2: 'text-2xl font-normal!',
h3: 'text-xl font-normal!',
h4: 'text-lg font-normal!',
h5: 'text-md font-normal!'
},
truncate: {
true: 'truncate',

View File

@ -0,0 +1,41 @@
'use client';
import React, { useMemo } from 'react';
import { Title } from '@/components/ui/typography';
import { useUserConfigContextSelector } from '@/context/Users';
import { NewChatInput } from './NewChatInput';
export const HomePageController: React.FC<{}> = () => {
const user = useUserConfigContextSelector((state) => state.user);
const userName = user?.name;
const isMorning = useMemo(() => {
const now = new Date();
const hours = now.getHours();
return hours < 12;
}, []);
const greeting = useMemo(() => {
if (isMorning) {
return `Good morning, ${userName}`;
}
return `Good afternoon, ${userName}`;
}, [userName]);
return (
<div className="flex flex-col items-center justify-center p-4.5">
<div className="mt-[150px] flex w-full max-w-[650px] flex-col space-y-6">
<div className="flex flex-col justify-center gap-y-2.5 text-center">
<Title as="h1" className="mb-0!">
{greeting}
</Title>
<Title as="h2" variant={'secondary'} className="mb-0! text-4xl!">
How can I help you today?
</Title>
</div>
<NewChatInput />
</div>
</div>
);
};

View File

@ -0,0 +1,5 @@
import React from 'react';
export const HomePageHeader: React.FC<{}> = () => {
return <div>Home</div>;
};

View File

@ -0,0 +1,40 @@
'use client';
import { InputTextAreaButton } from '@/components/ui/inputs/InputTextAreaButton';
import { useBusterNewChatContextSelector } from '@/context/Chats';
import { inputHasText } from '@/lib/text';
import { useMemoizedFn } from 'ahooks';
import { ChangeEvent, useMemo, useState } from 'react';
const autoResizeConfig = {
minRows: 3,
maxRows: 18
};
export const NewChatInput: React.FC<{}> = () => {
const onStartNewChat = useBusterNewChatContextSelector((state) => state.onStartNewChat);
const [inputValue, setInputValue] = useState('');
const disabledSubmit = useMemo(() => {
return !inputHasText(inputValue);
}, [inputValue]);
const onSubmit = useMemoizedFn((value: string) => {
onStartNewChat({ prompt: value });
});
const onChange = useMemoizedFn((e: ChangeEvent<HTMLTextAreaElement>) => {
setInputValue(e.target.value);
});
return (
<InputTextAreaButton
placeholder="Ask Buster a question..."
autoResize={autoResizeConfig}
onSubmit={onSubmit}
onChange={onChange}
autoFocus
disabledSubmit={disabledSubmit}
/>
);
};

View File

@ -0,0 +1,2 @@
export * from './HomePageController';
export * from './HomePageHeader';