Skip to content

Commit 86dda3d

Browse files
fix(ui): checkbox and radio error tooltip alignment (#16439)
Fixes error tooltip positioning for checkbox and radio fields so the caret aligns properly with the input. ### Changes **Checkbox field:** - Move `Error` component inside `CheckboxInput` so tooltip can position relative to the input wrapper - Add `Error` prop to `CheckboxInputProps` interface - Update positioning CSS to use absolute positioning with proper offset **Radio field:** - Wrap `Error` and `Label` in a div for proper tooltip positioning - Add `alignCaret="left"` to `FieldError` for consistent tooltip alignment **Tooltip:** - Adjust left caret position with proper spacing ### Other changes - Add required, disabled, and readOnly field variants to v4 Radio test collection
1 parent 5df759b commit 86dda3d

6 files changed

Lines changed: 110 additions & 19 deletions

File tree

packages/ui/src/elements/Tooltip/index.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373

7474
.tooltip--caret-left {
7575
&::after {
76-
left: 0;
76+
left: var(--spacer-3);
7777
}
7878
}
7979

packages/ui/src/fields/Checkbox/Input.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type CheckboxInputProps = {
1313
readonly BeforeInput?: React.ReactNode
1414
readonly checked?: boolean
1515
readonly className?: string
16+
readonly Error?: React.ReactNode
1617
readonly id?: string
1718
readonly inputRef?: React.RefObject<HTMLInputElement | null>
1819
readonly Label?: React.ReactNode
@@ -34,6 +35,7 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = ({
3435
BeforeInput,
3536
checked,
3637
className,
38+
Error,
3739
inputRef,
3840
Label,
3941
label,
@@ -88,6 +90,7 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = ({
8890
{!checked && partialChecked && <LineIcon />}
8991
</span>
9092
{AfterInput}
93+
{Error}
9194
</div>
9295
<RenderCustomComponent
9396
CustomComponent={Label}

packages/ui/src/fields/Checkbox/index.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
.tooltip:not([aria-hidden='true']) {
66
right: auto;
7-
position: static;
7+
left: calc(-1 * var(--spacer-2));
8+
position: absolute;
89
transform: translateY(calc(var(--caret-size) * -1));
910
margin-bottom: 0.2em;
1011
max-width: fit-content;

packages/ui/src/fields/Checkbox/index.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,18 @@ const CheckboxFieldComponent: CheckboxFieldClientComponent = (props) => {
106106
.join(' ')}
107107
style={styles}
108108
>
109-
<RenderCustomComponent
110-
CustomComponent={Error}
111-
Fallback={
112-
<FieldError alignCaret={isRTL ? 'right' : 'left'} path={path} showError={showError} />
113-
}
114-
/>
115109
<CheckboxInput
116110
AfterInput={AfterInput}
117111
BeforeInput={BeforeInput}
118112
checked={checked}
113+
Error={
114+
<RenderCustomComponent
115+
CustomComponent={Error}
116+
Fallback={
117+
<FieldError alignCaret={isRTL ? 'right' : 'left'} path={path} showError={showError} />
118+
}
119+
/>
120+
}
119121
id={fieldID}
120122
inputRef={null}
121123
Label={Label}

packages/ui/src/fields/RadioGroup/index.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,18 @@ const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => {
8181
.join(' ')}
8282
style={styles}
8383
>
84-
<RenderCustomComponent
85-
CustomComponent={Error}
86-
Fallback={<FieldError path={path} showError={showError} />}
87-
/>
88-
<RenderCustomComponent
89-
CustomComponent={Label}
90-
Fallback={
91-
<FieldLabel label={label} localized={localized} path={path} required={required} />
92-
}
93-
/>
84+
<div>
85+
<RenderCustomComponent
86+
CustomComponent={Error}
87+
Fallback={<FieldError alignCaret="left" path={path} showError={showError} />}
88+
/>
89+
<RenderCustomComponent
90+
CustomComponent={Label}
91+
Fallback={
92+
<FieldLabel label={label} localized={localized} path={path} required={required} />
93+
}
94+
/>
95+
</div>
9496
<div className={`${fieldBaseClass}__wrap`}>
9597
{BeforeInput}
9698
<ul className={`${baseClass}--group`} id={`field-${path.replace(/\./g, '__')}`}>

test/v4/collections/Radio/index.ts

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,95 @@ const RadioFields: CollectionConfig = {
1515
{ label: 'Podcast', value: 'podcast' },
1616
],
1717
},
18+
{
19+
name: 'contentTypeRequired',
20+
type: 'radio',
21+
label: 'Content Type (Required)',
22+
required: true,
23+
options: [
24+
{ label: 'Article', value: 'article' },
25+
{ label: 'Video', value: 'video' },
26+
{ label: 'Podcast', value: 'podcast' },
27+
],
28+
},
29+
{
30+
name: 'contentTypeDisabled',
31+
type: 'radio',
32+
label: 'Content Type (Disabled)',
33+
defaultValue: 'video',
34+
admin: {
35+
disabled: true,
36+
},
37+
options: [
38+
{ label: 'Article', value: 'article' },
39+
{ label: 'Video', value: 'video' },
40+
{ label: 'Podcast', value: 'podcast' },
41+
],
42+
},
43+
{
44+
name: 'contentTypeReadOnly',
45+
type: 'radio',
46+
label: 'Content Type (Read Only)',
47+
defaultValue: 'podcast',
48+
admin: {
49+
readOnly: true,
50+
},
51+
options: [
52+
{ label: 'Article', value: 'article' },
53+
{ label: 'Video', value: 'video' },
54+
{ label: 'Podcast', value: 'podcast' },
55+
],
56+
},
1857
{
1958
name: 'contentTypeVertical',
2059
type: 'radio',
21-
label: 'Content Type',
60+
label: 'Content Type (Vertical)',
61+
admin: {
62+
layout: 'vertical',
63+
},
64+
options: [
65+
{ label: 'Article', value: 'article' },
66+
{ label: 'Video', value: 'video' },
67+
{ label: 'Podcast', value: 'podcast' },
68+
],
69+
},
70+
{
71+
name: 'contentTypeVerticalRequired',
72+
type: 'radio',
73+
label: 'Content Type (Vertical, Required)',
74+
required: true,
75+
admin: {
76+
layout: 'vertical',
77+
},
78+
options: [
79+
{ label: 'Article', value: 'article' },
80+
{ label: 'Video', value: 'video' },
81+
{ label: 'Podcast', value: 'podcast' },
82+
],
83+
},
84+
{
85+
name: 'contentTypeVerticalDisabled',
86+
type: 'radio',
87+
label: 'Content Type (Vertical, Disabled)',
88+
defaultValue: 'article',
89+
admin: {
90+
layout: 'vertical',
91+
disabled: true,
92+
},
93+
options: [
94+
{ label: 'Article', value: 'article' },
95+
{ label: 'Video', value: 'video' },
96+
{ label: 'Podcast', value: 'podcast' },
97+
],
98+
},
99+
{
100+
name: 'contentTypeVerticalReadOnly',
101+
type: 'radio',
102+
label: 'Content Type (Vertical, Read Only)',
103+
defaultValue: 'video',
22104
admin: {
23105
layout: 'vertical',
106+
readOnly: true,
24107
},
25108
options: [
26109
{ label: 'Article', value: 'article' },

0 commit comments

Comments
 (0)