mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
chore: form patterns cursor rules (#41225)
* chore(studio): integrate cursor rules with form patterns * chore(studio): update FormLayout inner styling * chore(studio): align actions form field to the right * chore(design-system): wrap each form field in a CardContent
This commit is contained in:
1 parent
65a96b6363
commit
754af51312
7 files changed
+237
-117
No files matched your search
+173
-26
@@ -125,63 +125,106 @@ export const MyPageComponent = () => (
|
||||
|
||||
## Forms
|
||||
|
||||
- Build forms with `react-hook-form` + `zod`.
|
||||
- Use our `_Shadcn_` form primitives from `ui` and prefer `FormItemLayout` with layout="flex-row-reverse" for most controls (see `apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx`).
|
||||
- Keep imports from `ui` with `_Shadcn_` suffixes.
|
||||
- Forms should generally be wrapped in a Card unless specified
|
||||
- If the submit button is outside the form, add a new variable named formId outside the component, and set it as property id on the form element and formId on the button.
|
||||
Forms in Supabase Studio should follow consistent patterns to ensure a cohesive user experience across settings pages and side panels.
|
||||
|
||||
### Example (single field)
|
||||
### Core Principles
|
||||
|
||||
- Build forms with `react-hook-form` + `zod`
|
||||
- Always use `FormItemLayout` instead of manually composing `FormItem`, `FormLabel`, `FormMessage`, and `FormDescription`
|
||||
- Always wrap form inputs with `FormControl_Shadcn_` to ensure proper form integration
|
||||
- Keep imports from `ui` with `_Shadcn_` suffixes
|
||||
- Handle dirty state: Show cancel buttons and disable save buttons based on `form.formState.isDirty`
|
||||
- Show loading states on submit buttons using the `loading` prop
|
||||
- If the submit button is outside the form, add a `formId` variable outside the component, set it as `id` on the form element and `form` prop on the button
|
||||
|
||||
### Layout Selection
|
||||
|
||||
- **Page layouts**: Use `FormItemLayout` with `layout="flex-row-reverse"` for horizontal alignment. Forms should be wrapped in a `Card` with each form field in its own `CardContent`, and `CardFooter` for actions. The layout automatically handles consistent input widths (50% on md, 40% on xl, min-w-100).
|
||||
- **Side panels (wide)**: Use `FormItemLayout` with `layout="horizontal"`. Use `SheetSection` to wrap each field group.
|
||||
- **Side panels (narrow, size="sm" or below)**: Use `FormItemLayout` with `layout="vertical"`
|
||||
|
||||
### Page Layout Form Example
|
||||
|
||||
```tsx
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { Button, Form_Shadcn_, FormField_Shadcn_, FormControl_Shadcn_, Input_Shadcn_ } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
Form_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
FormControl_Shadcn_,
|
||||
Input_Shadcn_,
|
||||
Switch,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
const profileSchema = z.object({
|
||||
username: z.string().min(2, 'Username must be at least 2 characters'),
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
enableFeature: z.boolean(),
|
||||
})
|
||||
|
||||
const formId = `profile-form`
|
||||
|
||||
export function ProfileForm() {
|
||||
const form = useForm<z.infer<typeof profileSchema>>({
|
||||
resolver: zodResolver(profileSchema),
|
||||
defaultValues: { username: '' },
|
||||
export function SettingsForm() {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: { name: '', enableFeature: false },
|
||||
mode: 'onSubmit',
|
||||
reValidateMode: 'onBlur',
|
||||
})
|
||||
|
||||
function onSubmit(values: z.infer<typeof profileSchema>) {
|
||||
// handle values
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
// handle mutation with onSuccess/onError toast
|
||||
}
|
||||
|
||||
return (
|
||||
<Form_Shadcn_ {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<Card>
|
||||
<CardContent className="space-y-6">
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="username"
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="flex-row-reverse"
|
||||
label="Username"
|
||||
description="This is your public display name."
|
||||
label="Name"
|
||||
description="A descriptive name for this resource"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_ placeholder="shadcn" autoComplete="off" {...field} />
|
||||
<Input_Shadcn_ {...field} placeholder="Enter name" />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="justify-end">
|
||||
<Button type="primary" htmlType="submit">
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="enableFeature"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="flex-row-reverse"
|
||||
label="Enable Feature"
|
||||
description="Toggle this feature on or off"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="justify-end space-x-2">
|
||||
{form.formState.isDirty && (
|
||||
<Button type="default" onClick={() => form.reset()}>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
<Button type="primary" htmlType="submit" disabled={!form.formState.isDirty}>
|
||||
Submit
|
||||
</Button>
|
||||
</CardFooter>
|
||||
@@ -192,6 +235,106 @@ export function ProfileForm() {
|
||||
}
|
||||
```
|
||||
|
||||
### Side Panel Form Example
|
||||
|
||||
```tsx
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
|
||||
import {
|
||||
Button,
|
||||
Form_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
FormControl_Shadcn_,
|
||||
Input_Shadcn_,
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetSection,
|
||||
SheetTitle,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
})
|
||||
|
||||
const formId = 'sidepanel-form'
|
||||
|
||||
export function CreateResourcePanel() {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: { name: '' },
|
||||
})
|
||||
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
// handle mutation
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetContent size="lg" className="flex flex-col gap-0">
|
||||
<SheetHeader>
|
||||
<SheetTitle>Create Resource</SheetTitle>
|
||||
</SheetHeader>
|
||||
<Form_Shadcn_ {...form}>
|
||||
<form
|
||||
id={formId}
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="overflow-auto flex-grow px-0"
|
||||
>
|
||||
<SheetSection>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout layout="horizontal" label="Name" description="A descriptive name">
|
||||
<FormControl_Shadcn_ className="col-span-6 min-w-100">
|
||||
<Input_Shadcn_ {...field} placeholder="Enter name" />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</SheetSection>
|
||||
</form>
|
||||
</Form_Shadcn_>
|
||||
<SheetFooter>
|
||||
<Button type="default" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" form={formId} htmlType="submit">
|
||||
Create
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Common Form Field Types
|
||||
|
||||
- **Text Input**: `Input_Shadcn_` with `placeholder`
|
||||
- **Password Input**: `Input_Shadcn_` with `type="password"`
|
||||
- **Number Input**: `Input_Shadcn_` with `type="number"` and `onChange={(e) => field.onChange(Number(e.target.value))}`
|
||||
- **Input with Units**: Wrap `Input_Shadcn_` with `PrePostTab` component: `<PrePostTab postTab="MB"><Input_Shadcn_ /></PrePostTab>`
|
||||
- **Textarea**: `Textarea` component with `rows` and `className="resize-none"`
|
||||
- **Switch**: `Switch` with `checked={field.value} onCheckedChange={field.onChange}`
|
||||
- **Checkbox**: `Checkbox_Shadcn_` with label, use multiple for checkbox groups
|
||||
- **Select**: `Select_Shadcn_` with `SelectTrigger_Shadcn_`, `SelectContent_Shadcn_`, `SelectItem_Shadcn_`
|
||||
- **Multi-Select**: Use `MultiSelector` from `ui-patterns/multi-select`
|
||||
- **Radio Group**: `RadioGroupStacked` with `RadioGroupStackedItem` for stacked options with descriptions
|
||||
- **Date Picker**: `Calendar` inside `Popover_Shadcn_` with a trigger button
|
||||
- **Copyable Input**: Use `Input` from `ui-patterns/DataInputs/Input` with `copy` and `readOnly` props
|
||||
- **Field Array**: Use `useFieldArray` from `react-hook-form` for dynamic add/remove fields
|
||||
- **Action Field**: Use `FormItemLayout` without form control, just buttons for navigation or performable actions. Wrap buttons in a div with `justify-end` to align them to the right
|
||||
|
||||
## Cards
|
||||
|
||||
- Use cards when needing to group related pieces of information
|
||||
@@ -203,6 +346,11 @@ export function ProfileForm() {
|
||||
## Sheets
|
||||
|
||||
- Use a sheet when needing to reveal more complicated forms or information relating to an object and context switching away to a new page would be disruptive e.g. we list auth providers, clicking an auth provider opens a sheet with information about that provider and a form to enable, user can close sheet to go back to providers list
|
||||
- Use `SheetContent` with `size="lg"` for forms that need horizontal layout
|
||||
- Use `SheetHeader`, `SheetTitle`, `SheetSection`, and `SheetFooter` for consistent structure
|
||||
- Place submit/cancel buttons in `SheetFooter`
|
||||
- For forms in sheets, use `FormItemLayout` with `layout="horizontal"` for wider panels or `layout="vertical"` for narrow panels (size="sm" or below)
|
||||
- See the Forms section for a complete side panel form example
|
||||
|
||||
## React Query
|
||||
|
||||
@@ -223,7 +371,6 @@ export function ProfileForm() {
|
||||
|
||||
```jsx
|
||||
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from 'ui'
|
||||
|
||||
;<Table>
|
||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
||||
<TableHeader>
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
Separator,
|
||||
Switch,
|
||||
Textarea,
|
||||
} from 'ui'
|
||||
@@ -116,10 +115,10 @@ export default function FormPatternsPageLayout() {
|
||||
</PageSectionMeta>
|
||||
<PageSectionContent>
|
||||
<Form_Shadcn_ {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<Card>
|
||||
<CardContent className="pt-6 space-y-6">
|
||||
{/* Text Input */}
|
||||
{/* Text Input */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="name"
|
||||
@@ -128,7 +127,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="Text Input"
|
||||
description="Single-line text entry for short values"
|
||||
className="[&>div]:md:w-1/2"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_ {...field} placeholder="Enter text" />
|
||||
@@ -136,10 +134,10 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Password Input */}
|
||||
{/* Password Input */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="password"
|
||||
@@ -148,7 +146,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="Password Input"
|
||||
description="Masked input for secure text entry"
|
||||
className="[&>div]:md:w-1/2"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_ {...field} type="password" placeholder="Enter password" />
|
||||
@@ -156,10 +153,10 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Copyable Input */}
|
||||
{/* Copyable Input */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="apiKey"
|
||||
@@ -168,7 +165,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="Copyable Input"
|
||||
description="Read-only input with copy-to-clipboard functionality"
|
||||
className="[&>div]:md:w-1/2 [&>div>div]:md:w-full"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Input
|
||||
@@ -182,10 +178,10 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Number Input */}
|
||||
{/* Number Input */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="maxConnections"
|
||||
@@ -194,7 +190,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="Number Input"
|
||||
description="Numeric input with min/max validation"
|
||||
className="[&>div]:md:w-1/2"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_
|
||||
@@ -208,10 +203,10 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Input with Units */}
|
||||
{/* Input with Units */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="duration"
|
||||
@@ -220,7 +215,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="Input with Units"
|
||||
description="Input with additional unit label"
|
||||
className="[&>div]:md:w-1/2"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<PrePostTab postTab="MB" className="w-full">
|
||||
@@ -230,10 +224,10 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Textarea */}
|
||||
{/* Textarea */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="description"
|
||||
@@ -242,7 +236,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="Textarea"
|
||||
description="Multi-line text input for longer content"
|
||||
className="[&>div]:md:w-1/2"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Textarea
|
||||
@@ -255,10 +248,10 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Icon Upload */}
|
||||
{/* Icon Upload */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="description"
|
||||
@@ -267,7 +260,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="Icon upload"
|
||||
description="For icons, avatars, or small images with preview"
|
||||
className="[&>div]:md:w-1/2"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<div className="flex gap-4 items-center">
|
||||
@@ -322,10 +314,10 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* File Upload */}
|
||||
{/* File Upload */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="description"
|
||||
@@ -334,7 +326,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="File Upload"
|
||||
description="Drag-and-drop or select files for upload"
|
||||
className="[&>div]:md:w-1/2 [&>div>div]:md:w-full"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<div
|
||||
@@ -416,10 +407,10 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Switch */}
|
||||
{/* Switch */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="enableFeature"
|
||||
@@ -435,15 +426,14 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Checkbox */}
|
||||
{/* Checkbox */}
|
||||
<CardContent>
|
||||
<FormItemLayout
|
||||
layout="flex-row-reverse"
|
||||
label="Checkbox"
|
||||
description="Boolean values or multiple selections"
|
||||
className="[&>div]:md:w-1/2"
|
||||
>
|
||||
<div className="w-full flex flex-col gap-4">
|
||||
<FormField_Shadcn_
|
||||
@@ -511,10 +501,10 @@ export default function FormPatternsPageLayout() {
|
||||
/>
|
||||
</div>
|
||||
</FormItemLayout>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Select */}
|
||||
{/* Select */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="region"
|
||||
@@ -523,7 +513,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="Select (Dropdown)"
|
||||
description="Single selection from a list of options"
|
||||
className="[&>div]:md:w-1/2"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Select_Shadcn_ value={field.value} onValueChange={field.onChange}>
|
||||
@@ -546,10 +535,10 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Multi-Select */}
|
||||
{/* Multi-Select */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="schemas"
|
||||
@@ -558,7 +547,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="Multi-Select"
|
||||
description="Multiple selection from a list"
|
||||
className="[&>div]:md:w-1/2 [&>div>div]:md:w-full"
|
||||
>
|
||||
<MultiSelector
|
||||
onValuesChange={field.onChange}
|
||||
@@ -584,10 +572,10 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Radio Group */}
|
||||
{/* Radio Group */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="queueType"
|
||||
@@ -596,7 +584,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="Radio Group"
|
||||
description="Single selection from multiple options"
|
||||
className="[&>div]:md:w-1/2"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<RadioGroupStacked value={field.value} onValueChange={field.onChange}>
|
||||
@@ -615,10 +602,10 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Date Picker */}
|
||||
{/* Date Picker */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="expiryDate"
|
||||
@@ -627,7 +614,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="Date Picker"
|
||||
description="Date selection with calendar popover"
|
||||
className="[&>div]:md:w-1/2"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Popover_Shadcn_>
|
||||
@@ -653,10 +639,10 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Field Array */}
|
||||
{/* Field Array */}
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="redirectUris"
|
||||
@@ -665,7 +651,6 @@ export default function FormPatternsPageLayout() {
|
||||
layout="flex-row-reverse"
|
||||
label="Field Array"
|
||||
description="Dynamic list for adding/removing items"
|
||||
className="[&>div]:md:w-1/2"
|
||||
>
|
||||
<div className="space-y-2 w-full">
|
||||
{fields.map((field, index) => (
|
||||
@@ -704,17 +689,16 @@ export default function FormPatternsPageLayout() {
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
|
||||
|
||||
{/* Action Field */}
|
||||
{/* Action Field */}
|
||||
<CardContent>
|
||||
<FormItemLayout
|
||||
layout="flex-row-reverse"
|
||||
label="Action Field"
|
||||
description="Button or link for navigation or performable actions"
|
||||
className="[&>div]:md:w-1/2"
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
<Button
|
||||
type="default"
|
||||
icon={<ExternalLink size={14} />}
|
||||
|
||||
@@ -370,7 +370,6 @@ export const PerformanceSettingsForm = () => {
|
||||
<div className="relative">
|
||||
<PrePostTab postTab={chosenUnit === 'percent' ? '%' : 'connections'}>
|
||||
<Input_Shadcn_
|
||||
className="w-20"
|
||||
type="number"
|
||||
{...field}
|
||||
min={3}
|
||||
|
||||
+1
-1
@@ -310,7 +310,7 @@ export const ProtectionAuthSettingsForm = () => {
|
||||
label="Prevent use of leaked passwords"
|
||||
description="Rejects the use of known or easy to guess passwords on sign up or password change. "
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Badge variant={field.value ? 'success' : 'default'}>
|
||||
{field.value ? 'Enabled' : 'Disabled'}
|
||||
</Badge>
|
||||
|
||||
+2
-11
@@ -92,11 +92,7 @@ export const OrganizationDetailsForm = () => {
|
||||
render={({ field }) => (
|
||||
<FormItemLayout label="Organization name" layout="flex-row-reverse">
|
||||
<FormControl_Shadcn_>
|
||||
<Input
|
||||
{...field}
|
||||
className="w-full max-w-full md:w-96"
|
||||
disabled={!canUpdateOrganization || isUpdatingDetails}
|
||||
/>
|
||||
<Input {...field} disabled={!canUpdateOrganization || isUpdatingDetails} />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
@@ -110,12 +106,7 @@ export const OrganizationDetailsForm = () => {
|
||||
<CopyButton type="text" iconOnly text={selectedOrganization?.slug ?? ''} />
|
||||
}
|
||||
>
|
||||
<Input
|
||||
disabled
|
||||
className="w-full max-w-full md:w-64"
|
||||
id="slug"
|
||||
value={selectedOrganization?.slug ?? ''}
|
||||
/>
|
||||
<Input disabled id="slug" value={selectedOrganization?.slug ?? ''} />
|
||||
</PrePostTab>
|
||||
</FormItemLayout>
|
||||
</CardContent>
|
||||
|
||||
@@ -147,18 +147,16 @@ export const SecuritySettings = () => {
|
||||
<FormControl_Shadcn_>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled={
|
||||
!isPaidPlan ||
|
||||
!canUpdateMfaConfig ||
|
||||
!hasMFAEnabled ||
|
||||
isUpdatingMfa
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled={
|
||||
!isPaidPlan ||
|
||||
!canUpdateMfaConfig ||
|
||||
!hasMFAEnabled ||
|
||||
isUpdatingMfa
|
||||
}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
{(!canUpdateMfaConfig || !hasMFAEnabled) && (
|
||||
<TooltipContent side="bottom">
|
||||
|
||||
@@ -233,7 +233,8 @@ const FlexContainer = cva('', {
|
||||
},
|
||||
{
|
||||
layout: 'flex-row-reverse',
|
||||
className: 'flex flex-col justify-center items-start md:items-end shrink-0',
|
||||
className:
|
||||
'flex flex-col justify-center items-start md:items-end shrink-0 md:w-1/2 xl:w-2/5 md:min-w-100 [&>div]:md:w-full',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user