mirror of https://github.com/buster-so/buster.git
input search
This commit is contained in:
parent
4ae7b41191
commit
f8bf374772
|
@ -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',
|
'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: {
|
variants: {
|
||||||
buttonType: buttonTypeClasses,
|
variant: buttonTypeClasses,
|
||||||
size: sizeVariants,
|
size: sizeVariants,
|
||||||
iconButton: {
|
iconButton: {
|
||||||
true: '',
|
true: '',
|
||||||
|
@ -69,7 +69,7 @@ export const buttonVariants = cva(
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
buttonType: 'default',
|
variant: 'default',
|
||||||
size: 'default',
|
size: 'default',
|
||||||
iconButton: false,
|
iconButton: false,
|
||||||
block: false
|
block: false
|
||||||
|
@ -79,7 +79,7 @@ export const buttonVariants = cva(
|
||||||
|
|
||||||
export const buttonIconVariants = cva('', {
|
export const buttonIconVariants = cva('', {
|
||||||
variants: {
|
variants: {
|
||||||
buttonType: {
|
variant: {
|
||||||
default: 'text-icon-color!',
|
default: 'text-icon-color!',
|
||||||
black: 'text-white!',
|
black: 'text-white!',
|
||||||
primary: 'text-white!',
|
primary: 'text-white!',
|
||||||
|
@ -120,7 +120,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
className = '',
|
className = '',
|
||||||
buttonType = 'default',
|
variant = 'default',
|
||||||
size = 'default',
|
size = 'default',
|
||||||
asChild = false,
|
asChild = false,
|
||||||
prefix,
|
prefix,
|
||||||
|
@ -145,7 +145,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(
|
className={cn(
|
||||||
buttonVariants({ buttonType, size, iconButton, rounding, block, className }),
|
buttonVariants({ variant, size, iconButton, rounding, block, className }),
|
||||||
onClick && 'cursor-pointer'
|
onClick && 'cursor-pointer'
|
||||||
)}
|
)}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
@ -155,17 +155,17 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
data-selected={selected}
|
data-selected={selected}
|
||||||
{...props}>
|
{...props}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<LoadingIcon buttonType={buttonType} size={size} />
|
<LoadingIcon variant={variant} size={size} />
|
||||||
) : (
|
) : (
|
||||||
prefix && (
|
prefix && (
|
||||||
<span className={cn(buttonIconVariants({ buttonType, size }), prefixClassName)}>
|
<span className={cn(buttonIconVariants({ variant, size }), prefixClassName)}>
|
||||||
{prefix}
|
{prefix}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
{hasChildren && <span className="">{children}</span>}
|
{hasChildren && <span className="">{children}</span>}
|
||||||
{suffix && (
|
{suffix && (
|
||||||
<span className={cn(buttonIconVariants({ buttonType, size }), suffixClassName)}>
|
<span className={cn(buttonIconVariants({ variant, size }), suffixClassName)}>
|
||||||
{suffix}
|
{suffix}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
@ -177,22 +177,22 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
Button.displayName = 'Button';
|
Button.displayName = 'Button';
|
||||||
|
|
||||||
export const LoadingIcon: React.FC<{
|
export const LoadingIcon: React.FC<{
|
||||||
buttonType: ButtonProps['buttonType'];
|
variant: ButtonProps['variant'];
|
||||||
size: ButtonProps['size'];
|
size: ButtonProps['size'];
|
||||||
}> = ({ buttonType = 'default', size = 'default' }) => {
|
}> = ({ variant = 'default', size = 'default' }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center justify-center text-black dark:text-white',
|
'flex items-center justify-center text-black dark:text-white',
|
||||||
buttonType === 'black' && 'dark',
|
variant === 'black' && 'dark',
|
||||||
loadingSizeVariants[size || 'default']
|
loadingSizeVariants[size || 'default']
|
||||||
)}>
|
)}>
|
||||||
<CircleSpinnerLoader
|
<CircleSpinnerLoader
|
||||||
size={size === 'tall' ? 12.5 : 9.5}
|
size={size === 'tall' ? 12.5 : 9.5}
|
||||||
fill={
|
fill={
|
||||||
buttonType === 'black'
|
variant === 'black'
|
||||||
? 'var(--color-white)'
|
? 'var(--color-white)'
|
||||||
: buttonType === 'primary'
|
: variant === 'primary'
|
||||||
? 'var(--color-primary)'
|
? 'var(--color-primary)'
|
||||||
: 'var(--color-primary)'
|
: 'var(--color-primary)'
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ export const inputVariants = cva(
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
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'
|
ghost: 'border-none bg-transparent shadow-none disabled:bg-transparent'
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
|
|
|
@ -22,6 +22,7 @@ export interface InputTextAreaProps
|
||||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
extends React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
||||||
VariantProps<typeof inputTextAreaVariants> {
|
VariantProps<typeof inputTextAreaVariants> {
|
||||||
autoResize?: AutoResizeOptions;
|
autoResize?: AutoResizeOptions;
|
||||||
|
onPressMetaEnter?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextAreaProps>(
|
export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextAreaProps>(
|
||||||
|
@ -92,6 +93,14 @@ export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextArea
|
||||||
requestAnimationFrame(adjustHeight);
|
requestAnimationFrame(adjustHeight);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onPressMetaEnter = useMemoizedFn((e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
|
if (e.metaKey && e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onPressMetaEnter?.(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const textarea = textareaRef.current;
|
const textarea = textareaRef.current;
|
||||||
if (!textarea || !autoResize) return;
|
if (!textarea || !autoResize) return;
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,55 +1,115 @@
|
||||||
import React from 'react';
|
import React, { useMemo, useRef } from 'react';
|
||||||
import { InputTextArea, InputTextAreaProps } from './InputTextArea';
|
import { InputTextArea, InputTextAreaProps } from './InputTextArea';
|
||||||
import { cn } from '@/lib/classMerge';
|
import { cn } from '@/lib/classMerge';
|
||||||
import { cva } from 'class-variance-authority';
|
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', {
|
export interface InputTextAreaButtonProps extends Omit<InputTextAreaProps, 'variant' | 'onSubmit'> {
|
||||||
variants: {}
|
sendIcon?: React.ReactNode;
|
||||||
});
|
loadingIcon?: React.ReactNode;
|
||||||
|
loading?: boolean;
|
||||||
|
onSubmit: (text: string) => void;
|
||||||
|
variant?: 'default';
|
||||||
|
}
|
||||||
|
|
||||||
export const InputTextAreaButton: React.FC<InputTextAreaButtonProps> = ({
|
export const InputTextAreaButton: React.FC<InputTextAreaButtonProps> = ({
|
||||||
className,
|
className,
|
||||||
disabled,
|
disabled,
|
||||||
|
autoResize,
|
||||||
|
sendIcon = <ArrowUp />,
|
||||||
|
loadingIcon = <ShapeSquare />,
|
||||||
|
loading = false,
|
||||||
|
onSubmit,
|
||||||
|
variant = 'default',
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
const textRef = useRef<HTMLTextAreaElement>(null);
|
||||||
<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}
|
|
||||||
|
|
||||||
// ref={inputRef}
|
const onSubmitPreflight = useMemoizedFn(() => {
|
||||||
// variant="borderless"
|
if (disabled) return;
|
||||||
// onBlur={onBlurInput}
|
const text = textRef.current?.value || '';
|
||||||
// onFocus={onFocusInput}
|
if (text.trim() === '') return;
|
||||||
// className="inline-block w-full pt-2! pr-9! pb-2! pl-3.5! align-middle"
|
onSubmit(text);
|
||||||
// placeholder="Ask a follow up..."
|
});
|
||||||
// value={inputValue}
|
|
||||||
// autoFocus={true}
|
const onPressMetaEnter = useMemoizedFn(() => {
|
||||||
// onChange={onChangeInput}
|
onSubmitPreflight();
|
||||||
// onPressEnter={onPressEnter}
|
});
|
||||||
// disabled={loading}
|
|
||||||
// autoSize={autoSize}
|
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">
|
<div className="absolute right-2 bottom-2">
|
||||||
HERE
|
<SubmitButton
|
||||||
{/* <SubmitButton
|
disabled={disabled}
|
||||||
disableSendButton={disableSendButton}
|
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
sendIcon={sendIcon}
|
||||||
|
loadingIcon={loadingIcon}
|
||||||
onSubmitPreflight={onSubmitPreflight}
|
onSubmitPreflight={onSubmitPreflight}
|
||||||
/> */}
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { AppMaterialIcons } from '@/components/ui';
|
import { AppMaterialIcons } from '@/components/ui';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
|
Loading…
Reference in New Issue