Forms on ReUI controls: radio-group installed, native checkboxes and radios replaced

Radios (PanelForm) is ReUI's RadioGroup; Check takes rich text; a bare Tick covers
the table-cell ticks and the list-row switch. Settings IPv6 mode, Change Theme and all
Zones modals no longer draw their own native inputs. Matrix and the data-grid's own
selection stay as they are.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
DenozordecandClaude Sonnet 5.5 committed 2026-10-30 15:06:22 +07:00
1 parent 7f60dc9960
commit d2b05a1037
22 files changed
+458 -493

No files matched your search

+13 -2
View File
@@ -25,8 +25,12 @@ export function Check({
help,
disabled,
toggle = false,
text,
}: {
/** The control's accessible name, and the visible text unless `text` replaces it. */
label: string
/** Visible text with markup in it (a link), when `label` alone would lose it. */
text?: ReactNode
checked: boolean
onChange: (v: boolean) => void
help?: ReactNode
@@ -52,8 +56,15 @@ export function Check({
) : (
<Checkbox nativeButton render={<button type="button" />} {...common} />
)}
<span className={styles.text} onClick={() => !disabled && onChange(!checked)}>
{label}
<span
className={styles.text}
onClick={(e) => {
// A link inside the text is followed, not a click on the box.
if (disabled || (e.target as HTMLElement).closest('a')) return
onChange(!checked)
}}
>
{text ?? label}
</span>
</div>
{help && <div className={toggle ? `${styles.help} ${styles.helpSwitch}` : styles.help}>{help}</div>}
+40 -15
View File
@@ -1,4 +1,5 @@
import { createContext, type ReactNode, useContext, useEffect, useRef, useState } from 'react'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import { Alert } from './Alert'
import { Button } from './Button'
import { Empty } from './Empty'
@@ -242,14 +243,21 @@ export function Trailer({ children }: { children: ReactNode }) {
export interface RadioOption {
value: string
label: string
/** The option's text; with markup in it (a link), give `name` as well. */
label: ReactNode
/** The option's accessible name when `label` is not a string. */
name?: string
help?: ReactNode
disabled?: boolean
}
/*
The radio group. It shares its row and its help with `ui/Check`: they are the
same control with different cardinality, and when each screen wrote its own, a
radio's help and a checkbox's help did not land on the same left edge.
The radio group, ReUI's `RadioGroup`. It shares its row and its help with `ui/Check`:
they are the same control with different cardinality, and when each screen wrote
its own, a radio's help and a checkbox's help did not land on the same left edge.
The item carries its own name and the text beside it only forwards the click, as in
`Check`: a wrapping <label> would name the hidden input as well.
*/
export function Radios({
name,
@@ -257,32 +265,49 @@ export function Radios({
options,
onChange,
disabled,
label,
}: {
name: string
value: string
options: RadioOption[]
onChange: (v: string) => void
disabled?: boolean
/** The group's accessible name, when its heading is not a `GroupRow` label. */
label?: string
}) {
return (
<>
<RadioGroup
name={name}
aria-label={label}
value={value}
disabled={disabled}
onValueChange={(v) => onChange(String(v))}
className="gap-0"
>
{options.map((o) => (
<div key={o.value}>
<label className={check.check}>
<input
type="radio"
name={name}
<div className={check.check}>
<RadioGroupItem
value={o.value}
checked={value === o.value}
disabled={disabled}
onChange={() => onChange(o.value)}
nativeButton
render={<button type="button" />}
disabled={o.disabled}
aria-label={o.name ?? (typeof o.label === 'string' ? o.label : o.value)}
/>
<span className={check.text}>{o.label}</span>
</label>
<span
className={check.text}
onClick={(e) => {
if (disabled || o.disabled || (e.target as HTMLElement).closest('a')) return
onChange(o.value)
}}
>
{o.label}
</span>
</div>
{o.help && <div className={check.help}>{o.help}</div>}
</div>
))}
</>
</RadioGroup>
)
}
+38
View File
@@ -0,0 +1,38 @@
import { Checkbox } from '@/components/ui/checkbox'
import { Switch } from '@/components/ui/switch'
/*
The bare control, with no text beside it: a table cell's tick (a row selection, a
permission), or the enable switch of a list row. It is `ui/Check` without the row and
its label, and it is named the same way: through `aria-label`, announced as a
checkbox either way (a switch too), so the tests and the keyboard keep one role.
*/
export function Tick({
label,
checked,
onChange,
disabled,
toggle = false,
indeterminate,
}: {
/** The accessible name: there is no visible text. */
label: string
checked: boolean
onChange: (v: boolean) => void
disabled?: boolean
/** A setting that stays put (a switch), not a selection. */
toggle?: boolean
indeterminate?: boolean
}) {
const common = {
checked,
disabled,
'aria-label': label,
onCheckedChange: (v: boolean) => onChange(v),
}
return toggle ? (
<Switch role="checkbox" nativeButton render={<button type="button" />} {...common} />
) : (
<Checkbox nativeButton render={<button type="button" />} indeterminate={indeterminate} {...common} />
)
}