input search

This commit is contained in:
Nate Kelley 2025-02-24 19:11:26 -07:00
parent 4ae7b41191
commit f8bf374772
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
6 changed files with 184 additions and 49 deletions

View File

@ -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<HTMLButtonElement, ButtonProps>(
(
{
className = '',
buttonType = 'default',
variant = 'default',
size = 'default',
asChild = false,
prefix,
@ -145,7 +145,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
return (
<Comp
className={cn(
buttonVariants({ buttonType, size, iconButton, rounding, block, className }),
buttonVariants({ variant, size, iconButton, rounding, block, className }),
onClick && 'cursor-pointer'
)}
onClick={onClick}
@ -155,17 +155,17 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
data-selected={selected}
{...props}>
{loading ? (
<LoadingIcon buttonType={buttonType} size={size} />
<LoadingIcon variant={variant} size={size} />
) : (
prefix && (
<span className={cn(buttonIconVariants({ buttonType, size }), prefixClassName)}>
<span className={cn(buttonIconVariants({ variant, size }), prefixClassName)}>
{prefix}
</span>
)
)}
{hasChildren && <span className="">{children}</span>}
{suffix && (
<span className={cn(buttonIconVariants({ buttonType, size }), suffixClassName)}>
<span className={cn(buttonIconVariants({ variant, size }), suffixClassName)}>
{suffix}
</span>
)}
@ -177,22 +177,22 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
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 (
<div
className={cn(
'flex items-center justify-center text-black dark:text-white',
buttonType === 'black' && 'dark',
variant === 'black' && 'dark',
loadingSizeVariants[size || 'default']
)}>
<CircleSpinnerLoader
size={size === 'tall' ? 12.5 : 9.5}
fill={
buttonType === 'black'
variant === 'black'
? 'var(--color-white)'
: buttonType === 'primary'
: variant === 'primary'
? 'var(--color-primary)'
: 'var(--color-primary)'
}

View File

@ -9,7 +9,7 @@ export const inputVariants = cva(
variants: {
variant: {
default:
'shadow disabled:bg-item-select bg-background border placeholder:text-gray-light hover:border-primary focus:border-primary focus-visible:border-primary outline-none disabled:border-border',
'shadow disabled:bg-item-select bg-background border placeholder:text-gray-light hover:border-foreground focus:border-foreground focus-visible:border-foreground outline-none disabled:border-border',
ghost: 'border-none bg-transparent shadow-none disabled:bg-transparent'
},
size: {

View File

@ -22,6 +22,7 @@ export interface InputTextAreaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement>,
VariantProps<typeof inputTextAreaVariants> {
autoResize?: AutoResizeOptions;
onPressMetaEnter?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
}
export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextAreaProps>(
@ -92,6 +93,14 @@ export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextArea
requestAnimationFrame(adjustHeight);
});
const onPressMetaEnter = useMemoizedFn((e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.metaKey && e.key === 'Enter') {
e.preventDefault();
e.stopPropagation();
onPressMetaEnter?.(e);
}
});
useEffect(() => {
const textarea = textareaRef.current;
if (!textarea || !autoResize) return;

View File

@ -0,0 +1,66 @@
import type { Meta, StoryObj } from '@storybook/react';
import { InputTextAreaButton } from './InputTextAreaButton';
const meta: Meta<typeof InputTextAreaButton> = {
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<typeof InputTextAreaButton>;
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
}
};

View File

@ -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<InputTextAreaProps, 'variant'> {}
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<InputTextAreaProps, 'variant' | 'onSubmit'> {
sendIcon?: React.ReactNode;
loadingIcon?: React.ReactNode;
loading?: boolean;
onSubmit: (text: string) => void;
variant?: 'default';
}
export const InputTextAreaButton: React.FC<InputTextAreaButtonProps> = ({
className,
disabled,
autoResize,
sendIcon = <ArrowUp />,
loadingIcon = <ShapeSquare />,
loading = false,
onSubmit,
variant = 'default',
...props
}) => {
return (
<div
className={cn(
// styles.inputContainer,
// isFocused && 'focused',
// loading && 'loading',
inputTextAreaButtonVariants()
)}>
<InputTextArea
disabled={disabled}
variant="ghost"
className="inline-block w-full pt-2! pr-9! pb-2! pl-3.5! align-middle"
{...props}
const textRef = useRef<HTMLTextAreaElement>(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 (
<div className={cn(inputTextAreaButtonVariants(), 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')}
autoResize={autoResize}
onPressMetaEnter={onPressMetaEnter}
{...props}
/>
<div className="absolute right-2 bottom-2">
HERE
{/* <SubmitButton
disableSendButton={disableSendButton}
<SubmitButton
disabled={disabled}
loading={loading}
sendIcon={sendIcon}
loadingIcon={loadingIcon}
onSubmitPreflight={onSubmitPreflight}
/> */}
/>
</div>
</div>
);
};
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 (
<div
className={cn(
'relative h-4 w-4 transition-all duration-300 active:scale-80',
loading && '!cursor-default'
)}>
<div
className={`absolute inset-0 transition-all duration-300 ${loading ? 'scale-95 opacity-0' : 'scale-100 opacity-100'}`}>
{sendIcon}
</div>
<div
className={`absolute inset-0 flex items-center justify-center text-sm transition-all duration-300 ${loading ? 'scale-100 opacity-100' : 'scale-95 opacity-0'}`}>
{loadingIcon}
</div>
</div>
);
}, [loading, sendIcon, loadingIcon]);
return (
<Button
rounding={'large'}
variant="black"
prefix={memoizedPrefix}
className="active:scale-95"
onClick={onSubmitPreflight}
disabled={disabled}
/>
);
};

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React from 'react';
import { AppMaterialIcons } from '@/components/ui';
import { createStyles } from 'antd-style';
import { AnimatePresence, motion } from 'framer-motion';