mirror of https://github.com/buster-so/buster.git
home page
This commit is contained in:
parent
386875aa6d
commit
ac932fefee
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
)}>
|
||||
|
|
|
@ -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<
|
||||
|
|
|
@ -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]!'
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
import React from 'react';
|
||||
|
||||
export const HomePageHeader: React.FC<{}> = () => {
|
||||
return <div>Home</div>;
|
||||
};
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
export * from './HomePageController';
|
||||
export * from './HomePageHeader';
|
Loading…
Reference in New Issue