tangem-app-android-audited/.claude/skills/add-storybook-component/SKILL.md
2026-06-10 18:21:20 +03:00

7.9 KiB
Raw Blame History

name description allowed-tools argument-hint
add-storybook-component 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. Read, Grep, Glob, Bash, Edit, Write
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.ktbuildDsStories() page/ds/<component>/ DsStoryBookPage
2. Other components Anything else (legacy/cross-cutting components, backgrounds, effects, typography demos) ui/StoryBookListScreen.ktbuildStories() 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 enums (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):

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:

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):

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):

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():
    DsStoryItem(title = "🔘 TangemFoo", factory = tangemFooStoryFactory),
    
  • Target 2 (other): in ui/StoryBookListScreen.kt add the factory import and a row to buildStories():
    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

./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.