a11y: improve accessibility on rich text editor options.

This commit is contained in:
Benoit Marty 2025-06-16 16:56:59 +02:00
parent 4f9784c47a
commit 3db179edcf
2 changed files with 38 additions and 4 deletions

View file

@ -13,6 +13,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ripple import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -21,6 +22,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
@ -32,9 +35,10 @@ import io.element.android.libraries.designsystem.theme.iconSuccessPrimaryBackgro
@Composable @Composable
internal fun FormattingOption( internal fun FormattingOption(
state: FormattingOptionState, state: FormattingOptionState,
toggleable: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
imageVector: ImageVector, imageVector: ImageVector,
contentDescription: String?, contentDescription: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val backgroundColor = when (state) { val backgroundColor = when (state) {
@ -52,6 +56,7 @@ internal fun FormattingOption(
modifier = modifier modifier = modifier
.clickable( .clickable(
onClick = onClick, onClick = onClick,
enabled = state != FormattingOptionState.Disabled,
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
indication = ripple( indication = ripple(
bounded = false, bounded = false,
@ -59,6 +64,20 @@ internal fun FormattingOption(
), ),
) )
.size(48.dp) .size(48.dp)
.then(
if (toggleable) {
Modifier.toggleable(
value = state == FormattingOptionState.Selected,
enabled = state != FormattingOptionState.Disabled,
onValueChange = { onClick() },
)
} else {
Modifier
}
)
.clearAndSetSemantics {
this.contentDescription = contentDescription
}
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
@ -84,21 +103,24 @@ internal fun FormattingOptionPreview() = ElementPreview {
Row { Row {
FormattingOption( FormattingOption(
state = FormattingOptionState.Default, state = FormattingOptionState.Default,
toggleable = false,
onClick = { }, onClick = { },
imageVector = CompoundIcons.Bold(), imageVector = CompoundIcons.Bold(),
contentDescription = null, contentDescription = "",
) )
FormattingOption( FormattingOption(
state = FormattingOptionState.Selected, state = FormattingOptionState.Selected,
toggleable = true,
onClick = { }, onClick = { },
imageVector = CompoundIcons.Italic(), imageVector = CompoundIcons.Italic(),
contentDescription = null, contentDescription = "",
) )
FormattingOption( FormattingOption(
state = FormattingOptionState.Disabled, state = FormattingOptionState.Disabled,
toggleable = false,
onClick = { }, onClick = { },
imageVector = CompoundIcons.Underline(), imageVector = CompoundIcons.Underline(),
contentDescription = null, contentDescription = "",
) )
} }
} }

View file

@ -104,24 +104,28 @@ internal fun TextFormatting(
) { ) {
FormattingOption( FormattingOption(
state = state.actions[ComposerAction.BOLD].toButtonState(), state = state.actions[ComposerAction.BOLD].toButtonState(),
toggleable = true,
onClick = { onInlineFormatClick(InlineFormat.Bold) }, onClick = { onInlineFormatClick(InlineFormat.Bold) },
imageVector = CompoundIcons.Bold(), imageVector = CompoundIcons.Bold(),
contentDescription = stringResource(R.string.rich_text_editor_format_bold) contentDescription = stringResource(R.string.rich_text_editor_format_bold)
) )
FormattingOption( FormattingOption(
state = state.actions[ComposerAction.ITALIC].toButtonState(), state = state.actions[ComposerAction.ITALIC].toButtonState(),
toggleable = true,
onClick = { onInlineFormatClick(InlineFormat.Italic) }, onClick = { onInlineFormatClick(InlineFormat.Italic) },
imageVector = CompoundIcons.Italic(), imageVector = CompoundIcons.Italic(),
contentDescription = stringResource(R.string.rich_text_editor_format_italic) contentDescription = stringResource(R.string.rich_text_editor_format_italic)
) )
FormattingOption( FormattingOption(
state = state.actions[ComposerAction.UNDERLINE].toButtonState(), state = state.actions[ComposerAction.UNDERLINE].toButtonState(),
toggleable = true,
onClick = { onInlineFormatClick(InlineFormat.Underline) }, onClick = { onInlineFormatClick(InlineFormat.Underline) },
imageVector = CompoundIcons.Underline(), imageVector = CompoundIcons.Underline(),
contentDescription = stringResource(R.string.rich_text_editor_format_underline) contentDescription = stringResource(R.string.rich_text_editor_format_underline)
) )
FormattingOption( FormattingOption(
state = state.actions[ComposerAction.STRIKE_THROUGH].toButtonState(), state = state.actions[ComposerAction.STRIKE_THROUGH].toButtonState(),
toggleable = true,
onClick = { onInlineFormatClick(InlineFormat.StrikeThrough) }, onClick = { onInlineFormatClick(InlineFormat.StrikeThrough) },
imageVector = CompoundIcons.Strikethrough(), imageVector = CompoundIcons.Strikethrough(),
contentDescription = stringResource(R.string.rich_text_editor_format_strikethrough) contentDescription = stringResource(R.string.rich_text_editor_format_strikethrough)
@ -141,6 +145,7 @@ internal fun TextFormatting(
FormattingOption( FormattingOption(
state = state.actions[ComposerAction.LINK].toButtonState(), state = state.actions[ComposerAction.LINK].toButtonState(),
toggleable = true,
onClick = { linkDialogAction = state.linkAction }, onClick = { linkDialogAction = state.linkAction },
imageVector = CompoundIcons.Link(), imageVector = CompoundIcons.Link(),
contentDescription = stringResource(R.string.rich_text_editor_link) contentDescription = stringResource(R.string.rich_text_editor_link)
@ -148,42 +153,49 @@ internal fun TextFormatting(
FormattingOption( FormattingOption(
state = state.actions[ComposerAction.UNORDERED_LIST].toButtonState(), state = state.actions[ComposerAction.UNORDERED_LIST].toButtonState(),
toggleable = true,
onClick = { onToggleListClick(ordered = false) }, onClick = { onToggleListClick(ordered = false) },
imageVector = CompoundIcons.ListBulleted(), imageVector = CompoundIcons.ListBulleted(),
contentDescription = stringResource(R.string.rich_text_editor_bullet_list) contentDescription = stringResource(R.string.rich_text_editor_bullet_list)
) )
FormattingOption( FormattingOption(
state = state.actions[ComposerAction.ORDERED_LIST].toButtonState(), state = state.actions[ComposerAction.ORDERED_LIST].toButtonState(),
toggleable = true,
onClick = { onToggleListClick(ordered = true) }, onClick = { onToggleListClick(ordered = true) },
imageVector = CompoundIcons.ListNumbered(), imageVector = CompoundIcons.ListNumbered(),
contentDescription = stringResource(R.string.rich_text_editor_numbered_list) contentDescription = stringResource(R.string.rich_text_editor_numbered_list)
) )
FormattingOption( FormattingOption(
state = state.actions[ComposerAction.INDENT].toButtonState(), state = state.actions[ComposerAction.INDENT].toButtonState(),
toggleable = false,
onClick = { onIndentClick() }, onClick = { onIndentClick() },
imageVector = CompoundIcons.IndentIncrease(), imageVector = CompoundIcons.IndentIncrease(),
contentDescription = stringResource(R.string.rich_text_editor_indent) contentDescription = stringResource(R.string.rich_text_editor_indent)
) )
FormattingOption( FormattingOption(
state = state.actions[ComposerAction.UNINDENT].toButtonState(), state = state.actions[ComposerAction.UNINDENT].toButtonState(),
toggleable = false,
onClick = { onUnindentClick() }, onClick = { onUnindentClick() },
imageVector = CompoundIcons.IndentDecrease(), imageVector = CompoundIcons.IndentDecrease(),
contentDescription = stringResource(R.string.rich_text_editor_unindent) contentDescription = stringResource(R.string.rich_text_editor_unindent)
) )
FormattingOption( FormattingOption(
state = state.actions[ComposerAction.INLINE_CODE].toButtonState(), state = state.actions[ComposerAction.INLINE_CODE].toButtonState(),
toggleable = true,
onClick = { onInlineFormatClick(InlineFormat.InlineCode) }, onClick = { onInlineFormatClick(InlineFormat.InlineCode) },
imageVector = CompoundIcons.InlineCode(), imageVector = CompoundIcons.InlineCode(),
contentDescription = stringResource(R.string.rich_text_editor_inline_code) contentDescription = stringResource(R.string.rich_text_editor_inline_code)
) )
FormattingOption( FormattingOption(
state = state.actions[ComposerAction.CODE_BLOCK].toButtonState(), state = state.actions[ComposerAction.CODE_BLOCK].toButtonState(),
toggleable = true,
onClick = { onCodeBlockClick() }, onClick = { onCodeBlockClick() },
imageVector = CompoundIcons.Code(), imageVector = CompoundIcons.Code(),
contentDescription = stringResource(R.string.rich_text_editor_code_block) contentDescription = stringResource(R.string.rich_text_editor_code_block)
) )
FormattingOption( FormattingOption(
state = state.actions[ComposerAction.QUOTE].toButtonState(), state = state.actions[ComposerAction.QUOTE].toButtonState(),
toggleable = true,
onClick = { onQuoteClick() }, onClick = { onQuoteClick() },
imageVector = CompoundIcons.Quote(), imageVector = CompoundIcons.Quote(),
contentDescription = stringResource(R.string.rich_text_editor_quote) contentDescription = stringResource(R.string.rich_text_editor_quote)