Files
dndbeyond_src/ddb_main/tools/js/smartComponents/SavingThrowsSummary/SavingThrowsSummary.tsx
T

190 lines
6.4 KiB
TypeScript

import { Tooltip } from "@dndbeyond/character-common-components/es";
import {
AbilityManager,
CharacterTheme,
FormatUtils,
SituationalSavingThrowInfo,
SituationalSavingThrowInfoLookup,
} from "@dndbeyond/character-rules-engine/es";
import { RollTypes } from "@dndbeyond/pocket-dimension-dice/constants";
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";
import { NumberDisplay } from "~/components/NumberDisplay";
import a11yStyles from "~/styles/accessibility.module.css";
import BoxBackground from "../BoxBackground";
import { DigitalDiceWrapper } from "../Dice";
import { ProficiencyLevelIcon } from "../Icons";
import {
ThemedSavingThrowSelectionBoxSvg,
ThemedSavingThrowSelectionSmallBoxSvg,
ThemedSavingThrowRowBoxSvg,
ThemedSavingThrowRowSmallBoxSvg,
NegativeBonusNegativeSvg,
PositiveBonusPositiveSvg,
DarkModePositiveBonusPositiveSvg,
DarkModeNegativeBonusNegativeSvg,
} from "../Svg";
interface Props {
abilities: Array<AbilityManager>;
situationalBonusSavingThrowsLookup: SituationalSavingThrowInfoLookup;
onClick?: (ability: AbilityManager) => void;
rowStyle?: "small" | "normal";
className?: string;
diceEnabled?: boolean;
theme: CharacterTheme;
rollContext: RollContext;
}
export default function SavingThrowsSummary({
abilities,
situationalBonusSavingThrowsLookup,
onClick = (ability: AbilityManager) => {},
theme,
rollContext,
className = "",
diceEnabled = false,
rowStyle = "normal",
}: Props) {
const handleClick = (
ability: AbilityManager,
evt: React.MouseEvent
): void => {
if (onClick) {
evt.stopPropagation();
evt.nativeEvent.stopImmediatePropagation();
onClick(ability);
}
};
return (
<div className="ddbc-saving-throws-summary">
{abilities.map((ability) => {
let situationalBonusSavingThrows: Array<SituationalSavingThrowInfo> =
[];
let maxOptBonus: number = 0;
if (situationalBonusSavingThrowsLookup) {
situationalBonusSavingThrows =
situationalBonusSavingThrowsLookup[ability.getId()];
if (
situationalBonusSavingThrows &&
situationalBonusSavingThrows.length
) {
let situationalBonusSavingThrowsValues: Array<number> =
situationalBonusSavingThrows.map((optBonus) => optBonus.value);
maxOptBonus = Math.max(...situationalBonusSavingThrowsValues);
}
}
let situationalBonusClasses: Array<string> = [
"ddbc-saving-throws-summary__ability-situational",
];
let IconComponent: React.ComponentType<any> | null = null;
if (maxOptBonus < 0) {
situationalBonusClasses.push(
"ddbc-saving-throws-summary__ability-situational--bonus-neg"
);
IconComponent = theme?.isDarkMode
? DarkModeNegativeBonusNegativeSvg
: NegativeBonusNegativeSvg;
} else {
situationalBonusClasses.push(
"ddbc-saving-throws-summary__ability-situational--bonus-pos"
);
IconComponent = theme?.isDarkMode
? DarkModePositiveBonusPositiveSvg
: PositiveBonusPositiveSvg;
}
let StyleComponent: React.ComponentType<any> =
ThemedSavingThrowRowBoxSvg;
let SelectionBox = ThemedSavingThrowSelectionBoxSvg;
if (rowStyle === "small") {
StyleComponent = ThemedSavingThrowRowSmallBoxSvg;
SelectionBox = ThemedSavingThrowSelectionSmallBoxSvg;
}
const modifier = ability.getSave();
return (
<div
key={ability.getStatKey()}
className={`ddbc-saving-throws-summary__ability ddbc-saving-throws-summary__ability--${FormatUtils.slugify(
ability.getStatKey()
)}`}
onClick={(event) => handleClick(ability, event)}
>
<BoxBackground
StyleComponent={StyleComponent}
theme={{
...theme,
themeColor: `${theme.themeColor}80`,
}}
/>
<h3 className={a11yStyles.screenreaderOnly}>
{ability.getLabel()} Saving Throw
</h3>
<div className="ddbc-saving-throws-summary__ability-proficiency">
<ProficiencyLevelIcon
proficiencyLevel={ability.getProficiencyLevel()}
isModified={ability.getIsSaveProficiencyModified()}
theme={theme}
/>
</div>
<div
className={`ddbc-saving-throws-summary__ability-name ${
theme.isDarkMode
? "ddbc-saving-throws-summary__ability-name--dark-mode"
: ""
}`}
>
<abbr
title={ability.getLabel() ?? "Unknown Saving Throw"}
aria-hidden={true}
>
{ability.getName()}
</abbr>
</div>
<div className="ddbc-saving-throws-summary__ability-modifier">
<DigitalDiceWrapper
diceNotation={`1d20${
modifier > 0 ? `+${modifier}` : modifier !== 0 ? modifier : ""
}`}
rollType={RollTypes.Save}
action={ability.getName()}
isDiceEnabled={diceEnabled}
entityId={String(rollContext.entityId)}
entityType={String(rollContext.entityType)}
name={String(rollContext.name)}
>
<NumberDisplay
number={ability.getSave()}
type="signed"
isModified={ability.getIsSaveModifierModified()}
/>
</DigitalDiceWrapper>
<SelectionBox
theme={theme}
className={`ddbc-saving-throws-summary__ability-modifier-background`}
/>
</div>
{situationalBonusSavingThrows &&
situationalBonusSavingThrows.length > 0 && (
<Tooltip
className={situationalBonusClasses.join(" ")}
isDarkMode={theme.isDarkMode}
title={`Situational Bonus${
situationalBonusSavingThrows.length !== 1 ? "es" : ""
}`}
>
<IconComponent className="ddbc-saving-throws-summary__ability-situational-icon" />
</Tooltip>
)}
</div>
);
})}
</div>
);
}