diff --git a/web/src/components/ui/buttons/Button.tsx b/web/src/components/ui/buttons/Button.tsx index dead5a41a..70d540ca3 100644 --- a/web/src/components/ui/buttons/Button.tsx +++ b/web/src/components/ui/buttons/Button.tsx @@ -39,7 +39,7 @@ 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', { variants: { - buttonType: buttonTypeClasses, + variant: buttonTypeClasses, size: sizeVariants, iconButton: { true: '', @@ -69,7 +69,7 @@ export const buttonVariants = cva( } ], defaultVariants: { - buttonType: 'default', + variant: 'default', size: 'default', iconButton: false, block: false @@ -79,7 +79,7 @@ export const buttonVariants = cva( export const buttonIconVariants = cva('', { variants: { - buttonType: { + variant: { default: 'text-icon-color!', black: 'text-white!', primary: 'text-white!', @@ -120,7 +120,7 @@ export const Button = React.forwardRef( ( { className = '', - buttonType = 'default', + variant = 'default', size = 'default', asChild = false, prefix, @@ -145,7 +145,7 @@ export const Button = React.forwardRef( return ( ( data-selected={selected} {...props}> {loading ? ( - + ) : ( prefix && ( - + {prefix} ) )} {hasChildren && {children}} {suffix && ( - + {suffix} )} @@ -177,22 +177,22 @@ export const Button = React.forwardRef( Button.displayName = 'Button'; export const LoadingIcon: React.FC<{ - buttonType: ButtonProps['buttonType']; + variant: ButtonProps['variant']; size: ButtonProps['size']; -}> = ({ buttonType = 'default', size = 'default' }) => { +}> = ({ variant = 'default', size = 'default' }) => { return (
, VariantProps { autoResize?: AutoResizeOptions; + onPressMetaEnter?: (e: React.KeyboardEvent) => void; } export const InputTextArea = React.forwardRef( @@ -92,6 +93,14 @@ export const InputTextArea = React.forwardRef) => { + if (e.metaKey && e.key === 'Enter') { + e.preventDefault(); + e.stopPropagation(); + onPressMetaEnter?.(e); + } + }); + useEffect(() => { const textarea = textareaRef.current; if (!textarea || !autoResize) return; diff --git a/web/src/components/ui/inputs/InputTextAreaButton.stories.tsx b/web/src/components/ui/inputs/InputTextAreaButton.stories.tsx new file mode 100644 index 000000000..546371194 --- /dev/null +++ b/web/src/components/ui/inputs/InputTextAreaButton.stories.tsx @@ -0,0 +1,66 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { InputTextAreaButton } from './InputTextAreaButton'; + +const meta: Meta = { + title: 'Base/InputTextAreaButton', + component: InputTextAreaButton, + tags: ['autodocs'], + args: {}, + argTypes: { + disabled: { + control: 'boolean' + }, + placeholder: { + control: 'text' + }, + rows: { + control: 'number' + }, + className: { + control: 'text' + } + } +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + placeholder: 'Enter text here...', + rows: 4 + } +}; + +export const WithAutoResize: Story = { + args: { + placeholder: 'Type to see auto-resize in action...', + autoResize: { + minRows: 1, + maxRows: 6 + } + } +}; + +export const Disabled: Story = { + args: { + disabled: true, + placeholder: 'Disabled textarea', + value: 'Cannot edit this text', + rows: 4 + } +}; + +export const CustomPlaceholder: Story = { + args: { + placeholder: 'Type your message here...', + rows: 3 + } +}; + +export const WithInitialValue: Story = { + args: { + value: 'This is some initial text in the textarea...', + rows: 4 + } +}; diff --git a/web/src/components/ui/inputs/InputTextAreaButton.tsx b/web/src/components/ui/inputs/InputTextAreaButton.tsx index 6efd3b6ac..1caa436eb 100644 --- a/web/src/components/ui/inputs/InputTextAreaButton.tsx +++ b/web/src/components/ui/inputs/InputTextAreaButton.tsx @@ -1,55 +1,115 @@ -import React from 'react'; +import React, { useMemo, useRef } from 'react'; import { InputTextArea, InputTextAreaProps } from './InputTextArea'; import { cn } from '@/lib/classMerge'; import { cva } from 'class-variance-authority'; +import { Button } from '../buttons/Button'; +import { ArrowUp } from '../icons/NucleoIconOutlined'; +import { ShapeSquare } from '../icons/NucleoIconFilled'; +import { useMemoizedFn } from 'ahooks'; -export interface InputTextAreaButtonProps extends Omit {} +const inputTextAreaButtonVariants = cva( + 'relative flex w-full items-center overflow-hidden rounded 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' + } + } + } +); -const inputTextAreaButtonVariants = cva('relative flex w-full items-center overflow-hidden', { - variants: {} -}); +export interface InputTextAreaButtonProps extends Omit { + sendIcon?: React.ReactNode; + loadingIcon?: React.ReactNode; + loading?: boolean; + onSubmit: (text: string) => void; + variant?: 'default'; +} export const InputTextAreaButton: React.FC = ({ className, disabled, + autoResize, + sendIcon = , + loadingIcon = , + loading = false, + onSubmit, + variant = 'default', ...props }) => { - return ( -
- (null); - // ref={inputRef} - // variant="borderless" - // onBlur={onBlurInput} - // onFocus={onFocusInput} - // className="inline-block w-full pt-2! pr-9! pb-2! pl-3.5! align-middle" - // placeholder="Ask a follow up..." - // value={inputValue} - // autoFocus={true} - // onChange={onChangeInput} - // onPressEnter={onPressEnter} - // disabled={loading} - // autoSize={autoSize} + const onSubmitPreflight = useMemoizedFn(() => { + if (disabled) return; + const text = textRef.current?.value || ''; + if (text.trim() === '') return; + onSubmit(text); + }); + + const onPressMetaEnter = useMemoizedFn(() => { + onSubmitPreflight(); + }); + + return ( +
+
- HERE - {/* */} + />
); }; + +const SubmitButton: React.FC<{ + loading: boolean; + disabled?: boolean; + sendIcon: React.ReactNode; + loadingIcon: React.ReactNode; + onSubmitPreflight: () => void; +}> = ({ disabled, sendIcon, loading, loadingIcon, onSubmitPreflight }) => { + const memoizedPrefix = useMemo(() => { + return ( +
+
+ {sendIcon} +
+
+ {loadingIcon} +
+
+ ); + }, [loading, sendIcon, loadingIcon]); + + return ( +