190 lines
6.4 KiB
TypeScript
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>
|
|
);
|
|
}
|