Новый общий компонент
Требует зависимость androidx.compose.foundation.
Создаёт переиспользуемый Compose-компонент проекта. Замени {Component} на имя компонента
(например, SharedCard, BarcodeFrameBox), а {package} на целевой пакет.
Фаза 1: разместить файл компонента
Один файл на компонент — {Component}.kt. Размещай его прямо в shared/ui/components, если это
универсальный layout-примитив или обёртка (как SharedRow, SharedColumn); для компонента
конкретной предметной области создавай тематическую подпапку, например shared/ui/components/scan.
Фаза 2: выбери форму компонента
- Универсальный layout-примитив или обёртка, не завязанная на конкретную предметную область (как
SharedRow, SharedColumn) — без State, обычные параметры.
- Компонент, который не такой общий, а просто вынесен в отдельный файл и представляет конкретный
предметный смысл (иконка-пресет, карточка определённого содержимого и т. д.) — с
{Component}State наверху файла.
Универсальный примитив (без State)
package {package}
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun {Component}(
title: String,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
// component content
}
@Preview(showBackground = true)
@Composable
private fun {Component}Preview() {
{Component}(
title = "Example",
)
}
Компонент с State
Наверху файла размести {Component}State — data class, если данные варьируются свободно, или
sealed class с несколькими data object-вариантами, если у компонента всего пара
фиксированных пресетов. Сам компонент принимает один параметр state; modifier добавляй вторым
параметром, только если он реально нужен.
package {package}
// Добавь все необходимые импорты
sealed class {Component}State(
val iconRes: Int,
val iconModifier: Modifier
) {
data object SampleA: {Component}State(
iconRes = AppIcons.SampleA,
iconModifier = Modifier.size(107.dp, 131.dp)
)
data object SampleB: {Component}State(
iconRes = AppIcons.SampleB,
iconModifier = Modifier.size(164.dp, 164.dp)
)
}
@Composable
fun {Component}(
state: {Component}State
) {
Box(
modifier = Modifier
.size(192.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.surface),
contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(state.iconRes),
contentDescription = null,
modifier = state.iconModifier,
tint = Color.Unspecified
)
}
}
@PreviewWrapper(ThemeWrapper::class)
@Preview
@Composable
private fun {Component}Preview(
@PreviewParameter({Component}StatePreviewParameterProvider::class) state: {Component}State
) {
{Component}(
state = state
)
}
private class {Component}StatePreviewParameterProvider: PreviewParameterProvider<{Component}State> {
override val values: Sequence<{Component}State> = sequenceOf(
{Component}State.SampleA,
{Component}State.SampleB
)
}
Правила:
- Универсальный примитив принимает свои данные как обычные типизированные параметры — не
оборачивай их в
{Component}State, даже если параметров несколько.
- Компонент, который не такой общий, а просто вынесен в отдельный файл, оборачивает свои данные в
{Component}State наверху того же файла и принимает единственный параметр state; modifier
добавляй вторым параметром только при реальной необходимости.
- Порядок параметров универсального примитива: сначала обязательные (без значения по умолчанию),
затем
modifier: Modifier = Modifier, затем остальные параметры со значением по умолчанию, а
завершающей идёт обязательная trailing-лямбда content, если компонент оборачивает содержимое
(например content: @Composable ColumnScope.() -> Unit).
- Каждый компонент находится в своём файле; не объявляй несколько компонентов в одном файле.
@PreviewWrapper(ThemeWrapper::class) используй только для компонента с {Component}State —
ему нужна реальная тема приложения (иконки, цвета). Для универсального примитива без State
preview — обычный @Preview, без PreviewWrapper.
- Указывай
showBackground = true, только если у компонента нет собственного видимого фона —
например Text или список на прозрачном фоне (SharedRow, SharedColumn,
SharedLazyColumn). Если компонент сам рисует себе фон или форму (как SharedDragHandle или
{Component} с State выше), showBackground не нужен.
- Не создавай preview вовсе, если статичный снимок ничего не покажет без реального взаимодействия
или внешнего состояния — например обёртка видимости (
SharedAnimatedVisibility), хост снекбара
без активного снекбара (SharedSnackbarHost) или модальный bottom sheet.
- Для компонента с
{Component}State всегда создавай приватный
{Component}StatePreviewParameterProvider: PreviewParameterProvider<{Component}State> в том же
файле. Для универсального примитива без State создавай PreviewParameterProvider, только если у
компонента есть несколько содержательно разных состояний, которые стоит показать; иначе preview
вызывает компонент напрямую с представительными литеральными значениями.
- Preview-функция всегда
private.
1---2name: create-shared-component3description: Use when пользователь просит создать переиспользуемый Compose UI-компонент для `shared/ui/components`, или говорит "create a shared component", "add a reusable component", "new UI component". Строит либо простой composable с обычными типизированными параметрами (для универсальных layout-примитивов), либо composable с `{Component}State`-холдером (для компонентов конкретной предметной области, просто вынесенных в отдельный файл), плюс его preview. Не используй для диалога; используй вместо этого [create-feature-alert-dialog](../create-feature-alert-dialog/SKILL.md). Не используй для bottom sheet; используй вместо этого [create-feature-bottom-sheet](../create-feature-bottom-sheet/SKILL.md). Не используй для полного экрана, привязанного к ViewModel; используй вместо этого [create-feature-scaffold-screen](../create-feature-scaffold-screen/SKILL.md).4---56# Новый общий компонент78Требует зависимость `androidx.compose.foundation`.910Создаёт переиспользуемый Compose-компонент проекта. Замени `{Component}` на имя компонента11(например, `SharedCard`, `BarcodeFrameBox`), а `{package}` на целевой пакет.1213## Фаза 1: разместить файл компонента1415Один файл на компонент — `{Component}.kt`. Размещай его прямо в `shared/ui/components`, если это16универсальный layout-примитив или обёртка (как `SharedRow`, `SharedColumn`); для компонента17конкретной предметной области создавай тематическую подпапку, например `shared/ui/components/scan`.1819## Фаза 2: выбери форму компонента2021- Универсальный layout-примитив или обёртка, не завязанная на конкретную предметную область (как22 `SharedRow`, `SharedColumn`) — без State, обычные параметры.23- Компонент, который не такой общий, а просто вынесен в отдельный файл и представляет конкретный24 предметный смысл (иконка-пресет, карточка определённого содержимого и т. д.) — с25 `{Component}State` наверху файла.2627## Универсальный примитив (без State)2829```kotlin30package {package}3132import androidx.compose.runtime.Composable33import androidx.compose.ui.Modifier34import androidx.compose.ui.tooling.preview.Preview3536@Composable37fun {Component}(38 title: String,39 onClick: () -> Unit,40 modifier: Modifier = Modifier41) {42 // component content43}4445@Preview(showBackground = true)46@Composable47private fun {Component}Preview() {48 {Component}(49 title = "Example",50 onClick = {}51 )52}53```5455## Компонент с State5657Наверху файла размести `{Component}State` — `data class`, если данные варьируются свободно, или58`sealed class` с несколькими `data object`-вариантами, если у компонента всего пара59фиксированных пресетов. Сам компонент принимает один параметр `state`; `modifier` добавляй вторым60параметром, только если он реально нужен.6162```kotlin63package {package}6465// Добавь все необходимые импорты6667sealed class {Component}State(68 val iconRes: Int,69 val iconModifier: Modifier70) {71 data object SampleA: {Component}State(72 iconRes = AppIcons.SampleA,73 iconModifier = Modifier.size(107.dp, 131.dp)74 )7576 data object SampleB: {Component}State(77 iconRes = AppIcons.SampleB,78 iconModifier = Modifier.size(164.dp, 164.dp)79 )80}8182@Composable83fun {Component}(84 state: {Component}State85) {86 Box(87 modifier = Modifier88 .size(192.dp)89 .clip(CircleShape)90 .background(MaterialTheme.colorScheme.surface),91 contentAlignment = Alignment.Center92 ) {93 Icon(94 painter = painterResource(state.iconRes),95 contentDescription = null,96 modifier = state.iconModifier,97 tint = Color.Unspecified98 )99 }100}101102@PreviewWrapper(ThemeWrapper::class)103@Preview104@Composable105private fun {Component}Preview(106 @PreviewParameter({Component}StatePreviewParameterProvider::class) state: {Component}State107) {108 {Component}(109 state = state110 )111}112113private class {Component}StatePreviewParameterProvider: PreviewParameterProvider<{Component}State> {114 override val values: Sequence<{Component}State> = sequenceOf(115 {Component}State.SampleA,116 {Component}State.SampleB117 )118}119```120121Правила:122- Универсальный примитив принимает свои данные как обычные типизированные параметры — не123 оборачивай их в `{Component}State`, даже если параметров несколько.124- Компонент, который не такой общий, а просто вынесен в отдельный файл, оборачивает свои данные в125 `{Component}State` наверху того же файла и принимает единственный параметр `state`; `modifier`126 добавляй вторым параметром только при реальной необходимости.127- Порядок параметров универсального примитива: сначала обязательные (без значения по умолчанию),128 затем `modifier: Modifier = Modifier`, затем остальные параметры со значением по умолчанию, а129 завершающей идёт обязательная trailing-лямбда `content`, если компонент оборачивает содержимое130 (например `content: @Composable ColumnScope.() -> Unit`).131- Каждый компонент находится в своём файле; не объявляй несколько компонентов в одном файле.132- `@PreviewWrapper(ThemeWrapper::class)` используй только для компонента с `{Component}State` —133 ему нужна реальная тема приложения (иконки, цвета). Для универсального примитива без State134 preview — обычный `@Preview`, без `PreviewWrapper`.135- Указывай `showBackground = true`, только если у компонента нет собственного видимого фона —136 например `Text` или список на прозрачном фоне (`SharedRow`, `SharedColumn`,137 `SharedLazyColumn`). Если компонент сам рисует себе фон или форму (как `SharedDragHandle` или138 `{Component}` с State выше), `showBackground` не нужен.139- Не создавай preview вовсе, если статичный снимок ничего не покажет без реального взаимодействия140 или внешнего состояния — например обёртка видимости (`SharedAnimatedVisibility`), хост снекбара141 без активного снекбара (`SharedSnackbarHost`) или модальный bottom sheet.142- Для компонента с `{Component}State` всегда создавай приватный143 `{Component}StatePreviewParameterProvider: PreviewParameterProvider<{Component}State>` в том же144 файле. Для универсального примитива без State создавай `PreviewParameterProvider`, только если у145 компонента есть несколько содержательно разных состояний, которые стоит показать; иначе preview146 вызывает компонент напрямую с представительными литеральными значениями.147- Preview-функция всегда `private`.