A 336×111 horizontal voucher card with text content on the left and a perforated image frame on the right; ships in four state variants (limited, expiring, used, expired).
5119:1635), and Horizontal Voucher (5121:4533) are three parallel records of the same component. This one carries the canonical state coverage the others lack — state: limited | expiring | used | expired. The consolidation target is a single Voucher Card with orientation: vertical | horizontal × state (5 values if a default is added), text Slots for title / price / originalPrice / validity, a logo Slot for partner branding, and a composable badges array replacing the hardcoded state-to-badge mapping. Push the merge from here, not from the other siblings.main/vouchers/color/default/* and main/vouchers/color/expired/*). Typography uses named text styles (Primary/Multi-line Label/Base, Primary/Label/Small, Secondary/Bold/Small Caption). The card does carry its own layout and state treatment — but the logo raster, the "GET VOUCHER" CTA text, and the validity date are all frozen.state as a variant axis — the other two voucher-card siblings (Vertical Voucher, Horizontal Voucher) do not. But the three components ship as three separate records with divergent property shapes. Family-level inconsistency.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| State | N/A | N/A | state enum (4) | Drives bg, label colors, partner-image treatment, and corner badge. Add a default fifth state for vouchers without a status callout. |
| Title | N/A | N/A | Hardcoded "Buy Load Globe Go90" | No property; must be set via detach. |
| Price / original | N/A | N/A | crossedValue boolean (strings hardcoded) | "PHP 50.00" and "PHP 90.00" frozen; boolean only toggles visibility of the strikethrough. |
| Validity | N/A | N/A | Hardcoded "Validity: Dec 25 2022 - Jan 5 2023" | No property; must be set via detach. |
| Status badge | N/A | N/A | badge boolean + state-derived text | "Limited" / "Expiring" / "Used" / "Expired" are derived from state. Consumers cannot set their own badge text. |
| Partner logo | N/A | N/A | Raster GCash asset | No slot — logo is baked into the partner-image frame. |
| CTA | N/A | N/A | "GET VOUCHER" rotated text (non-interactive) | Card is the tap target; the rotated text is decorative, not a Button instance. |
| Tap state | N/A | N/A | Not modelled | No pressed/focused/disabled on the card frame. |
- State axis is modelled correctly. Unlike Vertical Voucher and Horizontal Voucher, this component ships a proper
statevariant driving background, label colors, partner-image tint, and badge style. The 4-value enum (limited / expiring / used / expired) is the canonical shape to port to the unified Voucher Card. C5 · Interaction State Coverage - Voucher color tokens are bound. Background (
main/vouchers/color/default/bg,main/vouchers/color/expired/bg), title (default/label-title#0A2757,expired/label-title#445C85), amount (label-amount-horizontal#2340A9,expired/label-amount#6780A9), original amount (label-amount-original#90A8D0), and metadata (expired/label-metadata#6780A9) all resolve through the voucher component variable collection. C3 · Token Coverage - Badge instance is a DS component. The corner ribbon is a real Badge instance — styled via
main/badge/information/heavy/background,main/badge/negative/heavy/background, ormain/badge/muted/light/backgrounddepending on state. Composition works; only the label string is state-derived rather than user-settable. C7 · Code Connect Linkability - Shadow uses the app elevation token.
app/shadow/shadow-low(0 0 4 0 #020e220f) is applied to the card frame. C3 · Token Coverage
- Three parallel components for one concept. Voucher Card Horizontal, Vertical Voucher (
5119:1635), and Horizontal Voucher (5121:4533) are three separate records of the same component. This is a family-level consolidation — the unifiedVoucher Cardneedsorientation: vertical | horizontal×state: default | limited | expiring | used | expired= 10 variants, not three divergent symbols. C4 · Native Mappability - All text content is hardcoded. Title "Buy Load Globe Go90", price "PHP 50.00", original price "PHP 90.00", and validity "Validity: Dec 25 2022 - Jan 5 2023" are frozen strings inside every variant. Booleans
badgeandcrossedValueonly toggle visibility — they do not accept content. Consumers cannot render a real voucher without detaching. C2 · Variant & Property Naming - Badge text is state-derived, not independently settable. The corner ribbon label flips between "Limited" / "Expiring" / "Used" / "Expired" based on the
stateenum. A consumer who wants to show "New" or "Featured" on a limited voucher cannot — state and badge label are conflated. Split intostate(drives visual treatment) +badge(independent Slot/string). C2 · Variant & Property Naming - Partner logo is a raster GCash asset with no slot.
imgLogoNoText,imgGCashLogosV2RgbIconBwWhiteTransparent, andimgVoucherImageV1are raster image fills inside the 96×111 partner-image frame. Vouchers for Globe, Smart, GrabFood, or Shopee all render with the GCash logo. No logo Slot exists. C6 · Asset & Icon Quality - Duplicated partner-image subtrees per state.
voucher(used by limited/expiring) andVoucher Image V1(used by used/expired) are two complete parallel subtrees inside the same frame — differing only by background fill (bg/color-bg-primaryvsbg/color-bg-overlay-weak). Should be a single subtree with state-driven tokens. C1 · Layer Structure & Naming - "GET VOUCHER" rotated text is not a Button. The CTA label is a rotated
<p>inside the partner-image frame — not a Button instance, no pressed/focused/disabled state, no onTap handler semantics. If tapping the partner half is supposed to redeem the voucher, that needs to be an actual Button or the whole card needs to be the tap target. C5 · Interaction State Coverage - Perforated ticket edge is a raster mask.
imgPerforateis a raster image used as a mask to produce the perforated dashed edge between the content block and partner frame. Should be a vector path or an SVG mask; at 1× / 2× / 3× the raster will alias. C6 · Asset & Icon Quality - No default (neutral) state. Every variant renders a corner badge. A voucher that is simply available (neither limited nor expiring) has no option to render without a badge beyond setting
badge=false, which drops the callout but keeps the limited-state visual treatment. Add adefaultstate for active-but-unflagged vouchers. C5 · Interaction State Coverage - Two-boolean + 4-enum surface cannot map 1:1 to native. A proper
EBVoucherCard(orientation:, state:, title:, price:, originalPrice:, validity:, logo:, badge:, onTap:)shape has no 1:1 correspondence in the current component — title/price/originalPrice/validity/logo are all hardcoded. Code Connect linkability requires the family consolidation and property-ification first. C7 · Code Connect Linkability
- Merge the three voucher cards into a single Voucher Card component. Vertical Voucher + Horizontal Voucher + Voucher Card Horizontal collapse into one component with
orientation: vertical | horizontal×state: default | limited | expiring | used | expired= 10 variants. Port this component's state axis to the unified schema; port Vertical Voucher's content-block structure; drop Horizontal Voucher entirely. Family - Promote every text string to a property. Add
title: String,price: String,originalPrice: String?,validity: String?. Retire thecrossedValueboolean — visibility falls out of whetheroriginalPriceis set. Keep the text-style bindings intact. Property - Split
state(visual treatment) frombadge(label). Keepstateas the 5-value enum that drives bg / label colors / partner-image treatment. Exposebadge: EBBadge?as an independent Slot so consumers can pick any badge style and text ("New", "Featured", "Limited", custom). The current state-to-badge-text mapping becomes the default badge when none is supplied. Property - Adopt a Figma Slot for the partner logo. Replace the raster GCash asset with a 64×64 logo Slot inside the partner-image frame. Consumers instance-swap partner brand marks (Globe, Smart, GrabFood, Shopee, etc.) without detaching. Keep the perforated ticket shape and overlay treatment in the frame itself. Slot
- Collapse the two partner-image subtrees into one.
voucher(limited/expiring) andVoucher Image V1(used/expired) are duplicate layer trees differing only by background token. A single subtree gated by state-driven fills (main/vouchers/color/{state}/partner-bg) removes the duplication. Composition - Add a
defaultstate. Current states are all "flagged" — active-but-unflagged vouchers have no clean render. Addstate: defaultwith the active (non-greyed) treatment and no corner badge by default. State - Replace the perforated raster mask with a vector path. The perforated ticket edge should be an SVG path or a vector mask — not a raster image. Same treatment as recommended for Voucher Asset. Asset
- Make the card the tap target; remove the rotated "GET VOUCHER" text as a faux-button. The entire card is the semantic action ("redeem / open voucher details"). Document the handoff as
onTap; keep "GET VOUCHER" only as a decorative label if product still wants it visible, or drop it entirely. Docs - Add pressed / focused / disabled to the card frame. Vouchers are always tappable — the unified Voucher Card needs a pressed state variant (e.g. opacity 0.8 or scale 0.98), a focused state for keyboard navigation, and a disabled treatment for unavailable vouchers distinct from
expired. State - Rename the duplicated
Badgelayers per state. All four badge layers are namedBadgewith no variant-qualifying name. After the family merge, there should be a singlebadge-slotlayer; until then, name thembadge-limited/badge-expiring/badge-used/badge-expiredfor clarity. Rename
Active voucher card layout — image left, title + amounts + metadata right. Default state used in voucher catalogs.
Active voucher with full-color title, brand-blue amount, strike-through original price, and a soft drop shadow.
| Role | Token | Default |
|---|---|---|
| Surface bg | main/vouchers/color/default/bg | #FFFFFF |
| Title | main/vouchers/color/default/label-title | #0A2757 |
| Amount | main/vouchers/color/label-amount-horizontal | #2340A9 |
| Original amount | main/vouchers/color/default/label-amount-original | #90A8D0 |
| Shadow | shadow/color-shadow-soft | #020E22 @ 6% |
Slim variant when the voucher has no compared/strike-through price. Renders a single amount line.
Past-validity voucher with muted title to signal it is no longer redeemable.
| Role | Token | Default |
|---|---|---|
| Surface bg | main/vouchers/color/expired/bg | #FFFFFF |
| Title | main/vouchers/color/expired/label-title | #445C85 |
Current shape is 4 state variants + 2 booleans with hardcoded content. The table below shows the target shape after the family consolidation — each row captures what the proposed EBVoucherCard replaces.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| — | orientation | orientation: EBVoucherOrientation |
state (4 values) | state (5 values) | state: EBVoucherState |
| hardcoded "Buy Load Globe Go90" | title (string) | title: String |
| hardcoded "PHP 50.00" | price (string) | price: String |
crossedValue (boolean, "PHP 90.00" frozen) | originalPrice (string) | originalPrice: String? |
| hardcoded "Validity: …" | validity (string) | validity: String? |
| raster GCash logo | logo Slot | trailing closure |
badge (boolean, state-derived label) | badge Slot (optional) | badge: EBBadge? |
| "GET VOUCHER" rotated text | — (remove or decorative) | onTap: () -> Void |
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Requires Rework | Two parallel partner-image subtrees (voucher vs Voucher Image V1) for active vs greyed states. Four badge layers all named Badge. Content block uses generic container / price names but text layers are unnamed. |
| C2 | Variant & Property Naming | Requires Rework | State enum drives bg + label colors + badge style + badge text all at once. Title, price, original price, validity are frozen strings. crossedValue boolean should be a nullable originalPrice string. |
| C3 | Token Coverage | Ready | All colors bound to main/vouchers/color/{default|expired}/* and main/badge/{information|negative|muted}/{heavy|light}/*. Typography uses named text styles. Shadow uses app/shadow/shadow-low. |
| C4 | Native Mappability | Requires Rework | Parallel to 2 other voucher components with divergent schemas. Native is one EBVoucherCard, not three. Strings/logo need property-ification before any 1:1 mapping. |
| C5 | Interaction State Coverage | Requires Rework | 4 state variants are good but no default, no pressed/focused/disabled on the card frame, and the "GET VOUCHER" CTA is a non-interactive rotated text label rather than a Button. |
| C6 | Asset & Icon Quality | Requires Rework | Partner logo is raster (imgLogoNoText, GCash PNG). Perforated ticket edge uses a raster mask (imgPerforate). Should be vector / SVG path. |
| C7 | Code Connect Linkability | Requires Rework | Cannot map with hardcoded strings and no logo slot. Linkability requires the family consolidation + property-ification first. |
Single axis: state. All 4 variants render at 336 × 111. Booleans badge (default true) and crossedValue (default true) apply uniformly across states.
| Node ID | Variant | Dimensions | Badge style | Partner-image treatment |
|---|---|---|---|---|
5119:1787 | state=limited | 336 × 111 | information/heavy #2340A9, label "Limited" | Full-color bg (bg/color-bg-primary #005CE5) + white GCash logo |
5119:1807 | state=expiring | 336 × 111 | negative/heavy #D61B2C, label "Expiring" | Full-color bg (bg/color-bg-primary #005CE5) + white GCash logo |
5119:1827 | state=used | 336 × 111 | muted/light #C2C5CA, label "Used" | Greyed overlay (bg/color-bg-overlay-weak rgba(2,14,34,0.24)), mix-blend-multiply |
5119:1847 | state=expired | 336 × 111 | muted/light #C2C5CA, label "Expired" | Greyed overlay (bg/color-bg-overlay-weak rgba(2,14,34,0.24)), mix-blend-multiply |
5119:1635), and Horizontal Voucher (5121:4533) into a single Voucher Card with orientation + state axes. Port this component's state coverage to the unified schema. Target: 2 × 5 = 10 variants instead of 3 divergent components. Open