feat: Use expanding text area in the Ai Assistant (SQL Editor) (#27930)

* Add a new textarea component which autoexpands depending on number of content rows.

* Use the new ExpandingTextArea in the AIAssistantPanel.

* Remove old unused text area.

* Move around some of the props in the ExpandingTextArea.

* Make the value prop mandatory.

* Fix minor stuff in the Design System app.

* Add examples for Expanding Text Area.

* add max height to the expanding text area.

* Add todo comment in database-new app.
This commit is contained in:
Ivan Vasilov authored and GitHub committed 2024-07-12 11:32:20 +02:00
1 parent fe4f3fc30c
commit 22c5324f0c
17 files changed
+182 -95

No files matched your search

+11
View File
@@ -1864,5 +1864,16 @@ export const Index: Record<string, any> = {
subcategory: "undefined",
chunks: []
},
"expanding-textarea-demo": {
name: "expanding-textarea-demo",
type: "docs:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/expanding-textarea-demo")),
source: "",
files: ["registry/default/example/expanding-textarea-demo.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
},
}
+1 -1
View File
@@ -6,7 +6,7 @@ import { ThemeProvider } from './Providers'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
title: 'Supabase Design System',
description: 'Generated by create next app',
}
+5
View File
@@ -374,6 +374,11 @@ export const docsConfig: DocsConfig = {
href: '/docs/components/tree-view',
items: [],
},
{
title: 'Expanding Textarea',
href: '/docs/components/expanding-textarea',
items: [],
},
],
},
],
@@ -0,0 +1,25 @@
---
title: Expanding Textarea
description: Displays a textarea which autoexpands (or shrinks) based on its content.
component: true
source:
shadcn: true
---
<ComponentPreview name="expanding-textarea-demo" peekCode wide />
## Usage
```tsx
import { ExpandingTextArea } from 'ui'
```
```tsx
<ExpandingTextArea />
```
## Examples
### Default
<ComponentPreview name="expanding-textarea-demo" peekCode wide />
+4 -2
View File
@@ -1,5 +1,7 @@
import { withContentlayer } from 'next-contentlayer2'
const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH || '/design-system'
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ['ui', 'common', 'shared-data', 'icons', 'tsconfig'],
@@ -22,11 +24,11 @@ const nextConfig = {
},
async redirects() {
return [
...(process.env.NEXT_PUBLIC_BASE_PATH?.length
...(BASE_PATH.length
? [
{
source: '/',
destination: process.env.NEXT_PUBLIC_BASE_PATH,
destination: BASE_PATH,
basePath: false,
permanent: false,
},
@@ -0,0 +1,16 @@
import { useState } from 'react'
import { ExpandingTextArea } from 'ui'
export default function ExpandingTextareaDemo() {
const [value, setValue] = useState('')
return (
<ExpandingTextArea
placeholder="Type your message in multiple lines here."
value={value}
onChange={(event) => {
setValue(event.target.value)
}}
/>
)
}
+5
View File
@@ -1065,4 +1065,9 @@ export const examples: Registry = [
type: 'docs:example',
files: ['example/color-usage-surface-studio-frame.tsx'],
},
{
name: 'expanding-textarea-demo',
type: 'docs:example',
files: ['example/expanding-textarea-demo.tsx'],
},
]
@@ -15,12 +15,12 @@ import { IS_PLATFORM, LOCAL_STORAGE_KEYS, OPT_IN_TAGS } from 'lib/constants'
import { useProfile } from 'lib/profile'
import { useAppStateSnapshot } from 'state/app-state'
import {
ExpandingTextArea,
Button,
FormControl_Shadcn_,
FormField_Shadcn_,
FormItem_Shadcn_,
Form_Shadcn_,
Input_Shadcn_,
cn,
} from 'ui'
import { AiIcon } from 'ui-patterns/Cmdk'
@@ -86,6 +86,8 @@ export const AiAssistantPanel = ({
}
}, [loading])
const formRef = useRef<HTMLFormElement>(null)
return (
<div className="flex flex-col h-full min-w-[400px] w-[400px] border-l border-control">
<div
@@ -150,6 +152,7 @@ export const AiAssistantPanel = ({
<Form_Shadcn_ {...form}>
<form
ref={formRef}
className="sticky p-5 flex-0 border-t"
onSubmit={form.handleSubmit((data: z.infer<typeof FormSchema>) => {
onSubmit(data.chat)
@@ -168,18 +171,23 @@ export const AiAssistantPanel = ({
control={form.control}
name="chat"
render={({ field }) => (
<FormItem_Shadcn_>
<FormItem_Shadcn_ asChild>
<FormControl_Shadcn_>
<div className="relative">
<AiIcon className="absolute top-2 left-3 [&>div>div]:border-black dark:[&>div>div]:border-white" />
<Input_Shadcn_
<ExpandingTextArea
{...field}
autoComplete="off"
disabled={loading}
autoFocus
className={`bg-surface-300 dark:bg-black rounded-full pl-10 ${
loading ? 'pr-10' : ''
}`}
spellCheck={false}
className="pl-12 text-sm rounded-[18px] max-h-96"
onKeyDown={(event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault()
formRef?.current?.requestSubmit()
}
}}
placeholder="Ask a question about your SQL query"
/>
{loading && <Loader2 className="absolute top-2 right-3 animate-spin" />}
@@ -95,6 +95,7 @@ const AssistantChatForm = React.forwardRef<HTMLFormElement, FormProps>(
{icon}
</div>
)}
{/* TODO: Replace this with ExpandingTextArea */}
<TextArea
ref={textAreaRef}
autoFocus
@@ -0,0 +1,53 @@
'use client'
import React, { createRef, useEffect } from 'react'
import { TextArea } from 'ui/src/components/shadcn/ui/text-area'
import { cn } from 'ui/src/lib/utils'
export interface ExpandingTextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const ExpandingTextArea = ({
className,
disabled = false,
value,
onSubmit,
placeholder,
...props
}: ExpandingTextAreaProps) => {
const textAreaRef = createRef<HTMLTextAreaElement>()
/**
* This effect is used to resize the textarea based on the content
*/
useEffect(() => {
if (textAreaRef) {
if (!value && textAreaRef && textAreaRef.current) {
textAreaRef.current.style.height = '40px'
} else if (textAreaRef && textAreaRef.current) {
const newHeight = textAreaRef.current.scrollHeight + 'px'
textAreaRef.current.style.height = newHeight
}
}
}, [value, textAreaRef])
return (
<TextArea
ref={textAreaRef}
rows={1}
contentEditable
aria-expanded={false}
className={cn(
'transition-all text-sm pr-10 rounded-[18px] resize-none box-border leading-6',
className
)}
placeholder={placeholder}
spellCheck={false}
value={value}
{...props}
/>
)
}
ExpandingTextArea.displayName = 'AssistantChatTextArea'
export { ExpandingTextArea }
+1 -4
View File
@@ -56,6 +56,7 @@ export * from './src/components/Radio'
export * from './src/components/Toggle'
export * from './src/components/Upload'
export * from './src/components/Form'
export * from './src/components/ExpandingTextArea'
// layout
export * from './src/components/LoadingLine'
@@ -257,10 +258,6 @@ export * from './src/layout/banners'
// export { default as Config } from './../ui.config'
// ARCHIVE
// export * from './src/components/Textarea'
// AUTH
// export * from './src/components/Auth'
@@ -0,0 +1,46 @@
'use client'
import React, { createRef, useEffect } from 'react'
import { cn } from '../../lib/utils'
import { TextArea } from '../shadcn/ui/text-area'
export interface ExpandingTextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
/* The value of the textarea. Required to calculate the height of the textarea. */
value: string
}
/**
* This is a custom TextArea component that expands based on the content.
*/
const ExpandingTextArea = ({ className, value, ...props }: ExpandingTextAreaProps) => {
const textAreaRef = createRef<HTMLTextAreaElement>()
/**
* This effect is used to resize the textarea based on the content
*/
useEffect(() => {
if (textAreaRef) {
if (!value && textAreaRef && textAreaRef.current) {
textAreaRef.current.style.height = '40px'
} else if (textAreaRef && textAreaRef.current) {
const newHeight = textAreaRef.current.scrollHeight + 'px'
textAreaRef.current.style.height = newHeight
}
}
}, [value, textAreaRef])
return (
<TextArea
ref={textAreaRef}
rows={1}
aria-expanded={false}
className={cn('transition-all resize-none leading-6 box-border', className)}
value={value}
{...props}
/>
)
}
ExpandingTextArea.displayName = 'ExpandingTextArea'
export { ExpandingTextArea }
@@ -1,11 +0,0 @@
.form-textarea:disabled {
@apply bg-gray-100 cursor-not-allowed;
}
.form-textarea--error {
@apply border-red-500;
}
.form-textarea--borderless {
@apply border-none;
}
@@ -1,41 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import './Textarea.css'
/**
* @deprecated Use ./TextArea_Shadcn_ instead
*/
const Textarea = ({
className = '',
isError = false,
rows = 3,
children,
borderless = false,
...props
}) => {
const classes = []
if (isError) {
classes.push('form-textarea--error')
}
if (borderless) {
classes.push('form-textarea--borderless')
}
return (
<textarea
className={`form-textarea border-solid ${classes.join(' ')} ${className}`}
rows={rows}
{...props}
/>
)
}
Textarea.propTypes = {
isError: PropTypes.bool,
className: PropTypes.string,
borderless: PropTypes.bool,
}
export default Textarea
@@ -1,15 +0,0 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import Textarea from './Textarea'
describe('#Textarea', () => {
it('should render textarea correctly', async () => {
render(<Textarea data-testid="form-textarea" />)
expect(screen.queryByTestId('form-textarea')).toBeInTheDocument()
})
it('should have "form-textarea--error" class', () => {
render(<Textarea isError data-testid="form-textarea" />)
expect(screen.queryByTestId('form-textarea')).toHaveClass('form-textarea border-solid form-textarea--error')
})
})
@@ -1,14 +0,0 @@
import React from 'react'
import { Textarea } from '.'
export default {
title: 'Archive/Textarea',
component: Textarea,
}
export const Default = (args) => <Textarea {...args} />
Default.args = {
placeholder: 'Type text here ...',
disabled: false
}
@@ -1 +0,0 @@
export { default as Textarea } from './Textarea'