The general-purpose dialog — an optional illustration, a centred title and description, and one or two actions.
EBTransactionReceipt. That is exactly what the 2026 rebuild did — the receipt layouts are now Modal - Transaction Receipt, and what remains here is the dialog on its own. The _space_* spacer rectangles are gone, the hardcoded placeholder circle is a real ⤷ IconSlot, and the mixed-casing enums are one clean ActionOrientation. Nothing is outstanding on the component itself; Code Connect stays open because the native library does not exist yet.Contexts are illustrative. Final screens will reference actual GCash patterns.
Add description here.
Container, Content, ⤷ IconSlot, ⤷ ActionSlot, #title and #description all match the conventions settled across Date Picker, Voucher and Countdown. hasIcon follows the boolean prefix already used by Ad Carousel's hasCTA.Button_New, the same component the receipt modal now uses.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| ActionOrientation=Vertical | N/A | N/A | 320 × 370 | Two full-width buttons stacked, primary on top. The action area is 156 tall. |
| ActionOrientation=Horizontal | N/A | N/A | 320 × 312 | Two 132-wide buttons side by side, primary on the right. Everything above the action area is unchanged. |
| hasIcon=true | N/A | N/A | default | Shows the 92 × 92 illustration slot with its Placeholder instance. |
| hasIcon=false | N/A | N/A | collapsed | Collapses the slot and the 16px below it, taking 108 off the height. |
- The receipt layouts moved out. The first assessment flagged this set for compressing a general-purpose dialog and a transaction receipt into one component with different information architectures and different native mappings.
transaction_v1andtransaction_v2are now Modal - Transaction Receipt, and the row inside it is Modal - Transaction Receipt Entry. C4 · Native Mappability - The spacer rectangles are gone. The old build used opacity-0
_space_*rectangles to fake gaps. Spacer nodes have no native equivalent — auto layout spacing does the job — and the rebuild removed them. C1 · Layer Structure & Naming - The placeholder circle is a real slot. The illustration used to be a hardcoded circle drawn into the component. It is now
⤷ IconSlot, a 92 × 92 slot holding aPlaceholderinstance that consumers swap. C6 · Asset & Icon Quality - The illustration can be switched off. The slot was empty and always on, so every dialog reserved 116px for an illustration whether or not it had one, with no way to collapse it. A
hasIconboolean now controls it, defaulting to true, and a Placeholder gives consumers a starting point. C5 · Interaction State Coverage - The enums are one clean property. The old set mixed casing and styles across two axes —
transaction_v1alongside1 - vertical— with a sparse CTA matrix. What is left isActionOrientation = Vertical | Horizontal, renamed during this pass to close the space it used to carry. C2 · Variant & Property Naming - Both modals share one button. This component was on
Button_Newwhile the receipt modal was still onButton - Large/Medium— two masters with different internal structure in one family. The receipt moved across, so the label is addressable as#labelin both. C4 · Native Mappability - The BarkAda description is deliberate. The first assessment left this open as a question for design. Confirmed: BarkAda is the secondary face, used for longer copy at smaller sizes, while Proxima Soft carries titles, labels and shorter or larger text. That is why this description is BarkAda 14/20 and the receipt modal's is Proxima Soft 16/20 — a rule, not drift. C3 · Token Coverage
- The Container wrapper earns its place. It has the same bounds and radius as the variant frame around it, which usually means a level that can be removed. Confirmed that it holds the auto layout, so it stays. C1 · Layer Structure & Naming
- Centred here, left-aligned in the receipt.
#titleand#descriptionare centred in this component and left-aligned in Modal - Transaction Receipt. Confirmed as intentional: a confirmation dialog centres, and a receipt is left-aligned tabular data. C1 · Layer Structure & Naming
- Tidy the Placeholder's own layer names. The instance filling
⤷ IconSlotwraps a lowercasecontainerframe. It belongs to the Placeholder component rather than to Modal, so it is not a fault here, but it is the same PascalCase convention the rest of the family has adopted and worth passing upstream. Docs - Give the dialog a loading state. Confirmations that trigger a network call leave the primary button with nowhere to go while it waits. The first assessment raised this and it is still true. Whether it lives on the button or on the modal is a Button family decision, so it belongs there rather than here. State
- Publish the token names once Dev Mode is read. Fills resolve to library variables and the text carries shared library styles — verified on the component's own nodes, and
#descriptionshares its variable with the receipt modal's row labels. The read-only tools return IDs rather than names, so the spec tables carry hex values only. Token - Pair it with Overlay in documentation. This component is the card. The scrim is Overlay. That split resolved the duplicate-scope flag from the first assessment, but it only works if both pages say so — otherwise the next person builds a scrim into the card again. Docs
- See siblings: Modal - Transaction Receipt is the receipt half that was split out of this component, and Modal - Transaction Receipt Entry is the row inside it. Overlay supplies the scrim. Bottom Sheet is the non-blocking alternative. Family
Add description here.
Eight roles, identical in both orientations — the action area changes shape, not colour. Four belong to this component; the other four are the appearance/* set, which is the Button’s own variable mode arriving through ⤷ ActionSlot rather than anything the modal sets. The 92 × 92 circle in the icon slot is a placeholder: real usage swaps it, and the illustration brings its own colour.
| Role | Token | Value |
|---|---|---|
| Surface | bg/color-bg-main | #FFFFFF |
| #title | text/color-text | #0A2757 |
| #description | text/color-text-weaker | #6780A9 |
| IconSlot placeholder | border/color-border | #D7E0EF |
| ActionSlot · filled fill | appearance/container/fill | #005CE5 |
| ActionSlot · filled label | appearance/label/color | #FFFFFF |
| ActionSlot · outlined stroke | appearance/stroke/color | #005CE5 |
| ActionSlot · outlined label | appearance/label/on-surface/color | #005CE5 |
Add description here.
iOS — Swift Package Manager
.package(url: "https://github.com/AY-Org/eb-ds-ios", from: "1.0.0")
Android — Gradle (Kotlin DSL)
implementation("com.eastblue.ds:modal:1.0.0")
Import
import EastBlueDS import com.eastblue.ds.modal.*
Planned API — the native library does not exist yet. The artifact is the Modal family: this component, <a href="/components/modal-transaction-receipt">Modal - Transaction Receipt</a> and <a href="/components/modal-transaction-receipt-entry">its Entry</a> all ship in <code>com.eastblue.ds:modal</code> and import <code>com.eastblue.ds.modal.*</code>.
Five properties, in the order the Figma property panel lists them. title and description have no rows because #title and #description are text layers rather than component properties — a designer sets them by overriding the layer. Each boolean keeps its own parameter beside the slot it gates rather than the slot being inferred from a nil, so every Figma property traces 1:1 for Code Connect. actionOrientation is a parameter in both languages rather than a SwiftUI modifier: it changes the layout of the action area, not the component’s appearance.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| ActionOrientation — Vertical, Horizontal | actionOrientation: EBActionOrientation | actionOrientation: EBActionOrientation |
| hasDescription — true, false | hasDescription: Bool = true | hasDescription: Boolean = true |
| hasIcon — true, false | hasIcon: Bool = true | hasIcon: Boolean = true |
| ⤷ IconSlot (slot) | @ViewBuilder icon: () -> Icon — shown when hasIcon | icon: @Composable (() -> Unit)? — shown when hasIcon |
| ⤷ ActionSlot (slot) | @ViewBuilder actions: () -> Actions | actions: @Composable () -> Unit |
EBModal( title: "Delete this recipient?", description: "You can add them again at any time.", actionOrientation: .vertical )
EBModal( title = "Delete this recipient?", description = "You can add them again at any time.", actionOrientation = EBActionOrientation.Vertical )
EBModal( title: "Delete this recipient?", description: "You can add them again at any time.", actionOrientation: .horizontal )
EBModal( title = "Delete this recipient?", description = "You can add them again at any time.", actionOrientation = EBActionOrientation.Horizontal )
| Requirement | iOS | Android |
|---|---|---|
| Dialog announced on present | .accessibilityAddTraits(.isModal), focus moves to #title | Dialog with Modifier.semantics { paneTitle = title } |
| Focus is trapped while open | .accessibilityFocused held inside the dialog | Dialog traps focus by default |
| Illustration is decorative | .accessibilityHidden(true) unless it carries meaning | contentDescription = null unless it carries meaning |
| Primary action is reachable first | Primary precedes secondary in the accessibility order in both orientations | Primary precedes secondary in the traversal order in both orientations |
| Dismissal has a non-visual path | Escape gesture maps to the secondary action | Back press maps to the secondary action |
Do
Use vertical actions when both labels are long or the secondary action matters.
Don't
Don't use horizontal actions with labels that will truncate at 132px.
Do
Switch hasIcon off when there is no illustration to show.
Don't
Don't leave the slot on and empty — it reserves 108px of nothing.
Do
Pair the modal with Overlay for the scrim.
Don't
Don't build a scrim into the card — this component is the card only.
Do
Use Modal - Transaction Receipt for receipts.
Don't
Don't rebuild a receipt inside this component; that split is what the restructure was for.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Ready | Container, Content, #title and #description all follow the family convention. The spacer rectangles from the first assessment are gone, and the Container wrapper is confirmed as holding the auto layout. |
| C2 | Variant & Property Naming | Ready | ActionOrientation closed its space this pass. hasIcon follows the boolean prefix convention and stays off the variant name. |
| C3 | Token Coverage | Ready | Every colour is bound and named. Four belong to this component — bg/color-bg-main, text/color-text, text/color-text-weaker and border/color-border on the 92 × 92 placeholder. The other four are the appearance/* set, the Button’s own variable mode arriving through ⤷ ActionSlot. All three text layers resolve, and Modal is the one component in this family that uses both faces: Primary/Headlines/Section for the title, Secondary/Default/Base for the description. |
| C4 | Native Mappability | Ready | Maps cleanly to a dialog with two view-builder slots. The whole family shares one action pattern — two Button_New instances in a ⤷ ActionSlot with 24px padding and an 8px gap, flipping between 156 tall stacked and 98 side by side — so the same actionOrientation parameter serves this component and Modal - Transaction Receipt alike. The one gap is that the two strings are text layers rather than properties; see C7. |
| C5 | Interaction State Coverage | Ready | Button states come from Button_New. hasIcon covers the with and without cases. A loading state belongs to the Button family rather than here. |
| C6 | Asset & Icon Quality | Ready | The hardcoded placeholder circle is now a real slot with a Placeholder instance, and the raster copy icon left with the receipt layouts. |
1 component set × 2 ActionOrientation values = 2 variants. hasDescription and hasIcon are boolean component properties, so they switch the description and the illustration without multiplying the set, and both slots take their content by swap rather than by variant.
| ActionOrientation | Size | Action area | Buttons | Node |
|---|---|---|---|---|
| Vertical | 320 × 370 | 320 × 156 | 2 × 272 × 50, filled on top | 5879:41275 |
| Horizontal | 320 × 312 | 320 × 98 | 2 × 132 × 50, filled on the right | 5879:41276 |
ActionOrientation is the driving property, so Vertical and Horizontal each get a card, in this panel’s order — which runs the opposite way to Modal - Transaction Receipt’s, so the two pages list their cards differently on purpose.hasDescription was undocumented. hasIcon was already on the page; the second boolean was not, though the Figma panel carries both defaulting to True. Nothing changed in Figma — get_node_info cannot read property definitions.#title is Primary/Headlines/Section and #description is Secondary/Default/Base — Proxima Soft over BarkAda. The action label is Primary/Label/Large. The page had carried font specs and a "names pending Dev Mode read" IOU.bg/color-bg-main, text/color-text, text/color-text-weaker, and border/color-border on the 92 × 92 placeholder — read by exporting the icon slot as a vector, which returns the circle with its fill. The other four are the appearance/* set, the Button’s own variable mode arriving through ⤷ ActionSlot..eb-preview-mdl declared font-family: inherit, which resolves to the site’s --font-body — BarkAda. That made #description look right by accident and #title silently wrong. The root now names Proxima Soft and the description names BarkAda, so each layer states its own face. Modal is the component that proves this cannot be a find-and-replace: a blanket sweep to Proxima Soft would have broken its description.gcash/east-blue-ios and com.gcash.eastblue:components:1.0.0, with no Import line. It now cites the Modal family artifact com.eastblue.ds:modal:1.0.0 and imports com.eastblue.ds.modal.*, completing the family on one artifact.hasIcon and ⤷ IconSlot both read icon: Image?, which collapses the switch and the content it reveals into one thing and leaves the boolean with nothing of its own to trace to. The boolean now gates a @ViewBuilder icon, matching how the receipt handles hasReferenceNo and its strip.hasDescription had no row, and #title and #description are text layers. Six rows became five, one per panel property, with both strings named in the description instead.#title and #description are text layers rather than component properties, Code Connect will have no anchor for them when the native library lands. The five properties that do exist all trace 1:1. Recorded against C7, which was already open on registration..ebActionOrientation(...) — a SwiftUI modifier — against its own Compose column’s parameter. Settled on the parameter, the third time in this family and for the same reason: it changes the layout of the action area, not the component’s appearance.ActionOrientation value.primary is not a term this component’s API uses. Checked against the nodes — the outlined button sits left and the filled right — and reworded to filled, which is what the button actually is.⤷ ActionSlot with 24px padding and an 8px gap, flipping between 156 tall stacked and 98 side by side — identical in this component and in the receipt, which is what lets one actionOrientation parameter serve both. C3’s note also named no tokens before; it now carries all four.getSnippet, so both language tabs were frozen on a static string; they now track both booleans._space_* spacer rectangles removed and the hardcoded placeholder circle replaced with ⤷ IconSlot.transaction_v1, transaction_v2 and 1 - vertical collapsed into ActionOrientation = Vertical | Horizontal, renamed this pass to close the space it carried.hasIcon added, defaulting to true, with a Placeholder instance in the slot — the illustration can now be switched off instead of reserving 108px of empty space.shape_half and shape_full PNGs left with the receipt layouts, where the copy control is now a vector instance.Button_New across the family, so the label is addressable as #label in both modals.18507:71705 (Sticker Sheets v2) to 5879:41278 (2026 Working File).47:329691) are maintained independently but overlap in intent. Family consolidation required. Open_space_* spacer rectangles + hardcoded icon-placeholder circle. Opentransaction_v1 vs 1 - vertical). Sparse CTA matrix. OpenEBTransactionReceipt. Openshape_half / shape_full PNGs should be a vector icon instance. OpenBarkAda (secondary font). Confirm with design — otherwise covered by the standing custom-font action item. Info