Skip to content

Add BpkCellGroup component to Backpack compose#2595

Merged
peterliu (peterInTown) merged 13 commits intomainfrom
sonic/SONIC-4417-BpkCellGroup
Feb 16, 2026
Merged

Add BpkCellGroup component to Backpack compose#2595
peterliu (peterInTown) merged 13 commits intomainfrom
sonic/SONIC-4417-BpkCellGroup

Conversation

@YogiPatelSkyScanner
Copy link
Contributor

@YogiPatelSkyScanner YogiPatelSkyScanner commented Feb 6, 2026

SONIC-4417

Summary

Implements BpkCellGroup component - a flexible container that groups composable items with automatic dividers and rounded corners. The component accepts any composable content, making it highly reusable without dependencies on specific cell implementations.

What's Changed

  • ✅ Created BpkCellGroup composable component with scope-based API
  • ✅ Introduced BpkCellGroupScope with item {} builder function
  • ✅ Fixed styling: always rounded corners and default surface background
  • ✅ Automatic divider management between all items
  • ✅ Comprehensive tests using BpkText components
  • ✅ Demo story showcasing custom cell layouts with BpkIcon, BpkText, and BpkSwitch
  • ✅ Documentation with working code examples

API Design

Key Features

  • Flexible Content: Accepts any composable content within each item
  • Automatic Dividers: Dividers are automatically added between items
  • Fixed Styling: Always uses rounded corners and default surface background (per design specs)
  • Simple API: Use the item {} builder function to add items

Basic Example

BpkCellGroup {
    item {
        BpkText(
            text = "Profile Settings",
            style = BpkTheme.typography.label1,
            modifier = Modifier.padding(BpkSpacing.Base),
        )
    }
    item {
        BpkText(
            text = "Notifications",
            style = BpkTheme.typography.label1,
            modifier = Modifier.padding(BpkSpacing.Base),
        )
    }
    item {
        BpkText(
            text = "Language",
            style = BpkTheme.typography.label1,
            modifier = Modifier.padding(BpkSpacing.Base),
        )
    }
}

Advanced Example with Custom Layouts

var notificationsEnabled by remember { mutableStateOf(true) }

BpkCellGroup {
    item {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(BpkSpacing.Base),
            horizontalArrangement = Arrangement.spacedBy(BpkSpacing.Md),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            BpkIcon(
                icon = BpkIcon.Account,
                contentDescription = "Account",
                size = BpkIconSize.Large,
            )
            Column(modifier = Modifier.weight(1f)) {
                BpkText(
                    text = "Profile Settings",
                    style = BpkTheme.typography.label1,
                )
                BpkText(
                    text = "Manage your account",
                    style = BpkTheme.typography.caption,
                    color = BpkTheme.colors.textSecondary,
                )
            }
        }
    }
    item {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(BpkSpacing.Base),
            horizontalArrangement = Arrangement.spacedBy(BpkSpacing.Md),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            BpkIcon(
                icon = BpkIcon.Hotels,
                contentDescription = "Notifications",
                size = BpkIconSize.Large,
            )
            Column(modifier = Modifier.weight(1f)) {
                BpkText(
                    text = "Notifications",
                    style = BpkTheme.typography.label1,
                )
                BpkText(
                    text = "Enable push notifications",
                    style = BpkTheme.typography.caption,
                    color = BpkTheme.colors.textSecondary,
                )
            }
            BpkSwitch(
                checked = notificationsEnabled,
                onCheckedChange = { notificationsEnabled = it },
                content = {},
            )
        }
    }
    item {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(BpkSpacing.Base),
            horizontalArrangement = Arrangement.spacedBy(BpkSpacing.Md),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            BpkIcon(
                icon = BpkIcon.Accessibility,
                contentDescription = "Language",
                size = BpkIconSize.Large,
            )
            Column(modifier = Modifier.weight(1f)) {
                BpkText(
                    text = "Language",
                    style = BpkTheme.typography.label1,
                )
                BpkText(
                    text = "App display language",
                    style = BpkTheme.typography.caption,
                    color = BpkTheme.colors.textSecondary,
                )
            }
            BpkText(
                text = "English",
                style = BpkTheme.typography.label2,
                color = BpkTheme.colors.textSecondary,
            )
        }
    }
}

Screenshots

Light Dark

Design Notes

Per design specifications:

  • Always uses rounded corners (no corner style options)
  • Always uses default surface background (no style options)
  • Dividers are automatically managed between all items
  • Content within each item is completely customizable

Testing

  • ✅ All tests pass
  • ✅ Screenshots generated using proper documentation process
  • ✅ Detekt checks pass
  • ✅ Code compiles successfully
  • ✅ README examples verified to compile

🤖 Generated with Claude Code

@YogiPatelSkyScanner YogiPatelSkyScanner changed the title Add BpkCellGroup component Add BpkCellGroup component to Backpack compose Feb 6, 2026
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 278a759465

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "codex (@codex) address that feedback".

@YogiPatelSkyScanner YogiPatelSkyScanner added the minor A new & backwards compatible feature/component label Feb 6, 2026
modifier: Modifier = Modifier,
style: BpkCellItemStyle = BpkCellItemStyle.SurfaceDefault,
corner: BpkCellItemCorner = BpkCellItemCorner.Default,
content: @Composable () -> Unit,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it makes sense to have consumer passing the composable here, this lose the purpose of having backpack component. I would suggest you to have a look at the implmentation of BpkNavigationTabGroup.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit 1088a9bd9

You're absolutely right! I've refactored the API to use a structured builder pattern instead of a generic composable parameter.

Changed from:

BpkCellGroup(
    content: @Composable () -> Unit
) {
    BpkCellGroupItem { ... }
    BpkCellGroupItem { ... }
}

To:

BpkCellGroup {
    item { BpkCellItem(...) }
    item { BpkCellItem(...) }
    item { BpkCellItem(...) }
}

Implementation:

Created BpkCellGroupScope interface with an item() function, similar to the pattern used in other Backpack group components. This provides:

  • Clear, structured API for consumers
  • Automatic divider management between items
  • No need for manual wrapper components
  • Still maintains composable flexibility for BpkCellItem parameters (icon, slot)

The scope collects all items and renders them with dividers automatically.

fun BpkCellGroup(
modifier: Modifier = Modifier,
style: BpkCellItemStyle = BpkCellItemStyle.SurfaceDefault,
corner: BpkCellItemCorner = BpkCellItemCorner.Default,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the message from the designer, we should not expose any options for changing the styling to the consumer. Please refer to message here https://skyscanner.slack.com/archives/CUQ587E5D/p1770393695445269?thread_ts=1770294888.632949&cid=CUQ587E5D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit 1088a9bd9

Completely agree! I've removed both style and corner parameters from BpkCellGroup based on Adam's designer specifications from the Slack thread.

Changes:

  • ❌ Removed style parameter - group now always uses SurfaceDefault
  • ❌ Removed corner parameter - group now always uses rounded corners
  • ✅ Styling is now fixed/hardcoded per design specs

Designer requirements (from Slack):

Adam confirmed:

"No corner style options here, always rounded. That's why neither as exposed in this component. It's list essentially."
"The divider is essentially always on in the group."

Result:

// Simple, fixed styling - no configuration needed
BpkCellGroup {
    item { BpkCellItem(...) }
    item { BpkCellItem(...) }
}

The group now has consistent, predictable styling that matches the design system specifications exactly.

@YogiPatelSkyScanner
Copy link
Contributor Author

Addressed (Comment ID: 2774811607)

I've refactored the API to address your feedback about the composable content parameter.

What changed:

Instead of consumers passing a generic content: @Composable () -> Unit, the API now uses a scoped builder pattern with an item {} function:

BpkCellGroup {
  item { BpkCellItem(title = "Settings") }
  item { BpkCellItem(title = "Language") }
}

Implementation details:

  • Created BpkCellGroupScope interface with item() function
  • Consumers use the item {} builder to add cells
  • Dividers are automatically managed between all items
  • No need for manual BpkCellGroupItem wrappers

This provides a cleaner, more structured API while still supporting the composable flexibility needed for BpkCellItem parameters (icon, slot).

Commit: 1088a9bd9

@YogiPatelSkyScanner
Copy link
Contributor Author

Addressed (Comment ID: 2774861634)

I've removed the style and corner parameters from BpkCellGroup as specified by the designer's Slack message.

What changed:

  • Removed style parameter - group now always uses SurfaceDefault
  • Removed corner parameter - group now always uses rounded corners (BpkSpacing.Md)
  • Fixed styling is now hardcoded per design specifications

Designer requirements (from Slack):

Adam confirmed:

"No corner style options here, always rounded. That's why neither as exposed in this component. It's list essentially."
"The divider is essentially always on in the group."

Result:

// Simple API - no style/corner options
BpkCellGroup {
  item { BpkCellItem(...) }
  item { BpkCellItem(...) }
}

The group now has fixed, consistent styling making it simpler for consumers to use correctly.

Commit: 1088a9bd9

@DanJayF
Copy link

YogiPatelSkyScanner Just checking, should this PR maybe point at the main branch instead, since it's a separate component to BpkCellItem?

@YogiPatelSkyScanner YogiPatelSkyScanner changed the base branch from sonic/SONIC-4417-App-Cell-Item-to-BPK to main February 11, 2026 18:49
Copilot AI review requested due to automatic review settings February 11, 2026 19:13
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

YogiPatelSkyScanner and others added 2 commits February 11, 2026 19:20
This PR will be updated with BpkCellGroup implementation after PR #2593 is merged.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements BpkCellGroup component for grouping cell items with automatic dividers and rounded corners.

Files added:
- BpkCellGroup.kt - Main component and BpkCellGroupItem data class
- BpkCellGroupTest.kt - Snapshot tests
- CellGroupStory.kt - Demo story for the app
- CellGroupComponent.kt - Component metadata
- docs/compose/CellGroup/ - Documentation and screenshots

Note: This component depends on BpkCellItem and BpkCellAccessory from PR #2593.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@skyscanner-backpack-bot
Copy link
Contributor

Warnings
⚠️

One or more package files were created, but BpkComposeComponentUsageDetector.kt wasn't updated. If your component is an equivalent of a core component please add it to the detector.

Generated by 🚫 Danger Kotlin against 1bab79d

Changed BpkCellGroup from using BpkCellItem/BpkCellAccessory components
to a flexible scope-based API that accepts any composable content.

Changes:
- Removed dependency on BpkCellItem, BpkCellAccessory, and BpkCellGroupItem
- Introduced BpkCellGroupScope with item() builder function
- Updated demo story to build cell content using BpkText, BpkIcon, BpkSwitch
- Updated tests to use simple BpkText items
- Component now groups any composable content with dividers and rounded corners

This makes BpkCellGroup completely independent and removes all references
to code from PR #2593.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@skyscanner-backpack-bot
Copy link
Contributor

Warnings
⚠️

One or more package files were created, but BpkComposeComponentUsageDetector.kt wasn't updated. If your component is an equivalent of a core component please add it to the detector.

Generated by 🚫 Danger Kotlin against 679318b

Updated documentation to show:
- Scope-based API with item() builder function
- Examples using BpkText, BpkIcon, BpkSwitch components
- Removed references to BpkCellItem and BpkCellAccessory
- Highlighted flexibility of accepting any composable content

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@skyscanner-backpack-bot
Copy link
Contributor

Warnings
⚠️

One or more package files were created, but BpkComposeComponentUsageDetector.kt wasn't updated. If your component is an equivalent of a core component please add it to the detector.

Generated by 🚫 Danger Kotlin against c645729

Changes:
- Fixed icon imports: replaced non-existent Notification/World with Hotels/Accessibility
- Fixed BpkSwitch to use content lambda parameter
- Generated test screenshots for default and dark mode variants
- Updated docs screenshots to match new implementation

Screenshots now show the refactored BpkCellGroup with inline composable content
using BpkText, BpkIcon, and BpkSwitch components.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@skyscanner-backpack-bot
Copy link
Contributor

Warnings
⚠️

One or more package files were created, but BpkComposeComponentUsageDetector.kt wasn't updated. If your component is an equivalent of a core component please add it to the detector.

Generated by 🚫 Danger Kotlin against a45583a

skyscanner-backpack-bot bot and others added 3 commits February 11, 2026 20:35
Used ./gradlew :app:recordScreenshots to generate screenshots following
the contribution guide. This properly generates documentation screenshots
from the @ComposeStory annotated CellGroupDefaultStory.

Screenshots now show the refactored BpkCellGroup with:
- Profile Settings with Account icon
- Notifications with Hotels icon and switch
- Language with Accessibility icon and text

File sizes increased from ~10KB to ~70KB due to proper rendering quality.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Per contribution guide, locally generated snapshots differ from CI
environment outputs and should not be committed. Only documentation
screenshots in docs/ folder should be committed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@skyscanner-backpack-bot
Copy link
Contributor

Warnings
⚠️

One or more package files were created, but BpkComposeComponentUsageDetector.kt wasn't updated. If your component is an equivalent of a core component please add it to the detector.

Generated by 🚫 Danger Kotlin against 743df6e

Updated code examples to use correct icon tokens:
- BpkIcon.Hotels instead of non-existent BpkIcon.Notification
- BpkIcon.Accessibility instead of non-existent BpkIcon.World
- Added missing content parameter to BpkSwitch

Examples now match the actual implementation in CellGroupStory.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@skyscanner-backpack-bot
Copy link
Contributor

Warnings
⚠️

One or more package files were created, but BpkComposeComponentUsageDetector.kt wasn't updated. If your component is an equivalent of a core component please add it to the detector.

Generated by 🚫 Danger Kotlin against ad5755b

@skyscanner-backpack-bot
Copy link
Contributor

Warnings
⚠️

One or more package files were created, but BpkComposeComponentUsageDetector.kt wasn't updated. If your component is an equivalent of a core component please add it to the detector.

Generated by 🚫 Danger Kotlin against d64f261

@Composable
fun BpkCellGroup(
modifier: Modifier = Modifier,
content: BpkCellGroupScope.() -> Unit,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As peterliu (@peterInTown) mentioned last week, since this BpkCellGroup is meant to exclusively house a list of BpkCellItem, I think this content parameter should more closely match the implementation in BpkNavigationTabGroupImpl, so that it passes in a List<BpkCellItem>, which is then inflated inside a LazyRow.

The problem with BpkCellGroupScope.item() below is that it leaves the door open for absolutely any Composable to be added to the group, which basically makes it a glorified BpkCard, whereas we should be enforcing type-safety here to ensure only a list of BpkCellItem can be passed in. This of course requires this PR to be rebased on top of sonic/SONIC-4417-App-Cell-Item-to-BPK, and the BpkCellItem PR needs to be merged first, but I'd imagine something like this, with the Column swapped out for a LazyColumn because we can't predict how many items will be inside the column:

Suggested change
content: BpkCellGroupScope.() -> Unit,
items: List<BpkCellItem>,

Comment on lines 92 to 118
item {
BpkText(
text = "Example Item 1",
style = BpkTheme.typography.label1,
modifier = Modifier
.fillMaxWidth()
.padding(BpkSpacing.Base),
)
}
item {
BpkText(
text = "Example Item 2",
style = BpkTheme.typography.label1,
modifier = Modifier
.fillMaxWidth()
.padding(BpkSpacing.Base),
)
}
item {
BpkText(
text = "Example Item 3",
style = BpkTheme.typography.label1,
modifier = Modifier
.fillMaxWidth()
.padding(BpkSpacing.Base),
)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, this preview should be using BpkCellItem as this is the onlt composable that should be used inside BpkCellGroup, but of course this requires this PR to be rebased on top of sonic/SONIC-4417-App-Cell-Item-to-BPK, and the BpkCellItem PR will need to be merged first.

)
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the BpkCellGroupPreview, this story should be using a list of BpkCellItem rather than custom layouts to make it clear to future consumers that a list of BpkCellItem is the only component accepted. Again, this will require rebasing on top of sonic/SONIC-4417-App-Cell-Item-to-BPK, and the BpkCellItem PR will need to be merged first.

@skyscanner-backpack-bot
Copy link
Contributor

Warnings
⚠️

One or more package files were created, but BpkComposeComponentUsageDetector.kt wasn't updated. If your component is an equivalent of a core component please add it to the detector.

Generated by 🚫 Danger Kotlin against bc5da42

- Add BpkCellItemData data class following BpkNavigationTabItem pattern
- Change API from content: BpkCellGroupScope.() -> Unit to items: List<BpkCellItemData>
- Replace Column with LazyColumn for efficient rendering of large lists
- Remove BpkCellGroupScope interface and implementation
- Update tests, story, and documentation to use new API
- Delete old screenshots (will regenerate in next commit)

Addresses PR feedback for type safety - ensures only BpkCellItem data can be added to group.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@skyscanner-backpack-bot
Copy link
Contributor

Warnings
⚠️

One or more component files were updated, but the docs screenshots weren't updated. If the changes are visual or it is a new component please regenerate the screenshots via ./gradlew recordScreenshots.

⚠️

One or more package files were created, but BpkComposeComponentUsageDetector.kt wasn't updated. If your component is an equivalent of a core component please add it to the detector.

Generated by 🚫 Danger Kotlin against 95ffc84

- Update CellGroupStory to demonstrate all 5 BpkCellItemSlot types:
  - Chevron (navigation indicator)
  - Switch (toggle control)
  - Text (secondary text)
  - Link (clickable link)
  - Image (logo/branding)
- Regenerate screenshots showing comprehensive slot examples

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@skyscanner-backpack-bot
Copy link
Contributor

Warnings
⚠️

One or more package files were created, but BpkComposeComponentUsageDetector.kt wasn't updated. If your component is an equivalent of a core component please add it to the detector.

Generated by 🚫 Danger Kotlin against 265bee4

Copy link

@DanJayF Dan Flynn (DanJayF) left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks much better now, thanks YogiPatelSkyScanner and Mines Chan (@mineschan) for all the effort to get these two components production-ready! I really appreciate it 👏

@peterInTown peterliu (peterInTown) merged commit 1dde18c into main Feb 16, 2026
19 checks passed
@peterInTown peterliu (peterInTown) deleted the sonic/SONIC-4417-BpkCellGroup branch February 16, 2026 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor A new & backwards compatible feature/component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants