fix: input-group don't have the proper validation attributes (#44703)

## Problem

The input groups components introduced in #44282 don't have the
validation attributes when invalid. This hurts accessibility and also
break the design:
<img width="1730" height="324" alt="image"
src="https://github.com/user-attachments/assets/a3fb8d86-f3a8-46bb-aa53-d0599c11f056"
/>

## Solution

This is because the wrapper `<FormControl_Shadcn_>` passes the
validation props to its direct child.
The solution is to avoid applying them on the `<InputGroup>` and to
apply them manually on the inputs.

I also fixed a small accessibility issue by moving the addon texts after
the input so that screen readers announce them in the correct order. No
visual change for this

<img width="587" height="158" alt="image"
src="https://github.com/user-attachments/assets/1f8858ea-6659-45f9-964e-8c43a7fe14ba"
/>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Style**
* Unified numeric input layout by moving unit labels/suffixes (e.g.,
"seconds", "GB", "%", "connections", "digits", "IOPS", "MB/s", "rows")
to appear after their inputs for a consistent, predictable form
appearance.

* **Accessibility**
* Form controls now expose IDs and ARIA attributes from form context
when available, improving screen-reader descriptions and error
association.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
Gildas GarciaandIvan Vasilov authored and GitHub committed 2026-04-10 11:45:30 +10:00
1 parent bf46092290
commit d95fdfd566
23 files changed
+330 -248

No files matched your search

@@ -13,12 +13,12 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
FormInputGroupTextArea,
Input_Shadcn_,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
Popover_Shadcn_,
PopoverContent_Shadcn_,
PopoverTrigger_Shadcn_,
@@ -237,7 +237,13 @@ export default function FormPatternsPageLayout() {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupInput {...field} type="number" min={5} max={30} />
<FormInputGroupInput
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
type="number"
min={5}
max={30}
/>
<InputGroupAddon align="inline-end">
<InputGroupText className="font-mono">MB</InputGroupText>
</InputGroupAddon>
@@ -285,7 +291,7 @@ export default function FormPatternsPageLayout() {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupTextarea
<FormInputGroupTextArea
{...field}
rows={4}
placeholder="Enter multi-line text"
@@ -10,6 +10,7 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
Input_Shadcn_,
InputGroup,
InputGroupAddon,
@@ -250,7 +251,7 @@ export default function FormPatternsSidePanel() {
>
<FormControl_Shadcn_ className="col-span-6">
<InputGroup>
<InputGroupInput {...field} type="number" min={5} max={30} />
<FormInputGroupInput {...field} type="number" min={5} max={30} />
<InputGroupAddon align="inline-end">
<InputGroupText>MB</InputGroupText>
</InputGroupAddon>
@@ -7,10 +7,10 @@ import {
Calendar,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
Input_Shadcn_,
InputGroup,
InputGroupAddon,
InputGroupInput,
Popover_Shadcn_,
PopoverContent_Shadcn_,
PopoverTrigger_Shadcn_,
@@ -229,17 +229,17 @@ const FormField = ({
<FormControl_Shadcn_ className="col-span-6">
{properties.units ? (
<InputGroup>
<InputGroupAddon align="inline-end">
<ReactMarkdown unwrapDisallowed disallowedElements={['p']}>
{properties.units}
</ReactMarkdown>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
id={name}
type="number"
onChange={(e) => field.onChange(Number(e.target.value))}
/>
<InputGroupAddon align="inline-end">
<ReactMarkdown unwrapDisallowed disallowedElements={['p']}>
{properties.units}
</ReactMarkdown>
</InputGroupAddon>
</InputGroup>
) : (
<Input_Shadcn_
@@ -11,11 +11,11 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
Input,
Input_Shadcn_,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
RadioGroupStacked,
RadioGroupStackedItem,
@@ -296,7 +296,7 @@ export const CreateOrUpdateCustomProviderSheet = ({
<InputGroupAddon align="inline-start">
<InputGroupText>custom:</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
placeholder="my-company"
disabled={isEditMode}
@@ -14,10 +14,10 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
Input_Shadcn_,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Select_Shadcn_,
SelectContent_Shadcn_,
@@ -374,16 +374,16 @@ export const MfaAuthSettingsForm = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>factors</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
min={0}
max={30}
{...field}
disabled={!canUpdateConfig}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>factors</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -481,16 +481,16 @@ export const MfaAuthSettingsForm = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>digits</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
min={6}
max={30}
{...field}
disabled={!canUpdateConfig || !hasAccessToMFA}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>digits</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -11,9 +11,9 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Select_Shadcn_,
SelectContent_Shadcn_,
@@ -226,22 +226,22 @@ export const PerformanceSettingsForm = () => {
}
>
<div className="flex flex-col gap-2">
<FormControl_Shadcn_>
<div className="relative">
<div className="relative">
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>seconds</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
min={5}
max={30}
{...field}
disabled={!canUpdateConfig || promptUpgrade}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>seconds</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
</FormControl_Shadcn_>
</FormControl_Shadcn_>
</div>
<p className="text-xs text-right text-foreground-muted">
10+ seconds recommended
@@ -368,16 +368,11 @@ export const PerformanceSettingsForm = () => {
</p>
}
>
<FormControl_Shadcn_>
<div className="flex flex-col gap-2">
<div className="relative">
<div className="flex flex-col gap-2">
<div className="relative">
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>
{chosenUnit === 'percent' ? '%' : 'connections'}
</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
{...field}
min={3}
@@ -388,24 +383,29 @@ export const PerformanceSettingsForm = () => {
}
disabled={!canUpdateConfig || promptUpgrade}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>
{chosenUnit === 'percent' ? '%' : 'connections'}
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
{isLoadingMaxConns ? (
<ShimmeringLoader className="py-2 w-16 ml-auto" />
) : (
<p className="text-xs text-right text-foreground-muted">
<span className="text-foreground-light">
{chosenUnit === 'percent'
? Math.floor(
maxConnectionLimit * (Math.min(100, field.value!) / 100)
).toString()
: Math.min(maxConnectionLimit, field.value!)}
</span>{' '}
/ {maxConnectionLimit}
</p>
)}
</FormControl_Shadcn_>
</div>
</FormControl_Shadcn_>
{isLoadingMaxConns ? (
<ShimmeringLoader className="py-2 w-16 ml-auto" />
) : (
<p className="text-xs text-right text-foreground-muted">
<span className="text-foreground-light">
{chosenUnit === 'percent'
? Math.floor(
maxConnectionLimit * (Math.min(100, field.value!) / 100)
).toString()
: Math.min(maxConnectionLimit, field.value!)}
</span>{' '}
/ {maxConnectionLimit}
</p>
)}
</div>
</FormItemLayout>
)}
/>
@@ -13,9 +13,9 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Switch,
Tooltip,
@@ -238,15 +238,15 @@ export const RateLimits = () => {
<TooltipTrigger asChild>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>emails/h</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
min={0}
{...field}
disabled={!canUpdateConfig || !canUpdateEmailLimit}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>emails/h</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</TooltipTrigger>
@@ -309,15 +309,15 @@ export const RateLimits = () => {
<TooltipTrigger asChild>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>sms/h</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
min={0}
{...field}
disabled={!canUpdateConfig || !canUpdateSMSRateLimit}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>sms/h</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</TooltipTrigger>
@@ -358,15 +358,15 @@ export const RateLimits = () => {
<TooltipTrigger asChild>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>requests/5 min</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
min={0}
{...field}
disabled={!canUpdateConfig}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>requests/5 min</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</TooltipTrigger>
@@ -406,15 +406,15 @@ export const RateLimits = () => {
<TooltipTrigger asChild>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>requests/5 min</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
min={0}
{...field}
disabled={!canUpdateConfig}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>requests/5 min</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</TooltipTrigger>
@@ -454,15 +454,15 @@ export const RateLimits = () => {
<TooltipTrigger asChild>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>requests/h</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
min={0}
{...field}
disabled={!canUpdateConfig || !canUpdateAnonymousUsersRateLimit}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>requests/h</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</TooltipTrigger>
@@ -501,15 +501,15 @@ export const RateLimits = () => {
<TooltipTrigger asChild>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>requests/5 min</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
min={0}
{...field}
disabled={!canUpdateConfig}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>requests/5 min</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</TooltipTrigger>
@@ -549,15 +549,15 @@ export const RateLimits = () => {
<TooltipTrigger asChild>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>requests/5 min</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
min={0}
{...field}
disabled={!canUpdateConfig || !canUpdateWeb3RateLimit}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>requests/5 min</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</TooltipTrigger>
@@ -12,9 +12,9 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Switch,
} from 'ui'
@@ -242,15 +242,15 @@ export const SessionsAuthSettingsForm = () => {
>
<FormControl_Shadcn_ className="w-full">
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>seconds</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
min={0}
{...field}
disabled={!canUpdateConfig}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>seconds</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -329,17 +329,17 @@ export const SessionsAuthSettingsForm = () => {
>
<FormControl_Shadcn_ className="w-full">
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>
<HoursOrNeverText value={field.value || 0} />
</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
min={0}
{...field}
disabled={!canUpdateConfig || !hasUserSessionsEntitlement}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>
<HoursOrNeverText value={field.value || 0} />
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -359,17 +359,17 @@ export const SessionsAuthSettingsForm = () => {
>
<FormControl_Shadcn_ className="w-full">
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>
<HoursOrNeverText value={field.value || 0} />
</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
{...field}
className="flex-1"
disabled={!canUpdateConfig || !hasUserSessionsEntitlement}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>
<HoursOrNeverText value={field.value || 0} />
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -13,10 +13,10 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
Input_Shadcn_,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Switch,
} from 'ui'
@@ -396,15 +396,15 @@ export const SmtpForm = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>seconds</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
value={field.value}
onChange={(e) => field.onChange(Number(e.target.value))}
disabled={!canUpdateConfig}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>seconds</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -8,9 +8,9 @@ import {
Badge,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Select_Shadcn_,
SelectContent_Shadcn_,
@@ -71,16 +71,16 @@ export const AdvancedSettings = ({
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>milliseconds</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
value={field.value ?? ''}
onChange={handleNumberChange(field)}
placeholder="Default: 10000"
/>
<InputGroupAddon align="inline-end">
<InputGroupText>milliseconds</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -105,16 +105,16 @@ export const AdvancedSettings = ({
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>workers</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
value={field.value ?? ''}
onChange={handleNumberChange(field)}
placeholder="Default: 4"
/>
<InputGroupAddon align="inline-end">
<InputGroupText>workers</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -142,16 +142,16 @@ export const AdvancedSettings = ({
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>connections</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
value={field.value ?? ''}
onChange={handleNumberChange(field)}
placeholder="Default: 2"
/>
<InputGroupAddon align="inline-end">
<InputGroupText>connections</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -217,16 +217,16 @@ export const AdvancedSettings = ({
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>connections</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
value={field.value ?? ''}
onChange={handleNumberChange(field)}
placeholder="Default: 4"
/>
<InputGroupAddon align="inline-end">
<InputGroupText>connections</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -260,16 +260,16 @@ export const AdvancedSettings = ({
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>minutes</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
value={field.value ?? ''}
onChange={handleNumberChange(field)}
placeholder="Default: None (No staleness limit)"
/>
<InputGroupAddon align="inline-end">
<InputGroupText>minutes</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -5,9 +5,9 @@ import {
cn,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from 'ui'
import { Admonition } from 'ui-patterns'
@@ -70,10 +70,7 @@ export const AutoScaleFields = ({ form }: AutoScaleFieldProps) => {
>
<FormControl_Shadcn_ className="max-w-20">
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>%</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
value={field.value ?? undefined}
@@ -90,6 +87,9 @@ export const AutoScaleFields = ({ form }: AutoScaleFieldProps) => {
}}
placeholder={String(DISK_AUTOSCALE_CONFIG_DEFAULTS.growthPercent)}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>%</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -116,10 +116,7 @@ export const AutoScaleFields = ({ form }: AutoScaleFieldProps) => {
>
<FormControl_Shadcn_ className="max-w-32">
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>GB</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
value={field.value ?? undefined}
@@ -136,6 +133,9 @@ export const AutoScaleFields = ({ form }: AutoScaleFieldProps) => {
}}
placeholder={String(DISK_AUTOSCALE_CONFIG_DEFAULTS.minIncrementSize)}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>GB</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -157,10 +157,7 @@ export const AutoScaleFields = ({ form }: AutoScaleFieldProps) => {
>
<FormControl_Shadcn_ className="max-w-32">
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>GB</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
value={field.value ?? undefined}
@@ -173,6 +170,9 @@ export const AutoScaleFields = ({ form }: AutoScaleFieldProps) => {
}}
placeholder={String(DISK_AUTOSCALE_CONFIG_DEFAULTS.maxSizeGb)}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>GB</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -6,10 +6,10 @@ import {
Button,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
@@ -100,6 +100,22 @@ export function DiskSizeField({
<FormItemLayout label="Disk Size" layout="vertical" id={field.name}>
<FormControl_Shadcn_ className="max-w-32">
<InputGroup>
<FormInputGroupInput
type="number"
id={field.name}
{...field}
disabled={disableInput || isError}
onWheel={(e) => e.currentTarget.blur()}
onChange={(e) => {
setValue('totalSize', e.target.valueAsNumber, {
shouldDirty: true,
shouldValidate: true,
})
trigger('provisionedIOPS')
trigger('throughput')
}}
min={includedDiskGB}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>GB</InputGroupText>
{isDirty ? (
@@ -118,22 +134,6 @@ export function DiskSizeField({
</InputGroupButton>
) : null}
</InputGroupAddon>
<InputGroupInput
type="number"
id={field.name}
{...field}
disabled={disableInput || isError}
onWheel={(e) => e.currentTarget.blur()}
onChange={(e) => {
setValue('totalSize', e.target.valueAsNumber, {
shouldDirty: true,
shouldValidate: true,
})
trigger('provisionedIOPS')
trigger('throughput')
}}
min={includedDiskGB}
/>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -5,9 +5,9 @@ import {
Button,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
InputGroup,
InputGroupAddon,
InputGroupInput,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
@@ -107,8 +107,7 @@ export function IOPSField({ form, disableInput }: IOPSFieldProps) {
>
<FormControl_Shadcn_ className="max-w-32">
<InputGroup>
<InputGroupAddon align="inline-end">IOPS</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
{...field}
value={field.value}
@@ -120,6 +119,7 @@ export function IOPSField({ form, disableInput }: IOPSFieldProps) {
})
}}
/>
<InputGroupAddon align="inline-end">IOPS</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -6,9 +6,9 @@ import { UseFormReturn } from 'react-hook-form'
import {
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
@@ -120,10 +120,7 @@ export function ThroughputField({ form, disableInput }: ThroughputFieldProps) {
>
<FormControl_Shadcn_ className="max-w-32">
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>MB/s</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
type="number"
{...field}
value={field.value}
@@ -135,6 +132,9 @@ export function ThroughputField({ form, disableInput }: ThroughputFieldProps) {
}}
disabled={disableInput || disableIopsInput || watchedStorageType === 'io2'}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>MB/s</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -35,10 +35,10 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
Input_Shadcn_,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Modal,
} from 'ui'
@@ -329,10 +329,7 @@ const JWTSettings = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>seconds</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
id="JWT_EXP"
type="number"
@@ -344,6 +341,9 @@ const JWTSettings = () => {
)
}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>seconds</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -13,10 +13,10 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
FormMessage_Shadcn_,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Switch,
} from 'ui'
@@ -278,15 +278,15 @@ export const RealtimeSettings = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>connections</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
disabled={!canUpdateConfig}
value={field.value || ''}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>connections</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -314,15 +314,15 @@ export const RealtimeSettings = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>clients</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
disabled={!canUpdateConfig}
value={field.value || ''}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>clients</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -341,15 +341,15 @@ export const RealtimeSettings = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>events/s</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
disabled={!isUsageBillingEnabled || !canUpdateConfig}
value={field.value || ''}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>events/s</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -395,15 +395,15 @@ export const RealtimeSettings = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>events/s</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
disabled={!isUsageBillingEnabled || !canUpdateConfig}
value={field.value || ''}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>events/s</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -449,15 +449,15 @@ export const RealtimeSettings = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>KB</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
disabled={!isUsageBillingEnabled || !canUpdateConfig}
value={field.value || ''}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>KB</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -15,10 +15,10 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
FormItem_Shadcn_,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Skeleton,
Switch,
@@ -611,16 +611,16 @@ export const PostgrestConfig = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>rows</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
size="small"
disabled={!canUpdatePostgrestConfig}
{...field}
type="number"
onChange={(e) => field.onChange(Number(e.target.value))}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>rows</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -641,10 +641,7 @@ export const PostgrestConfig = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>connections</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
size="small"
disabled={!canUpdatePostgrestConfig}
{...field}
@@ -657,6 +654,9 @@ export const PostgrestConfig = () => {
}
value={field.value === null ? '' : field.value}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>connections</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -15,9 +15,9 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Separator,
} from 'ui'
@@ -266,10 +266,7 @@ export const ConnectionPooling = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>connections</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
className="w-full"
@@ -283,6 +280,9 @@ export const ConnectionPooling = () => {
)
}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>connections</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
{!!maxConnData &&
@@ -330,10 +330,7 @@ export const ConnectionPooling = () => {
>
<FormControl_Shadcn_>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>clients</InputGroupText>
</InputGroupAddon>
<InputGroupInput
<FormInputGroupInput
{...field}
type="number"
className="w-full"
@@ -347,6 +344,9 @@ export const ConnectionPooling = () => {
)
}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>clients</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
@@ -15,10 +15,10 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
InfoIcon,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Modal,
WarningIcon,
@@ -195,19 +195,19 @@ const DiskSizeConfigurationModal = ({
layout="vertical"
label="New disk size"
>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>GB</InputGroupText>
</InputGroupAddon>
<FormControl_Shadcn_>
<InputGroupInput
<FormControl_Shadcn_>
<InputGroup>
<FormInputGroupInput
{...field}
id="new-disk-size"
type="number"
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl_Shadcn_>
</InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupText>GB</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
@@ -10,10 +10,10 @@ import {
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormInputGroupInput,
Input_Shadcn_,
InputGroup,
InputGroupAddon,
InputGroupInput,
Select_Shadcn_,
SelectContent_Shadcn_,
SelectItem_Shadcn_,
@@ -256,14 +256,16 @@ export const CreateTableSheet = ({ open, onOpenChange }: CreateTableSheetProps)
control={form.control}
name={`columns.${idx}.name`}
render={({ field }) => (
<FormControl_Shadcn_>
<Input_Shadcn_
{...field}
placeholder="Provide a column name"
disabled={isCreating}
className="h-auto"
/>
</FormControl_Shadcn_>
<FormItemLayout>
<FormControl_Shadcn_>
<Input_Shadcn_
{...field}
placeholder="Provide a column name"
disabled={isCreating}
className="h-auto"
/>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
<FormField_Shadcn_
@@ -308,26 +310,28 @@ export const CreateTableSheet = ({ open, onOpenChange }: CreateTableSheetProps)
key={`columns.${idx}.${x.name}`}
name={`columns.${idx}.${x.name}` as any}
render={({ field }) => (
<InputGroup>
<InputGroupAddon align="inline-start">
{x.name}
</InputGroupAddon>
<FormItemLayout>
<FormControl_Shadcn_>
<InputGroupInput
{...field}
type={x.type === 'number' ? 'number' : 'text'}
disabled={isCreating}
className="h-[34px] rounded-l-none"
onChange={(event) =>
field.onChange(
isNaN(event.target.valueAsNumber)
? null
: event.target.valueAsNumber
)
}
/>
<InputGroup>
<InputGroupAddon align="inline-start">
{x.name}
</InputGroupAddon>
<FormInputGroupInput
{...field}
type={x.type === 'number' ? 'number' : 'text'}
disabled={isCreating}
className="h-[34px] rounded-l-none"
onChange={(event) =>
field.onChange(
isNaN(event.target.valueAsNumber)
? null
: event.target.valueAsNumber
)
}
/>
</InputGroup>
</FormControl_Shadcn_>
</InputGroup>
</FormItemLayout>
)}
/>
))}
+2
View File
@@ -142,6 +142,8 @@ export {
FormMessage as FormMessage_Shadcn_,
FormField as FormField_Shadcn_,
useWatch as useWatch_Shadcn_,
FormInputGroupInput,
FormInputGroupTextArea,
} from './src/components/shadcn/ui/form'
export {
@@ -15,7 +15,10 @@ import {
} from 'react-hook-form'
import { cn } from '../../../lib/utils/cn'
import type { InputProps } from './input'
import { InputGroupInput, InputGroupTextarea } from './input-group'
import { Label } from './label'
import type { TextareaProps } from './textarea'
const Form = FormProvider
@@ -179,6 +182,32 @@ const FormMessage = React.forwardRef<
FormMessage.displayName = 'FormMessage'
function FormInputGroupInput(props: InputProps) {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<InputGroupInput
id={formItemId}
aria-describedby={!error ? formDescriptionId : `${formDescriptionId} ${formMessageId}`}
aria-invalid={!!error}
{...props}
/>
)
}
function FormInputGroupTextArea(props: TextareaProps) {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<InputGroupTextarea
id={formItemId}
aria-describedby={!error ? formDescriptionId : `${formDescriptionId} ${formMessageId}`}
aria-invalid={!!error}
{...props}
/>
)
}
export {
Form,
FormControl,
@@ -188,5 +217,7 @@ export {
FormLabel,
FormMessage,
useFormField,
FormInputGroupInput,
FormInputGroupTextArea,
useWatch,
}
@@ -6,7 +6,39 @@ import { Button } from '../../Button'
import { Input, type InputProps } from './input'
import { Textarea, type TextareaProps } from './textarea'
function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
interface InputGroupProps extends React.ComponentProps<'div'> {
/**
* This props is passed in by <FormControl_Shadcn_> but they should be applied on the input itself. When using
* <InputGroup> inside a <Form_Shadcn_>, use <FormInputGroupInput> and <FormInputGroupTextArea> instead of the
* regular <Input> and <Textarea> components.
*/
id?: string
/**
* This props is passed in by <FormControl_Shadcn_> but they should be applied on the input itself. When using
* <InputGroup> inside a <Form_Shadcn_>, use <FormInputGroupInput> and <FormInputGroupTextArea> instead of the
* regular <Input> and <Textarea> components.
*/
'aria-invalid'?: React.AriaAttributes['aria-invalid']
/**
* This props is passed in by <FormControl_Shadcn_> but they should be applied on the input itself. When using
* <InputGroup> inside a <Form_Shadcn_>, use <FormInputGroupInput> and <FormInputGroupTextArea> instead of the
* regular <Input> and <Textarea> components.
*/
'aria-describedby'?: string
}
/*
* Used to group input elements together with addons like labels, buttons, or text. When using this component
* inside a <Form_Shadcn_>, use FormInputGroupInput and FormInputGroupTextArea instead of the regular Input
* and Textarea components to ensure proper form field association and accessibility.
*/
function InputGroup({
className,
id,
'aria-invalid': ariaInvalid,
'aria-describedby': ariaDescribedby,
...props
}: InputGroupProps) {
return (
<div
data-slot="input-group"
@@ -123,6 +155,9 @@ function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
)
}
/*
* If you need to use this component inside a <Form_Shadcn_>, use FormInputGroupInput instead.
*/
function InputGroupInput({ className, ...props }: InputProps) {
return (
<Input
@@ -136,6 +171,9 @@ function InputGroupInput({ className, ...props }: InputProps) {
)
}
/*
* If you need to use this component inside a <Form_Shadcn_>, use FormInputGroupTextArea instead.
*/
function InputGroupTextarea({ className, ...props }: TextareaProps) {
return (
<Textarea