New source found from dndbeyond.com

This commit is contained in:
2026-07-08 01:00:09 -07:00
parent 9a983a6d7b
commit dcefa0d2f2
2865 changed files with 222467 additions and 49053 deletions
@@ -0,0 +1,599 @@
import clsx from "clsx";
import { FC, HTMLAttributes, ReactNode } from "react";
import { useSelector } from "react-redux";
import { useSearchParams } from "react-router-dom";
import {
characterActions,
CharacterUtils,
ClassUtils,
Constants,
HelperUtils,
RaceUtils,
RuleDataUtils,
} from "@dndbeyond/character-rules-engine";
import { CheckboxGroup, CheckboxInfo } from "~/components/CheckboxGroup";
import { PrivacyTypeRadio } from "~/components/PrivacyTypeRadio";
import { SourceCategoryDescription } from "~/constants";
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
import { useDispatch } from "~/hooks/useDispatch";
import { useModalManager } from "~/subApps/builder/contexts/ModalManager";
import { appEnvActions } from "~/tools/js/Shared/actions";
import SimpleClassSpellList from "~/tools/js/Shared/components/SimpleClassSpellList";
import { appEnvSelectors } from "~/tools/js/Shared/selectors";
import config from "~/tools/js/config";
import { SelectField } from "../../components/SelectField";
import pageStyles from "../../styles/page.module.css";
import { PremadeForm } from "./PremadeForm";
import styles from "./styles.module.css";
export interface HomeProps extends HTMLAttributes<HTMLDivElement> {}
export const Home: FC<HomeProps> = ({ className, ...props }) => {
const dispatch = useDispatch();
const {
preferences,
ruleData,
activeSourceCategories,
classes,
race: species,
classSpellListSpellsLookup,
} = useCharacterEngine();
const diceEnabled = useSelector(appEnvSelectors.getDiceEnabled);
const { createModal } = useModalManager();
const [searchParams] = useSearchParams();
const isVttView = searchParams.get("view") === "vtt";
const {
useHomebrewContent,
encumbranceType,
hitPointType,
progressionType,
abilityScoreDisplayType,
privacyType,
ignoreCoinWeight,
enforceFeatRules,
enforceMulticlassRules,
showScaledSpells,
enableOptionalOrigins,
enableOptionalClassFeatures,
} = preferences;
const handleSourceCategoryChange = (
sourceId: number,
isActive: boolean
): void => {
let newSourceCats: Array<any> = [];
if (isActive) {
newSourceCats = [...activeSourceCategories, sourceId];
} else {
newSourceCats = activeSourceCategories.filter((id) => id !== sourceId);
}
dispatch(characterActions.activeSourceCategoriesSet(newSourceCats));
};
const handlePartneredSourceChangeAll = (
sourceIds: number[],
isActive: boolean
): void => {
let newSourceCats: Array<any> = [];
if (isActive) {
newSourceCats = [...activeSourceCategories, ...sourceIds];
} else {
newSourceCats = activeSourceCategories.filter(
(id) => !sourceIds.includes(id)
);
}
dispatch(characterActions.activeSourceCategoriesSet(newSourceCats));
};
const handlePreferenceChange = (prefKey: string, value: boolean): void => {
const typedPrefKey = CharacterUtils.getPreferenceKey(prefKey);
if (typedPrefKey !== null) {
dispatch(characterActions.preferenceChoose(typedPrefKey, value));
}
};
const handleIntPreferenceChange = (prefKey: string, value: string): void => {
const typedPrefKey = CharacterUtils.getPreferenceKey(prefKey);
if (typedPrefKey !== null) {
dispatch(
characterActions.preferenceChoose(
typedPrefKey,
HelperUtils.parseInputInt(value)
)
);
}
};
const handleDiceToggle = (): void => {
const newDiceEnabledSetting: boolean = !diceEnabled;
try {
localStorage.setItem("dice-enabled", newDiceEnabledSetting.toString());
} catch (e) {}
dispatch(
appEnvActions.dataSet({
diceEnabled: newDiceEnabledSetting,
})
);
};
const handleOptionalClassFeaturesPreferenceChange = (
newIsEnabled: boolean,
accept: () => void,
reject: () => void
): void => {
const spellListIds: Array<number> =
ClassUtils.getUpdateEnableOptionalClassFeaturesSpellListIdsToRemove(
classes,
newIsEnabled
);
const hasSpellsToRemove = spellListIds.some((id) =>
classSpellListSpellsLookup.hasOwnProperty(id)
);
if (!hasSpellsToRemove) {
handlePreferenceChange("enableOptionalClassFeatures", newIsEnabled);
accept();
} else {
createModal({
content: (
<div>
<p>
Are you sure you want to disable{" "}
<strong>Optional Class Features</strong> for this character?
</p>
<p>
After doing so, the following spells provided by these features
will be removed from your character:
</p>
<SimpleClassSpellList
spellListIds={spellListIds}
classSpellListSpellsLookup={classSpellListSpellsLookup}
/>
</div>
),
props: {
heading: "Optional Class Features",
size: "fit-content",
variant: "remove",
onConfirm: () => {
handlePreferenceChange("enableOptionalClassFeatures", newIsEnabled);
accept();
},
onClose: () => {
reject();
},
},
});
}
};
const handleOptionalOriginsPreferenceChange = (
newIsEnabled: boolean,
accept: () => void,
reject: () => void
): void => {
if (!species) {
handlePreferenceChange("enableOptionalOrigins", newIsEnabled);
accept();
return;
}
const spellListIds: Array<number> =
RaceUtils.getUpdateEnableOptionalOriginsSpellListIdsToRemove(
species,
newIsEnabled
);
const hasSpellsToRemove = spellListIds.some((id) =>
classSpellListSpellsLookup.hasOwnProperty(id)
);
if (!hasSpellsToRemove) {
handlePreferenceChange("enableOptionalOrigins", newIsEnabled);
accept();
} else {
createModal({
content: (
<div>
<p>
Are you sure you want to disable{" "}
<strong>Customized Origins</strong> for this character?
</p>
<p>
After doing so, the following spells provided by these features
will be removed from your character:
</p>
<SimpleClassSpellList
spellListIds={spellListIds}
classSpellListSpellsLookup={classSpellListSpellsLookup}
/>
</div>
),
props: {
heading: "Customized Origin Features",
size: "fit-content",
variant: "remove",
onConfirm: () => {
handlePreferenceChange("enableOptionalOrigins", newIsEnabled);
accept();
},
onClose: () => {
reject();
},
},
});
}
};
const handleProgressionPreferenceChangePromise = (
newValue: string,
oldValue: string,
accept: () => void,
reject: () => void
): void => {
const prefKey = CharacterUtils.getPreferenceKey("progressionType");
const newIdValue = HelperUtils.parseInputInt(newValue);
let content: ReactNode;
let heading: string | null = null;
if (newIdValue === Constants.PreferenceProgressionTypeEnum.XP) {
heading = "XP Advancement";
content = (
<div>
<p>
Are you sure you want to change your advancement method to XP
progression?
</p>
<p>You will begin with the base XP value for your current level.</p>
</div>
);
} else if (
newIdValue === Constants.PreferenceProgressionTypeEnum.MILESTONE
) {
heading = "Milestone Advancement";
content = (
<div>
<p>
Are you sure you want to change your advancement method to Milestone
progression?
</p>
<p>Your current XP values will be lost.</p>
</div>
);
}
if (prefKey !== null && heading) {
createModal({
content,
props: {
heading,
size: "fit-content",
onConfirm: () => {
dispatch(characterActions.preferenceChoose(prefKey, newIdValue));
accept();
},
onClose: () => {
reject();
},
},
});
}
};
const handleHitPointPreferenceChangePromise = (
newValue: string,
oldValue: string,
accept: () => void,
reject: () => void
): void => {
const prefKey = CharacterUtils.getPreferenceKey("hitPointType");
const newIdValue = HelperUtils.parseInputInt(newValue);
let content: ReactNode;
let heading: string | null = null;
if (newIdValue === Constants.PreferenceHitPointTypeEnum.FIXED) {
heading = "Fixed Hit Points";
content = (
<div>
<p>
Are you sure you want to change your hit points to the fixed value?
</p>
<p>Any rolled hit point totals will be lost.</p>
</div>
);
} else if (newIdValue === Constants.PreferenceHitPointTypeEnum.MANUAL) {
heading = "Manual Hit Points";
content = (
<div>
<p>Are you sure you want to change your hit points manual entry?</p>
<p>
After doing so, use Manage HP in the Class section to enter your
rolled values.
</p>
</div>
);
}
if (prefKey !== null && heading) {
createModal({
content,
props: {
heading,
size: "fit-content",
onConfirm: () => {
dispatch(characterActions.preferenceChoose(prefKey, newIdValue));
accept();
},
onClose: () => {
reject();
},
},
});
}
};
const renderSourceToggles = () => {
let sourceToggles: Array<CheckboxInfo> = [];
let partneredSourceCheckboxes: Array<CheckboxInfo> = [];
let allPartneredSources: Array<number> = [];
RuleDataUtils.getSourceCategories(ruleData).forEach((sourceCategory) => {
if (!sourceCategory.isToggleable) {
return null;
}
const checkbox: CheckboxInfo = {
label: `${sourceCategory.name}`,
initiallyEnabled: activeSourceCategories.includes(sourceCategory.id),
onChange: (e) =>
handleSourceCategoryChange(sourceCategory.id, e.target.checked),
sortOrder: sourceCategory.sortOrder,
description: sourceCategory.description ?? "",
};
if (sourceCategory.isPartneredContent) {
delete checkbox.description; // remove description from partnered content
partneredSourceCheckboxes.push(checkbox);
allPartneredSources.push(sourceCategory.id);
} else {
sourceToggles.push(checkbox);
}
});
return (
<>
<CheckboxGroup
className={styles.checkboxGroup}
title="Sources"
description={SourceCategoryDescription.official}
name="sources"
checkboxes={[
...sourceToggles,
{
label: "Homebrew",
description: SourceCategoryDescription.homebrew,
initiallyEnabled: useHomebrewContent,
onChange: (e) =>
handlePreferenceChange("useHomebrewContent", e.target.checked),
sortOrder: 0,
},
]}
showAccordion={false}
/>
<CheckboxGroup
className={styles.checkboxGroup}
title="Partnered Content"
description={SourceCategoryDescription.partnered}
name="partneredContent"
checkboxes={partneredSourceCheckboxes}
accordionSummary="Choose Partners"
showAccordion={true}
toggleAllLabel="Enable All Partnered Content"
onToggleAll={(e) =>
handlePartneredSourceChangeAll(
allPartneredSources,
e.target.checked
)
}
/>
</>
);
};
return (
<div className={clsx([pageStyles.page, styles.home, className])} {...props}>
<PremadeForm />
<h2 className={pageStyles.title}>Character Preferences</h2>
<div className="home-manage-preferences">
{renderSourceToggles()}
{!isVttView && (
<CheckboxGroup
className={styles.checkboxGroup}
title="Dice Rolling"
description="Enables digital dice rolling for all characters on this browser"
name="dice-rolling"
checkboxes={[
{
label: "Enable Dice Rolling",
initiallyEnabled: diceEnabled,
onChange: handleDiceToggle,
},
]}
/>
)}
<CheckboxGroup
className={styles.checkboxGroup}
title="Optional Features"
name="optional-features"
description="Allow or restrict optional features for this character"
checkboxes={[
{
label: "Optional Class Features",
initiallyEnabled: enableOptionalClassFeatures,
onChangeConfirm: handleOptionalClassFeaturesPreferenceChange,
},
{
label: "Customize Your Origin",
initiallyEnabled: enableOptionalOrigins,
onChangeConfirm: handleOptionalOriginsPreferenceChange,
},
]}
/>
<SelectField
label="Advancement Type"
description="Story-based character progression / XP-based character progression"
onChangeConfirm={handleProgressionPreferenceChangePromise}
hidePlaceholderOption={true}
options={[
{
label: "Milestone",
value: Constants.PreferenceProgressionTypeEnum.MILESTONE,
},
{ label: "XP", value: Constants.PreferenceProgressionTypeEnum.XP },
]}
value={"" + progressionType}
name={"advancementType"}
/>
<SelectField
label="Hit Point Type"
description="When leveling up, increase hit points by the fixed value for your chosen class or manually enter a rolled value"
onChangeConfirm={handleHitPointPreferenceChangePromise}
hidePlaceholderOption={true}
options={[
{
label: "Fixed",
value: Constants.PreferenceHitPointTypeEnum.FIXED,
},
{
label: "Manual",
value: Constants.PreferenceHitPointTypeEnum.MANUAL,
},
]}
value={"" + hitPointType}
name={"hitPointType"}
/>
<CheckboxGroup
className={styles.checkboxGroup}
title="Use Prerequisites"
description="Allow or restrict choices based on rule prerequisites for the following for this character"
name="use-prerequisites"
checkboxes={[
{
label: "Feats",
initiallyEnabled: enforceFeatRules,
onChange: (e) =>
handlePreferenceChange("enforceFeatRules", e.target.checked),
},
{
label: "Multiclass Requirements",
initiallyEnabled: enforceMulticlassRules,
onChange: (e) =>
handlePreferenceChange(
"enforceMulticlassRules",
e.target.checked
),
},
]}
/>
<CheckboxGroup
className={styles.checkboxGroup}
title="Show Level-Scaled Spells"
description="Display and highlight available spells to cast with higher level spell slots"
name="show-scaled-spells"
checkboxes={[
{
label: "Show Level-Scaled Spells",
initiallyEnabled: showScaledSpells,
onChange: (e) =>
handlePreferenceChange("showScaledSpells", e.target.checked),
},
]}
/>
<SelectField
label="Encumbrance Type"
description="Use the standard encumbrance rules / Disable the encumbrance display / Use the more detailed rules for encumbrance"
onChange={(value: string) =>
handleIntPreferenceChange("encumbranceType", value)
}
hidePlaceholderOption={true}
options={[
{
label: "Use Encumbrance",
value: Constants.PreferenceEncumbranceTypeEnum.ENCUMBRANCE,
},
{
label: "No Encumbrance",
value: Constants.PreferenceEncumbranceTypeEnum.NONE,
},
{
label: "Variant Encumbrance",
value: Constants.PreferenceEncumbranceTypeEnum.VARIANT,
},
]}
value={"" + encumbranceType}
name={"encumbranceType"}
/>
<CheckboxGroup
className={styles.checkboxGroup}
title="Ignore Coin Weight"
description="Coins do not count against your total weight carried (50 coins weigh 1 lb.)"
name="ignore-coin-weight"
checkboxes={[
{
label: "Ignore Coin Weight",
initiallyEnabled: ignoreCoinWeight,
onChange: (e) =>
handlePreferenceChange("ignoreCoinWeight", e.target.checked),
},
]}
/>
<SelectField
label="Ability Score/Modifier Display"
description="Reverse the arrangement of ability modifiers and scores"
onChange={(value: string) =>
handleIntPreferenceChange("abilityScoreDisplayType", value)
}
hidePlaceholderOption={true}
options={[
{
label: "Modifiers Top",
value:
Constants.PreferenceAbilityScoreDisplayTypeEnum.MODIFIERS_TOP,
},
{
label: "Scores Top",
value: Constants.PreferenceAbilityScoreDisplayTypeEnum.SCORES_TOP,
},
]}
value={"" + abilityScoreDisplayType}
name="abilityScoreDisplayType"
/>
<PrivacyTypeRadio
className={styles.privacyTypeRadio}
initialValue={privacyType}
onChange={(e) =>
handleIntPreferenceChange("privacyType", e.target.value)
}
/>
</div>
<hr className={pageStyles.divider} />
<div className={styles.versionInfo}>
<p>
Version: <span>{config.version}</span>
</p>
</div>
</div>
);
};
@@ -0,0 +1,386 @@
import clsx from "clsx";
import {
FC,
HTMLAttributes,
FocusEvent,
ChangeEvent,
useState,
useEffect,
} from "react";
import { useSelector } from "react-redux";
import {
characterActions,
PremadeInfo,
PremadeInfoStatus,
RuleDataUtils,
SimpleSourcedDefinitionContract,
} from "@dndbeyond/character-rules-engine";
import { RadioGroup } from "~/components/RadioGroup";
import { Select } from "~/components/Select";
import { Toggle } from "~/components/Toggle";
import { useCharacterEngine } from "~/hooks/useCharacterEngine";
import { useDispatch } from "~/hooks/useDispatch";
import { useSource } from "~/hooks/useSource";
import { InputField } from "~/subApps/builder/components/InputField";
import { useModalManager } from "~/subApps/builder/contexts/ModalManager";
import UserRoles from "~/tools/js/Shared/constants/UserRoles";
import { appEnvSelectors } from "~/tools/js/Shared/selectors";
import pageStyles from "../../../styles/page.module.css";
import styles from "./styles.module.css";
export interface PremadeFormProps extends HTMLAttributes<HTMLDivElement> {}
export const PremadeForm: FC<PremadeFormProps> = ({ className, ...props }) => {
const dispatch = useDispatch();
const { premadeInfo, characterId, ruleData } = useCharacterEngine();
const userRoles = useSelector(appEnvSelectors.getUserRoles);
const { getGroupedOptionsBySourceCategory } = useSource();
const { createModal } = useModalManager();
const [premadeState, setPremadeState] = useState<PremadeInfo | null>(
premadeInfo
);
useEffect(() => {
setPremadeState(premadeInfo);
}, [premadeInfo]);
const canAccessPremadeForm =
userRoles?.includes(UserRoles.LOREKEEPER) ||
userRoles?.includes(UserRoles.ADMIN);
const inputAttributes = {
disabled: !premadeState,
} as HTMLAttributes<HTMLInputElement>;
const sourceOptions: Array<SimpleSourcedDefinitionContract> =
RuleDataUtils.getSourceData(ruleData).map((sourceData) => {
return {
id: sourceData.id,
name: sourceData.description,
sources: [
{
pageNumber: null,
sourceId: sourceData.id,
sourceType: 1,
},
],
};
});
const premadePrimarySource = premadeState?.sources?.find(
(source) => source.isPrimary
);
const handleAddPremadeInfo = (): void => {
const defaultData: PremadeInfo = {
characterId: characterId,
publishStatus: PremadeInfoStatus.DRAFT,
sources: [],
definition: {
longDescription: null,
shortDescription: null,
imageUrl: null,
imageAltText: null,
mobileImageUrl: null,
mobileImageAccessibility: null,
themeColor: null,
},
};
dispatch(characterActions.premadeInfoAdd(defaultData));
};
const handleDeletePremadeInfo = (): void => {
dispatch(characterActions.premadeInfoDelete(characterId));
};
const handlePremadeInfoChanged = (premadeInfo: PremadeInfo): void => {
setPremadeState(premadeInfo);
dispatch(characterActions.premadeInfoUpdate(premadeInfo));
};
const handlePremadePublishStatusChange = (
e: ChangeEvent<HTMLInputElement>,
newValue: string,
oldValue: string,
accept: () => void,
reject: () => void
): void => {
if (premadeState) {
const content = (
<div>
<p>
Are you sure you want to change the publish status from{" "}
<strong>{oldValue}</strong> to <strong>{newValue}</strong>?
</p>
{newValue === PremadeInfoStatus.PUBLISHED && (
<p>
<strong>Warning:</strong> Publishing this character will make it
publically visible to users on D&D Beyond!! Proceed with Caution!
</p>
)}
</div>
);
createModal({
content,
props: {
heading: "Confirm Premade Status Change",
size: "fit-content",
onConfirm: () => {
handlePremadeInfoChanged({
...premadeState,
publishStatus: newValue as PremadeInfoStatus,
});
accept();
},
onClose: () => {
reject();
},
},
});
}
};
const isReadyToPublish = (): boolean => {
if (!premadeState) return false;
if (premadeState.publishStatus === PremadeInfoStatus.PUBLISHED) return true;
// check that all fields are filled out before enabling publish
return (
premadeState.sources.length >= 1 &&
premadeState.definition.longDescription !== null &&
premadeState.definition.imageUrl !== null &&
premadeState.definition.mobileImageUrl !== null &&
premadeState.definition.shortDescription !== null &&
premadeState.definition.themeColor !== null
);
};
const notReadyToPublishDescription = (): string => {
if (!premadeState) return "";
if (premadeState.sources.length === 0) {
if (premadeState.publishStatus !== PremadeInfoStatus.PUBLISHED) {
return "You must select a source before you can publish this character.";
} else {
return "This premade was published before sources were required. A source should be added!";
}
}
const missingFields: string[] = [];
if (!premadeState.definition.longDescription) {
missingFields.push("Character Description");
}
if (!premadeState.definition.imageUrl) {
missingFields.push("Image Url");
}
if (!premadeState.definition.mobileImageUrl) {
missingFields.push("Mobile Image Url");
}
if (!premadeState.definition.shortDescription) {
missingFields.push("Mobile Description");
}
if (!premadeState.definition.themeColor) {
missingFields.push("Mobile Card Theme Color");
}
if (missingFields.length > 0) {
return `The following fields are required to publish: ${missingFields.join(
", "
)}`;
}
return "";
};
return canAccessPremadeForm ? (
<div className={clsx([styles.premadeForm, className])} {...props}>
<h2 className={pageStyles.title}>
Premade Character Preferences - Lorekeepers Only
</h2>
<div className={styles.form}>
<div>
<label className={styles.label} id="toggle-premade-character">
Premade Character
</label>
<p className={styles.toggleDescription}>
Toggle on to make this a premade character. You cannot disable this
if the premade status is Published.
</p>
<Toggle
onClick={(isEnabled: boolean) => {
isEnabled ? handleAddPremadeInfo() : handleDeletePremadeInfo();
}}
checked={!!premadeState}
color="secondary"
aria-labelledby="toggle-premade-character"
disabled={
premadeState?.publishStatus === PremadeInfoStatus.PUBLISHED
}
/>
</div>
{premadeState && (
<>
<RadioGroup
name="premadeStatus"
title="Publish Status"
description="Mark this character as published when you are ready for it to be publically visible."
initialValue={premadeState.publishStatus}
options={[
{ label: "Draft", value: PremadeInfoStatus.DRAFT },
{
label: "Published",
value: PremadeInfoStatus.PUBLISHED,
description: notReadyToPublishDescription(),
disabled: !isReadyToPublish(),
},
{ label: "Archived", value: PremadeInfoStatus.ARCHIVED },
]}
disabled={!premadeState}
onChangeConfirm={handlePremadePublishStatusChange}
/>
<div>
<label className={styles.label} htmlFor="premade-source-mapping">
Source
</label>
<Select
id="premade-source-mapping"
name="premade-source-mapping"
options={getGroupedOptionsBySourceCategory(sourceOptions)}
value={premadePrimarySource?.sourceId || null}
onChange={(value) => {
const sourceData = RuleDataUtils.getSourceDataInfo(
Number(value),
ruleData
);
const sources = sourceData
? [
{
sourceId: Number(value),
sourceName: sourceData.description || "Unknown",
displayOrder: sourceData.premadeDisplayOrder || 1,
isPrimary: true,
},
]
: [];
handlePremadeInfoChanged({
...premadeState,
sources,
});
}}
placeholder={"-- Choose a Source --"}
/>
</div>
<InputField
label="Character Description"
initialValue={premadeState.definition.longDescription}
inputProps={inputAttributes}
onBlur={(e: FocusEvent<HTMLInputElement>) => {
handlePremadeInfoChanged({
...premadeState,
definition: {
...premadeState.definition,
longDescription: e.target.value,
},
});
}}
/>
<InputField
label="Image Url"
initialValue={premadeState.definition.imageUrl}
inputProps={inputAttributes}
onBlur={(e: FocusEvent<HTMLInputElement>) => {
handlePremadeInfoChanged({
...premadeState,
definition: {
...premadeState.definition,
imageUrl: e.target.value,
},
});
}}
/>
<InputField
label="Image Alt Text"
initialValue={premadeState.definition.imageAltText}
maxLength={150}
inputProps={inputAttributes}
onBlur={(e: FocusEvent<HTMLInputElement>) => {
handlePremadeInfoChanged({
...premadeState,
definition: {
...premadeState.definition,
imageAltText: e.target.value,
},
});
}}
/>
<h3 className={styles.mobileHeader}>Mobile Specific Fields</h3>
<InputField
label="Mobile Description"
initialValue={premadeState.definition.shortDescription}
inputProps={inputAttributes}
onBlur={(e: FocusEvent<HTMLInputElement>) => {
handlePremadeInfoChanged({
...premadeState,
definition: {
...premadeState.definition,
shortDescription: e.target.value,
},
});
}}
/>
<InputField
label="Mobile Image Url"
initialValue={premadeState.definition.mobileImageUrl}
inputProps={inputAttributes}
onBlur={(e: FocusEvent<HTMLInputElement>) => {
handlePremadeInfoChanged({
...premadeState,
definition: {
...premadeState.definition,
mobileImageUrl: e.target.value,
},
});
}}
/>
<InputField
label="Mobile Alt Text"
initialValue={premadeState.definition.mobileImageAccessibility}
inputProps={inputAttributes}
maxLength={150}
onBlur={(e: FocusEvent<HTMLInputElement>) => {
handlePremadeInfoChanged({
...premadeState,
definition: {
...premadeState.definition,
mobileImageAccessibility: e.target.value,
},
});
}}
/>
<InputField
className={styles.colorPicker}
label="Mobile Card Theme Color"
type="color"
initialValue={premadeState.definition.themeColor || "#8A9BA8"} // default color provided by Mobile team
inputProps={inputAttributes}
onBlur={(e: FocusEvent<HTMLInputElement>) => {
handlePremadeInfoChanged({
...premadeState,
definition: {
...premadeState.definition,
themeColor: e.target.value,
},
});
}}
/>
</>
)}
</div>
</div>
) : null;
};