fansunited-frontend-components 0.0.49 → 0.0.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/Betslip.d.ts +5 -0
  2. package/Betslip.d.ts.map +1 -0
  3. package/Betslip.js +5 -0
  4. package/ChanceGame.js +87 -86
  5. package/ClassicQuizPlay.js +174 -173
  6. package/CollectLead.js +33 -32
  7. package/Discussion.js +1 -1
  8. package/EitherOrPlay.js +171 -170
  9. package/EventGamePlay.js +25 -24
  10. package/Leaderboard.js +88 -87
  11. package/MatchQuizPlay.js +28 -27
  12. package/PersonalityQuizPlay.js +21 -20
  13. package/PickThePair.js +87 -86
  14. package/PollVote.js +19 -18
  15. package/Predictor.js +4756 -4693
  16. package/README.md +523 -1
  17. package/chunks/{AlertMessage-Ds45MBHB.js → AlertMessage-CQ9WJHSb.js} +25 -24
  18. package/chunks/{ArrowRightAlt-DKADQN2o.js → ArrowRightAlt-C1U8SNu-.js} +5 -5
  19. package/chunks/{Avatar-K1EWhhkA.js → Avatar-COVJnfGS.js} +79 -78
  20. package/chunks/Button-COvmPv4P.js +2270 -0
  21. package/chunks/{ChanceGameEmbedVariant-DqFg4YKU.js → ChanceGameEmbedVariant-lfZlSATv.js} +44 -43
  22. package/chunks/{CheckCircle-CV9iMpkZ.js → CheckCircle-h7-xLUq-.js} +27 -26
  23. package/chunks/{Close-C6Cw96SJ.js → Close-BTC6xyyj.js} +1 -1
  24. package/chunks/{Close-BHp4N1-6.js → Close-DBaorsP1.js} +1 -1
  25. package/chunks/{CollectLeadForm-DnLKA8qz.js → CollectLeadForm-O7Cpe0ix.js} +128 -127
  26. package/chunks/{Divider-BvUimRFw.js → Divider-DBCeQt9x.js} +15 -14
  27. package/chunks/{Edit-CZ3ni-Ur.js → Edit-BwwxJdNv.js} +35 -34
  28. package/chunks/{EmojiEvents-DpmLq98V.js → EmojiEvents-DVPGlTU_.js} +3 -3
  29. package/chunks/{FavoriteBorder-DUK_DrY4.js → FavoriteBorder-BnhfbKy1.js} +1 -1
  30. package/chunks/{FormControl-BUItlVIg.js → FormControl-COjw1gQd.js} +38 -37
  31. package/chunks/{FormLabel-CeTNPUn7.js → FormLabel-e5_v5cjP.js} +18 -17
  32. package/chunks/{Input-MJCQcYzo.js → Input-er-Yyqtt.js} +17 -16
  33. package/chunks/{KeyboardArrowDown-SZpn2GhJ.js → KeyboardArrowDown-BeaYH92n.js} +1 -1
  34. package/chunks/{ModalDialog-Cfgv9UAA.js → ModalDialog-XkhZtgYm.js} +58 -57
  35. package/chunks/{NotFoundSkeleton-Cs6DtD9u.js → NotFoundSkeleton-C71TRYl5.js} +95 -94
  36. package/chunks/{Notification-pcKtlRms.js → Notification-CkPupg9q.js} +57 -56
  37. package/chunks/{OverlayApiErrorSkeleton-Dl3s5Oaf.js → OverlayApiErrorSkeleton-CkuDu9uV.js} +20 -19
  38. package/chunks/{OverlayLeadAfterCollection-F2TAV9wK.js → OverlayLeadAfterCollection-Dkxl4cKn.js} +28 -27
  39. package/chunks/{OverlayScoreStateSkeleton-LQ0svzeX.js → OverlayScoreStateSkeleton-RauDjIg5.js} +16 -15
  40. package/chunks/{Person-BGdniS_p.js → Person-WCt2MN5j.js} +32 -31
  41. package/chunks/{PickOneOfX-Ci4ZJf7G.js → PickOneOfX-DabfTR2W.js} +8 -7
  42. package/chunks/{Popper-DOVSI4rJ.js → Popper-OPgbZzY7.js} +127 -126
  43. package/chunks/{Portal-Cs0dgwmH.js → Portal-BQilulgu.js} +5 -5
  44. package/chunks/{PrizeCard-DF3QxwQJ.js → PrizeCard-DgHyperX.js} +5238 -6653
  45. package/chunks/{Select-BHeTdOYr.js → Select-9gmhEo71.js} +133 -132
  46. package/chunks/{Spinner-DbUB64Gj.js → Spinner-BoysVPn7.js} +12 -11
  47. package/chunks/{SplitLeadAfterCollection-DSw6X5fx.js → SplitLeadAfterCollection-C5oCDYT1.js} +15 -14
  48. package/chunks/{Stack-BpHk5eb0.js → Stack-INH9bhkC.js} +30 -29
  49. package/chunks/{StandardContentSkeleton-Bae1LLQ_.js → StandardContentSkeleton-DsAhzMfU.js} +1 -1
  50. package/chunks/{Tabs-hwJ7jsUl.js → Tabs-DaVatjCJ.js} +60 -59
  51. package/chunks/{Textarea-zOtMdgc5.js → Textarea-DeMxp3Zn.js} +48 -47
  52. package/chunks/{TimerOutlined-DWE7f0YX.js → TimerOutlined-DJh2vL3s.js} +3 -3
  53. package/chunks/{helpers-NuC1SzL3.js → helpers-Bd9FT9hj.js} +1 -1
  54. package/chunks/{index-D1K1OB4p.js → index-5MPtbA_r.js} +255 -239
  55. package/chunks/index-BjfKDjAM.js +4 -0
  56. package/chunks/main-DDb9jezz.js +1758 -0
  57. package/chunks/{main-hB01WcoP.js → main-DWw1vqVY.js} +38 -37
  58. package/chunks/{useEventCallback-CizlfWBo.js → useEventCallback-vbTEFVPa.js} +8 -7
  59. package/chunks/{useForwardedInput-RWDntqBU.js → useForwardedInput-DumYVfgj.js} +26 -25
  60. package/chunks/{useImageVariant-Cex1eIqq.js → useImageVariant-B1ObJh_B.js} +1 -1
  61. package/components.d.ts +2 -1
  62. package/components.d.ts.map +1 -1
  63. package/index.d.ts +1 -0
  64. package/index.d.ts.map +1 -1
  65. package/index.js +5 -3
  66. package/package.json +5 -1
  67. package/chunks/index-BaypiKlO.js +0 -4
package/README.md CHANGED
@@ -3072,6 +3072,7 @@ Full-featured football score predictor component with a multi-tab interface for
3072
3072
  - Consent management with required/optional consent gates before predicting
3073
3073
  - Customisable banners in 6 positions within the Play tab
3074
3074
  - Authentication-aware UI with configurable sign-in CTA
3075
+ - Built-in Betslip integration — renders the Betslip widget automatically via the `betslip` prop with two trigger modes
3075
3076
  - Custom theming and multi-language support
3076
3077
 
3077
3078
  #### Required Props
@@ -3093,7 +3094,8 @@ Full-featured football score predictor component with a multi-tab interface for
3093
3094
  | `defaultImagePlaceholderUrl` | `string` | Fallback image URL for the hero header |
3094
3095
  | `playTabBanners` | `PlayTabBanner[]` | Custom banners rendered in the Play tab |
3095
3096
  | `consents` | `ConsentDef[]` | Consent definitions required before predicting |
3096
- | `matchCardBgImageUrl` | `string` | Background image URL for match prediction cards |
3097
+ | `matchCardBgImageUrl` | `string` | Background image URL for match prediction cards |
3098
+ | `betslip` | `PredictorBetslipConfig` | Betslip integration config. Omit to disable. See [Betslip Integration](#betslip-integration) |
3097
3099
 
3098
3100
  #### Tabs
3099
3101
 
@@ -3229,6 +3231,67 @@ const consents: ConsentDef[] = [
3229
3231
  - Optional consents are shown in the same modal but do not block progression
3230
3232
  - Once accepted, consents are stored and the modal does not reappear
3231
3233
 
3234
+ #### Betslip Integration
3235
+
3236
+ When the `betslip` prop is provided, the Predictor automatically renders a `Betslip` widget as a sibling alongside its own UI — you do not need to add a separate `<Betslip />` component to the page. The widget is configured entirely through `PredictorBetslipConfig`.
3237
+
3238
+ ```tsx
3239
+ import { PredictorBetslipConfig } from "fansunited-frontend-core";
3240
+
3241
+ const betslipConfig: PredictorBetslipConfig = {
3242
+ trigger: "predictions-only",
3243
+ position: "side-right",
3244
+ currency: "£",
3245
+ stakePresets: [5, 10, 25, 50],
3246
+ ctaUrlTemplate:
3247
+ "https://your-bookmaker.com/bet?ids={selectionIds}&stake={stake}&ref={currentUrl}",
3248
+ };
3249
+
3250
+ <Predictor {...otherProps} betslip={betslipConfig} />;
3251
+ ```
3252
+
3253
+ **`PredictorBetslipConfig` fields:**
3254
+
3255
+ | Field | Type | Default | Description |
3256
+ | --------------------- | ------------------------- | -------------------- | ----------------------------------------------------------------------------------------- |
3257
+ | `trigger` | `PredictorBetslipTrigger` | `"predictions-only"` | When to send selections to the betslip |
3258
+ | `position` | `BetslipPosition` | `"bottom-right"` | Viewport position of the Betslip widget |
3259
+ | `maxSelections` | `number` | `20` | Hard cap on simultaneous selections |
3260
+ | `stakePresets` | `number[]` | `[5, 10, 20]` | Quick-select stake buttons |
3261
+ | `oddsPollingInterval` | `number` | `30000` | Odds refresh interval in milliseconds |
3262
+ | `currency` | `string` | `"€"` | Currency symbol |
3263
+ | `ctaUrlTemplate` | `string` | — | Bookmaker deep-link URL with `{placeholder}` substitution |
3264
+ | `brandingLogoUrl` | `string` | — | Operator logo URL |
3265
+ | `labels` | `BetslipLabels` | — | UI text overrides |
3266
+ | `themeOptions` | `CustomThemeOptions` | — | Theme for the Betslip widget. Falls back to the Predictor's `themeOptions` when omitted |
3267
+
3268
+ **Trigger modes (`PredictorBetslipTrigger`):**
3269
+
3270
+ ```tsx
3271
+ type PredictorBetslipTrigger =
3272
+ | "predictions-only" // score changes feed betslip (before submit)
3273
+ | "odds-only"; // odds button click feeds betslip (after submit)
3274
+ ```
3275
+
3276
+ | Mode | When selections are sent |
3277
+ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3278
+ | `"predictions-only"` | Every time the user edits a score (home/away increment or decrement). If the user submits without editing (e.g. a default 0:0), the selection is sent at submit time as a fallback. Derived outcome: `"1"` (home win), `"X"` (draw), `"2"` (away win). |
3279
+ | `"odds-only"` | Only when the user clicks an odds button on the post-submit odds display. The odds button becomes a command-bus trigger instead of a direct bookmaker link. |
3280
+
3281
+ **Selection format sent by Predictor:**
3282
+
3283
+ ```
3284
+ "{matchId}:FT_1X2:{outcomeKey}"
3285
+ // Examples:
3286
+ // "fb:m:451634:FT_1X2:1" → home win
3287
+ // "fb:m:451634:FT_1X2:X" → draw
3288
+ // "fb:m:451634:FT_1X2:2" → away win
3289
+ ```
3290
+
3291
+ **Automatic removal:** When a user deletes a submitted prediction, the corresponding selection is automatically removed from the betslip via `betslipApi.removeSelection()`. When a score is edited after submission (in `"predictions-only"` mode), the new outcome replaces the old one in the betslip (upsert — no explicit removal needed).
3292
+
3293
+ **Theme fallback:** If `betslip.themeOptions` is not provided, the Betslip widget inherits the Predictor's own `themeOptions`. A single `themeOptions` on the Predictor is enough to theme both components consistently.
3294
+
3232
3295
  #### TypeScript Support
3233
3296
 
3234
3297
  Import types from `fansunited-frontend-core`:
@@ -3237,6 +3300,8 @@ Import types from `fansunited-frontend-core`:
3237
3300
  import {
3238
3301
  PredictorProps,
3239
3302
  PredictorTab,
3303
+ PredictorBetslipConfig,
3304
+ PredictorBetslipTrigger,
3240
3305
  PlayTabBanner,
3241
3306
  PlayTabBannerPosition,
3242
3307
  ConsentDef,
@@ -3328,6 +3393,462 @@ import { Predictor } from "fansunited-frontend-components";
3328
3393
  />
3329
3394
  ```
3330
3395
 
3396
+ ##### With Betslip — Predictions Trigger
3397
+
3398
+ Selections are sent to the Betslip automatically as the user edits scores, before they submit.
3399
+
3400
+ ```tsx
3401
+ <Predictor
3402
+ entityId="predictor-template-123"
3403
+ sdk={sdkInstance}
3404
+ language="en"
3405
+ userIsLoggedIn={true}
3406
+ tabs={["play", "leaderboard"]}
3407
+ betslip={{
3408
+ trigger: "predictions-only",
3409
+ position: "side-right",
3410
+ currency: "£",
3411
+ stakePresets: [5, 10, 25, 50],
3412
+ ctaUrlTemplate:
3413
+ "https://your-bookmaker.com/bet?ids={selectionIds}&stake={stake}&ref={currentUrl}",
3414
+ labels: {
3415
+ disclaimer: "18+ | Please gamble responsibly",
3416
+ },
3417
+ }}
3418
+ themeOptions={{ mode: "dark" }}
3419
+ />
3420
+ ```
3421
+
3422
+ ##### With Betslip — Odds Trigger
3423
+
3424
+ Selections are sent only when the user explicitly clicks an odds button on the post-submit view.
3425
+
3426
+ ```tsx
3427
+ <Predictor
3428
+ entityId="predictor-template-123"
3429
+ sdk={sdkInstance}
3430
+ language="en"
3431
+ userIsLoggedIn={true}
3432
+ betslip={{
3433
+ trigger: "odds-only",
3434
+ position: "bottom-right",
3435
+ currency: "€",
3436
+ ctaUrlTemplate:
3437
+ "https://your-bookmaker.com/bet?ids={selectionIds}&stake={stake}",
3438
+ }}
3439
+ />
3440
+ ```
3441
+
3442
+ ---
3443
+
3444
+ ### Betslip
3445
+
3446
+ A floating betslip component that collects selections from any external source and routes users to a bookmaker via a configurable deep-link URL. The component communicates with the rest of the page through a singleton command bus (`betslipApi`), so it decouples cleanly from host-page code.
3447
+
3448
+ **Key Features:**
3449
+
3450
+ - Six viewport positions: four corner anchors and two full-height side panels
3451
+ - Singleton command bus with pre-mount queuing — selections sent before the widget mounts are replayed on load
3452
+ - Automatic odds polling from the SDK with configurable interval
3453
+ - Bookmaker branding color automatically used as the primary accent (overridable via `themeOptions`)
3454
+ - Configurable CTA deep-link URL template with dynamic placeholder substitution
3455
+ - Shadow DOM style isolation — no CSS conflicts with the host application
3456
+ - Multi-language support and fully overridable UI text labels
3457
+
3458
+ #### Quick Start
3459
+
3460
+ ```tsx
3461
+ import React from "react";
3462
+ import { Betslip } from "fansunited-frontend-components";
3463
+ import { betslipApi } from "fansunited-frontend-core";
3464
+ import { FansUnitedSDK } from "fansunited-sdk-esm";
3465
+
3466
+ const sdk = FansUnitedSDK({ /* your config */ });
3467
+
3468
+ const App: React.FC = () => (
3469
+ <>
3470
+ {/* Place once anywhere in the tree */}
3471
+ <Betslip
3472
+ sdk={sdk}
3473
+ language="en"
3474
+ position="bottom-right"
3475
+ ctaUrlTemplate="https://your-bookmaker.com/betslip?selections={selectionIds}&stake={stake}"
3476
+ />
3477
+
3478
+ {/* From anywhere in your app, add or remove selections */}
3479
+ <button onClick={() => betslipApi.setSelection("fb:m:451678:FT_1X2:1")}>
3480
+ Back Home Win
3481
+ </button>
3482
+ </>
3483
+ );
3484
+ ```
3485
+
3486
+ #### Required Props
3487
+
3488
+ | Prop | Type | Description |
3489
+ | ---------- | -------------------- | ---------------- |
3490
+ | `sdk` | `FansUnitedSDKModel` | SDK instance |
3491
+ | `language` | `LanguageType` | Display language |
3492
+
3493
+ #### Optional Props
3494
+
3495
+ | Prop | Type | Default | Description |
3496
+ | ---------------------- | -------------------- | ---------------- | --------------------------------------------------------------- |
3497
+ | `position` | `BetslipPosition` | `"bottom-right"` | Viewport anchor. See [Positions](#positions) below |
3498
+ | `maxSelections` | `number` | `20` | Hard cap on simultaneous selections |
3499
+ | `stakePresets` | `number[]` | `[5, 10, 20]` | Quick-select stake buttons shown in the footer |
3500
+ | `oddsPollingInterval` | `number` | `30000` | Odds refresh interval in milliseconds |
3501
+ | `currency` | `string` | `"€"` | Currency symbol shown next to stake and potential win |
3502
+ | `ctaUrlTemplate` | `string` | — | Deep-link URL template with `{placeholder}` substitution |
3503
+ | `brandingLogoUrl` | `string` | — | Operator logo URL that overrides the bookmaker logo |
3504
+ | `labels` | `BetslipLabels` | — | UI text overrides. See [Labels](#labels) below |
3505
+ | `themeOptions` | `CustomThemeOptions` | — | Theme tokens. See [Theming](#theming-1) below |
3506
+
3507
+ #### Positions
3508
+
3509
+ ```tsx
3510
+ import { BetslipPosition } from "fansunited-frontend-core";
3511
+
3512
+ type BetslipPosition =
3513
+ | "bottom-right" // Fixed corner anchor, slides up on open (default)
3514
+ | "bottom-left" // Fixed corner anchor, slides up on open
3515
+ | "top-right" // Fixed corner anchor, slides down on open
3516
+ | "top-left" // Fixed corner anchor, slides down on open
3517
+ | "side-right" // Full-height panel, slides in from the right
3518
+ | "side-left"; // Full-height panel, slides in from the left
3519
+ ```
3520
+
3521
+ **Corner positions** (`bottom-*`, `top-*`):
3522
+ - 320 px wide, fixed 16 px from the viewport edges
3523
+ - Header shows an expand/collapse chevron
3524
+ - Collapsed state renders a compact summary (combined odds, stake, potential win)
3525
+ - Expanded state shows the full selection list (scrollable at 300 px max height) plus the stake/CTA footer
3526
+
3527
+ **Side positions** (`side-*`):
3528
+ - 340 px wide panel covering full viewport height (100 vh)
3529
+ - A 32 × 64 px tab handle peeks out at 33 % from the top when closed — it shows the bookmaker/branding logo rotated 90°, or three grip lines when no logo is available
3530
+ - A selection count badge appears on the closed tab when `maxSelections` is set
3531
+ - Smooth 0.3 s slide-in/out transition
3532
+
3533
+ #### Command Bus (`betslipApi`)
3534
+
3535
+ The betslip exposes a singleton command bus for external code to manage selections without needing a direct reference to the React component.
3536
+
3537
+ ```tsx
3538
+ import { betslipApi } from "fansunited-frontend-core";
3539
+
3540
+ // Add or replace a selection (same eventId + market = outcome replaced)
3541
+ betslipApi.setSelection("fb:m:451678:FT_1X2:1");
3542
+
3543
+ // Remove a selection by its exact id
3544
+ betslipApi.removeSelection("fb:m:451678:FT_1X2:1");
3545
+ ```
3546
+
3547
+ **Selection ID format:**
3548
+
3549
+ ```
3550
+ "{eventId}:{market}:{outcome}"
3551
+ ```
3552
+
3553
+ | Segment | Examples |
3554
+ | ---------- | ---------------------------------------------- |
3555
+ | `eventId` | `fb:m:451678` |
3556
+ | `market` | `FT_1X2`, `DOUBLE_CHANCE`, `OVER_GOALS_2_5`, `CORRECT_SCORE`, `PLAYER_SCORE_FIRST_GOAL` |
3557
+ | `outcome` | `1` (home), `x` (draw), `2` (away), `1x`, `yes`, `no`, `1-2` (correct score) |
3558
+
3559
+ **Pre-mount queuing:**
3560
+
3561
+ Calls to `betslipApi.setSelection` made before the `<Betslip />` component has mounted are queued and automatically replayed once the widget finishes initializing. There is no need to delay external code to wait for the widget.
3562
+
3563
+ ```tsx
3564
+ // Safe to call before <Betslip /> is rendered
3565
+ betslipApi.setSelection("fb:m:451678:FT_1X2:1");
3566
+
3567
+ // Later, when <Betslip /> mounts, it will receive the queued selection
3568
+ ReactDOM.render(<Betslip sdk={sdk} language="en" />, root);
3569
+ ```
3570
+
3571
+ #### Predictor Integration
3572
+
3573
+ The `Predictor` component has first-class Betslip support via its `betslip` prop. When configured, the Predictor renders the `Betslip` widget internally — no separate `<Betslip />` component is needed. See the [Predictor — Betslip Integration](#betslip-integration) section for full details.
3574
+
3575
+ ```tsx
3576
+ import { Predictor } from "fansunited-frontend-components";
3577
+
3578
+ <Predictor
3579
+ entityId="predictor-123"
3580
+ sdk={sdk}
3581
+ language="en"
3582
+ betslip={{
3583
+ trigger: "predictions-only", // or "odds-only"
3584
+ position: "side-right",
3585
+ currency: "£",
3586
+ stakePresets: [5, 10, 25, 50],
3587
+ ctaUrlTemplate: "https://your-bookmaker.com/bet?ids={selectionIds}&stake={stake}",
3588
+ }}
3589
+ />
3590
+ ```
3591
+
3592
+ **Trigger modes:**
3593
+
3594
+ | Mode | When selections are sent to Betslip |
3595
+ | --------------------- | --------------------------------------------------------------------------------- |
3596
+ | `"predictions-only"` | Each time the user changes a score prediction (before submitting) |
3597
+ | `"odds-only"` | When the user clicks an odds button on the post-submit odds view |
3598
+
3599
+ #### CTA URL Template
3600
+
3601
+ The `ctaUrlTemplate` prop defines the bookmaker deep-link. Placeholders are replaced at click time with live values from the current betslip state.
3602
+
3603
+ ```tsx
3604
+ ctaUrlTemplate="https://betano.bg/mybet/?selection_ids={selectionIds}&stake={stake}&return_url={currentUrl}"
3605
+ ```
3606
+
3607
+ **Available placeholders:**
3608
+
3609
+ | Placeholder | Value |
3610
+ | ----------------- | ------------------------------------------------------------------------- |
3611
+ | `{selectionIds}` | Comma-separated SDK `selection.id` values (up to the first 10) |
3612
+ | `{stake}` | Current stake input value (float) |
3613
+ | `{currentUrl}` | `window.location.href`, URI-encoded |
3614
+ | `{eventId}` | `eventId` of the first selection |
3615
+ | `{oddsDecimal}` | Decimal odds of the first selection |
3616
+ | `{market}` | Market code of the first selection |
3617
+ | `{outcome}` | Outcome label of the first selection |
3618
+
3619
+ #### Theming
3620
+
3621
+ The Betslip derives its primary accent color from a three-level priority chain:
3622
+
3623
+ 1. **Custom primary** — `themeOptions.colorSchemes[mode].palette.primary.plainColor`
3624
+ 2. **Bookmaker color** — `bookmaker.branding.backgroundColor` (or `bookmaker.assets[0].backgroundColor` as fallback) extracted live from the SDK odds response
3625
+ 3. **Default theme primary**
3626
+
3627
+ This means you get bookmaker brand colors for free when odds are available, and can override them at any time via `themeOptions`.
3628
+
3629
+ ```tsx
3630
+ import { CustomThemeOptions } from "fansunited-frontend-core";
3631
+
3632
+ // Override primary accent — disables the bookmaker color fallback
3633
+ const themeOptions: CustomThemeOptions = {
3634
+ mode: "dark",
3635
+ colorSchemes: {
3636
+ dark: {
3637
+ palette: {
3638
+ primary: {
3639
+ plainColor: "#FF5722", // your brand color
3640
+ primaryContainer: "#FF7043",
3641
+ onPrimary: "#FFFFFF",
3642
+ },
3643
+ success: {
3644
+ plainColor: "#4CAF50",
3645
+ softBg: "#042F04",
3646
+ },
3647
+ danger: {
3648
+ plainColor: "#F44336",
3649
+ softBg: "#430A0A",
3650
+ },
3651
+ },
3652
+ surface: "#212121",
3653
+ onSurface: "#121212",
3654
+ surfaceVariant: "#333333",
3655
+ textPrimary: "#FAFAFA",
3656
+ textSecondary: "#BDBDBD",
3657
+ outlineEnabledBorder: "#555555",
3658
+ },
3659
+ },
3660
+ customFontFamily: {
3661
+ dark: {
3662
+ primary: "Inter, sans-serif",
3663
+ secondary: "Roboto, sans-serif",
3664
+ },
3665
+ },
3666
+ };
3667
+
3668
+ <Betslip sdk={sdk} language="en" themeOptions={themeOptions} />;
3669
+ ```
3670
+
3671
+ > **Note:** When neither a custom primary nor bookmaker odds are present, the widget falls back to the default theme primary (`#1A77D2`).
3672
+
3673
+ #### Labels
3674
+
3675
+ Override any piece of UI text without switching languages:
3676
+
3677
+ ```tsx
3678
+ import { BetslipLabels } from "fansunited-frontend-core";
3679
+
3680
+ const labels: BetslipLabels = {
3681
+ title: "My Betslip",
3682
+ placeBetLabel: "Place Bet",
3683
+ emptyTitle: "Your betslip is empty",
3684
+ emptyDescription: "Add selections to get started",
3685
+ continueLabel: "Continue",
3686
+ disclaimer: "18+ | Gamble responsibly",
3687
+ combinedOddsLabel: "Combined Odds",
3688
+ stakeLabel: "Stake",
3689
+ totalStakeLabel: "Total Stake",
3690
+ potentialWinLabel: "Potential Win",
3691
+ oddsUnavailableLabel: "Odds N/A",
3692
+ suspendedLabel: "Suspended",
3693
+
3694
+ // Override individual outcome labels
3695
+ outcomeLabels: {
3696
+ yes: "Over",
3697
+ no: "Under",
3698
+ draw: "Draw",
3699
+ or: "/",
3700
+ nobody: "No Scorer",
3701
+ noGoal: "No Goal",
3702
+ },
3703
+
3704
+ // Override market display names
3705
+ marketLabels: {
3706
+ FT_1X2: "Full Time",
3707
+ DOUBLE_CHANCE: "Double Chance",
3708
+ OVER_GOALS_2_5: "Over/Under 2.5 Goals",
3709
+ },
3710
+ };
3711
+
3712
+ <Betslip sdk={sdk} language="en" labels={labels} />;
3713
+ ```
3714
+
3715
+ #### Conflict Detection
3716
+
3717
+ The betslip automatically detects selections that are mutually exclusive — i.e., two selections with the same `eventId` and market. When conflicts are detected:
3718
+
3719
+ - A warning banner appears at the top of the expanded selection list
3720
+ - Conflicting selection cards are highlighted with danger styling
3721
+ - The CTA button is disabled until the conflict is resolved
3722
+
3723
+ Conflicts resolve automatically when the user removes one of the conflicting selections. No extra configuration is required.
3724
+
3725
+ #### Odds Behavior
3726
+
3727
+ - **Polling**: Odds are fetched from `sdk.odds.getByMatchIds()` on mount and refreshed every `oddsPollingInterval` ms (default 30 s)
3728
+ - **Movement indicator**: A green ▲ or red ▼ is shown when the bookmaker response includes a `movement` field (`"UP"` or `"DOWN"`) on the matched selection. No indicator is shown when `movement` is absent
3729
+ - **Unavailable**: When no odds are returned for a selection, the card shows `oddsUnavailableLabel` and the CTA is disabled
3730
+ - **Suspended**: Selections with status `"suspended"` are highlighted and block the CTA
3731
+ - **Settled states**: Cards display ✓ (won), ✗ (lost), or ≈ (void) once a selection is settled
3732
+
3733
+ **Over/Under markets:** The `outcome` code in the selection ID uses `"yes"` for Over and `"no"` for Under (not `"over"`/`"under"`). The widget handles the conversion and label display internally.
3734
+
3735
+ **Correct Score market:** Outcomes use hyphen notation, e.g. `"1-2"`. The widget parses both `-` and `:` as separators.
3736
+
3737
+ #### TypeScript Support
3738
+
3739
+ Import types from `fansunited-frontend-core`:
3740
+
3741
+ ```tsx
3742
+ import {
3743
+ BetslipProps,
3744
+ BetslipLabels,
3745
+ BetslipPosition,
3746
+ BetslipSelectionInput,
3747
+ BetslipOutcomeLabels,
3748
+ SelectionStatus,
3749
+ CustomThemeOptions,
3750
+ LanguageType,
3751
+ betslipApi,
3752
+ } from "fansunited-frontend-core";
3753
+ ```
3754
+
3755
+ #### Examples
3756
+
3757
+ ##### Basic Betslip
3758
+
3759
+ ```tsx
3760
+ import { Betslip } from "fansunited-frontend-components";
3761
+ import { FansUnitedSDK } from "fansunited-sdk-esm";
3762
+
3763
+ const sdk = FansUnitedSDK({ /* config */ });
3764
+
3765
+ <Betslip
3766
+ sdk={sdk}
3767
+ language="en"
3768
+ ctaUrlTemplate="https://your-bookmaker.com/bet?selections={selectionIds}"
3769
+ />;
3770
+ ```
3771
+
3772
+ ##### Side Panel with Custom Branding
3773
+
3774
+ ```tsx
3775
+ import { Betslip } from "fansunited-frontend-components";
3776
+
3777
+ <Betslip
3778
+ sdk={sdk}
3779
+ language="en"
3780
+ position="side-right"
3781
+ maxSelections={10}
3782
+ stakePresets={[5, 10, 25, 50]}
3783
+ currency="£"
3784
+ brandingLogoUrl="https://your-cdn.com/logo.png"
3785
+ ctaUrlTemplate="https://your-bookmaker.com/bet?ids={selectionIds}&stake={stake}&ref={currentUrl}"
3786
+ labels={{
3787
+ title: "Your Selections",
3788
+ placeBetLabel: "Bet Now",
3789
+ disclaimer: "18+ | Please gamble responsibly | BeGambleAware.org",
3790
+ }}
3791
+ themeOptions={{
3792
+ mode: "dark",
3793
+ colorSchemes: {
3794
+ dark: {
3795
+ palette: {
3796
+ primary: {
3797
+ plainColor: "#FF5722",
3798
+ primaryContainer: "#FF7043",
3799
+ onPrimary: "#FFFFFF",
3800
+ },
3801
+ },
3802
+ surface: "#1A1A1A",
3803
+ textPrimary: "#FAFAFA",
3804
+ },
3805
+ },
3806
+ }}
3807
+ />
3808
+ ```
3809
+
3810
+ ##### Programmatic Selection Management
3811
+
3812
+ ```tsx
3813
+ import { betslipApi } from "fansunited-frontend-core";
3814
+
3815
+ // Add selections (safe to call before the widget mounts)
3816
+ betslipApi.setSelection("fb:m:451678:FT_1X2:1");
3817
+ betslipApi.setSelection("fb:m:451679:DOUBLE_CHANCE:1x");
3818
+
3819
+ // Replace an outcome for the same event+market
3820
+ betslipApi.setSelection("fb:m:451678:FT_1X2:2"); // replaces the home-win
3821
+
3822
+ // Remove a specific selection
3823
+ betslipApi.removeSelection("fb:m:451679:DOUBLE_CHANCE:1x");
3824
+ ```
3825
+
3826
+ ##### Predictor + Betslip Integration
3827
+
3828
+ The `Predictor` renders the `Betslip` widget internally when `betslip` is provided — no separate `<Betslip />` is needed.
3829
+
3830
+ ```tsx
3831
+ import { Predictor } from "fansunited-frontend-components";
3832
+
3833
+ <Predictor
3834
+ entityId="predictor-123"
3835
+ sdk={sdk}
3836
+ language="en"
3837
+ betslip={{
3838
+ trigger: "predictions-only",
3839
+ position: "side-right",
3840
+ currency: "€",
3841
+ stakePresets: [5, 10, 20, 50],
3842
+ ctaUrlTemplate:
3843
+ "https://your-bookmaker.com/bet?ids={selectionIds}&stake={stake}",
3844
+ labels: {
3845
+ disclaimer: "18+ | Gamble responsibly",
3846
+ },
3847
+ }}
3848
+ themeOptions={{ mode: "light" }}
3849
+ />
3850
+ ```
3851
+
3331
3852
  ---
3332
3853
 
3333
3854
  ## Available Components
@@ -3345,6 +3866,7 @@ This package exports the following components:
3345
3866
  - **`Discussion`** - Threaded comments with reactions, replies, reporting, and authentication support
3346
3867
  - **`Leaderboard`** - Ranked standings for Classic Quiz, Match Quiz, Top X, and Template entities with multi-entity support
3347
3868
  - **`Predictor`** - Multi-tab football score predictor with leaderboard, private leagues, rules, and prizes
3869
+ - **`Betslip`** - Floating betslip with bookmaker deep-link integration, six viewport positions, and command bus API
3348
3870
 
3349
3871
  ## Related Packages
3350
3872