Grabbed dndbeyond's source code

```
~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js
```
This commit is contained in:
2025-05-28 11:50:03 -07:00
commit 8df9031d27
3592 changed files with 319051 additions and 0 deletions
@@ -0,0 +1,752 @@
//TODO: REI need to convert DB constants to enum
import axios, { Canceler } from "axios";
import { orderBy } from "lodash";
import React, { useContext } from "react";
import { connect, DispatchProp } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import {
Checkbox,
Collapsible,
LoadingPlaceholder,
Select,
} from "@dndbeyond/character-components/es";
import {
CharacterTheme,
Constants,
Creature,
CreatureGroupRulesLookup,
CreatureRule,
CreatureUtils,
ExtraManager,
ExtrasManager,
HelperUtils,
MonsterDefinitionContract,
RuleData,
rulesEngineSelectors,
SnippetData,
VehicleManager,
} from "@dndbeyond/character-rules-engine/es";
import { HtmlContent } from "~/components/HtmlContent";
import { Reference } from "~/components/Reference";
import { useExtras } from "~/hooks/useExtras";
import { CreatureBlock } from "~/subApps/sheet/components/CreatureBlock";
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
import VehicleBlock from "../../../components/VehicleBlock";
import { ThemeButton } from "../../../components/common/Button";
import { ExtrasManagerContext } from "../../../managers/ExtrasManagerContext";
import * as appEnvSelectors from "../../../selectors/appEnv";
import { SharedAppState } from "../../../stores/typings";
import {
AppLoggerUtils,
AppNotificationUtils,
ComponentUtils,
FilterUtils,
GD_VehicleBlockProps,
} from "../../../utils";
import ExtraManagePaneAddListing from "./ExtraManagePaneAddListing";
import ExtraManagePaneCurrentListing from "./ExtraManagePaneCurrentListing";
//TODO: this comes from ExtrasManager types
interface ShoppeState {
creatureDefinitions: Array<MonsterDefinitionContract>;
creatureDefinitionLookup: Record<number, MonsterDefinitionContract>;
creatures: Array<ExtraManager>;
vehicles: Array<ExtraManager>;
}
interface CollapsibleComponentProps {
metaItems: Array<React.ReactNode>;
heading: string | null;
}
interface CreatureBlockProps {
variant: "default" | "basic";
creature: Creature;
ruleData: RuleData;
className: string;
}
export interface PagedListingProps {
key: number | string;
extra: ExtraManager;
onAdd: (extra: ExtraManager, quantity: number) => void;
collapsibleComponentProps: CollapsibleComponentProps;
ContentComponent: React.ComponentType<any>;
contentComponentProps: GD_VehicleBlockProps | CreatureBlockProps;
showQuantity?: boolean;
}
enum DataRetrievalStatusEnum {
NONE = "NONE",
LOADING = "LOADING",
LOADED = "LOADED",
}
interface Props extends DispatchProp {
ruleData: RuleData;
pageSize: number;
creatureGroupRulesLookup: CreatureGroupRulesLookup;
isReadonly: boolean;
snippetData: SnippetData;
theme: CharacterTheme;
extras: Array<ExtraManager>;
extrasManager: ExtrasManager;
}
interface State {
selectedGroup: number | null;
dataStatus: DataRetrievalStatusEnum;
currentPage: number;
filterQuery: string;
filterRules: boolean;
filterChallengeMin: number | null;
filterChallengeMax: number | null;
isCurrentExtrasCollapsed: boolean;
extrasShoppe: ShoppeState;
}
class ExtraManagePane extends React.PureComponent<Props, State> {
static defaultProps = {
pageSize: 20,
};
loadMonstersCanceler: null | Canceler = null;
loadVehiclesCanceler: null | Canceler = null;
constructor(props: Props) {
super(props);
this.state = {
selectedGroup: null,
dataStatus: DataRetrievalStatusEnum.NONE,
currentPage: 0,
filterQuery: "",
filterRules: true,
filterChallengeMin: null,
filterChallengeMax: null,
isCurrentExtrasCollapsed: false,
extrasShoppe: {
creatureDefinitions: [],
creatureDefinitionLookup: {},
creatures: [],
vehicles: [],
},
};
}
// componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>): void {
// const { definitionPool } = this.props;
// if (definitionPool !== prevProps.definitionPool) {
// //TODO: maybe need to handle this with the Shoppe
// // this.setState(this.generateVehicleStateData(this.props));
// }
// }
componentDidMount() {
const { extrasManager } = this.props;
if (extrasManager) {
this.setState({
dataStatus: DataRetrievalStatusEnum.LOADING,
});
extrasManager
.getExtrasShoppe({
groupId: null,
onSuccess: (shoppeState: ShoppeState) => {
this.setState({
dataStatus: DataRetrievalStatusEnum.LOADED,
extrasShoppe: shoppeState,
});
},
additionalConfig: {
cancelToken: new axios.CancelToken((c) => {
this.loadMonstersCanceler = c;
}),
},
})
.then((response) => {
this.loadMonstersCanceler = null;
return response;
})
.catch(AppLoggerUtils.handleAdhocApiError);
} else {
this.setState({
dataStatus: DataRetrievalStatusEnum.LOADED,
});
}
}
componentWillUnmount(): void {
//TODO do these cancelers even work?
if (this.loadMonstersCanceler !== null) {
this.loadMonstersCanceler();
}
if (this.loadVehiclesCanceler !== null) {
this.loadVehiclesCanceler();
}
}
getLastPageIdx = <T extends any>(filteredItems: Array<T>): number => {
const { pageSize } = this.props;
return Math.ceil(filteredItems.length / pageSize) - 1;
};
getFilteredCreatures = (): Array<ExtraManager> => {
const { extrasManager } = this.props;
const {
filterRules,
filterQuery,
filterChallengeMin,
filterChallengeMax,
selectedGroup,
extrasShoppe,
} = this.state;
const { creatures, creatureDefinitionLookup } = extrasShoppe;
let creatureToFilter: Array<ExtraManager> = filterRules
? extrasManager.getCreaturesFilteredByRules(
creatures,
creatureDefinitionLookup,
selectedGroup
)
: creatures;
let filteredCreatures = creatureToFilter.filter((extra) => {
if (
filterQuery !== "" &&
!FilterUtils.doesQueryMatchData(
filterQuery,
extra.getName(),
extra.getSearchTags()
)
) {
return false;
}
const creature = extra.simulateExtraData(
selectedGroup,
creatureDefinitionLookup
) as Creature;
let challengeInfo = CreatureUtils.getChallengeInfo(creature);
const hideCr = CreatureUtils.getHideCr(creature);
// Treat hide Challenge Rating like a 0 for challenge rating values
if (hideCr) {
if (filterChallengeMin !== null && 0 < filterChallengeMin) {
return false;
}
if (filterChallengeMax !== null && 0 > filterChallengeMax) {
return false;
}
} else {
if (
filterChallengeMin !== null &&
challengeInfo &&
challengeInfo.value < filterChallengeMin
) {
return false;
}
if (
filterChallengeMax !== null &&
challengeInfo &&
challengeInfo.value > filterChallengeMax
) {
return false;
}
}
return true;
});
return orderBy(filteredCreatures, (extra) => extra.getName());
};
getCreateNames = (name: string, quantity: number): Array<string | null> => {
let names: Array<string | null> = [];
if (quantity > 1) {
for (let i = 1; i <= quantity; i++) {
names.push(`${name} ${i}`);
}
} else {
names.push(null);
}
return names;
};
isFiltered = (): boolean => {
const { filterQuery, filterRules } = this.state;
if (filterRules) {
return true;
}
if (filterQuery) {
return true;
}
return false;
};
handlePageMore = (): void => {
this.setState((prevState) => ({
currentPage: prevState.currentPage + 1,
}));
};
handleQueryChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
const query = evt.target.value;
this.setState({
filterQuery: query,
currentPage: 0,
});
};
handleChallengeMinChange = (value: string): void => {
this.setState({
filterChallengeMin: HelperUtils.parseInputFloat(value),
});
};
handleChallengeMaxChange = (value: string): void => {
this.setState({
filterChallengeMax: HelperUtils.parseInputFloat(value),
});
};
handleRawToggle = (): void => {
this.setState((prevState) => ({
filterRules: !prevState.filterRules,
currentPage: 0,
}));
};
handleSelectedGroupChange = (value: string): void => {
const { extrasManager } = this.props;
const { extrasShoppe } = this.state;
let selectedGroup = HelperUtils.parseInputInt(value, null);
extrasManager.updateExtrasShoppe({
currentShoppe: extrasShoppe,
groupId: selectedGroup,
onSuccess: (shoppeState: ShoppeState) => {
this.setState((prevState) => {
let filterChallengeMin = prevState.filterChallengeMin;
let filterChallengeMax = prevState.filterChallengeMax;
if (
selectedGroup !== null &&
extrasManager.hack__isSidekickGroup(selectedGroup)
) {
filterChallengeMin = null;
filterChallengeMax = null;
}
return {
selectedGroup,
isCurrentExtrasCollapsed: true,
filterChallengeMax,
filterChallengeMin,
extrasShoppe: shoppeState,
};
});
},
});
};
handleCreateExtra = (extra: ExtraManager, quantity: number): void => {
const { selectedGroup } = this.state;
extra.handleAdd(
{ quantity, selectedGroup },
AppNotificationUtils.handleExtraCreateAccepted.bind(this, extra.extra)
);
};
handleRemoveExtra = (extra: ExtraManager): void => {
extra.handleRemove();
};
handleCurrentExtrasVisibilityChange = (isCollapsed: boolean): void => {
this.setState({
isCurrentExtrasCollapsed: isCollapsed,
});
};
renderFilterUi = (): React.ReactNode => {
const {
filterQuery,
filterChallengeMin,
filterChallengeMax,
selectedGroup,
} = this.state;
const { extrasManager } = this.props;
let challengeOptions = extrasManager.getChallengeOptions();
let isSidekickGroup =
selectedGroup !== null &&
extrasManager.hack__isSidekickGroup(selectedGroup);
return (
<div className="ct-extra-manage-pane__filters">
<div className="ct-extra-manage-pane__filter">
<div className="ct-extra-manage-pane__filter-heading">Filter</div>
<input
type="search"
className="ct-filter__query"
value={filterQuery}
onChange={this.handleQueryChange}
placeholder="Name, Type, Subtype, Habitat, Tag, etc."
spellCheck={false}
autoComplete="off"
/>
</div>
{!isSidekickGroup && (
<div className="ct-extra-manage-pane__filters-challenge">
<div className="ct-extra-manage-pane__filters-challenge-heading">
Challenge Range
</div>
<div className="ct-extra-manage-pane__filters-challenge-fields">
<div className="ct-extra-manage-pane__filters-challenge-field">
<Select
value={filterChallengeMin}
options={challengeOptions}
onChange={this.handleChallengeMinChange}
placeholder="--"
/>
</div>
<div className="ct-extra-manage-pane__filters-challenge-sep">
--
</div>
<div className="ct-extra-manage-pane__filters-challenge-field">
<Select
value={filterChallengeMax}
options={challengeOptions}
onChange={this.handleChallengeMaxChange}
placeholder="--"
/>
</div>
</div>
</div>
)}
</div>
);
};
renderPagedListing = <T extends ExtraManager>(
extras: Array<T>,
listingComponentPropMappingFunc: (extra: T) => PagedListingProps
): React.ReactNode => {
const { currentPage } = this.state;
const { pageSize, isReadonly } = this.props;
const pagedExtras = extras.slice(0, (currentPage + 1) * pageSize);
return (
<div className="ct-extra-manage-pane__listing">
{pagedExtras.map((extra) => (
<ExtraManagePaneAddListing
{...listingComponentPropMappingFunc(extra)}
isReadonly={isReadonly}
/>
))}
</div>
);
};
renderCreatures = (): React.ReactNode => {
const { ruleData, theme, extrasManager } = this.props;
const { extrasShoppe, selectedGroup } = this.state;
const { creatureDefinitionLookup } = extrasShoppe;
let filteredCreatures = this.getFilteredCreatures();
return (
<div className="ct-extra-manage-pane__extras">
{this.renderFilterUi()}
{this.renderPagedListing(filteredCreatures, (extraManager) => {
const creature = extraManager.simulateExtraData(
selectedGroup,
creatureDefinitionLookup
) as Creature;
const sourceNames = extraManager.getSourceNames();
const References = extraManager.isHomebrew()
? [<Reference isDarkMode={theme.isDarkMode} name="Homebrew" />]
: sourceNames.map((source) => (
<Reference
name={source}
isDarkMode={theme.isDarkMode}
key={uuidv4()}
/>
));
return {
key: extraManager.getId(),
extra: extraManager,
onAdd: this.handleCreateExtra,
collapsibleComponentProps: {
metaItems: [
...extrasManager.generateCreatureMeta(creature),
...References,
],
heading: extraManager.getName(),
},
ContentComponent: CreatureBlock,
contentComponentProps: {
variant: "basic",
creature,
ruleData: ruleData,
className: "ddbc-creature-block",
},
};
})}
{this.renderPager(filteredCreatures)}
</div>
);
};
renderVehicles = (): React.ReactNode => {
const { theme } = this.props;
const { selectedGroup, extrasShoppe } = this.state;
const { vehicles } = extrasShoppe;
let orderedVehicles = orderBy(vehicles, (vehicle) => vehicle.getName());
return (
<div className="ct-extra-manage-pane__extras">
{this.renderPagedListing(orderedVehicles, (vehicleExtra) => {
const vehicle = vehicleExtra.simulateExtraData(
selectedGroup
) as VehicleManager;
const sourceNames = vehicleExtra.getSourceNames();
const References = vehicleExtra.isHomebrew()
? [<Reference isDarkMode={theme.isDarkMode} name="Homebrew" />]
: sourceNames.map((source) => (
<Reference name={source} isDarkMode={theme.isDarkMode} />
));
return {
key: vehicleExtra.getId(),
extra: vehicleExtra,
onAdd: this.handleCreateExtra,
collapsibleComponentProps: {
metaItems: [...vehicle.generateVehicleMeta(), ...References],
heading: vehicleExtra.getName(),
},
ContentComponent: VehicleBlock,
contentComponentProps:
ComponentUtils.generateVehicleBlockProps(vehicle),
showQuantity: false,
};
})}
{this.renderPager(vehicles)}
</div>
);
};
renderGroupSelector = (): React.ReactNode => {
const { selectedGroup, filterRules, dataStatus } = this.state;
const { creatureGroupRulesLookup, extrasManager } = this.props;
const selectedGroupInfo = extrasManager.getCreatureGroupInfo(selectedGroup);
let groupRules: Array<CreatureRule> | null =
selectedGroup === null
? null
: HelperUtils.lookupDataOrFallback(
creatureGroupRulesLookup,
selectedGroup
);
let isSidekickGroup: boolean =
selectedGroup !== null &&
extrasManager.hack__isSidekickGroup(selectedGroup);
let hasGroupRules: boolean = !!groupRules || isSidekickGroup;
let showNotDefaultNoRules: boolean =
selectedGroupInfo !== null &&
!selectedGroupInfo.enabledByDefault &&
!hasGroupRules;
return (
<div className="ct-extra-manage-pane__selected-group">
<div className="ct-extra-manage-pane__selected-group-chooser">
<Select
options={extrasManager.getGroupOptions()}
value={selectedGroup}
onChange={this.handleSelectedGroupChange}
placeholder="-- Choose a Category --"
disabled={dataStatus === DataRetrievalStatusEnum.LOADING}
/>
</div>
{selectedGroup && (
<div className="ct-extra-manage-pane__selected-group-content">
{selectedGroupInfo && selectedGroupInfo.description && (
<HtmlContent
className="ct-extra-manage-pane__selected-group-description"
html={selectedGroupInfo.description}
withoutTooltips
/>
)}
{showNotDefaultNoRules && (
<div className="ct-extra-manage-pane__selected-group-warning">
Adding a creature of this category will be outside of normal
game rules.
</div>
)}
{selectedGroupInfo && hasGroupRules && (
<div className="ct-extra-manage-pane__selected-group-rules">
<Checkbox
initiallyEnabled={filterRules}
onChange={this.handleRawToggle}
label={`Filter Using ${selectedGroupInfo.name} Rules`}
/>
</div>
)}
</div>
)}
</div>
);
//`
};
renderManager = (): React.ReactNode => {
const { dataStatus, selectedGroup } = this.state;
if (dataStatus !== DataRetrievalStatusEnum.LOADED) {
return <LoadingPlaceholder />;
}
let extrasNode: React.ReactNode;
if (selectedGroup !== null) {
if (selectedGroup === Constants.HACK_VEHICLE_GROUP_ID) {
extrasNode = this.renderVehicles();
} else {
extrasNode = this.renderCreatures();
}
}
return (
<div className="ct-extra-manage-pane__manager">
{this.renderGroupSelector()}
{extrasNode}
</div>
);
};
renderPager = <T extends any>(filteredItems: Array<T>): React.ReactNode => {
const { currentPage } = this.state;
const lastPageIdx = this.getLastPageIdx(filteredItems);
if (lastPageIdx < 1 || currentPage >= lastPageIdx) {
return null;
}
return (
<div className="ct-extra-manage-pane__pager">
<ThemeButton block={true} onClick={this.handlePageMore}>
Load More
</ThemeButton>
</div>
);
};
renderCurrentExtras = (
extras: Array<ExtraManager>,
heading: string | null,
groupId: number | Constants.ExtraGroupTypeEnum
): React.ReactNode => {
const { ruleData, theme } = this.props;
if (!extras.length) {
return null;
}
let orderedExtras = orderBy(extras, (extra) => extra.getName());
return (
<div className="ct-extra-manage-pane__group" key={groupId}>
<div className="ct-extra-manage-pane__group-heading">{heading}</div>
<div className="ct-extra-manage-pane__group-items">
{orderedExtras.map((extra) => (
<ExtraManagePaneCurrentListing
theme={theme}
key={extra.getUniqueKey()}
extra={extra}
ruleData={ruleData}
onRemove={this.handleRemoveExtra}
/>
))}
</div>
</div>
);
};
renderCharacterExtras = (): React.ReactNode => {
const { extrasManager, extras } = this.props;
if (!extras.length) {
return (
<div className="ct-extra-manage-pane__default">
Extras that you add will display here.
</div>
);
}
const groups = extrasManager.getExtrasGroups();
const orderedCreatureGroupInfos = extrasManager.getGroupInfosForExtras();
return (
<div className="ct-extra-manage-pane__inventory">
{orderedCreatureGroupInfos.map((groupInfo) =>
this.renderCurrentExtras(
groups[groupInfo.id],
groupInfo.name,
groupInfo.id
)
)}
</div>
);
};
render() {
const { isCurrentExtrasCollapsed } = this.state;
return (
<div className="ct-extra-manage-pane">
<Header>Manage Extras</Header>
<Collapsible header="Add an Extra" initiallyCollapsed={false}>
{this.renderManager()}
</Collapsible>
<Collapsible
header="Current Extras"
collapsed={isCurrentExtrasCollapsed}
onChangeHandler={this.handleCurrentExtrasVisibilityChange}
>
{this.renderCharacterExtras()}
</Collapsible>
</div>
);
}
}
function mapStateToProps(state: SharedAppState) {
return {
ruleData: rulesEngineSelectors.getRuleData(state),
creatureGroupRulesLookup:
rulesEngineSelectors.getCreatureGroupRulesLookup(state),
isReadonly: appEnvSelectors.getIsReadonly(state),
snippetData: rulesEngineSelectors.getSnippetData(state),
theme: rulesEngineSelectors.getCharacterTheme(state),
};
}
function ExtraManagePaneContainer(props) {
const { extrasManager } = useContext(ExtrasManagerContext);
const extras = useExtras();
return (
<ExtraManagePane extrasManager={extrasManager} extras={extras} {...props} />
);
}
export default connect(mapStateToProps)(ExtraManagePaneContainer);
@@ -0,0 +1,65 @@
import React, { useState } from "react";
import { HelperUtils } from "@dndbeyond/character-rules-engine/es";
import SimpleQuantity from "../../../../components/SimpleQuantity";
import { ThemeButton } from "../../../../components/common/Button";
import { PagedListingProps } from "../ExtraManagePane";
import ExtraManagePaneListingExtra from "../ExtraManagePaneListingExtra";
interface Props extends PagedListingProps {
isReadonly: boolean;
minimum?: number;
maximum?: number;
}
export const ExtraManagePaneAddListing: React.FC<Props> = ({
isReadonly,
minimum = 1,
maximum = 10,
extra,
collapsibleComponentProps,
ContentComponent,
contentComponentProps,
onAdd,
showQuantity = true,
}) => {
const [quantity, setQuantity] = useState<number>(1);
return (
<ExtraManagePaneListingExtra
headerCallout={
<ThemeButton onClick={() => onAdd(extra, 1)} size="small">
Add
</ThemeButton>
}
{...collapsibleComponentProps}
>
<ContentComponent {...contentComponentProps} />
{!isReadonly && showQuantity && (
<div className="ct-extra-manage-pane__quantity">
<div className="ct-extra-manage-pane__quantity-manager">
<SimpleQuantity
quantity={quantity}
minimum={minimum}
maximum={maximum}
onUpdate={(quantity) => setQuantity(quantity)}
isReadonly={isReadonly}
/>
</div>
<div className="ct-extra-manage-pane__quantity-action">
<ThemeButton
onClick={() =>
onAdd(extra, HelperUtils.clampInt(quantity, minimum, maximum))
}
size="small"
>
Add {quantity}
</ThemeButton>
</div>
</div>
)}
</ExtraManagePaneListingExtra>
);
};
export default ExtraManagePaneAddListing;
@@ -0,0 +1,4 @@
import ExtraManagePaneAddListing from "./ExtraManagePaneAddListing";
export default ExtraManagePaneAddListing;
export { ExtraManagePaneAddListing };
@@ -0,0 +1,83 @@
import React from "react";
import { MarketplaceCta } from "@dndbeyond/character-components/es";
import {
CharacterTheme,
Creature,
ExtraManager,
RuleData,
VehicleManager,
} from "@dndbeyond/character-rules-engine/es";
import { Reference } from "~/components/Reference";
import { CreatureBlock } from "~/subApps/sheet/components/CreatureBlock";
import VehicleBlock from "../../../../components/VehicleBlock";
import { RemoveButton } from "../../../../components/common/Button";
import { ComponentUtils } from "../../../../utils";
import ExtraManagePaneListingExtra from "../ExtraManagePaneListingExtra";
interface Props {
extra: ExtraManager;
ruleData: RuleData;
onRemove: (extra: ExtraManager) => void;
theme: CharacterTheme;
}
export const ExtraManagePaneCurrentListing: React.FC<Props> = ({
extra,
ruleData,
onRemove,
theme,
}) => {
const extraData = extra.getExtraData();
let label: string = "Extra";
let ContentComponent: React.ReactNode;
if (extra.isCreature()) {
label = "Creature";
ContentComponent = (
<CreatureBlock
variant="default"
creature={extraData as Creature}
ruleData={ruleData}
className="ddbc-creature-block"
/>
);
} else if (extra.isVehicle()) {
label = "Vehicle";
const vehicle = extraData as VehicleManager;
ContentComponent = vehicle.isAccessible() ? (
<VehicleBlock
theme={theme}
{...ComponentUtils.generateVehicleBlockProps(vehicle)}
/>
) : (
<MarketplaceCta
sourceNames={extra.getSourceNames()}
description={`To unlock this ${label}, check out the Marketplace to view purchase options.`}
/>
);
}
const References = extra.isHomebrew()
? [<Reference isDarkMode={theme.isDarkMode} name="Homebrew" />]
: extra
.getSourceNames()
.map((sourceName) => (
<Reference name={sourceName} isDarkMode={theme.isDarkMode} />
));
return (
<ExtraManagePaneListingExtra
headerCallout={<RemoveButton onClick={() => onRemove(extra)} />}
metaItems={[...extra.getMetaText(), ...References]}
heading={extra.getName()}
>
{ContentComponent}
</ExtraManagePaneListingExtra>
);
};
export default ExtraManagePaneCurrentListing;
@@ -0,0 +1,4 @@
import ExtraManagePaneCurrentListing from "./ExtraManagePaneCurrentListing";
export default ExtraManagePaneCurrentListing;
export { ExtraManagePaneCurrentListing };
@@ -0,0 +1,38 @@
import React from "react";
import {
Collapsible,
CollapsibleHeaderContent,
} from "@dndbeyond/character-components/es";
interface Props {
headerCallout: React.ReactNode;
heading: React.ReactNode;
metaItems: Array<React.ReactNode>;
}
export const ExtraManagePaneListingExtra: React.FC<Props> = ({
headerCallout,
heading,
metaItems,
children,
}) => {
let header: React.ReactNode = (
<CollapsibleHeaderContent
heading={heading}
metaItems={metaItems}
callout={headerCallout}
/>
);
return (
<Collapsible
layoutType={"minimal"}
header={header}
className="ct-extra-manage-pane__extra"
>
{children}
</Collapsible>
);
};
export default ExtraManagePaneListingExtra;
@@ -0,0 +1,4 @@
import ExtraManagePaneListingExtra from "./ExtraManagePaneListingExtra";
export default ExtraManagePaneListingExtra;
export { ExtraManagePaneListingExtra };
@@ -0,0 +1,12 @@
import ExtraManagePane from "./ExtraManagePane";
import ExtraManagePaneAddListing from "./ExtraManagePaneAddListing";
import ExtraManagePaneCurrentListing from "./ExtraManagePaneCurrentListing";
import ExtraManagePaneListingExtra from "./ExtraManagePaneListingExtra";
export default ExtraManagePane;
export {
ExtraManagePane,
ExtraManagePaneAddListing,
ExtraManagePaneCurrentListing,
ExtraManagePaneListingExtra,
};