New source found from dndbeyond.com
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import clsx from "clsx";
|
||||
import { FC, HTMLAttributes, useMemo, useState } from "react";
|
||||
import { FC, FocusEvent, useMemo, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import {
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
HP_BONUS_VALUE,
|
||||
HP_OVERRIDE_MAX_VALUE,
|
||||
} from "~/subApps/sheet/constants";
|
||||
import { FormInputField } from "~/tools/js/Shared/components/common/FormInputField";
|
||||
|
||||
import { InputField } from "../InputField";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
export interface HpManageModalProps
|
||||
@@ -65,52 +65,55 @@ export const HpManageModal: FC<HpManageModalProps> = ({
|
||||
reset();
|
||||
};
|
||||
|
||||
const transformBaseHp = (value: string): number => {
|
||||
const handleBaseHp = (e: FocusEvent<HTMLInputElement>) => {
|
||||
let parsedNumber = HelperUtils.parseInputInt(
|
||||
value,
|
||||
e.target.value,
|
||||
RuleDataUtils.getMinimumHpTotal(ruleData)
|
||||
);
|
||||
return HelperUtils.clampInt(
|
||||
|
||||
const clampedValue = HelperUtils.clampInt(
|
||||
parsedNumber,
|
||||
RuleDataUtils.getMinimumHpTotal(ruleData),
|
||||
HP_BASE_MAX_VALUE
|
||||
);
|
||||
|
||||
setBaseHp(clampedValue);
|
||||
};
|
||||
|
||||
const transformBonusHp = (value: string): number | null => {
|
||||
let parsedNumber = HelperUtils.parseInputInt(value);
|
||||
if (parsedNumber === null) {
|
||||
return parsedNumber;
|
||||
}
|
||||
return HelperUtils.clampInt(
|
||||
const handleBonusHp = (e: FocusEvent<HTMLInputElement>) => {
|
||||
let parsedNumber = HelperUtils.parseInputInt(e.target.value);
|
||||
|
||||
if (parsedNumber === null) return parsedNumber;
|
||||
|
||||
const clampedValue = HelperUtils.clampInt(
|
||||
parsedNumber,
|
||||
HP_BONUS_VALUE.MIN,
|
||||
HP_BONUS_VALUE.MAX
|
||||
);
|
||||
|
||||
setBonusHp(clampedValue);
|
||||
};
|
||||
|
||||
const transformOverrideHp = (value: string): number | null => {
|
||||
let parsedNumber = HelperUtils.parseInputInt(value, null);
|
||||
const handleOverrideHp = (e: FocusEvent<HTMLInputElement>) => {
|
||||
let parsedNumber = HelperUtils.parseInputInt(e.target.value, null);
|
||||
|
||||
if (parsedNumber === null) {
|
||||
setOverrideHp(null);
|
||||
return parsedNumber;
|
||||
}
|
||||
return HelperUtils.clampInt(
|
||||
|
||||
const clampedValue = HelperUtils.clampInt(
|
||||
parsedNumber,
|
||||
RuleDataUtils.getMinimumHpTotal(ruleData)
|
||||
);
|
||||
};
|
||||
|
||||
const handleOverrideHpUpdate = (value: number | null): void => {
|
||||
const newOverride =
|
||||
value === null
|
||||
? null
|
||||
: HelperUtils.clampInt(
|
||||
value,
|
||||
RuleDataUtils.getMinimumHpTotal(ruleData),
|
||||
HP_OVERRIDE_MAX_VALUE
|
||||
);
|
||||
const clampedOverride = HelperUtils.clampInt(
|
||||
clampedValue,
|
||||
RuleDataUtils.getMinimumHpTotal(ruleData),
|
||||
HP_OVERRIDE_MAX_VALUE
|
||||
);
|
||||
|
||||
setOverrideHp(newOverride);
|
||||
setOverrideHp(clampedOverride);
|
||||
};
|
||||
|
||||
const totalHp = useMemo(() => {
|
||||
@@ -148,35 +151,33 @@ export const HpManageModal: FC<HpManageModalProps> = ({
|
||||
<span className={styles.baseHpValue}>{baseHp}</span>
|
||||
</p>
|
||||
) : (
|
||||
<FormInputField
|
||||
<InputField
|
||||
className={styles.inputField}
|
||||
label="Rolled HP"
|
||||
initialValue={baseHp}
|
||||
type="number"
|
||||
onBlur={(value) => setBaseHp(value as number)}
|
||||
transformValueOnBlur={transformBaseHp}
|
||||
onBlur={handleBaseHp}
|
||||
/>
|
||||
)}
|
||||
<FormInputField
|
||||
<InputField
|
||||
className={styles.inputField}
|
||||
label="HP Modifier"
|
||||
initialValue={bonusHp}
|
||||
type="number"
|
||||
placeholder={"--"}
|
||||
onBlur={(value) => setBonusHp(value as number)}
|
||||
transformValueOnBlur={transformBonusHp}
|
||||
placeholder="--"
|
||||
onBlur={handleBonusHp}
|
||||
/>
|
||||
<FormInputField
|
||||
inputAttributes={
|
||||
{
|
||||
min: RuleDataUtils.getMinimumHpTotal(ruleData),
|
||||
max: HP_OVERRIDE_MAX_VALUE,
|
||||
} as HTMLAttributes<HTMLInputElement>
|
||||
}
|
||||
<InputField
|
||||
className={styles.inputField}
|
||||
label="Override HP"
|
||||
initialValue={overrideHp}
|
||||
type="number"
|
||||
placeholder={"--"}
|
||||
onBlur={handleOverrideHpUpdate}
|
||||
transformValueOnBlur={transformOverrideHp}
|
||||
inputProps={{
|
||||
min: RuleDataUtils.getMinimumHpTotal(ruleData),
|
||||
max: HP_OVERRIDE_MAX_VALUE,
|
||||
}}
|
||||
onBlur={handleOverrideHp}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import clsx from "clsx";
|
||||
import {
|
||||
ChangeEvent,
|
||||
FC,
|
||||
FocusEvent,
|
||||
HTMLAttributes,
|
||||
HTMLInputTypeAttribute,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface InputProps extends HTMLAttributes<HTMLInputElement> {
|
||||
min?: number;
|
||||
max?: number;
|
||||
autoComplete?: string;
|
||||
}
|
||||
|
||||
export interface InputFieldProps extends HTMLAttributes<HTMLInputElement> {
|
||||
errorMessage?: string;
|
||||
initialValue?: string | number | null;
|
||||
label: string;
|
||||
maxLength?: number;
|
||||
placeholder?: string;
|
||||
type?: HTMLInputTypeAttribute;
|
||||
inputProps?: InputProps;
|
||||
}
|
||||
|
||||
export const InputField: FC<InputFieldProps> = ({
|
||||
className,
|
||||
errorMessage,
|
||||
initialValue = "",
|
||||
inputProps,
|
||||
label,
|
||||
maxLength,
|
||||
onBlur,
|
||||
onChange,
|
||||
onFocus,
|
||||
placeholder,
|
||||
type = "text",
|
||||
...props
|
||||
}) => {
|
||||
// Set default error message if not provided
|
||||
errorMessage ||= `The maximum length is ${maxLength} characters.`;
|
||||
// Set up component state and a shared id
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const [showError, setShowError] = useState(false);
|
||||
const id = props.id ?? `input-field-${uuidv4()}`;
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const valueLength = e.target.value.length;
|
||||
if (maxLength) {
|
||||
// If the value length exceeds maxLength, show error
|
||||
if (valueLength > maxLength) {
|
||||
setShowError(true);
|
||||
//return; Prevent further processing if maxLength exceeded
|
||||
return;
|
||||
}
|
||||
|
||||
// If the value length is equal to maxLength, show error
|
||||
if (valueLength === maxLength) {
|
||||
setShowError(true);
|
||||
}
|
||||
// If the value length is less than maxLength, hide error
|
||||
if (valueLength < maxLength) {
|
||||
setShowError(false);
|
||||
}
|
||||
}
|
||||
// Update component state
|
||||
setValue(e.target.value);
|
||||
// Call the provided onChange handler if it exists
|
||||
if (onChange) onChange(e);
|
||||
return;
|
||||
};
|
||||
|
||||
const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
|
||||
const intValue = parseInt(e.target.value);
|
||||
|
||||
// If entered value is below the minimum, reset to minimum
|
||||
if (!isNaN(intValue) && inputProps?.min && intValue < inputProps?.min) {
|
||||
setValue(inputProps.min);
|
||||
}
|
||||
|
||||
// If entered value is above the maximum and no initialValue exists, reset to maximum
|
||||
if (!isNaN(intValue) && inputProps?.max && intValue > inputProps?.max) {
|
||||
setValue(inputProps.max);
|
||||
}
|
||||
|
||||
// Call the provided onBlur handler if it exists
|
||||
if (onBlur) onBlur(e);
|
||||
|
||||
// Reset error state on blur
|
||||
setShowError(false);
|
||||
};
|
||||
|
||||
const handleFocus = (e: FocusEvent<HTMLInputElement>) => {
|
||||
// Call the provided onFocus handler if it exists
|
||||
if (onFocus) onFocus(e);
|
||||
// Check for maxLength error
|
||||
const valueLength = e.target.value.length;
|
||||
if (maxLength && valueLength > maxLength - 1) setShowError(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Update the value when initialValue changes
|
||||
setValue(initialValue);
|
||||
// Reset error state when initial value changes
|
||||
setShowError(false);
|
||||
}, [initialValue]);
|
||||
|
||||
const inputAttrs = { id, type, placeholder };
|
||||
|
||||
return (
|
||||
<div className={clsx([styles.inputField, className])} {...props}>
|
||||
<label className={styles.label} htmlFor={id}>
|
||||
{label}
|
||||
</label>
|
||||
<input
|
||||
className={styles.input}
|
||||
value={value ?? ""}
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
{...inputAttrs}
|
||||
{...inputProps}
|
||||
/>
|
||||
{showError && <span className={styles.error}>{errorMessage}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -16,11 +16,11 @@ import {
|
||||
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
|
||||
import { builderActions } from "~/tools/js/CharacterBuilder/actions";
|
||||
import { builderSelectors } from "~/tools/js/CharacterBuilder/selectors";
|
||||
import { FormInputField } from "~/tools/js/Shared/components/common/FormInputField";
|
||||
import { PortraitManager } from "~/tools/js/Shared/containers/panes/DecoratePane";
|
||||
import Tooltip from "~/tools/js/commonComponents/Tooltip";
|
||||
import { CharacterAvatarPortrait } from "~/tools/js/smartComponents/CharacterAvatar";
|
||||
|
||||
import { InputField } from "../InputField";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||
@@ -56,7 +56,7 @@ export const PortraitName: FC<Props> = ({
|
||||
setCurrentSuggestedNames(suggestedNames);
|
||||
}, [suggestedNames]);
|
||||
|
||||
const handleNameFieldSet = (value: string | null): void => {
|
||||
const handleNameFieldSet = (value: string) => {
|
||||
let updatedName = value ?? "";
|
||||
|
||||
// If the flag useDefaultCharacterName is true and value is falsy, set the name to the DefaultCharacterName
|
||||
@@ -137,18 +137,16 @@ export const PortraitName: FC<Props> = ({
|
||||
</>
|
||||
)}
|
||||
<div className={styles.inputContainer}>
|
||||
<FormInputField
|
||||
<InputField
|
||||
label="Character Name"
|
||||
onBlur={handleNameFieldSet}
|
||||
initialValue={localValue}
|
||||
inputAttributes={
|
||||
{
|
||||
spellCheck: false,
|
||||
autoComplete: "off",
|
||||
} as HTMLAttributes<HTMLInputElement>
|
||||
}
|
||||
maxLength={InputLimits.characterNameMaxLength}
|
||||
maxLengthErrorMsg={CharacterNameLimitMsg}
|
||||
errorMessage={CharacterNameLimitMsg}
|
||||
onBlur={(e) => handleNameFieldSet(e.target.value)}
|
||||
inputProps={{
|
||||
spellCheck: false,
|
||||
autoComplete: "off",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className={styles.suggestionButton}
|
||||
|
||||
@@ -11,7 +11,6 @@ import Collapsible, {
|
||||
CollapsibleHeaderContent,
|
||||
} from "~/tools/js/smartComponents/Collapsible";
|
||||
import { TodoIcon } from "~/tools/js/smartComponents/Icons";
|
||||
import PrerequisiteFailureSummary from "~/tools/js/smartComponents/PrerequisiteFailureSummary";
|
||||
import { CharacterTheme } from "~/types";
|
||||
|
||||
import {
|
||||
@@ -110,10 +109,9 @@ export const Feat: FC<Props> = ({
|
||||
<Reference name={feat.getPrimarySourceName()} />
|
||||
</span>
|
||||
{showFailures && (
|
||||
<PrerequisiteFailureSummary
|
||||
failures={feat.getPrerequisiteFailures()}
|
||||
className={styles.failures}
|
||||
/>
|
||||
<div className={styles.failures}>
|
||||
{feat.getPrerequisiteFailuresText()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user