Bridge Training
UI Bridge

Usage

How to integrate and use @workspace/ui-bridge in your application.

Installation

Add the dependency to your app's package.json:

{
  "dependencies": {
    "@workspace/ui-bridge": "workspace:*"
  }
}

Peer Dependencies

The package requires React and react-i18next as peer dependencies:

{
  "peerDependencies": {
    "react": "^19",
    "react-dom": "^19",
    "react-i18next": "^15"
  }
}

Import Styles

Import the package stylesheet in your app's root CSS:

globals.css
@import "@workspace/ui-bridge/styles.css";

Provider Setup

Global Provider

Wrap your application with BridgeProvider to configure the engine for all child components:

import { BridgeProvider } from '@workspace/ui-bridge/bridge-context/bridge-provider';

function App() {
  return (
    <BridgeProvider context={{ teachingMode: 'auction', locale: 'fr' }}>
      {/* All bridge components use this engine */}
    </BridgeProvider>
  );
}

The context prop accepts a BridgeContext object (see Bridge Usage for details).

Without a Provider

All components work without a provider. When no BridgeProvider is in the tree, useBridgeEngine() falls back to a default engine with the classic variant:

import { SuitDisplay } from '@workspace/ui-bridge/suit/suit-display';

// Works without any provider — uses classic variant defaults
function Standalone() {
  return <SuitDisplay suit="S" />;
}

Per-Component Override

Every exported component that uses the engine internally is wrapped with withBridgeContext. This adds an optional context prop that creates a local provider, overriding any parent provider:

import { BridgeProvider } from '@workspace/ui-bridge/bridge-context/bridge-provider';
import { SuitDisplay } from '@workspace/ui-bridge/suit/suit-display';

function App() {
  return (
    <BridgeProvider context={{ teachingMode: 'auction', locale: 'fr' }}>
      {/* Uses the global provider (classic variant, French notation) */}
      <SuitDisplay suit="S" />

      {/* Overrides with petit-bridge variant for this component */}
      <SuitDisplay suit="S" context={{ teachingMode: 'petit-bridge' }} />
    </BridgeProvider>
  );
}

This is useful for preview elements that need a different variant or locale than the rest of the app.

Components

Display Components

Simple components for rendering bridge entities:

import { SuitDisplay } from '@workspace/ui-bridge/suit/suit-display';
import { CardDisplay } from '@workspace/ui-bridge/card/card-display';
import { BidDisplay } from '@workspace/ui-bridge/components/bid/bid-display';

<SuitDisplay suit="S" />
<SuitDisplay suit="H" colorVariant="bid" />

<CardDisplay card="SA" />

<BidDisplay bid="2H" />

Hand Component

The standalone Hand component renders a full bridge hand with inputs:

import { Hand } from '@workspace/ui-bridge/components/hand/hand';

<Hand
  player="N"
  cards={{ S: ['A', 'K'], H: ['Q', 'J'], D: ['10', '9'], C: ['8'] }}
  readOnly
/>

Hand Compound Component

For more control, use the compound component API:

import { Hand } from '@workspace/ui-bridge/hand/hand';

const SUITS = ['S', 'H', 'D', 'C'] as const;

<Hand.Root player="N" cards={cards} onCardsChange={handleChange}>
  {SUITS.map(suit => (
    <Hand.InputRow key={suit} suit={suit} value={cards[suit]} />
  ))}
</Hand.Root>

Editor primitives

Headless compound components own the editing state; the auction and play components read it from context. See AuctionEditor and PlayEditor.

import { AuctionGrid } from '@workspace/ui-bridge/components/auction/auction-grid';
import { AuctionEditor } from '@workspace/ui-bridge/primitives/editor/auction-editor';

<AuctionEditor.Root auction={auction} onAuctionChange={setAuction} vulnerability="NONE">
  <AuctionEditor.Table>
    <AuctionGrid />
  </AuctionEditor.Table>
</AuctionEditor.Root>

Fields

One field per bridge value, one page each under Fields: seat, deal vulnerability, side vulnerability, scoring, level, denomination, doubling, tricks, plus the grouped teaching-mode and expected-result selects. Options and defaults come from the engine, labels from the Strings Provider, and every field renders as a dropdown or, with variant="toggle", as an inline toggle group. The two variants share one rendering core, EnumField, so a responsive form switches variant without changing component.

import { VulnerabilitySelect } from '@workspace/ui-bridge/fields/vulnerability-select';
import { LevelSelect } from '@workspace/ui-bridge/fields/level-select';

<VulnerabilitySelect value={vulnerability} onChange={setVulnerability} />
<LevelSelect variant="toggle" value={level} onChange={setLevel} />

BridgePrimitivesProvider

For apps that use the rich text editor, BridgePrimitivesProvider combines the engine context with the final comment editor context, and exposes an imperative ref:

import {
  BridgePrimitivesProvider,
  type BridgePrimitivesRef,
} from '@workspace/ui-bridge/provider';
import { useRef } from 'react';

function Editor() {
  const ref = useRef<BridgePrimitivesRef>(null);

  const handleSave = () => {
    const html = ref.current?.getFinalCommentHTML();
    const pbn = ref.current?.getFinalCommentPBN();
  };

  return (
    <BridgePrimitivesProvider
      ref={ref}
      context={{ teachingMode: 'auction', locale: 'fr' }}
    >
      {/* Editor components */}
    </BridgePrimitivesProvider>
  );
}

Internationalization (i18n)

@workspace/ui-bridge does not depend on i18next. Components read their text from BridgeStringsProvider (@workspace/ui-bridge/strings), which ships English defaults (defaultBridgeStrings). Apps pass partial overrides built from their own translations:

import {
  type BridgeStringsOverrides,
  BridgeStringsProvider,
} from "@workspace/ui-bridge/strings";

export function BridgeStringsBundle({ children }: { children: ReactNode }) {
  const { t } = useBridgeTranslation();

  const overrides = useMemo<BridgeStringsOverrides>(
    () => ({
      game: {
        contract: t("labels.game.contract"),
        trump: t("labels.game.trump"),
      },
      // ...other slices: bid, comment, position, scoring, suit, ...
    }),
    [t],
  );

  return <BridgeStringsProvider overrides={overrides}>{children}</BridgeStringsProvider>;
}

See apps/app/src/lib/i18n/BridgeStringsBundle.tsx for the full mapping. The translation files themselves live in @workspace/i18n-messages.