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,52 @@
import {
AbilityManager,
HelperUtils,
} from "@dndbeyond/character-rules-engine/es";
import { FormInputField } from "~/tools/js/Shared/components/common/FormInputField";
interface Props {
abilities: Array<AbilityManager>;
minScore?: number;
maxScore?: number;
}
export default function AbilityScoreManagerManual({
abilities,
minScore = 3,
maxScore = 18,
}: Props) {
const handleTransformValueOnBlur = (value: string): number | null => {
const parsedValue = HelperUtils.parseInputInt(value);
let clampedValue: number | null = null;
if (parsedValue !== null) {
clampedValue = HelperUtils.clampInt(parsedValue, minScore, maxScore);
}
return clampedValue;
};
return (
<div className=" ability-score-manager ability-score-manager-manual">
<div className="ability-score-manager-stats">
{abilities.map((abilityScore) => (
<div
className="ability-score-manager-stat"
key={abilityScore.getId()}
data-stat-id={abilityScore.getId()}
>
<FormInputField
transformValueOnBlur={handleTransformValueOnBlur}
onBlur={(value) => abilityScore.handleScoreChange(Number(value))}
label={abilityScore.getLabel() ?? ""}
initialValue={abilityScore.getBaseScore()}
/>
<div className="ability-score-manager-stat-total">
Total: {abilityScore.getTotalScore() ?? "--"}
</div>
</div>
))}
</div>
</div>
);
}
@@ -0,0 +1,4 @@
import AbilityScoreManagerManual from "./AbilityScoreManagerManual";
export default AbilityScoreManagerManual;
export { AbilityScoreManagerManual };
@@ -0,0 +1,130 @@
import { uniqueId } from "lodash";
import { Select } from "@dndbeyond/character-components/es";
import {
AbilityManager,
HelperUtils,
HtmlSelectOption,
} from "@dndbeyond/character-rules-engine/es";
const totalPoints: number = 27;
const scoreChoices: Array<number> = [8, 9, 10, 11, 12, 13, 14, 15];
const scorePoints: Record<number, number> = {
8: 0,
9: 1,
10: 2,
11: 3,
12: 4,
13: 5,
14: 7,
15: 9,
};
function renderScoreLabel(score: number, currentScore: number | null): string {
if (currentScore !== null) {
if (score > currentScore) {
const pointDiff = scorePoints[score] - scorePoints[currentScore];
return `${score} (-${pointDiff} Point${pointDiff === 1 ? "" : "s"})`;
} else if (score < currentScore) {
const pointDiff = scorePoints[currentScore] - scorePoints[score];
return `${score} (+${pointDiff} Point${pointDiff === 1 ? "" : "s"})`;
}
}
return "" + score;
}
interface Props {
abilities: Array<AbilityManager>;
}
export default function AbilityScoreManagerPointBuy({ abilities }: Props) {
const currentPoints = abilities.reduce((acc: number, abilityScore) => {
const baseScore = abilityScore.getBaseScore();
if (baseScore !== null) {
return acc + scorePoints[baseScore];
}
return acc;
}, 0);
const remainingPoints: number = totalPoints - currentPoints;
return (
<div className="ability-score-manager ability-score-manager-point">
<div className="ability-score-manager-points">
<div className="ability-score-manager-points-heading">
Points Remaining
</div>
<div className="ability-score-manager-points-value">
<span className="ability-score-manager-points-value-remaining">
{remainingPoints}
</span>
<span className="ability-score-manager-points-value-sep">/</span>
<span className="ability-score-manager-points-value-total">
{totalPoints}
</span>
</div>
</div>
<div className="ability-score-manager-stats">
{abilities.map((abilityScore) => {
const uId = uniqueId("qry_");
const baseScore = abilityScore.getBaseScore();
const availableScores = scoreChoices.reduce(
(acc: Array<number>, score) => {
let pointDiff = scorePoints[score];
if (baseScore !== null) {
pointDiff -= scorePoints[baseScore];
}
if (
pointDiff < 0 ||
(pointDiff > 0 && pointDiff <= remainingPoints) ||
score === abilityScore.getBaseScore()
) {
acc.push(score);
}
return acc;
},
[]
);
const availableScoreOptions: Array<HtmlSelectOption> =
availableScores.map((score) => ({
label: renderScoreLabel(score, abilityScore.getBaseScore()),
value: score,
}));
return (
<div
className="ability-score-manager-stat"
key={abilityScore.getId()}
>
<div className="builder-field form-select-field">
<span className="builder-field-label">
<label
className="builder-field-heading form-select-field-label"
htmlFor={uId}
>
{abilityScore.getLabel()}
</label>
</span>
<span className="builder-field-input">
<Select
id={uId}
options={availableScoreOptions}
value={abilityScore.getBaseScore()}
onChange={(value) =>
abilityScore.handleScoreChange(Number(value))
}
initialOptionRemoved={true}
/>
</span>
<div className="ability-score-manager-stat-total">
Total: {abilityScore.getTotalScore() ?? "--"}
</div>
</div>
</div>
);
})}
</div>
</div>
);
}
@@ -0,0 +1,4 @@
import AbilityScoreManagerPointBuy from "./AbilityScoreManagerPointBuy";
export default AbilityScoreManagerPointBuy;
export { AbilityScoreManagerPointBuy };
@@ -0,0 +1,82 @@
import { uniqueId } from "lodash";
import { Select } from "@dndbeyond/character-components/es";
import {
AbilityManager,
HelperUtils,
HtmlSelectOption,
} from "@dndbeyond/character-rules-engine/es";
import { TypeScriptUtils } from "../../../Shared/utils";
const standardScores: Array<number> = [8, 10, 12, 13, 14, 15];
interface Props {
abilities: Array<AbilityManager>;
}
export default function AbilityScoreManagerStandardArray({ abilities }: Props) {
const usedScores = abilities
.map((abilityScore) => abilityScore.getBaseScore())
.filter(TypeScriptUtils.isNotNullOrUndefined);
return (
<div className="ability-score-manager ability-score-manager-standard">
<div className="ability-score-manager-stats">
{abilities.map((abilityScore) => {
const uId = uniqueId("qry_");
const availableScores = standardScores.reduce(
(acc: Array<number>, score) => {
if (
!usedScores.includes(score) ||
score === abilityScore.getBaseScore()
) {
acc.push(score);
}
return acc;
},
[]
);
const availableScoreOptions: Array<HtmlSelectOption> =
availableScores.map((score) => ({
label: "" + score,
value: score,
}));
return (
<div
className="ability-score-manager-stat"
key={abilityScore.getId()}
>
<div className="builder-field form-select-field">
<span className="builder-field-label">
<label
className="builder-field-heading form-select-field-label"
htmlFor={uId}
>
{abilityScore.getLabel()}
</label>
</span>
<span className="builder-field-input">
<Select
id={uId}
options={availableScoreOptions}
value={abilityScore.getBaseScore()}
onChange={(value) => {
const parsedValue = HelperUtils.parseInputInt(value);
abilityScore.handleScoreChange(parsedValue);
}}
placeholder={"--"}
/>
</span>
<div className="ability-score-manager-stat-total">
Total: {abilityScore.getTotalScore() ?? "--"}
</div>
</div>
</div>
);
})}
</div>
</div>
);
}
@@ -0,0 +1,4 @@
import AbilityScoreManagerStandardArray from "./AbilityScoreManagerStandardArray";
export default AbilityScoreManagerStandardArray;
export { AbilityScoreManagerStandardArray };
@@ -0,0 +1,34 @@
import React from "react";
import { Button } from "@dndbeyond/character-components/es";
interface withBuilderProps {
clsNames: Array<string>;
}
function withBuilderButton<P extends object, C>(
WrappedComponent: React.JSXElementConstructor<P> & C
) {
type Props = JSX.LibraryManagedAttributes<C, P>;
return class WithBuilderButton extends React.PureComponent<
Props & withBuilderProps
> {
static defaultProps = {
clsNames: [],
};
render() {
const { children, clsNames } = this.props;
return (
<WrappedComponent
{...(this.props as any)}
clsNames={[...clsNames, "builder-button"]}
>
{children}
</WrappedComponent>
);
}
};
}
export default withBuilderButton(Button);
@@ -0,0 +1,4 @@
import Button from "./Button";
export default Button;
export { Button };
@@ -0,0 +1,68 @@
import FormControl from "@mui/material/FormControl";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormLabel from "@mui/material/FormLabel";
import Radio from "@mui/material/Radio";
import MuiRadioGroup from "@mui/material/RadioGroup";
import Typography from "@mui/material/Typography";
import { ChangeEvent } from "react";
import { TitleH3 } from "../shared-styles";
type RadioGroupProps = {
name?: string;
defaultValue?: any;
label: string;
options?: any;
subtitle?: string;
onChange?: (event: ChangeEvent, value: any) => void;
disabled?: boolean;
value?: any;
};
export const RadioGroup = ({
name,
defaultValue,
label,
options,
subtitle,
onChange,
disabled = false,
value,
}: RadioGroupProps) => {
return (
<FormControl disabled={disabled}>
<FormLabel
sx={{ ...TitleH3, color: "#000" }}
id={`radio-button-group-${label.split(" ").join("-")}`}
>
{label}
</FormLabel>
<Typography
variant="subtitle1"
sx={{ color: "#12181ca3", fontSize: "14px" }}
>
{subtitle}
</Typography>
<MuiRadioGroup
aria-labelledby={`radio-button-group-${label.split(" ").join("-")}`}
name={name}
onChange={onChange}
{...{
...(defaultValue && { defaultValue }),
...(value && { value }),
}}
>
{options.map((option) => {
return (
<FormControlLabel
value={option.value}
control={<Radio color="secondary" />}
label={option.label}
key={option.value}
/>
);
})}
</MuiRadioGroup>
</FormControl>
);
};
@@ -0,0 +1,4 @@
import { RadioGroup } from "./RadioGroup";
export default RadioGroup;
export { RadioGroup };
@@ -0,0 +1,8 @@
export const TitleH3 = {
fontSize: "16px",
fontWeight: "bold",
fontFamily: "Roboto Condensed",
letterSpacing: "normal",
lineHeight: 1.5,
marginTop: 4,
};
@@ -0,0 +1,46 @@
import React from "react";
import { ClassDefinitionContract } from "@dndbeyond/character-rules-engine/es";
import Button from "../Button";
interface Props {
charClass: ClassDefinitionContract;
onRequestChange: () => void;
}
export default class ClassDisplaySimple extends React.PureComponent<Props> {
handleChangeClick = () => {
const { onRequestChange } = this.props;
if (onRequestChange) {
onRequestChange();
}
};
render() {
const { charClass } = this.props;
let portraitAvatarUrl = charClass.portraitAvatarUrl;
return (
<div className="builder-field builder-field-valid class-simple">
<div className="builder-field-heading">Selected Class</div>
<div className="class-simple-chosen">
<div className="class-simple-chosen-preview">
<img
className="class-simple-chosen-preview-img"
src={portraitAvatarUrl ? portraitAvatarUrl : ""}
alt=""
/>
</div>
<div className="class-simple-chosen-heading">{charClass.name}</div>
<div className="class-simple-chosen-action">
<Button onClick={this.handleChangeClick} size="small">
Change
</Button>
</div>
</div>
</div>
);
}
}
@@ -0,0 +1,4 @@
import ClassDisplaySimple from "./ClassDisplaySimple";
export default ClassDisplaySimple;
export { ClassDisplaySimple };
@@ -0,0 +1,93 @@
import React, { useContext } from "react";
import { Select } from "@dndbeyond/character-components/es";
import { FeatList, FormatUtils } from "@dndbeyond/character-rules-engine";
import { Accordion } from "~/components/Accordion";
import { FeatDetail } from "~/subApps/sheet/components/Sidebar/panes/FeatsManagePane/FeatDetail";
import { CharacterFeaturesManagerContext } from "~/tools/js/Shared/managers/CharacterFeaturesManagerContext";
type GrantedFeatProps = {
featList: FeatList;
requiredLevel?: number;
};
export const GrantedFeat: React.FC<GrantedFeatProps> = ({
featList,
requiredLevel,
}) => {
const { characterFeaturesManager } = useContext(
CharacterFeaturesManagerContext
);
// Only show the Feat List section if there is something to show.
if (featList.isEmpty()) {
return null;
}
let featContent: React.ReactNode | undefined;
let selectBox: React.ReactNode | undefined;
let infoMessage: React.ReactNode | undefined;
let hasUnfinishedChoices = featList.hasChoiceToMake();
let subtitleItems = ["Granted Feat"];
// Show a message when the character already has every feat in the feat list from somewhere else.
if (featList.alreadyHasEveryFeat() && featList.chosenFeatId === null) {
infoMessage = <p>This feat already exists on your character.</p>;
} else {
// Show the feat details if a feat has been chosen from the feat list.
if (featList.chosenFeatId !== null) {
const featId = featList.chosenFeatId;
const featManager = characterFeaturesManager.getFeatById(featId);
if (featManager !== null) {
const needToChooseFeatOptions =
featManager.getUnfinishedChoices().length > 0;
hasUnfinishedChoices = hasUnfinishedChoices || needToChooseFeatOptions;
featContent = <FeatDetail featManager={featManager} />;
if (featManager.isHiddenFeat()) {
subtitleItems = []; // don't call it a feat if it isn't really a feat.
}
const choiceCount = featManager.getChoices().length;
if (choiceCount) {
subtitleItems.push(
`${choiceCount} Choice${choiceCount !== 1 ? "s" : ""}`
);
}
}
}
// When there is more than one feat in the list, show the select box to choose one.
if (!featList.isSingleFeat()) {
selectBox = (
<Select
onChangePromise={(...args) => featList.handleChoiceSelected(...args)}
options={featList.availableChoices}
value={featList.chosenFeatId !== null ? featList.chosenFeatId : -1}
clsNames={["description-manage-background-granted-feat-chooser"]}
/>
);
}
}
if (requiredLevel) {
subtitleItems.push(`${FormatUtils.ordinalize(requiredLevel)} level`);
}
return (
<Accordion
summary={featList.definition.name}
summaryMetaItems={subtitleItems}
showAlert={hasUnfinishedChoices}
variant="paper"
key={featList.definition.id}
>
{infoMessage}
{selectBox}
{featContent}
</Accordion>
);
};
@@ -0,0 +1,263 @@
import React from "react";
import { Checkbox, Select } from "@dndbeyond/character-components/es";
import {
Constants,
FormatUtils,
HtmlSelectOption,
AccessUtils,
} from "@dndbeyond/character-rules-engine/es";
import { CollapsibleContent } from "~/components/CollapsibleContent";
import { Link } from "~/components/Link";
export interface AffectedFeatureInfo {
name: string;
definitionKey: string;
disabled: boolean;
}
interface OptionalFeatureProps {
name: string;
requiredLevel?: number;
description: string | null;
featureType: Constants.FeatureTypeEnum | null;
definitionKey: string;
affectedFeatureDefinitionKey: string | null;
isSelected: boolean;
onSelection: (
definitionKey: string,
affectedFeatureDefinitionKey: string | null
) => void;
onRemoveSelectionPromise?: (
definitionKey: string,
newIsEnabled: boolean,
accept: () => void,
reject: () => void
) => void;
onChangeReplacementPromise?: (
definitionKey: string,
newAffectedDefinitionKey: string | null,
oldAffectedDefinitionKey: string | null,
accept: () => void,
reject: () => void
) => void;
affectedFeatures: Array<AffectedFeatureInfo>;
accessType: Constants.AccessTypeEnum;
}
export class OptionalFeature extends React.PureComponent<
OptionalFeatureProps,
{}
> {
handleSelection = (isEnabled: boolean): void => {
const { featureType, onSelection, definitionKey, affectedFeatures } =
this.props;
switch (featureType) {
case Constants.FeatureTypeEnum.REPLACEMENT: {
if (isEnabled) {
const affectedFeatureDefinitionKey: string | null =
affectedFeatures.length === 1
? affectedFeatures[0].definitionKey
: null;
onSelection(definitionKey, affectedFeatureDefinitionKey);
}
break;
}
case Constants.FeatureTypeEnum.ADDITIONAL: {
if (isEnabled) {
onSelection(definitionKey, null);
}
break;
}
default:
//not implemented
}
};
transformDefinitionKey = (definitionKey: string): string | null => {
return definitionKey.length ? definitionKey : null;
};
handleReplacementChangePromise = (
newAffectedDefinitionKey: string,
oldAffectedDefinitionKey: string,
accept: () => void,
reject: () => void
) => {
const { onChangeReplacementPromise, definitionKey } = this.props;
if (onChangeReplacementPromise) {
onChangeReplacementPromise(
definitionKey,
this.transformDefinitionKey(newAffectedDefinitionKey),
this.transformDefinitionKey(oldAffectedDefinitionKey),
accept,
reject
);
}
};
handleUnselectPromise = (
newIsEnabled: boolean,
accept: () => void,
reject: () => void
) => {
const { onRemoveSelectionPromise, definitionKey } = this.props;
if (onRemoveSelectionPromise) {
onRemoveSelectionPromise(definitionKey, newIsEnabled, accept, reject);
}
};
renderReplacements = (): React.ReactNode => {
const {
affectedFeatures,
isSelected,
featureType,
affectedFeatureDefinitionKey,
} = this.props;
if (!affectedFeatures.length) {
return null;
}
const introText: Array<string> = ["Replaces:"];
const disabledText: string = "(already replaced)";
if (affectedFeatures.length === 1) {
introText.push(affectedFeatures[0].name);
if (affectedFeatures[0].disabled) {
introText.push(disabledText);
}
}
const options: Array<HtmlSelectOption> = [];
if (affectedFeatures.length > 1) {
affectedFeatures.forEach((feature) => {
const label: Array<string> = [feature.name];
if (feature.disabled) {
label.push(disabledText);
}
options.push({
label: label.join(" "),
value: feature.definitionKey,
disabled: feature.disabled,
});
});
}
const classNames: Array<string> = ["ct-optional-feature__select"];
if (
featureType === Constants.FeatureTypeEnum.REPLACEMENT &&
isSelected &&
affectedFeatureDefinitionKey === null
) {
classNames.push("ct-optional-feature__select--todo");
}
return (
<React.Fragment>
<div className="ct-optional-feature__type">{introText.join(" ")}</div>
{options.length > 1 && (
<div className={classNames.join(" ")}>
<Select
options={options}
value={affectedFeatureDefinitionKey}
onChangePromise={this.handleReplacementChangePromise}
disabled={!isSelected}
preventClickPropagating={true}
/>
</div>
)}
</React.Fragment>
);
};
renderFeatureTypeContent = (): React.ReactNode => {
const { featureType } = this.props;
switch (featureType) {
case Constants.FeatureTypeEnum.ADDITIONAL:
return (
<div className="ct-optional-feature__type">Additional feature</div>
);
case Constants.FeatureTypeEnum.REPLACEMENT:
return this.renderReplacements();
default:
return null;
}
};
renderDescription = (): React.ReactNode => {
const { description, accessType } = this.props;
let contentNode: React.ReactNode = null;
if (AccessUtils.isAccessible(accessType)) {
contentNode = description ? (
<CollapsibleContent className="ct-optional-feature__description">
{description}
</CollapsibleContent>
) : null;
} else {
contentNode = (
<div className="ct-character-tools__marketplace-callout">
To fully unlock this feature, check out the{" "}
<Link href="/marketplace">Marketplace</Link>.
</div>
);
}
return contentNode;
};
render() {
const { isSelected, name, affectedFeatures, requiredLevel } = this.props;
const isDisabled: boolean =
affectedFeatures.length === 1 && affectedFeatures[0].disabled;
const classNames: Array<string> = ["ct-optional-feature"];
if (isDisabled) {
classNames.push("ct-optional-feature--is-disabled");
}
return (
<div className={classNames.join(" ")}>
<div className="ct-optional-feature__selection">
<div className="ct-optional-feature__checkmark">
<Checkbox
initiallyEnabled={isSelected}
onChange={this.handleSelection}
stopPropagation={true}
onChangePromise={
isSelected ? this.handleUnselectPromise : undefined
}
isInteractive={!isDisabled}
/>
</div>
</div>
<div className="ct-optional-feature__primary">
<div className="ct-optional-feature__name">{name}</div>
{requiredLevel && (
<div className="ct-optional-feature__level">
{`${FormatUtils.ordinalize(requiredLevel)} level`}
</div>
)}
{this.renderDescription()}
<div className="ct-optional-feature__secondary">
{this.renderFeatureTypeContent()}
</div>
</div>
</div>
);
}
}
export default OptionalFeature;
@@ -0,0 +1,18 @@
import React from "react";
interface Props {
clsNames: Array<string>;
}
export default class Page extends React.PureComponent<Props> {
static defaultProps = {
clsNames: [],
};
render() {
const { clsNames, children } = this.props;
const conClassNames = [...clsNames, "builder-page"];
return <div className={conClassNames.join(" ")}>{children}</div>;
}
}
@@ -0,0 +1,4 @@
import Page from "./Page";
export default Page;
export { Page };
@@ -0,0 +1,23 @@
import { FC, HTMLAttributes } from "react";
import { useSelector } from "react-redux";
import { BuilderMethod } from "~/subApps/builder/constants";
import { PortraitName } from "../../../../../subApps/builder/components/PortraitName";
import { builderSelectors } from "../../selectors";
interface Props extends HTMLAttributes<HTMLDivElement> {}
export const PageBody: FC<Props> = ({ children, ...props }) => {
const builderMethod = useSelector(builderSelectors.getBuilderMethod);
return (
<div className="builder-page-body" {...props}>
{builderMethod === BuilderMethod.STEP_BY_STEP && (
<div className="builder-page-body-character-name">
<PortraitName useDefaultCharacterName={true} />
</div>
)}
{children}
</div>
);
};
@@ -0,0 +1,9 @@
import React from "react";
export default class PageHeader extends React.PureComponent {
render() {
const { children } = this.props;
return <div className="builder-page-header">{children}</div>;
}
}
@@ -0,0 +1,4 @@
import PageHeader from "./PageHeader";
export default PageHeader;
export { PageHeader };
@@ -0,0 +1,9 @@
import React from "react";
export default class PageSubHeader extends React.PureComponent {
render() {
const { children } = this.props;
return <div className="builder-page-subheader">{children}</div>;
}
}
@@ -0,0 +1,4 @@
import PageSubHeader from "./PageSubHeader";
export default PageSubHeader;
export { PageSubHeader };
@@ -0,0 +1,63 @@
import React from "react";
import {
RaceDefinitionContract,
RuleData,
RuleDataUtils,
} from "@dndbeyond/character-rules-engine/es";
import Button from "../Button";
interface Props {
species: RaceDefinitionContract | null;
onRequestAction: () => void;
actionText: string;
headingText: string;
ruleData: RuleData;
}
export default class SpeciesDisplaySimple extends React.PureComponent<Props> {
static defaultProps = {
actionText: "Change",
headingText: "Selected ",
};
render() {
const { species, actionText, headingText, ruleData, onRequestAction } =
this.props;
if (species === null) {
return null;
}
const { portraitAvatarUrl, baseName, subRaceShortName } = species;
const previewUrl: string | null = portraitAvatarUrl
? portraitAvatarUrl
: RuleDataUtils.getDefaultRaceImageUrl(ruleData);
return (
<div className="builder-field builder-field-valid race-simple">
<div className="builder-field-heading">{headingText}</div>
<div className="race-simple-content">
<div className="race-simple-preview">
<img
className="race-simple-preview-img"
src={previewUrl ? previewUrl : ""}
alt=""
/>
</div>
<div className="race-simple-info">
{subRaceShortName ? (
<div className="race-simple-subclass">{subRaceShortName}</div>
) : null}
<div className="race-simple-parent">{baseName}</div>
</div>
<div className="race-simple-action">
<Button onClick={onRequestAction} size="small">
{actionText}
</Button>
</div>
</div>
</div>
);
}
}
@@ -0,0 +1,4 @@
import SpeciesDisplaySimple from "./SpeciesDisplaySimple";
export default SpeciesDisplaySimple;
export { SpeciesDisplaySimple };
@@ -0,0 +1,74 @@
import React from "react";
import {
AnySimpleDataType,
ApiAdapterPromise,
ApiAdapterRequestConfig,
ApiResponse,
CharacterPreferences,
ChoiceData,
DefinitionPool,
FeatDefinitionContract,
FeatLookup,
OptionalOriginLookup,
PrerequisiteData,
RacialTrait,
RacialTraitUtils,
} from "@dndbeyond/character-rules-engine/es";
import { SpeciesManageSpeciesTrait } from "../../containers/pages/SpeciesManage";
interface Props {
speciesTraits: RacialTrait[];
featLookup: FeatLookup;
loadAvailableFeats: (
additionalConfig?: Partial<ApiAdapterRequestConfig>
) => ApiAdapterPromise<ApiResponse<FeatDefinitionContract[]>>;
handleSpeciesTraitChoiceChange: (
speciesTraitId: number,
choiceId: string,
choiceType: number,
optionValue: AnySimpleDataType
) => void;
choiceInfo: ChoiceData;
prerequisiteData: PrerequisiteData;
preferences: CharacterPreferences;
optionalOriginLookup: OptionalOriginLookup;
definitionPool: DefinitionPool;
speciesName?: string | null;
}
const SpeciesTraitList: React.FC<Props> = ({
speciesTraits,
featLookup,
loadAvailableFeats,
handleSpeciesTraitChoiceChange,
choiceInfo,
prerequisiteData,
preferences,
optionalOriginLookup,
definitionPool,
speciesName,
}) => {
return (
<>
{speciesTraits.map((speciesTrait) => (
<SpeciesManageSpeciesTrait
key={RacialTraitUtils.getId(speciesTrait)}
speciesTrait={speciesTrait}
featLookup={featLookup}
loadAvailableFeats={loadAvailableFeats}
onChoiceChange={handleSpeciesTraitChoiceChange}
choiceInfo={choiceInfo}
prerequisiteData={prerequisiteData}
preferences={preferences}
optionalOriginLookup={optionalOriginLookup}
definitionPool={definitionPool}
speciesName={speciesName}
/>
))}
</>
);
};
export default SpeciesTraitList;
@@ -0,0 +1,4 @@
import SpeciesTraitList from "./SpeciesTraitList";
export default SpeciesTraitList;
export { SpeciesTraitList };