Install the Opportunities Widget as an NPM package for first-class React (and TypeScript) support.
npm install @legionhandtech/lht-react-widgetsReact 17+ is a peer dependency. If your project already has React installed you're good to go.
1import { LegionOpportunities } from "@legionhandtech/lht-react-widgets";
2
3function App() {
4 return (
5 <LegionOpportunities
6 baseUrl="https://your-legion-domain.com"
7 count={5}
8 layout="horizontal"
9 theme="light"
10 tags="retail,food"
11 />
12 );
13}| Prop | Type | Default | Description |
|---|---|---|---|
baseUrl | string | required | Base URL of your Legion deployment |
count | number | 5 | Number of opportunities to display (1–20) |
layout | "horizontal" | "vertical" | "horizontal" | Layout direction |
variant | "portrait" | "square" | "banner" | "list" | "hero" | auto | Card style (portrait for horizontal, list for vertical) |
tags | string | — | Comma-separated list of tags to filter by |
theme | "light" | "dark" | "minimal" | "bold" | "corporate" | "vibrant" | "light" | Preset theme |
height | string | "auto" | Widget height — "auto", "100%", or a CSS value like "400px" |
apiKey | string | — | Optional API key for authenticated content |
className | string | — | CSS class applied to the outer container |
style | React.CSSProperties | — | Inline styles merged into the container |
onLoad | (detail) => void | — | Called when the widget iframe finishes loading |
onError | (message: string) => void | — | Called if the widget iframe fails to load |
All theming props are optional. When omitted the widget uses its built-in defaults.
The card system keeps text off the image: the photo lives in a fixed-ratio frame and copy sits on a solid surface. The legacy overlay-panel props (oppPanelStart, oppPanelBlur, …) and shadow props were removed with the card revamp.
| Prop | CSS Variable | Example |
|---|---|---|
oppAccent | --opp-accent | "#5a48e6" |
oppAccentContrast | --opp-accent-contrast | "#ffffff" |
oppCardBg | --opp-card-bg | "#ffffff" |
oppTextColor | --opp-text-color | "#16181d" |
oppCardRadius | --opp-card-radius | "18px" |
oppCardShadow | --opp-card-shadow | "0 4px 12px rgba(0,0,0,.1)" |
oppCardHoverShadow | --opp-card-hover-shadow | "0 8px 24px rgba(0,0,0,.15)" |
oppCardBorder | --opp-card-border | "1px solid #e6e8ec" |
oppFontFamily | --opp-font-family | "Inter, sans-serif" |
| Prop | CSS Variable | Example |
|---|---|---|
oppMediaRatio | --opp-media-ratio | "5 / 4" |
oppMediaBg | --opp-media-bg | "#dfe2e7" |
oppCategoryBg | --opp-category-bg | "rgba(16,18,24,.5)" |
oppCategoryColor | --opp-category-color | "#ffffff" |
oppRewardBg | --opp-reward-bg | "#fdeecb" |
oppRewardColor | --opp-reward-color | "#8a5a00" |
oppRewardSize | --opp-reward-size | "12.5px" |
| Prop | CSS Variable | Example |
|---|---|---|
oppTitleSize | --opp-title-size | "19px" |
oppTitleColor | --opp-title-color | "#16181d" |
oppTitleWeight | --opp-title-weight | "700" |
oppTitleFontFamily | --opp-title-font-family | "Inter, sans-serif" |
| Prop | CSS Variable | Example |
|---|---|---|
oppDescriptionSize | --opp-description-size | "13.5px" |
oppDescriptionColor | --opp-description-color | "#626a78" |
oppDescriptionFontFamily | --opp-description-font-family | "Inter, sans-serif" |
oppDescriptionLineHeight | --opp-description-line-height | "1.45" |
| Prop | CSS Variable | Example |
|---|---|---|
oppExpiresSize | --opp-expires-size | "12.5px" |
oppExpiresColor | --opp-expires-color | "#8b93a1" |
| Prop | CSS Variable | Example |
|---|---|---|
oppButtonRadius | --opp-button-radius | "12px" |
oppSaveBorder | --opp-save-border | "#e0e3e8" |
oppSaveBg | --opp-save-bg | "transparent" |
oppSaveColor | --opp-save-color | "#626a78" |
| Prop | CSS Variable | Example |
|---|---|---|
oppTransitionDuration | --opp-transition-duration | "0.3s" |
Use the theme prop for quick preset styling instead of individual theming props:
| Theme | Description |
|---|---|
light | Default — white surface, dark accent button |
dark | Dark card surface with light text |
minimal | Border-only cards, no shadows |
bold | Larger titles, rounder corners |
corporate | Slate accent, sharp corners, no animations |
vibrant | Indigo accent with colored hover glow |
1<LegionOpportunities
2 baseUrl="https://your-legion-domain.com"
3 theme="dark"
4 count={5}
5/>Use a React ref to call methods on the widget:
1import { useRef } from "react";
2import {
3 LegionOpportunities,
4 LegionOpportunitiesHandle,
5} from "@legionhandtech/lht-react-widgets";
6
7function App() {
8 const widgetRef = useRef<LegionOpportunitiesHandle>(null);
9
10 return (
11 <>
12 <button onClick={() => widgetRef.current?.refresh()}>
13 Refresh Widget
14 </button>
15 <LegionOpportunities
16 ref={widgetRef}
17 baseUrl="https://your-legion-domain.com"
18 />
19 </>
20 );
21}| Method | Description |
|---|---|
refresh() | Reloads the widget iframe |
If you prefer the <legion-opportunities> custom element but want to install via NPM instead of a script tag:
import "@legionhandtech/lht-react-widgets/web-component";Then use it in your HTML or JSX:
1<legion-opportunities
2 base-url="https://your-legion-domain.com"
3 count="5"
4 layout="horizontal"
5 theme="light"
6>
7</legion-opportunities>The package ships with full TypeScript declarations. All prop interfaces are exported:
1import type {
2 OpportunitiesWidgetProps,
3 OpportunitiesThemingProps,
4 LegionOpportunitiesHandle,
5} from "@legionhandtech/lht-react-widgets";Fired when the widget iframe has loaded. Receives a detail object:
1<LegionOpportunities
2 baseUrl="https://your-legion-domain.com"
3 onLoad={(detail) => {
4 console.log(detail.widget); // "opportunities"
5 console.log(detail.count); // number
6 console.log(detail.layout); // "horizontal" | "vertical"
7 console.log(detail.tags); // string
8 }}
9/>Fired if the widget iframe fails to load:
1<LegionOpportunities
2 baseUrl="https://your-legion-domain.com"
3 onError={(message) => console.error(message)}
4/>