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:
@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.