Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@ title: Wheel
// @sample: com/sdds/compose/uikit/fixtures/samples/wheel/Wheel_Style.kt
```

## Индикатор выбранного элемента

Компонент поддерживает отображение прямоугольного индикатора, выделяющего центральный (выбранный) элемент. Индикатор рисуется под всеми колёсами как единый прямоугольник, охватывающий всю группу.

Индикатор настраивается через `WheelStyle` и включается флагом `itemSelectorEnabled`:

```kotlin
WheelStyle.builder()
.itemSelectorEnabled(true)
.itemSelectorShape(RoundedCornerShape(8.dp))
.colors {
itemSelectorColor(Color(0x1A0066FF))
}
.dimensions {
itemSelectorPaddingTop(2.dp)
itemSelectorPaddingBottom(2.dp)
}
.style()
```

## WheelConstraints

Ограничение колёс по ширине.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sdds.compose.uikit.fixtures.stories.wheel

import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.sdds.compose.sandbox.ComposeBaseStory
Expand All @@ -21,9 +22,10 @@ import com.sdds.sandbox.UiState
*
* @property variant Вариант отображения.
* @property itemLabel Заголовок элемента.
* @property itemTextAfter Текст после значения.
* @property textAfter Текст после значения.
* @property description Описание колеса.
* @property hasControls Флаг отображения кнопок управления.
* @property fillMaxWidth Заполнить максимальную ширину.
* @property wheelCount Количество колес.
* @property visibleItemsCount Количество видимых элементов.
* @property separatorType Тип разделителя между элементами.
Expand All @@ -33,12 +35,13 @@ data class WheelUiState(
override val variant: String = "",
override val appearance: String = "",
val itemLabel: String = "Label",
val itemTextAfter: String = "",
val textAfter: String = "TA",
val description: String = "",
val hasControls: Boolean = true,
val wheelCount: Int = 2,
val visibleItemsCount: Int = 3,
val separatorType: WheelSeparator = WheelSeparator.Dots,
val fillMaxWidth: Boolean = true,
) : UiState {

override fun updateVariant(appearance: String, variant: String): UiState {
Expand All @@ -59,7 +62,7 @@ object WheelStory : ComposeBaseStory<WheelUiState, WheelStyle>(
state: WheelUiState,
) {
Wheel(
modifier = Modifier,
modifier = if (state.fillMaxWidth) Modifier.fillMaxWidth() else Modifier,
style = style,
hasControls = state.hasControls,
wheelCount = state.wheelCount,
Expand All @@ -72,11 +75,12 @@ object WheelStory : ComposeBaseStory<WheelUiState, WheelStyle>(
WheelDataSet(
dataSet = List(30) {
WheelItemData(
text = state.itemLabel,
textAfter = state.itemTextAfter,
text = "${state.itemLabel}$it",
textAfter = state.textAfter,
)
},
description = state.description,
staticTextAfter = state.textAfter,
)
}
}
Expand All @@ -94,11 +98,8 @@ object WheelStory : ComposeBaseStory<WheelUiState, WheelStyle>(
wheelSeparator = WheelSeparator.None,
) { wheelIndex ->
WheelDataSet(
List(20) {
WheelItemData(
"Label",
"TA",
)
dataSet = List(20) {
WheelItemData("Label")
},
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-01
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
## Context

Compose-компонент `Wheel` (`sdds-core/uikit-compose`) состоит из публичного composable `Wheel.kt` и внутреннего `BaseWheel.kt`, который рендерит `LazyColumn` внутри `Box`. Конфигурация стиля задаётся через `WheelStyle`, `WheelColors`, `WheelDimensions`.

View-реализация (`WheelItemView.kt`) уже содержит `_selectorDrawable: ShapeDrawable`, который рисуется в `onDraw` под элементами при `itemSelectorEnabled && _listViewHasFocus`. В конфиге `WheelProperties` присутствуют поля `itemSelectorEnabled`, `itemSelectorShape`, `itemSelectorColor`, `itemSelectorPaddingTop/Bottom/Start/End`, но генератор `WheelComposeVariationGenerator` их не обрабатывает, а `WheelStyle` их не имеет.

В проекте уже есть примеры компонентов с `StatefulValue<Shape>` (`NavigationDrawerStyle`, `ListItemStyle`) и `StatefulValue<Dp>` (`ListItemStyle`). Генератор `ComposeVariationGenerator` поддерживает `appendDimension` (→ `StatefulValue<Dp>`) и `getShape` (→ простой `Shape`, затем враппируется в `StatefulValue` через builder-перегрузку). Для цвета-кисти используется `getGradientOrWrappedColor` → `StatefulValue<Brush>`.

## Goals / Non-Goals

**Goals:**
- Добавить в `WheelStyle` / `WheelColors` / `WheelDimensions` свойства для настройки индикатора выбранного элемента с `StatefulValue`-типизацией для цвета, формы и дименшенов.
- Отрисовать прямоугольный индикатор (форма + brush) за колёсами в `BaseWheel.kt`, занимающий полную ширину viewport.
- Подключить генерацию этих свойств в `WheelComposeVariationGenerator`.

**Non-Goals:**
- Изменения View-стека (`WheelItemView`, `Wheel.kt` View).
- Изменения `WheelConfig.kt` (все поля уже есть).
- Токенные пакеты (`tokens/**`).

## Decisions

### 1. Цвет индикатора: `StatefulValue<Brush>` в `WheelColors`

Хранится в `WheelColors.itemSelectorBrush: StatefulValue<Brush>`. Builder принимает `InteractiveColor` — `asStatefulBrush()` уже есть в `interactions/InteractiveColor.kt`. Генератор вызывает `getGradientOrWrappedColor("itemSelectorColor", color)` — этот метод уже корректно обрабатывает `SolidColor` и `Gradient`.

### 2. Форма индикатора: `StatefulValue<Shape>` в `WheelStyle`

По аналогии с `NavigationDrawerStyle.selectorShape: StatefulValue<Shape>`. Builder предоставляет две перегрузки: `itemSelectorShape(Shape)` (враппирует в `asStatefulValue()`) и `itemSelectorShape(StatefulValue<Shape>)`. Генератор вызывает `getShape(shape, variationId, "itemSelectorShape")` — возвращает `.itemSelectorShape(ThemeClass.shapes.xxx)`, builder-перегрузка с `Shape` принимает его и конвертирует.

Дефолт: `RectangleShape.asStatefulValue()`.

### 3. Отступы индикатора: `StatefulValue<Dp>` в `WheelDimensions`

По аналогии с `ListItemStyle`: `itemSelectorPaddingTop/Bottom/Start/End: StatefulValue<Dp>`. Builder: первичный метод принимает `StatefulValue<Dp>`, convenience-перегрузка принимает `Dp` и враппирует. Генератор вызывает `appendDimension("item_selector_padding_top", it, variationId)` — уже возвращает нужный stateful-фрагмент.

Дефолт: `0.dp.asStatefulValue()` для всех padding.

### 4. Флаг включения: `itemSelectorEnabled: Boolean` в `WheelStyle`

Простой `Boolean`, не stateful — по аналогии с View-реализацией. Дефолт: `false` (обратно совместимо).

### 5. Расположение индикатора: отдельный `Box` под LazyColumn

Внутри viewport `Box` в `BaseWheel.kt` добавить дочерний `Box` с:
```
Modifier
.fillMaxWidth()
.height((itemHeight - paddingTop - paddingBottom).coerceAtLeast(0))
.align(Alignment.Center)
.background(brush, shape)
```

Этот `Box` должен быть первым child в viewport `Box` — Compose рисует детей в порядке добавления, поэтому он окажется за `LazyColumn` по z-order. Не использовать `drawBehind`, чтобы корректно работала форма через `Modifier.background(brush, shape)`.

## Risks / Trade-offs

- **`itemHeight == 0` до первого layout**: индикатор не рисуется — корректное начальное состояние, пользователь ничего не заметит.
- **`itemSelectorEnabled = false` по умолчанию** — полностью обратно совместимо, существующие стили без изменений.
- **Ширина индикатора**: при `WheelConstraints.Loose` ширина viewport может быть `WRAP_CONTENT`. Индикатор использует `fillMaxWidth()` — займёт ширину viewport как есть.
- **`StatefulValue<Dp>` для padding**: `getValue(interactionSource)` нужно будет вызвать в composable-контексте. Паттерн уже используется в `ListItemStyle` — следуем ему.

## Валидация

После реализации запустить:
- `./gradlew :sdds-core:uikit-compose:test`
- `./gradlew :sdds-core:plugin_theme_builder:test`
- `./gradlew :sdds-core:uikit-compose:detekt`
- `./gradlew :sdds-core:plugin_theme_builder:detekt`
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Why

Compose-реализация компонента `Wheel` не имеет визуального индикатора выбранного элемента — прямоугольника с кастомизируемой формой и цветом, располагающегося позади колёс. View-реализация (`WheelItemView`) поддерживает `itemSelector`, но в Compose (`WheelStyle`, `BaseWheel`) эта возможность отсутствует, хотя конфиг (`WheelProperties`) уже содержит все нужные поля.

## What Changes

- `WheelStyle` (модуль `sdds-core/uikit-compose`): добавить свойства `itemSelectorEnabled`, `itemSelectorBrush: StatefulValue<Brush>`, `itemSelectorShape: Shape`, `itemSelectorPaddingTop/Bottom/Start/End: Dp` в `WheelColors`/`WheelDimensions` и соответствующие builder-методы.
- `BaseWheel.kt` (модуль `sdds-core/uikit-compose`): добавить отрисовку прямоугольного индикатора позади (`drawBehind` или дополнительный `Box` ниже по z-order) для центрального элемента колеса, занимающего полную ширину viewport.
- `WheelComposeVariationGenerator.kt` (модуль `sdds-core/plugin_theme_builder`): добавить генерацию builder-вызовов для свойств `itemSelectorEnabled`, `itemSelectorColor` (через `getGradientOrWrappedColor`), `itemSelectorShape`, `itemSelectorPadding*`.

## Capabilities

### New Capabilities

- `wheel-compose-selection-indicator`: визуальный индикатор выбранного элемента для Compose-компонента Wheel — прямоугольник с настраиваемой формой, кистью (`StatefulValue<Brush>`) и отступами, отрисовываемый за колёсами.

### Modified Capabilities

_Нет изменений требований к существующим спецификациям._

## Impact

- Публичный API: `WheelStyle`, `WheelColors`, `WheelColorsBuilder`, `WheelDimensions`, `WheelDimensionsBuilder` — добавляются новые свойства и методы (не ломающие изменения, все новые поля опциональны / имеют дефолты).
- Внутренняя реализация: `BaseWheel.kt` — добавляется новый composable-слой под списком.
- `WheelComposeVariationGenerator.kt` — добавляется генерация для selector-полей, уже присутствующих в `WheelProperties`.
- `WheelConfig.kt` — не изменяется (все нужные поля уже есть).
- Затронутые модули: `sdds-core/uikit-compose`, `sdds-core/plugin_theme_builder`.
- Токены и View-стек не затрагиваются.
Loading
Loading