mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Fix to ensure labels, descriptions and validation errors are correctly linked to their inputs (#50080)
## Problem `FormItemLayout` does not correctly binds inputs descriptions and validation messages to their inputs. This is because the input ids are generated and not correctly propagated to the `FormMessage` and `FormDescription` components. Besides, we still pass `name` or `id` directly to the inputs or `FormItemLayout` in some places. ## Solution - Fix `FormItemLayout` to correctly binds inputs descriptions and validation messages to their inputs - Fix incorrect usages - Fix Design System documentation ## How to test The issue is visible in production: - Open https://supabase.com/design-system/docs/ui-patterns/forms - Open the devtool and check the labels `for`, the description `id` and the input `id` or `aria-describedby` attributes. You'll see they often don't match Do the same on staging: - Open https://design-system-git-fix-a11y-form-input-descriptions-supabase.vercel.app/design-system/docs/ui-patterns/forms - Open the devtool and check the labels `for`, the description `id` and the input `id` or `aria-describedby` attributes. They now match Dashboard fixes: - https://studio-staging-git-fix-a11y-form-input-descriptions-supabase.vercel.app/dashboard/account/tokens: _Expires in_ select button is now correctly linked to its label - https://studio-staging-git-fix-a11y-form-input-descriptions-supabase.vercel.app/dashboard/account/me: the switches are now correctly linked to their label - In Database/Indexes: the select buttons when creating an index are now correctly linked to their label - All other changes are the same things
This commit is contained in:
1 parent
66a748cdd1
commit
bfb0737d14
11 files changed
+291
-306
No files matched your search
@@ -8,6 +8,7 @@ import {
|
||||
Button,
|
||||
Calendar,
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
@@ -53,11 +54,13 @@ export default function DatePickerForm() {
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Date of birth</FormLabel>
|
||||
<DatePicker>
|
||||
<DatePickerTrigger asChild>
|
||||
<DatePickerButton isInvalid={fieldState.invalid}>
|
||||
{field.value ? format(field.value, 'PPP') : 'Pick a date'}
|
||||
</DatePickerButton>
|
||||
</DatePickerTrigger>
|
||||
<FormControl>
|
||||
<DatePickerTrigger asChild>
|
||||
<DatePickerButton isInvalid={fieldState.invalid}>
|
||||
{field.value ? format(field.value, 'PPP') : 'Pick a date'}
|
||||
</DatePickerButton>
|
||||
</DatePickerTrigger>
|
||||
</FormControl>
|
||||
<DatePickerContent>
|
||||
<Calendar
|
||||
mode="single"
|
||||
|
||||
@@ -613,14 +613,16 @@ export default function FormPatternsPageLayout() {
|
||||
values={field.value}
|
||||
size="small"
|
||||
>
|
||||
<MultiSelectorTrigger
|
||||
mode="inline-combobox"
|
||||
label="Select options..."
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
deletableBadge
|
||||
className="w-full"
|
||||
/>
|
||||
<FormControl>
|
||||
<MultiSelectorTrigger
|
||||
mode="inline-combobox"
|
||||
label="Select options..."
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
deletableBadge
|
||||
className="w-full"
|
||||
/>
|
||||
</FormControl>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="public">public</MultiSelectorItem>
|
||||
@@ -675,23 +677,23 @@ export default function FormPatternsPageLayout() {
|
||||
label="Date Picker"
|
||||
description="Date selection with calendar popover"
|
||||
>
|
||||
<FormControl>
|
||||
<DatePicker>
|
||||
<DatePicker>
|
||||
<FormControl>
|
||||
<DatePickerTrigger asChild>
|
||||
<DatePickerButton block isInvalid={fieldState.invalid}>
|
||||
{field.value ? format(field.value, 'PPP') : 'Pick a date'}
|
||||
</DatePickerButton>
|
||||
</DatePickerTrigger>
|
||||
<DatePickerContent>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
initialFocus
|
||||
/>
|
||||
</DatePickerContent>
|
||||
</DatePicker>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
<DatePickerContent>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
initialFocus
|
||||
/>
|
||||
</DatePickerContent>
|
||||
</DatePicker>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -610,14 +610,16 @@ export default function FormPatternsSidePanel() {
|
||||
size="small"
|
||||
className="w-full"
|
||||
>
|
||||
<MultiSelectorTrigger
|
||||
mode="inline-combobox"
|
||||
label="Select options..."
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
deletableBadge
|
||||
className="w-full"
|
||||
/>
|
||||
<FormControl>
|
||||
<MultiSelectorTrigger
|
||||
mode="inline-combobox"
|
||||
label="Select options..."
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
deletableBadge
|
||||
className="w-full"
|
||||
/>
|
||||
</FormControl>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="public">public</MultiSelectorItem>
|
||||
@@ -677,23 +679,23 @@ export default function FormPatternsSidePanel() {
|
||||
label="Date Picker"
|
||||
description="Date selection with calendar popover"
|
||||
>
|
||||
<FormControl className="col-span-6">
|
||||
<DatePicker>
|
||||
<DatePicker>
|
||||
<FormControl className="col-span-6">
|
||||
<DatePickerTrigger asChild>
|
||||
<DatePickerButton block isInvalid={fieldState.invalid}>
|
||||
{field.value ? format(field.value, 'PPP') : 'Pick a date'}
|
||||
</DatePickerButton>
|
||||
</DatePickerTrigger>
|
||||
<DatePickerContent>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
initialFocus
|
||||
/>
|
||||
</DatePickerContent>
|
||||
</DatePicker>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
<DatePickerContent>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
initialFocus
|
||||
/>
|
||||
</DatePickerContent>
|
||||
</DatePicker>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
+16
-16
@@ -72,25 +72,25 @@ export const TokenDetails = ({ control, setValue }: TokenDetailsProps) => {
|
||||
name="expiresAt"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout id="expiresAt" label="Expires in" layout="flex-row-reverse">
|
||||
<FormItemLayout label="Expires in" layout="flex-row-reverse">
|
||||
<div className="flex gap-2 w-full">
|
||||
<FormControl className="grow">
|
||||
<Select value={field.value} onValueChange={handleExpiryChange}>
|
||||
<SelectTrigger id="expiresAt">
|
||||
<Select value={field.value} onValueChange={handleExpiryChange}>
|
||||
<FormControl className="grow">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select an expiry" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{EXPIRY_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<span className="flex items-center gap-2">
|
||||
{option.label}
|
||||
{option.recommended && <Badge variant="success">Recommended</Badge>}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{EXPIRY_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<span className="flex items-center gap-2">
|
||||
{option.label}
|
||||
{option.recommended && <Badge variant="success">Recommended</Badge>}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{field.value === 'custom' && (
|
||||
<FormField
|
||||
|
||||
@@ -26,15 +26,9 @@ export function DashboardToggle<T extends FieldValues>({
|
||||
control={form.control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="flex-row-reverse"
|
||||
label={label}
|
||||
description={description}
|
||||
id={name}
|
||||
>
|
||||
<FormItemLayout layout="flex-row-reverse" label={label} description={description}>
|
||||
<FormControl>
|
||||
<Switch
|
||||
id={name}
|
||||
checked={field.value}
|
||||
onCheckedChange={(value) => {
|
||||
field.onChange(value)
|
||||
|
||||
@@ -118,8 +118,8 @@ const FormField = ({
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<FormControl>
|
||||
<Popover>
|
||||
<Popover>
|
||||
<FormControl>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -130,18 +130,18 @@ const FormField = ({
|
||||
{field.value ? format(new Date(field.value), 'PPP') : 'Pick a date'}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={(date) => {
|
||||
field.onChange(date?.toISOString())
|
||||
}}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={(date) => {
|
||||
field.onChange(date?.toISOString())
|
||||
}}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
@@ -170,16 +170,9 @@ const FormField = ({
|
||||
>
|
||||
<FormControl className="col-span-6">
|
||||
{properties.isSecret ? (
|
||||
<DataInput
|
||||
{...field}
|
||||
id={name}
|
||||
size="small"
|
||||
copy
|
||||
reveal
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
<DataInput {...field} size="small" copy reveal readOnly={readOnly} />
|
||||
) : (
|
||||
<Input {...field} id={name} readOnly={readOnly} />
|
||||
<Input {...field} readOnly={readOnly} />
|
||||
)}
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
@@ -211,7 +204,6 @@ const FormField = ({
|
||||
<FormControl className="col-span-6">
|
||||
<Textarea
|
||||
{...field}
|
||||
id={name}
|
||||
rows={4}
|
||||
placeholder="Enter multi-line text"
|
||||
className="resize-none"
|
||||
@@ -244,12 +236,11 @@ const FormField = ({
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<FormControl className="col-span-6">
|
||||
{properties.units ? (
|
||||
{properties.units ? (
|
||||
<FormControl className="col-span-6">
|
||||
<InputGroup>
|
||||
<FormInputGroupInput
|
||||
{...field}
|
||||
id={name}
|
||||
type="number"
|
||||
onChange={(e) =>
|
||||
field.onChange(e.target.value === '' ? '' : Number(e.target.value))
|
||||
@@ -262,18 +253,19 @@ const FormField = ({
|
||||
</ReactMarkdown>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
) : (
|
||||
</FormControl>
|
||||
) : (
|
||||
<FormControl className="col-span-6">
|
||||
<Input
|
||||
{...field}
|
||||
id={name}
|
||||
type="number"
|
||||
onChange={(e) =>
|
||||
field.onChange(e.target.value === '' ? '' : Number(e.target.value))
|
||||
}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
)}
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
@@ -310,12 +302,7 @@ const FormField = ({
|
||||
}
|
||||
>
|
||||
<FormControl className="col-span-6">
|
||||
<Switch
|
||||
id={name}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
size="small"
|
||||
/>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} size="small" />
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
@@ -347,33 +334,33 @@ const FormField = ({
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<FormControl className="col-span-6">
|
||||
<Select
|
||||
defaultValue={properties.enum[0]?.value}
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<Select
|
||||
defaultValue={properties.enum[0]?.value}
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<FormControl className="col-span-6">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select an option" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{properties.enum.map((option: Enum) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<span className="flex gap-2 items-center">
|
||||
{option.icon ? (
|
||||
<img
|
||||
alt={`${option.label} icon`}
|
||||
className="h-6 w-6"
|
||||
src={`${BASE_PATH}/img/icons/${option.icon}`}
|
||||
/>
|
||||
) : null}
|
||||
{option.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{properties.enum.map((option: Enum) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<span className="flex gap-2 items-center">
|
||||
{option.icon ? (
|
||||
<img
|
||||
alt={`${option.label} icon`}
|
||||
className="h-6 w-6"
|
||||
src={`${BASE_PATH}/img/icons/${option.icon}`}
|
||||
/>
|
||||
) : null}
|
||||
{option.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -201,16 +201,15 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp
|
||||
control={form.control}
|
||||
name="schema"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout layout="horizontal" label="Select a schema" id="schema">
|
||||
<FormControl className="col-span-6">
|
||||
<Popover
|
||||
modal={false}
|
||||
open={schemaDropdownOpen}
|
||||
onOpenChange={setSchemaDropdownOpen}
|
||||
>
|
||||
<FormItemLayout layout="horizontal" label="Select a schema">
|
||||
<Popover
|
||||
modal={false}
|
||||
open={schemaDropdownOpen}
|
||||
onOpenChange={setSchemaDropdownOpen}
|
||||
>
|
||||
<FormControl className="col-span-6">
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="schema"
|
||||
variant="default"
|
||||
size={'medium'}
|
||||
className={`w-full [&>span]:w-full text-left`}
|
||||
@@ -225,56 +224,56 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp
|
||||
{field.value || 'Choose a schema'}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
side="bottom"
|
||||
align="start"
|
||||
sameWidthAsTrigger
|
||||
>
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Find schema..."
|
||||
value={schemaSearchTerm}
|
||||
onValueChange={setSchemaSearchTerm}
|
||||
/>
|
||||
<CommandList
|
||||
className={cn(
|
||||
(schemas ?? []).length > 7 && 'max-h-[210px]! overflow-y-auto'
|
||||
)}
|
||||
onWheel={(event) => event.stopPropagation()}
|
||||
>
|
||||
<CommandEmpty>No schemas found</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{(schemas ?? []).map((schema) => (
|
||||
<CommandItem
|
||||
key={schema.name}
|
||||
value={schema.name}
|
||||
className="cursor-pointer flex items-center space-x-2 w-full"
|
||||
onSelect={() => {
|
||||
field.onChange(schema.name)
|
||||
form.setValue('table', '')
|
||||
form.setValue('columns', [])
|
||||
form.setValue('type', INDEX_TYPES[0].value)
|
||||
setSearchTerm('')
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
'text-brand',
|
||||
schema.name === field.value ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
strokeWidth={2}
|
||||
size={16}
|
||||
/>
|
||||
<span>{schema.name}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
side="bottom"
|
||||
align="start"
|
||||
sameWidthAsTrigger
|
||||
>
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Find schema..."
|
||||
value={schemaSearchTerm}
|
||||
onValueChange={setSchemaSearchTerm}
|
||||
/>
|
||||
<CommandList
|
||||
className={cn(
|
||||
(schemas ?? []).length > 7 && 'max-h-[210px]! overflow-y-auto'
|
||||
)}
|
||||
onWheel={(event) => event.stopPropagation()}
|
||||
>
|
||||
<CommandEmpty>No schemas found</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{(schemas ?? []).map((schema) => (
|
||||
<CommandItem
|
||||
key={schema.name}
|
||||
value={schema.name}
|
||||
className="cursor-pointer flex items-center space-x-2 w-full"
|
||||
onSelect={() => {
|
||||
field.onChange(schema.name)
|
||||
form.setValue('table', '')
|
||||
form.setValue('columns', [])
|
||||
form.setValue('type', INDEX_TYPES[0].value)
|
||||
setSearchTerm('')
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
'text-brand',
|
||||
schema.name === field.value ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
strokeWidth={2}
|
||||
size={16}
|
||||
/>
|
||||
<span>{schema.name}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
@@ -290,25 +289,23 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp
|
||||
<FormItemLayout
|
||||
layout="horizontal"
|
||||
label="Select a table"
|
||||
id="table"
|
||||
description={
|
||||
isSelectEntityDisabled &&
|
||||
!isLoadingEntities &&
|
||||
'Create a table in this schema via the Table or SQL editor first'
|
||||
}
|
||||
>
|
||||
<FormControl className="col-span-6">
|
||||
<Popover
|
||||
modal={false}
|
||||
open={tableDropdownOpen}
|
||||
onOpenChange={setTableDropdownOpen}
|
||||
>
|
||||
<Popover
|
||||
modal={false}
|
||||
open={tableDropdownOpen}
|
||||
onOpenChange={setTableDropdownOpen}
|
||||
>
|
||||
<FormControl className="col-span-6">
|
||||
<PopoverTrigger
|
||||
asChild
|
||||
disabled={isSelectEntityDisabled || isLoadingEntities}
|
||||
>
|
||||
<Button
|
||||
id="table"
|
||||
variant="default"
|
||||
size="medium"
|
||||
className={cn(
|
||||
@@ -330,65 +327,65 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp
|
||||
: 'Choose a table'}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
side="bottom"
|
||||
align="start"
|
||||
sameWidthAsTrigger
|
||||
>
|
||||
{/* [Terry] shouldFilter context:
|
||||
</FormControl>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
side="bottom"
|
||||
align="start"
|
||||
sameWidthAsTrigger
|
||||
>
|
||||
{/* [Terry] shouldFilter context:
|
||||
https://github.com/pacocoursey/cmdk/issues/267#issuecomment-2252717107 */}
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Find table..."
|
||||
value={searchTerm}
|
||||
onValueChange={handleSearchChange}
|
||||
/>
|
||||
<CommandList
|
||||
className={cn(
|
||||
entityTypes.length > 7 && 'max-h-[210px]! overflow-y-auto'
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Find table..."
|
||||
value={searchTerm}
|
||||
onValueChange={handleSearchChange}
|
||||
/>
|
||||
<CommandList
|
||||
className={cn(
|
||||
entityTypes.length > 7 && 'max-h-[210px]! overflow-y-auto'
|
||||
)}
|
||||
onWheel={(event) => event.stopPropagation()}
|
||||
>
|
||||
<CommandEmpty>
|
||||
{isLoadingEntities ? (
|
||||
<div className="flex items-center gap-2 text-center justify-center">
|
||||
<Loader2 size={12} className="animate-spin" />
|
||||
Loading...
|
||||
</div>
|
||||
) : (
|
||||
'No tables found'
|
||||
)}
|
||||
onWheel={(event) => event.stopPropagation()}
|
||||
>
|
||||
<CommandEmpty>
|
||||
{isLoadingEntities ? (
|
||||
<div className="flex items-center gap-2 text-center justify-center">
|
||||
<Loader2 size={12} className="animate-spin" />
|
||||
Loading...
|
||||
</div>
|
||||
) : (
|
||||
'No tables found'
|
||||
)}
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{entityTypes.map((entity) => (
|
||||
<CommandItem
|
||||
key={entity.name}
|
||||
className="cursor-pointer flex items-center space-x-2 w-full"
|
||||
onSelect={() => {
|
||||
field.onChange(entity.name)
|
||||
setTableDropdownOpen(false)
|
||||
form.setValue('columns', [])
|
||||
form.setValue('type', INDEX_TYPES[0].value)
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
'text-brand',
|
||||
entity.name === field.value ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
strokeWidth={2}
|
||||
size={16}
|
||||
/>
|
||||
<span>{entity.name}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</FormControl>
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{entityTypes.map((entity) => (
|
||||
<CommandItem
|
||||
key={entity.name}
|
||||
className="cursor-pointer flex items-center space-x-2 w-full"
|
||||
onSelect={() => {
|
||||
field.onChange(entity.name)
|
||||
setTableDropdownOpen(false)
|
||||
form.setValue('columns', [])
|
||||
form.setValue('type', INDEX_TYPES[0].value)
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
'text-brand',
|
||||
entity.name === field.value ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
strokeWidth={2}
|
||||
size={16}
|
||||
/>
|
||||
<span>{entity.name}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -124,7 +124,7 @@ export const ColumnType = ({
|
||||
className={className}
|
||||
>
|
||||
<FormControl>
|
||||
<Input {...field} id={name} disabled readOnly />
|
||||
<Input {...field} disabled readOnly />
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)
|
||||
@@ -135,29 +135,31 @@ export const ColumnType = ({
|
||||
Type
|
||||
</FormLabel>
|
||||
<Popover modal open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
role="combobox"
|
||||
size={'small'}
|
||||
aria-expanded={open}
|
||||
aria-controls={listboxId}
|
||||
className={cn(
|
||||
'w-full justify-between bg-background-control',
|
||||
!field.value && 'text-foreground-lighter'
|
||||
)}
|
||||
iconRight={<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />}
|
||||
title={field.value && field.value.replaceAll('"', '')}
|
||||
>
|
||||
{field.value ? (
|
||||
<div className="flex gap-2 items-center">
|
||||
<span>{inferIcon(getOptionByName(field.value)?.type ?? '')}</span>
|
||||
<span className="block truncate">{field.value.replaceAll('"', '')}</span>
|
||||
</div>
|
||||
) : (
|
||||
'Choose a column type...'
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<FormControl>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
role="combobox"
|
||||
size={'small'}
|
||||
aria-expanded={open}
|
||||
aria-controls={listboxId}
|
||||
className={cn(
|
||||
'w-full justify-between bg-background-control',
|
||||
!field.value && 'text-foreground-lighter'
|
||||
)}
|
||||
iconRight={<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />}
|
||||
title={field.value && field.value.replaceAll('"', '')}
|
||||
>
|
||||
{field.value ? (
|
||||
<div className="flex gap-2 items-center">
|
||||
<span>{inferIcon(getOptionByName(field.value)?.type ?? '')}</span>
|
||||
<span className="block truncate">{field.value.replaceAll('"', '')}</span>
|
||||
</div>
|
||||
) : (
|
||||
'Choose a column type...'
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</FormControl>
|
||||
<PopoverContent id={listboxId} className="w-[460px] p-0" side="bottom" align="center">
|
||||
<Command>
|
||||
<CommandInput
|
||||
|
||||
@@ -58,36 +58,35 @@ export const OrganizationSelector = ({
|
||||
control={form.control}
|
||||
name="organization"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout id="organization" label="Organization" layout="horizontal">
|
||||
<FormItemLayout label="Organization" layout="horizontal">
|
||||
{(organizations?.length ?? 0) > 0 && (
|
||||
<FormControl>
|
||||
<Select
|
||||
name="organization"
|
||||
onValueChange={(slug) => {
|
||||
field.onChange(slug)
|
||||
router.push(`/new/${slug}`)
|
||||
}}
|
||||
value={field.value}
|
||||
defaultValue={field.value}
|
||||
disabled={disableOrganizationSelection}
|
||||
>
|
||||
<SelectTrigger id="organization">
|
||||
<Select
|
||||
onValueChange={(slug) => {
|
||||
field.onChange(slug)
|
||||
router.push(`/new/${slug}`)
|
||||
}}
|
||||
value={field.value}
|
||||
defaultValue={field.value}
|
||||
disabled={disableOrganizationSelection}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select an organization" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{organizations?.map((x) => (
|
||||
<SelectItem key={x.id} value={x.slug}>
|
||||
<div className="flex justify-between items-center gap-2 w-full">
|
||||
<span>{x.name}</span>
|
||||
<Badge className="mt-px">{x.plan.name}</Badge>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{organizations?.map((x) => (
|
||||
<SelectItem key={x.id} value={x.slug}>
|
||||
<div className="flex justify-between items-center gap-2 w-full">
|
||||
<span>{x.name}</span>
|
||||
<Badge className="mt-px">{x.plan.name}</Badge>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</FormItemLayout>
|
||||
)}
|
||||
|
||||
@@ -16,9 +16,9 @@ export const ProjectNameInput = ({ form }: ProjectNameInputProps) => {
|
||||
control={form.control}
|
||||
name="projectName"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout id="projectName" label="Project name" layout="horizontal">
|
||||
<FormItemLayout label="Project name" layout="horizontal">
|
||||
<FormControl>
|
||||
<Input {...field} id="projectName" placeholder="Project name" />
|
||||
<Input {...field} placeholder="Project name" />
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
|
||||
@@ -333,7 +333,6 @@ export const FormLayout = React.forwardRef<
|
||||
<FormDescription
|
||||
className={cn(DescriptionVariants({ size, layout }))}
|
||||
data-formlayout-id={'description'}
|
||||
id={`${id}-description`}
|
||||
>
|
||||
{description}
|
||||
</FormDescription>
|
||||
|
||||
Reference in new issue
Block a user