Skip to content

Commit d7870bb

Browse files
authored
fix: remove UI issues in poll related components (#3118)
1 parent 20b4022 commit d7870bb

12 files changed

Lines changed: 396 additions & 262 deletions

File tree

examples/vite/src/index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ body {
3737
min-height: 0;
3838
height: 100%;
3939
width: 100%;
40+
background: var(--str-chat__background-color);
4041
}
4142

4243
/* Fills viewport minus in-flow `.str-chat__system-notification` when present */

src/components/AudioPlayback/styling/PlaybackRateButton.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
padding: var(--button-padding-y-sm) var(--spacing-xs);
1313
background-color: inherit;
1414
border-radius: var(--button-radius-lg);
15-
border: 1px solid var(--control-playback-toggle-border);
15+
border: 1px solid var(--chat-border-on-chat-incoming);
1616
color: var(--control-playback-toggle-text, var(--text-primary));
1717
font: var(--str-chat__metadata-emphasis-text);
1818

src/components/ChatView/styling/ChatView.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
height: 100%;
2121
min-height: 0;
2222
position: relative;
23+
background-color: var(--str-chat__background-color);
2324

2425
.str-chat__chat-view__selector {
2526
display: flex;

src/components/Form/NumericInput.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,10 @@ export const NumericInput = forwardRef<HTMLInputElement, NumericInputProps>(
124124
)}
125125
disabled={disabled || atMin}
126126
onClick={handleDecrement}
127+
size='xs'
127128
variant='secondary'
128129
>
129-
<span aria-hidden className='str-chat__form-numeric-input__stepper-icon'>
130-
<IconMinus />
131-
</span>
130+
<IconMinus className='str-chat__form-numeric-input__stepper-icon' />
132131
</Button>
133132
<input
134133
className='str-chat__form-numeric-input__input'
@@ -151,7 +150,7 @@ export const NumericInput = forwardRef<HTMLInputElement, NumericInputProps>(
151150
)}
152151
disabled={disabled || atMax}
153152
onClick={handleIncrement}
154-
size='sm'
153+
size='xs'
155154
variant='secondary'
156155
>
157156
<IconPlusSmall className='str-chat__form-numeric-input__stepper-icon' />

src/components/Form/TextInput.tsx

Lines changed: 177 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,17 @@ import { IconCheckmark, IconExclamationMark } from '../Icons';
66

77
export type TextInputVariant = 'outline' | 'ghost';
88

9+
/** Where the active field message (error, success, or neutral) sits relative to the bordered control */
10+
export type TextInputFieldMessagePlacement = 'outside' | 'inside';
11+
912
export type TextInputProps = Omit<ComponentProps<'input'>, 'className'> & {
13+
/** Root class name */
14+
className?: string;
15+
/**
16+
* `outside` (default): message below the bordered wrapper.
17+
* `inside`: message under the value row, inside the border (error, success, or neutral).
18+
*/
19+
fieldMessagePlacement?: TextInputFieldMessagePlacement;
1020
/** Optional label above the input */
1121
label?: string;
1222
/** Optional leading content (e.g. icon) inside the input area */
@@ -17,20 +27,109 @@ export type TextInputProps = Omit<ComponentProps<'input'>, 'className'> & {
1727
trailingText?: string;
1828
/** Neutral/helper message below the input (no icon) */
1929
message?: ReactNode;
20-
/** Error message below the input; shown when error is true, with errorMessageIcon */
30+
/** Error message; shown when `error` is true, with `errorMessageIcon` */
2131
errorMessage?: ReactNode;
22-
/** Icon shown before error message (default: IconExclamationMark) */
32+
/** Icon before error text (default: exclamation) */
2333
errorMessageIcon?: ReactNode;
24-
/** Success message below the input; shown when provided, with successMessageIcon */
34+
/** Success message below the input */
2535
successMessage?: ReactNode;
26-
/** Icon shown before success message (default: IconCheckmark) */
36+
/** Icon before success text (default: checkmark) */
2737
successMessageIcon?: ReactNode;
28-
/** When true, shows error border and error message styling */
38+
/** When true, error border and error styling */
2939
error?: boolean;
30-
/** Visual variant: outline = border always visible, ghost = border only on focus */
40+
/** `outline` = border always; `ghost` = border on focus */
3141
variant?: TextInputVariant;
32-
/** Optional class name for the root wrapper */
33-
className?: string;
42+
};
43+
44+
type TextInputIconMessageLineProps = {
45+
icon: ReactNode;
46+
text: ReactNode;
47+
};
48+
49+
const TextInputIconMessageLine = ({ icon, text }: TextInputIconMessageLineProps) => (
50+
<>
51+
<span aria-hidden className='str-chat__form-text-input__message-icon'>
52+
{icon}
53+
</span>
54+
<span className='str-chat__form-text-input__message-text'>{text}</span>
55+
</>
56+
);
57+
58+
/** At most one of error / success / neutral is shown under the field */
59+
type TextInputFieldMessageProps =
60+
| {
61+
kind: 'error';
62+
id?: string;
63+
insidePlacement: boolean;
64+
errorMessageIcon?: ReactNode;
65+
text: ReactNode;
66+
}
67+
| {
68+
kind: 'success';
69+
id?: string;
70+
insidePlacement: boolean;
71+
successMessageIcon?: ReactNode;
72+
text: ReactNode;
73+
}
74+
| {
75+
kind: 'neutral';
76+
id?: string;
77+
insidePlacement: boolean;
78+
text: ReactNode;
79+
};
80+
81+
const TextInputFieldMessage = (props: TextInputFieldMessageProps) => {
82+
if (props.kind === 'neutral') {
83+
return (
84+
<div
85+
className={clsx(
86+
'str-chat__form-text-input__message',
87+
props.insidePlacement &&
88+
'str-chat__form-text-input__message--field-message-inside',
89+
)}
90+
id={props.id}
91+
>
92+
{props.text}
93+
</div>
94+
);
95+
} else if (props.kind === 'success') {
96+
return (
97+
<div
98+
className={clsx(
99+
'str-chat__form-text-input__message',
100+
'str-chat__form-text-input__message--success',
101+
props.insidePlacement &&
102+
'str-chat__form-text-input__message--field-message-inside',
103+
)}
104+
id={props.id}
105+
>
106+
<TextInputIconMessageLine
107+
icon={props.successMessageIcon ?? <IconCheckmark />}
108+
text={props.text}
109+
/>
110+
</div>
111+
);
112+
} else if (props.kind === 'error') {
113+
return (
114+
<div
115+
className={clsx(
116+
'str-chat__form-text-input__message',
117+
'str-chat__form-field-error',
118+
props.insidePlacement &&
119+
'str-chat__form-text-input__message--field-message-inside',
120+
)}
121+
id={props.id}
122+
role='alert'
123+
>
124+
<TextInputIconMessageLine
125+
icon={props.errorMessageIcon ?? <IconExclamationMark />}
126+
text={props.text}
127+
/>
128+
</div>
129+
);
130+
}
131+
132+
return null;
34133
};
35134

36135
export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(function TextInput(
@@ -40,6 +139,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(function T
40139
error = false,
41140
errorMessage,
42141
errorMessageIcon,
142+
fieldMessagePlacement = 'outside',
43143
id: idProp,
44144
label,
45145
leading,
@@ -53,93 +153,99 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(function T
53153
},
54154
ref,
55155
) {
56-
const generatedId = useStableId();
57-
const id = idProp ?? generatedId;
156+
const autoId = useStableId();
157+
const id = idProp ?? autoId;
58158

59-
const displayError = error && (errorMessage != null || message != null);
60-
const displaySuccess = successMessage != null;
61-
const displayNeutralMessage = message != null && !error;
62-
const displayMessage = displayError || displaySuccess || displayNeutralMessage;
159+
const hasError = error && (errorMessage != null || message != null);
160+
const showSuccess = !hasError && successMessage != null;
161+
const showNeutral = !hasError && !showSuccess && message != null;
162+
const hasFeedback = hasError || showSuccess || showNeutral;
163+
const messageInside = fieldMessagePlacement === 'inside' && hasFeedback;
63164

64-
const messageId = displayMessage ? `${id}-message` : undefined;
165+
const messageId = hasError
166+
? `${id}-field-error`
167+
: showSuccess || showNeutral
168+
? `${id}-message`
169+
: undefined;
170+
const describedBy = messageId;
65171

66-
const messageContent = displayError ? (
67-
<>
68-
<span aria-hidden className='str-chat__form-text-input__message-icon'>
69-
{errorMessageIcon ?? <IconExclamationMark />}
70-
</span>
71-
{errorMessage ?? message}
72-
</>
73-
) : displaySuccess ? (
74-
<>
75-
<span aria-hidden className='str-chat__form-text-input__message-icon'>
76-
{successMessageIcon ?? <IconCheckmark />}
77-
</span>
78-
{successMessage}
79-
</>
80-
) : displayNeutralMessage ? (
81-
(message as ReactNode)
172+
const fieldMessage = hasError ? (
173+
<TextInputFieldMessage
174+
errorMessageIcon={errorMessageIcon}
175+
id={messageId}
176+
insidePlacement={messageInside}
177+
kind='error'
178+
text={errorMessage ?? message}
179+
/>
180+
) : showSuccess ? (
181+
<TextInputFieldMessage
182+
id={messageId}
183+
insidePlacement={messageInside}
184+
kind='success'
185+
successMessageIcon={successMessageIcon}
186+
text={successMessage}
187+
/>
188+
) : showNeutral ? (
189+
<TextInputFieldMessage
190+
id={messageId}
191+
insidePlacement={messageInside}
192+
kind='neutral'
193+
text={message}
194+
/>
82195
) : null;
83196

84197
return (
85198
<div
86199
className={clsx(
87200
'str-chat__form-text-input',
88201
error && 'str-chat__form-text-input--error',
89-
displaySuccess && 'str-chat__form-text-input--success',
202+
showSuccess && 'str-chat__form-text-input--success',
90203
disabled && 'str-chat__form-text-input--disabled',
204+
messageInside && 'str-chat__form-text-input--field-message-inside',
91205
className,
92206
)}
93207
>
94-
{!!label && (
208+
{label ? (
95209
<label className='str-chat__form-text-input__label' htmlFor={id}>
96210
{label}
97211
</label>
98-
)}
212+
) : null}
99213
<div
100214
className={clsx(
101215
'str-chat__form-text-input__wrapper',
102216
`str-chat__form-text-input__wrapper--${variant}`,
217+
messageInside && 'str-chat__form-text-input__wrapper--field-message-inside',
103218
)}
104219
>
105-
{!!leading && (
106-
<span aria-hidden className='str-chat__form-text-input__leading'>
107-
{leading}
108-
</span>
109-
)}
110-
<input
111-
aria-describedby={messageId}
112-
aria-invalid={error}
113-
className='str-chat__form-text-input__input'
114-
disabled={disabled}
115-
id={id}
116-
ref={ref}
117-
{...inputProps}
118-
/>
119-
{trailingText != null && (
120-
<span aria-hidden className='str-chat__form-text-input__suffix'>
121-
{trailingText}
122-
</span>
123-
)}
124-
{!!trailing && (
125-
<span aria-hidden className='str-chat__form-text-input__trailing'>
126-
{trailing}
127-
</span>
128-
)}
129-
</div>
130-
{messageContent != null && (
131-
<div
132-
className={clsx(
133-
'str-chat__form-text-input__message',
134-
displayError && 'str-chat__form-field-error',
135-
displaySuccess && 'str-chat__form-text-input__message--success',
136-
)}
137-
id={messageId}
138-
role={error ? 'alert' : undefined}
139-
>
140-
{messageContent}
220+
<div className='str-chat__form-text-input__control-row'>
221+
{leading ? (
222+
<span aria-hidden className='str-chat__form-text-input__leading'>
223+
{leading}
224+
</span>
225+
) : null}
226+
<input
227+
aria-describedby={describedBy}
228+
aria-invalid={error}
229+
className='str-chat__form-text-input__input'
230+
disabled={disabled}
231+
id={id}
232+
ref={ref}
233+
{...inputProps}
234+
/>
235+
{trailingText != null ? (
236+
<span aria-hidden className='str-chat__form-text-input__suffix'>
237+
{trailingText}
238+
</span>
239+
) : null}
240+
{trailing ? (
241+
<span aria-hidden className='str-chat__form-text-input__trailing'>
242+
{trailing}
243+
</span>
244+
) : null}
141245
</div>
142-
)}
246+
{messageInside ? fieldMessage : null}
247+
</div>
248+
{messageInside ? null : fieldMessage}
143249
</div>
144250
);
145251
});

0 commit comments

Comments
 (0)