Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-10 18:21:20 +03:00
parent 8781945418
commit 740da6cddd
12 changed files with 1154 additions and 0 deletions

View file

@ -0,0 +1,163 @@
# Design System
The app currently hosts **three generations of the design system (DS)** side by side. They differ by
folder, token set (colors / typography / dimensions), and the `@Preview` wrapper. Knowing which
generation a component belongs to is essential so you don't mix tokens or pull the wrong building blocks.
## Three generations
| Generation | Folder | Colors | Typography | Dimensions | Preview wrapper |
|---|---|---|---|---|---|
| **DS1** (legacy) | `core/ui/src/main/java/com/tangem/core/ui/components/` | `TangemTheme.colors` | `TangemTheme.typography` | `TangemTheme.dimens` | `TangemThemePreview` |
| **DS2** (redesign) | `core/ui/src/main/java/com/tangem/core/ui/ds/` | `TangemTheme.colors2` | `TangemTheme.typography2` | `TangemTheme.dimens2` | `TangemThemePreviewRedesign` |
| **DS3** (target) | `core/ui/src/main/java/com/tangem/core/ui/ds2/` | `TangemTheme.colors3` | `TangemTheme.typography3` | `TangemTheme.dimens2` | `TangemThemePreviewRedesign` |
> Mind the numbering mismatch: **folder `ds` is DS2**, **folder `ds2` is DS3**.
> The `colors2` / `typography2` tokens are `@Deprecated` (ReplaceWith `colors3` / `typography3`).
- **DS1** — the entire current app is built on it. Do **not** add new components here.
- **DS2** — redesign components. A transitional generation; don't write new components in it, only
maintain what already exists.
- **DS3** — the newest design system; **the whole app is being migrated to it**. Build new DS
components here.
## Using DS3 in features
**All DS3 components (folder `ds2`) may be used in features starting from app version 6.0.** Before
6.0 they must not be used on product screens.
If a needed component does not yet exist in DS3, **add it by analogy with the existing ones** (see the
pattern below).
## DS3 component pattern
Study the existing components as references:
- Simple: `ds2/checkbox/TangemCheckmark.kt` — single file, a public `@Composable` function + `@Preview`.
- Composite: `ds2/button/``TangemButton.kt` (public API), `TangemButtonInternal.kt` (private inner
layout), `TangemButtonExt.kt` (variant / size tokens).
Pattern rules:
1. **Package & location.** `com.tangem.core.ui.ds2.<component>`, folder
`core/ui/.../ds2/<component>/`. The component name is `Tangem<Name>`.
2. **DS3 tokens only.** Colors — `TangemTheme.colors3.*`, text — `TangemTheme.typography3.*`,
dimensions — `TangemTheme.dimens2.*`. No `colors` / `colors2` / hardcoded values (literal dp/colors
are acceptable only inside `@Preview`, where you add `@Suppress("MagicNumber")`).
3. **Signature.** `modifier: Modifier = Modifier` is mandatory (defaulting to `Modifier`, placed first
among the optional params or right after the required ones). Express variants/sizes via a nested
`enum` in `object Tangem<Name>` (like `TangemButton.Variant` / `TangemButton.Size`), not boolean flags.
4. **Accessibility.** Pass `contentDescription`, set the `Role`, mark `disabled()` in `semantics`, and
handle focus/press state via `interactionSource`.
5. **KDoc + Figma link.** Above the public function — KDoc describing behavior, every parameter, and a
link to the Figma node (see the KDoc requirements below).
6. **Previews.** Two `@Preview`s (Light + Dark via `UI_MODE_NIGHT_YES`), wrapped in
`TangemThemePreviewRedesign { ... }`, with `TangemTheme.colors3.bg.primary` as the background.
Preview helpers (`PreviewRow`, `Section`, etc.) are private in the same file.
7. **Composite components** (many variants / heavy layout) are split into 3 files like the button:
public `Tangem<Name>.kt`, private `Tangem<Name>Internal.kt`, tokens `Tangem<Name>Ext.kt`.
## API conventions
### Public properties live in the `object`
Any public type the component exposes — variant/size/role/align enums, status classes, constants —
is declared inside the namesake `object Tangem<Name>`, **not** as a top-level type. This keeps a single
`Tangem<Name>.Variant` / `Tangem<Name>.Size` / `Tangem<Name>.Role` namespace at the call site and
avoids polluting the package.
```kotlin
object TangemTopNavigation {
/** Horizontal alignment of the center content slot. */
enum class ContentAlign { Start, Center }
}
// usage: TangemTopNavigation.ContentAlign.Center
```
References: `TangemTopNavigation.ContentAlign`, `TangemNavigationText.Role`, `TangemButton.Variant` /
`TangemButton.Size`.
### Provide convenient overloads
A component should ship ergonomic overloads so callers don't assemble boilerplate for the common case.
Two acceptable shapes:
1. **Additional `@Composable fun` overloads** with simpler parameters that delegate to the base one.
`TangemTopNavigation` has a low-level slot-based overload (`startButton`/`endButton`/`contentColumn`
lambdas) plus several high-level overloads taking `title` / `subtitle` / `onBack` / `onClose` that
wire the predefined buttons and the title/subtitle center for you.
2. **Extension functions on the `object`** for named presets — e.g. `@Composable fun TangemButton.Back(…)`
and `TangemButton.Close(…)` in `TangemButtonExt.kt` expose ready-made button presets while reading
as `TangemButton.Back { … }` at the call site.
Each overload keeps the same rules as the base component (`modifier` first among optionals, DS3 tokens,
its own KDoc — see below).
### Sub-components are first-class
Internal building blocks that are themselves public (e.g. `TangemNavigationText`, used for the
`TangemTopNavigation` title/subtitle slots) follow the **exact same rules** as a top-level component:
DS3 tokens only, `modifier: Modifier = Modifier`, public properties in their own `object`
(`TangemNavigationText.Role`), full KDoc, and their own Storybook entry where it makes sense. Don't
treat "helper" composables as second-class — if a feature can call it, it is a documented DS component.
## KDoc requirements for components
Every public DS component (and any non-trivial public composable) must carry a KDoc block. Use
`ds2/button/TangemButton.kt` and `ds2/checkbox/TangemCheckmark.kt` as the canonical examples.
A component KDoc must contain, in order:
1. **Summary line.** One sentence stating what the component is and which generation it belongs to —
start with `Design-system v2 …` for DS3 components (matches the existing wording).
2. **Figma link.** A markdown link to the exact Figma node:
`[Figma](https://www.figma.com/design/…?node-id=…)`. A component without a Figma reference is not
review-ready.
3. **Behavior notes** (when behavior is non-obvious). A short prose paragraph or a bulleted
`Behavior notes:` list covering state-dependent rendering — loading, disabled/enabled, icon-only
vs. labeled, focus ring, animations, what overrides what. Describe *observable behavior*, not the
implementation.
4. **`@param` for every parameter.** No parameter may be left undocumented — including `modifier`
when its effect is non-trivial (e.g. "Pass `Modifier.fillMaxWidth()` to switch to fixed-width
layout"). Each `@param` states the meaning **and** the consequences of notable values
(`null` → non-interactive, `false` → dimmed & clicks ignored, etc.).
5. **Accessibility guidance** where relevant — e.g. when `contentDescription` should be supplied
(icon-only buttons, loading state, disabled state) and what it announces.
Additional rules:
- Document the **nested `enum`s** (`Variant`, `Size`, `Status`, …) too: a short KDoc on the enum and,
where the options aren't self-explanatory, a one-line description per entry (see `TangemButton.Variant`).
- Keep KDoc about **contract and behavior**, not internals. Implementation comments explaining *why*
a specific approach was taken belong to inline `//` comments inside the body, not the KDoc.
- Reference other DS types with `[TangemSurface]` / `[TangemButton.Variant]` link syntax so they
resolve in the IDE.
- Detekt enforces missing-KDoc-on-public-API style checks on `core:ui`; run `./gradlew :core:ui:detektMain`.
## Storybook
Add every DS3 component to the **Storybook** (module `features/tester`) — a live on-device/emulator
component gallery (Tester → Storybook → DS Components).
Use the **`add-storybook-component`** skill — it wires the entity, the Build factory, the Composable
page, and registers it in the correct list. Run: `/add-storybook-component TangemCheckmark (DS)`.
Page layout guidelines live in
`features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/STORYBOOK.md`.
## Checklist: adding a new DS3 component
- [ ] Component created under `core/ui/.../ds2/<component>/`, package `com.tangem.core.ui.ds2.<component>`.
- [ ] Named `Tangem<Name>`; first optional parameter is `modifier: Modifier = Modifier`.
- [ ] Uses **only** DS3 tokens: `colors3`, `typography3`, `dimens2`. No hardcoded values outside previews.
- [ ] Variants/sizes expressed as an `enum` inside `object Tangem<Name>` (not a set of boolean flags).
- [ ] All public types (enums, statuses, constants) declared inside the `object Tangem<Name>`.
- [ ] Convenient overloads provided (simpler `@Composable` overloads and/or `object` extension presets).
- [ ] Public sub-components (e.g. `TangemNavigationText`) follow the same rules + KDoc as a full component.
- [ ] States handled: enabled/disabled, press/focus (`interactionSource`), loading (if applicable).
- [ ] Accessibility: `contentDescription`, `Role`, `disabled()` in `semantics`.
- [ ] KDoc per the requirements above (summary + Figma link + behavior notes + every `@param` + a11y).
- [ ] Two `@Preview`s (Light/Dark) in `TangemThemePreviewRedesign`, background `colors3.bg.primary`.
- [ ] Heavy component split into `Tangem<Name>.kt` / `…Internal.kt` / `…Ext.kt`.
- [ ] Storybook page added (`add-storybook-component` skill).
- [ ] Detekt passes: `./gradlew :core:ui:detektMain` (plus
`./gradlew :features:tester:impl:assembleGoogleDebug` if you touched the Storybook).
- [ ] Use in product features only from app version **6.0** onward.

View file

@ -0,0 +1,150 @@
---
name: add-storybook-component
description: Add a component showcase page to the Tangem storybook (in features/tester). Wires the entity, Build factory, Composable page, and registers it either in the "DS Components" sub-list (first/default target — for design-system components under core.ui.ds2.*) or in the root storybook list (second target — for any other component). Use when asked to "add a storybook page/story", "add <Component> to the storybook", "сделай сторибук для <компонент>", "добавь стори/историю в storybook", or to showcase a DS component in the tester.
allowed-tools: Read, Grep, Glob, Bash, Edit, Write
argument-hint: [component to add, e.g. "TangemCheckbox (DS)" or "MyLegacyCard"]
---
Add a new component page to the Tangem storybook. The storybook lives in
`features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/`
and renders interactive DS/component showcases on a device or emulator.
This is an **interactive** skill: read the real production component first to get its actual
parameters, enums, and package — never guess the API. Then mirror the closest existing story.
## Two placement targets — pick one
| Target | Use for | List screen | Page dir | Entity supertype |
|---|---|---|---|---|
| **1. DS Components (default)** | Design-system components under `com.tangem.core.ui.ds2.*` (the newest "DS3"/redesign components: `TangemButton`, `TangemBadge`, `TangemRow`, `TangemLoader`, …) | `page/ds/DsComponentsListScreen.kt``buildDsStories()` | `page/ds/<component>/` | `DsStoryBookPage` |
| **2. Other components** | Anything else (legacy/cross-cutting components, backgrounds, effects, typography demos) | `ui/StoryBookListScreen.kt``buildStories()` | `page/<component>/` | `StoryBookPage` |
**Default to Target 1 (DS Components)** when the component lives under `core.ui.ds2.*` or the user
mentions "DS"/"ds3"/"design system". Only the **list screen** and **page directory** differ between
the two targets — everything else (entity declaration file, `StoryBookScreen.kt` routing, factory
pattern) is identical.
> The ONLY behavioral difference of `DsStoryBookPage` vs `StoryBookPage`: `StoryBookViewModel.onBackClick`
> routes a `DsStoryBookPage` back to the DS sub-list, while a plain `StoryBookPage` routes back to the
> root list. That's it.
## Reference
`features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/STORYBOOK.md` is the canonical doc — read it for the **design guidelines** (mandatory
page layout: single live preview pinned at top + one control per parameter below, chip-selector pattern,
colors, realistic text). This skill covers the *wiring*; STORYBOOK.md covers the *look*.
Best reference implementations to mirror:
- **Stateful DS page with many controls:** `page/ds/button/` (TangemButton — variant/size/background
selectors, toggles, text-scale slider, blur backdrop). Read all three files: `Build.kt`,
`TangemButtonStory.kt`, and the `TangemButtonStory` entity in `entity/StoryBookPage.kt`.
- **Simple stateful page:** `page/ds/loader/` (TangemLoader — single size selector).
- **Stateless page (no params):** a `data object` sibling such as `ButtonsStory`.
## Workflow
1. **Read the production component.** Grep `core/ui/src/main/java/com/tangem/core/ui/ds2/<name>/`
(or wherever it lives) for the composable signature, its `enum`s (Variant/Size/Status/…), and
required vs optional params. The set of parameters becomes the set of controls.
2. **Decide stateless vs stateful:**
- **Stateless** (`data object`) — ONLY if the component has no configurable parameters at all.
- **Stateful** (`data class`) — the normal case: one field per parameter the user can change, each
paired with an `onXxxChange`/`onXxxToggle` lambda.
3. **Pick the target** (see table above) and **mirror the closest sibling**.
4. **Do the 4 edits + 1 new dir** (Steps AE below).
5. **Verify it compiles** (see Build).
## The edits
Assume component `Foo` rendered by `com.tangem.core.ui.ds2.foo.TangemFoo` with a `Variant` enum and an
`isEnabled` flag. Adjust names to the real component. `<page-dir>` =
`page/ds/foo/` for Target 1, or `page/foo/` for Target 2.
### A. Declare the entity in `entity/StoryBookPage.kt`
Stateful (normal):
```kotlin
internal data class TangemFooStory(
val variant: TangemFoo.Variant,
val isEnabled: Boolean,
val onVariantChange: (TangemFoo.Variant) -> Unit,
val onEnabledToggle: () -> Unit,
) : DsStoryBookPage // <- StoryBookPage for Target 2
```
Stateless: `internal data object TangemFooStory : DsStoryBookPage` (or `StoryBookPage`).
Add the matching import for the production type at the top of the file.
### B. Create `<page-dir>/Build.kt`
Stateful — uses `storyPageFactory` + `StateUpdater`:
```kotlin
internal fun StateUpdater<TangemFooStory>.build(): TangemFooStory {
return TangemFooStory(
variant = TangemFoo.Variant.Primary,
isEnabled = true,
onVariantChange = { v -> updateStory { it.copy(variant = v) } },
onEnabledToggle = { updateStory { it.copy(isEnabled = !it.isEnabled) } },
)
}
internal val tangemFooStoryFactory
get() = storyPageFactory(StateUpdater<TangemFooStory>::build)
```
Stateless: `internal val tangemFooStoryFactory: StoryPageFactory = StoryPageFactory { TangemFooStory }`
### C. Create `<page-dir>/TangemFooStory.kt`
`@Composable internal fun TangemFooStory(state: TangemFooStory, modifier: Modifier = Modifier)`
(drop `state` for stateless). Follow STORYBOOK.md design guidelines: live preview pinned at the top
in a `Column`, controls scrolling below. Reuse the chip-selector / toggle-row patterns from
`page/ds/button/TangemButtonStory.kt` (its `Section`, `ChipGrid`, `Chip`, `ToggleRow` are private —
copy the ones you need into the new file). Use representative text, not "Btn".
### D. Register routing in `ui/StoryBookScreen.kt`
Add both imports (entity + page composable share the simple name — Kotlin resolves them by position):
```kotlin
import com.tangem.feature.tester.presentation.storybook.entity.TangemFooStory
import com.tangem.feature.tester.presentation.storybook.page.ds.foo.TangemFooStory
```
Add a branch to the `when (storyState)`:
```kotlin
is TangemFooStory -> TangemFooStory(state = storyState) // stateless: TangemFooStory -> TangemFooStory()
```
### E. Register in the list screen (target-specific)
- **Target 1 (DS):** in `page/ds/DsComponentsListScreen.kt` add the factory import and a row to
`buildDsStories()`:
```kotlin
DsStoryItem(title = "🔘 TangemFoo", factory = tangemFooStoryFactory),
```
- **Target 2 (other):** in `ui/StoryBookListScreen.kt` add the factory import and a row to
`buildStories()`:
```kotlin
StoryItem(title = "🔘 Foo", factory = tangemFooStoryFactory),
```
**Every title must start with an emoji** matching the component category (🔘 buttons, 🏷️ badge,
📋 row, ⏳ loader, 🔤 typography, 🔍 search, 🧭 navigation, 💀 placeholder, ✨ effects, 🪙 token…).
## Build
```bash
./gradlew :features:tester:impl:assembleGoogleDebug
```
Detekt runs via the convention plugin; keep `@file:Suppress("MagicNumber")` on showcase files that use
literal dp/colors (the button story does this). Then run the app, open Tester → Storybook → (DS
Components →) your entry, and confirm the preview + every control works.
## Checklist
- [ ] Read the real component; every meaningful parameter has a control.
- [ ] Entity in `StoryBookPage.kt` extends the correct supertype (`DsStoryBookPage` for DS, else `StoryBookPage`).
- [ ] `Build.kt` factory name is `<camelCaseName>StoryFactory`.
- [ ] Page composable shares the entity's simple name; both imported in `StoryBookScreen.kt`.
- [ ] `when` branch added in `StoryBookScreen.kt` (`is` prefix for stateful, bare for stateless).
- [ ] Registered in the correct list screen with an emoji-prefixed title.
- [ ] Live preview pinned at top, controls below (STORYBOOK.md layout rule).
- [ ] `:features:tester:impl:assembleGoogleDebug` passes.

View file

@ -0,0 +1,246 @@
package com.tangem.core.ui.ds2.checkbox
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.selection.triStateToggleable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import com.tangem.core.ui.extensions.conditionalCompose
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.res.generated.icons.Icons
import com.tangem.core.ui.res.generated.icons.ic_control_box_24
import com.tangem.core.ui.res.generated.icons.ic_control_box_24_filled
import com.tangem.core.ui.res.generated.icons.ic_control_checkmark_24
import com.tangem.core.ui.res.generated.icons.ic_control_indeterminate_24
/**
* Design-system v2 tri-state checkbox: an `unchecked` outline box, a `checked` filled box with a
* checkmark, or an `indeterminate` filled box with a dash.
*
* [Figma](https://www.figma.com/design/y8arHOHCa6HjMpOMJ0Ykj6/DS-64-%7C-Token-Icon?node-id=3650-628)
*
* @param state Current tri-state value. See [ToggleableState].
* @param onClick Invoked on toggle. `null` makes the checkbox non-interactive.
* @param isEnabled When `false`, the checkbox is dimmed and clicks are ignored.
* @param contentDescription Accessibility label announced by TalkBack.
* @param interactionSource Interaction source for press/focus state.
*/
@Suppress("MagicNumber", "LongMethod")
@Composable
fun TangemCheckbox(
state: ToggleableState,
onClick: (() -> Unit)?,
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
contentDescription: String? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val isPressed by interactionSource.collectIsPressedAsState()
val isFocused by interactionSource.collectIsFocusedAsState()
val contentAlpha = if (isEnabled) 1f else 0.4f // opacity/disabled
// Whole control shrinks slightly while pressed and springs back on release.
val pressScale by animateFloatAsState(
targetValue = if (isPressed) 0.92f else 1f,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessMediumLow),
label = "pressScale",
)
// Press fill fades in/out instead of toggling instantly.
val pressColor by animateColorAsState(
targetValue = if (isPressed) TangemTheme.colors3.interaction.press.default else Color.Transparent,
animationSpec = tween(durationMillis = 100),
label = "pressColor",
)
// Drives the filled box growing over the outline (0 = unchecked, 1 = filled).
val fillProgress by animateFloatAsState(
targetValue = if (state == ToggleableState.Off) 0f else 1f,
animationSpec = tween(durationMillis = 150),
label = "fillProgress",
)
Box(
modifier = modifier
.graphicsLayer {
alpha = contentAlpha
scaleX = pressScale
scaleY = pressScale
}
.semantics(mergeDescendants = true) {
if (!isEnabled) disabled()
contentDescription?.let { this.contentDescription = it }
}
.conditionalCompose(onClick != null) {
triStateToggleable(
state = state,
onClick = requireNotNull(onClick),
enabled = isEnabled,
role = Role.Checkbox,
interactionSource = interactionSource,
indication = null,
)
}
.size(24.dp)
.clip(CheckboxShape)
.drawBehind { drawRect(pressColor) }
.conditionalCompose(isFocused) {
border(
width = 2.dp, // border-width/md
color = TangemTheme.colors3.interaction.focusRing.brand,
shape = CheckboxShape,
)
},
contentAlignment = Alignment.Center,
) {
// Outline box — always present; the filled box grows over it.
Icon(
imageVector = Icons.ic_control_box_24,
contentDescription = null,
tint = TangemTheme.colors3.icon.primary,
)
// Filled box — fades and scales in from the center as the checkbox becomes filled.
Icon(
imageVector = Icons.ic_control_box_24_filled,
contentDescription = null,
tint = TangemTheme.colors3.icon.primary,
modifier = Modifier.graphicsLayer {
alpha = fillProgress
val markScale = lerp(start = 0.5f, stop = 1f, fraction = fillProgress)
scaleX = markScale
scaleY = markScale
},
)
// Mark — checkmark or dash pops in, and crossfades when switching between the two.
AnimatedContent(
targetState = state,
transitionSpec = {
val enter = scaleIn(
initialScale = 0.5f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMediumLow,
),
) + fadeIn()
val exit = scaleOut(targetScale = 0.5f) + fadeOut()
enter togetherWith exit
},
label = "mark",
) { current ->
when (current) {
ToggleableState.On -> Icon(
imageVector = Icons.ic_control_checkmark_24,
contentDescription = null,
tint = TangemTheme.colors3.icon.inverse,
)
ToggleableState.Indeterminate -> Icon(
imageVector = Icons.ic_control_indeterminate_24,
contentDescription = null,
tint = TangemTheme.colors3.icon.inverse,
)
ToggleableState.Off -> Box(Modifier.size(24.dp))
}
}
}
}
/**
* Boolean (checked / unchecked) overload of [TangemCheckbox] for the common two-state case.
*
* @param checked Whether the checkbox is checked.
* @param onCheckedChange Invoked with the toggled value. `null` makes the checkbox non-interactive.
*/
@Composable
fun TangemCheckbox(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
contentDescription: String? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
TangemCheckbox(
state = ToggleableState(checked),
onClick = onCheckedChange?.let { { it(!checked) } },
modifier = modifier,
isEnabled = isEnabled,
contentDescription = contentDescription,
interactionSource = interactionSource,
)
}
private val CheckboxShape = RoundedCornerShape(6.dp)
@Preview(name = "Light", showBackground = true)
@Preview(name = "Dark", uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES, showBackground = true)
@Composable
private fun TangemCheckboxPreview() {
TangemThemePreviewRedesign {
Column(
modifier = Modifier
.background(TangemTheme.colors3.bg.primary)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(20.dp),
) {
PreviewRow(label = "Enabled", isEnabled = true)
PreviewRow(label = "Disabled", isEnabled = false)
}
}
}
@Composable
private fun PreviewRow(label: String, isEnabled: Boolean) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
text = label,
color = TangemTheme.colors3.text.secondary,
style = TangemTheme.typography3.body.medium,
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
ToggleableState.entries.forEach { state ->
TangemCheckbox(state = state, onClick = {}, isEnabled = isEnabled)
}
}
}
}

View file

@ -0,0 +1,214 @@
package com.tangem.core.ui.ds2.checkbox
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import com.tangem.core.ui.extensions.conditionalCompose
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.res.generated.icons.Icons
import com.tangem.core.ui.res.generated.icons.ic_control_checkmark_24
import com.tangem.core.ui.res.generated.icons.ic_control_circle_24
import com.tangem.core.ui.res.generated.icons.ic_control_circle_24_filled
/**
* Design-system v2 circular checkmark: an `unchecked` outline circle or a `checked` filled circle
* with a checkmark. Unlike [TangemCheckbox], this control is round (border-radius/full) and
* boolean-only it has no indeterminate state.
*
* [Figma](https://www.figma.com/design/AsnJ5CPHib4Qxw12gszjMS/%F0%9F%92%A0-DS-Components?node-id=3671-5693)
*
* @param checked Whether the checkmark is checked.
* @param onCheckedChange Invoked with the toggled value. `null` makes the control non-interactive.
* @param isEnabled When `false`, the control is dimmed and clicks are ignored.
* @param contentDescription Accessibility label announced by TalkBack.
* @param interactionSource Interaction source for press/focus state.
*/
@Suppress("MagicNumber", "LongMethod")
@Composable
fun TangemCheckmark(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
contentDescription: String? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val isPressed by interactionSource.collectIsPressedAsState()
val isFocused by interactionSource.collectIsFocusedAsState()
val contentAlpha = if (isEnabled) 1f else 0.4f
// Whole control shrinks slightly while pressed and springs back on release.
val pressScale by animateFloatAsState(
targetValue = if (isPressed) 0.92f else 1f,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessMediumLow),
label = "pressScale",
)
// Press fill fades in/out instead of toggling instantly.
val pressColor by animateColorAsState(
targetValue = if (isPressed) TangemTheme.colors3.interaction.press.default else Color.Transparent,
animationSpec = tween(durationMillis = 100),
label = "pressColor",
)
// Drives the filled circle growing over the outline (0 = unchecked, 1 = filled).
val fillProgress by animateFloatAsState(
targetValue = if (checked) 1f else 0f,
animationSpec = tween(durationMillis = 150),
label = "fillProgress",
)
Box(
modifier = modifier
.graphicsLayer {
alpha = contentAlpha
scaleX = pressScale
scaleY = pressScale
}
.semantics(mergeDescendants = true) {
if (!isEnabled) disabled()
contentDescription?.let { this.contentDescription = it }
}
.conditionalCompose(onCheckedChange != null) {
toggleable(
value = checked,
onValueChange = requireNotNull(onCheckedChange),
enabled = isEnabled,
role = Role.Checkbox,
interactionSource = interactionSource,
indication = null,
)
}
.size(24.dp)
.clip(CircleShape)
.drawBehind { drawRect(pressColor) }
.conditionalCompose(isFocused) {
border(
width = 2.dp,
color = TangemTheme.colors3.interaction.focusRing.brand,
shape = CircleShape,
)
},
contentAlignment = Alignment.Center,
) {
// Outline circle — always present; the filled circle grows over it.
Icon(
imageVector = Icons.ic_control_circle_24,
contentDescription = null,
tint = TangemTheme.colors3.icon.primary,
)
// Filled circle — fades and scales in from the center as the checkmark becomes filled.
Icon(
imageVector = Icons.ic_control_circle_24_filled,
contentDescription = null,
tint = TangemTheme.colors3.icon.primary,
modifier = Modifier.graphicsLayer {
alpha = fillProgress
val markScale = lerp(start = 0.5f, stop = 1f, fraction = fillProgress)
scaleX = markScale
scaleY = markScale
},
)
// Checkmark pops in and fades out as the checked state toggles.
AnimatedContent(
targetState = checked,
transitionSpec = {
val enter = scaleIn(
initialScale = 0.5f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMediumLow,
),
) + fadeIn()
val exit = scaleOut(targetScale = 0.5f) + fadeOut()
enter togetherWith exit
},
label = "mark",
) { isChecked ->
if (isChecked) {
Icon(
imageVector = Icons.ic_control_checkmark_24,
contentDescription = null,
tint = TangemTheme.colors3.icon.inverse,
)
} else {
Box(Modifier.size(24.dp))
}
}
}
}
@Preview(name = "Light", showBackground = true)
@Preview(name = "Dark", uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES, showBackground = true)
@Composable
private fun TangemCheckmarkPreview() {
TangemThemePreviewRedesign {
Column(
modifier = Modifier
.background(TangemTheme.colors3.bg.primary)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(20.dp),
) {
PreviewRow(label = "Enabled", isEnabled = true)
PreviewRow(label = "Disabled", isEnabled = false)
}
}
}
@Composable
private fun PreviewRow(label: String, isEnabled: Boolean) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
text = label,
color = TangemTheme.colors3.text.secondary,
style = TangemTheme.typography3.body.medium,
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
listOf(false, true).forEach { checked ->
TangemCheckmark(checked = checked, onCheckedChange = {}, isEnabled = isEnabled)
}
}
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.feature.tester.presentation.storybook.entity
import androidx.compose.runtime.Immutable
import androidx.compose.ui.state.ToggleableState
import com.tangem.core.ui.ds.badge.TangemBadgeColor
import com.tangem.core.ui.ds.field.search.TangemFieldShape
import com.tangem.core.ui.ds.message.TangemMessageEffect
@ -318,6 +319,20 @@ internal data class TangemSearchStory(
}
}
internal data class TangemCheckboxV2Story(
val state: ToggleableState,
val isEnabled: Boolean,
val onStateChange: (ToggleableState) -> Unit,
val onEnabledToggle: () -> Unit,
) : DsStoryBookPage
internal data class TangemCheckmarkStory(
val isChecked: Boolean,
val isEnabled: Boolean,
val onCheckedChange: (Boolean) -> Unit,
val onEnabledToggle: () -> Unit,
) : DsStoryBookPage
internal data class TangemBadgeV2Story(
val variant: TangemBadge.Variant,
val status: TangemBadge.Status,

View file

@ -17,6 +17,8 @@ import com.tangem.feature.tester.presentation.storybook.entity.DsComponentsListS
import com.tangem.feature.tester.presentation.storybook.entity.StoryPageFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.badge.tangemBadgeV2StoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.button.tangemButtonStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.checkbox.tangemCheckboxV2StoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.checkmark.tangemCheckmarkStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.fade.tangemFadeStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.loader.tangemLoaderStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.row.tangemRowStoryFactory
@ -30,6 +32,8 @@ private fun buildDsStories() = listOf(
DsStoryItem(title = "⏳ TangemLoader", factory = tangemLoaderStoryFactory),
DsStoryItem(title = "🔘 TangemButton", factory = tangemButtonStoryFactory),
DsStoryItem(title = "🏷️ TangemBadge", factory = tangemBadgeV2StoryFactory),
DsStoryItem(title = "☑️ TangemCheckbox", factory = tangemCheckboxV2StoryFactory),
DsStoryItem(title = "⭕ TangemCheckmark", factory = tangemCheckmarkStoryFactory),
DsStoryItem(title = "📋 TangemRow", factory = tangemRowStoryFactory),
DsStoryItem(title = "🔎 TangemSearch", factory = tangemSearchStoryFactory),
DsStoryItem(title = "✨ TangemShimmer", factory = tangemShimmerStoryFactory),

View file

@ -0,0 +1,22 @@
package com.tangem.feature.tester.presentation.storybook.page.ds.checkbox
import androidx.compose.ui.state.ToggleableState
import com.tangem.feature.tester.presentation.storybook.entity.TangemCheckboxV2Story
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
internal fun StateUpdater<TangemCheckboxV2Story>.build(): TangemCheckboxV2Story {
return TangemCheckboxV2Story(
state = ToggleableState.Off,
isEnabled = true,
onStateChange = { state ->
updateStory { it.copy(state = state) }
},
onEnabledToggle = {
updateStory { it.copy(isEnabled = !it.isEnabled) }
},
)
}
internal val tangemCheckboxV2StoryFactory
get() = storyPageFactory(StateUpdater<TangemCheckboxV2Story>::build)

View file

@ -0,0 +1,190 @@
@file:Suppress("MagicNumber")
package com.tangem.feature.tester.presentation.storybook.page.ds.checkbox
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.ds2.checkbox.TangemCheckbox
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.tester.presentation.storybook.entity.TangemCheckboxV2Story
@Composable
internal fun TangemCheckboxV2Story(state: TangemCheckboxV2Story, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize()
.background(TangemTheme.colors.background.primary)
.padding(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
// Preview stays pinned at the top.
ComponentPreview(state = state)
// Only the controls scroll.
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
StateSelector(selected = state.state, onSelect = state.onStateChange)
Toggles(state = state)
}
}
}
@Composable
private fun ComponentPreview(state: TangemCheckboxV2Story) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors3.bg.primary)
.padding(vertical = 48.dp),
) {
// Clicking the live checkbox cycles Off -> On -> Indeterminate -> Off.
TangemCheckbox(
state = state.state,
onClick = { state.onStateChange(state.state.next()) },
isEnabled = state.isEnabled,
modifier = Modifier.scale(2f),
)
}
}
private fun ToggleableState.next(): ToggleableState = when (this) {
ToggleableState.Off -> ToggleableState.On
ToggleableState.On -> ToggleableState.Indeterminate
ToggleableState.Indeterminate -> ToggleableState.Off
}
@Composable
private fun StateSelector(selected: ToggleableState, onSelect: (ToggleableState) -> Unit) {
Section(label = "State") {
ChipGrid(
items = ToggleableState.entries,
label = { it.name },
isSelected = { it == selected },
onSelect = onSelect,
)
}
}
@Composable
private fun Toggles(state: TangemCheckboxV2Story) {
Section(label = "Flags") {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
ToggleRow(label = "isEnabled", checked = state.isEnabled, onToggle = state.onEnabledToggle)
}
}
}
@Composable
private fun Section(label: String, content: @Composable () -> Unit) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
text = label,
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
content()
}
}
@Composable
private fun <T> ChipGrid(items: List<T>, label: (T) -> String, isSelected: (T) -> Boolean, onSelect: (T) -> Unit) {
val shape = RoundedCornerShape(50)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(shape)
.background(TangemTheme.colors2.surface.level2)
.border(
width = 1.dp,
color = TangemTheme.colors2.border.neutral.secondary,
shape = shape,
)
.padding(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
items.forEach { item ->
Chip(
label = label(item),
selected = isSelected(item),
onClick = { onSelect(item) },
modifier = Modifier.weight(1f),
)
}
}
}
@Composable
private fun Chip(label: String, selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
val chipShape = RoundedCornerShape(50)
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.clip(chipShape)
.background(
if (selected) TangemTheme.colors2.surface.level3 else TangemTheme.colors2.surface.level2,
)
.clickable(onClick = onClick)
.padding(vertical = 8.dp, horizontal = 4.dp),
) {
Text(
text = label,
style = TangemTheme.typography.caption2,
color = if (selected) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.secondary,
)
}
}
@Composable
private fun ToggleRow(label: String, checked: Boolean, onToggle: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(TangemTheme.colors2.surface.level2)
.clickable(onClick = onToggle)
.padding(horizontal = 12.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = label,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = if (checked) "ON" else "OFF",
style = TangemTheme.typography.caption2,
color = if (checked) TangemTheme.colors.text.accent else TangemTheme.colors.text.secondary,
)
}
}

View file

@ -0,0 +1,21 @@
package com.tangem.feature.tester.presentation.storybook.page.ds.checkmark
import com.tangem.feature.tester.presentation.storybook.entity.TangemCheckmarkStory
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
internal fun StateUpdater<TangemCheckmarkStory>.build(): TangemCheckmarkStory {
return TangemCheckmarkStory(
isChecked = false,
isEnabled = true,
onCheckedChange = { checked ->
updateStory { it.copy(isChecked = checked) }
},
onEnabledToggle = {
updateStory { it.copy(isEnabled = !it.isEnabled) }
},
)
}
internal val tangemCheckmarkStoryFactory
get() = storyPageFactory(StateUpdater<TangemCheckmarkStory>::build)

View file

@ -0,0 +1,125 @@
@file:Suppress("MagicNumber")
package com.tangem.feature.tester.presentation.storybook.page.ds.checkmark
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.ds2.checkbox.TangemCheckmark
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.tester.presentation.storybook.entity.TangemCheckmarkStory
@Composable
internal fun TangemCheckmarkStory(state: TangemCheckmarkStory, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize()
.background(TangemTheme.colors.background.primary)
.padding(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
// Preview stays pinned at the top.
ComponentPreview(state = state)
// Only the controls scroll.
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Toggles(state = state)
}
}
}
@Composable
private fun ComponentPreview(state: TangemCheckmarkStory) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors3.bg.primary)
.padding(vertical = 48.dp),
) {
// Clicking the live checkmark toggles checked on/off.
TangemCheckmark(
checked = state.isChecked,
onCheckedChange = state.onCheckedChange,
isEnabled = state.isEnabled,
modifier = Modifier.scale(2f),
)
}
}
@Composable
private fun Toggles(state: TangemCheckmarkStory) {
Section(label = "Flags") {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
ToggleRow(
label = "checked",
checked = state.isChecked,
onToggle = { state.onCheckedChange(!state.isChecked) },
)
ToggleRow(label = "isEnabled", checked = state.isEnabled, onToggle = state.onEnabledToggle)
}
}
}
@Composable
private fun Section(label: String, content: @Composable () -> Unit) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
text = label,
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
content()
}
}
@Composable
private fun ToggleRow(label: String, checked: Boolean, onToggle: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(TangemTheme.colors2.surface.level2)
.clickable(onClick = onToggle)
.padding(horizontal = 12.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = label,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = if (checked) "ON" else "OFF",
style = TangemTheme.typography.caption2,
color = if (checked) TangemTheme.colors.text.accent else TangemTheme.colors.text.secondary,
)
}
}

View file

@ -39,6 +39,8 @@ import com.tangem.feature.tester.presentation.storybook.page.deviceicon.DeviceIc
import com.tangem.feature.tester.presentation.storybook.page.ds.DsComponentsListStory
import com.tangem.feature.tester.presentation.storybook.page.ds.badge.TangemBadgeV2Story
import com.tangem.feature.tester.presentation.storybook.page.ds.button.TangemButtonStory
import com.tangem.feature.tester.presentation.storybook.page.ds.checkbox.TangemCheckboxV2Story
import com.tangem.feature.tester.presentation.storybook.page.ds.checkmark.TangemCheckmarkStory
import com.tangem.feature.tester.presentation.storybook.page.ds.fade.TangemFadeStory
import com.tangem.feature.tester.presentation.storybook.page.ds.loader.TangemLoaderStory
import com.tangem.feature.tester.presentation.storybook.page.ds.row.TangemRowStory
@ -92,6 +94,8 @@ internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier)
is TangemLoaderStory -> TangemLoaderStory(state = storyState)
is TangemButtonStory -> TangemButtonStory(state = storyState)
is TangemBadgeV2Story -> TangemBadgeV2Story(state = storyState)
is TangemCheckboxV2Story -> TangemCheckboxV2Story(state = storyState)
is TangemCheckmarkStory -> TangemCheckmarkStory(state = storyState)
is TangemRowStory -> TangemRowStory(state = storyState)
is TangemSearchStory -> TangemSearchStory(state = storyState)
is TangemShimmerStory -> TangemShimmerStory(state = storyState)