Material 3 Expressive実装例 : Button group

Material 3 Expressive の実装例の紹介シリーズです。
今回は新たに追加されたコンポーネントである Button group を紹介します。
(`androidx.compose.material3:material3:1.4.0-alpha15` 時点での内容になります。)

## Button group とは

複数のボタンをグループ化し、ボタンの種類、幅、色を自由に組み合わせることで優先度であったり状態を視覚的に関連付けるコンポーネントです。
Standard と Connected の 2 パターンがあります。
また Expressive では Segmented button が Deprecated になり、置き換え先がこの Button group になっています。

https://m3.material.io/components/button-groups/overview

## 実装

### `ButtonGroup`

標準で `ButtonGroup` が用意されています。
コンテンツ部分には `clickableItem`、`toggleableItem`、`customItem` の関数が用意されています。
標準の `ButtonGroup` ではボタン押下時のインタラクションの適用であったり、画面に入りきらないとき省略する機能があります。

```kotlin
ButtonGroup(
    overflowIndicator = { menuState ->
        FilledIconButton(
            onClick = {
                if (menuState.isExpanded) {
                    menuState.dismiss()
                } else {
                    menuState.show()
                }
            }
        ) {
            Icon(
                painter = painterResource(R.drawable.ic_more),
                contentDescription = "More options"
            )
        }
    },
) {
    clickableItem(
        onClick = {},
        label = "Edit"
    )
    clickableItem(
        onClick = {},
        icon = {
            Icon(
                painter = painterResource(R.drawable.ic_edit),
                contentDescription = null
            )
        },
        label = "Edit"
    )
    toggleableItem(
        checked = isChecked,
        onCheckedChange = { isChecked = it },
        label = "Favorited"
    )
    customItem(
        buttonGroupContent = {
            val interactionSource = remember {  MutableInteractionSource() }
            OutlinedIconButton(
                onClick = {},
                interactionSource = interactionSource,
                modifier = Modifier.animateWidth(interactionSource),
            ) {
                Icon(painterResource(R.drawable.ic_add), contentDescription = "Add")
            }
        },
        menuContent = {
            DropdownMenuItem(
                text = {
                    Text("Add")
                },
                onClick = {},
                leadingIcon = {
                    Icon(painterResource(R.drawable.ic_add), contentDescription = null)
                }
            )
        }
    )
}
```

|標準|Overflow時|
|:--:|:--:|
| ![Screenshot_20250609_155612.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/7d8af995-3d9c-4a46-934c-046ae89cbc7d.png) | ![Screenshot_20250609_155625.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/908987ae-dd4c-4c14-951a-53f117e175ff.png) |

### Connected button group

Connected button group は `ButtonGroup` のような標準の実装は現時点で用意されていないため実装する必要があります。
Connected button group の Shape のみ標準で用意されているのでそれを適用する形になります。

```kotlin
Row(
    horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),
) {
    options.forEachIndexed { index, option ->
        ToggleButton(
            checked = selectedIndex == index,
            onCheckedChange = { selectedIndex = index },
            modifier = Modifier.semantics { role = Role.RadioButton },
            shapes =
                when (index) {
                    0 -> ButtonGroupDefaults.connectedLeadingButtonShapes()
                    options.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes()
                    else -> ButtonGroupDefaults.connectedMiddleButtonShapes()
                }
        ) {
            Icon(
                painter = painterResource(option.icon),
                contentDescription = null
            )
            Spacer(Modifier.size(ToggleButtonDefaults.IconSpacing))
            Text(option.label)
        }
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/1ebecc18-2e64-4703-9141-5849613c0517.png" width="300px" />