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
@@ -1,10 +1,10 @@
import React from "react";
import { PureComponent, PropsWithChildren } from "react";
import { ThemeButton } from "../ThemeButton";
//Eventually remove and use character-components/Button/RemoveButton with theme override when needed
interface Props {
interface Props extends PropsWithChildren {
onClick?: () => void;
className?: string;
disabled?: boolean;
@@ -17,7 +17,7 @@ interface Props {
enableConfirm?: boolean;
isInteractive?: boolean;
}
export default class RemoveButton extends React.PureComponent<Props, {}> {
export default class RemoveButton extends PureComponent<Props, {}> {
static defaultProps = {
size: "small",
style: "outline",
@@ -1,9 +1,9 @@
import React from "react";
import { PureComponent, MouseEvent, PropsWithChildren } from "react";
import { Button } from "@dndbeyond/character-components/es";
interface Props {
onClick?: ((evt: React.MouseEvent<HTMLButtonElement>) => void) | (() => void);
interface Props extends PropsWithChildren {
onClick?: ((evt: MouseEvent<HTMLButtonElement>) => void) | (() => void);
className: string;
disabled?: boolean;
styledDisabled?: boolean;
@@ -17,7 +17,7 @@ interface Props {
confirmDuration?: number;
isInteractive?: boolean;
}
export default class ThemeButton extends React.PureComponent<Props, {}> {
export default class ThemeButton extends PureComponent<Props, {}> {
static defaultProps = {
className: "",
style: "filled",
@@ -1,137 +0,0 @@
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import List from "@mui/material/List";
import ListSubheader from "@mui/material/ListSubheader";
import MenuItem from "@mui/material/MenuItem";
import Popover from "@mui/material/Popover";
import React from "react";
import { GroupedMenuOption } from "@dndbeyond/character-rules-engine/es";
import { ThemeButton } from "..";
interface Props {
children: React.ReactNode;
groupedOptions: Array<GroupedMenuOption>;
className?: string;
size?: string;
buttonStyle?: string;
showSingleOption?: boolean;
onSelect: (containerDefinitionKey: string) => void;
containerEl?: HTMLElement;
}
export const ThemeButtonWithMenu: React.FC<Props> = ({
className,
children,
groupedOptions,
size = "small",
buttonStyle = "filled",
onSelect,
showSingleOption = false,
containerEl,
}) => {
const singleOption =
groupedOptions.length === 1 &&
groupedOptions[0].options?.length === 1 &&
!showSingleOption;
const classNames = [className, "ct-theme-button-with-menu"];
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
if (!singleOption) {
setAnchorEl(event.currentTarget);
} else if (singleOption) {
onSelect(groupedOptions[0].options[0]?.value);
}
};
const handleClose = (value?: string) => {
if (value) {
onSelect(value);
}
setAnchorEl(null);
};
return (
<span className={classNames.join(" ")}>
<ThemeButton
size={size}
style={buttonStyle}
onClick={(evt: React.MouseEvent<HTMLButtonElement>) => {
handleClick(evt);
}}
data-testid={`theme-button-with-menu-${children?.toString()}`
.toLowerCase()
.replace(/\s/g, "-")}
>
{children}{" "}
{!singleOption && (
<KeyboardArrowDownIcon sx={{ width: 8, height: 8 }} />
)}
</ThemeButton>
{!singleOption && (
<Popover
anchorEl={anchorEl}
container={containerEl}
keepMounted
open={Boolean(anchorEl)}
onClose={(evt: any) => {
evt.stopPropagation();
evt.nativeEvent?.stopImmediatePropagation();
handleClose();
}}
>
<List
sx={{ bgcolor: "background.default" }}
onClick={(evt) => {
evt.nativeEvent?.stopImmediatePropagation();
evt.stopPropagation();
}}
>
{groupedOptions.map((groupedOption) => (
<React.Fragment key={groupedOption.label}>
<ListSubheader
sx={{
lineHeight: "32px",
textTransform: "uppercase",
fontWeight: 700,
fontSize: "12px",
bgcolor: "background.default",
}}
data-testid={`theme-button-with-menu-subheader-${groupedOption.label}`
.toLowerCase()
.replace(/\s/g, "-")}
>
{groupedOption.label}
</ListSubheader>
{groupedOption.options.map((option) => {
return (
<MenuItem
sx={{ bgcolor: "background.default" }}
key={option.value}
onClick={(evt) => {
evt.stopPropagation();
evt.nativeEvent?.stopImmediatePropagation();
handleClose(option.value);
}}
data-testid={`theme-button-with-menu-item-${option.label}`
.toLowerCase()
.replace(/\s/g, "-")}
>
{option.label}
</MenuItem>
);
})}
</React.Fragment>
))}
</List>
</Popover>
)}
</span>
);
};
export default ThemeButtonWithMenu;
@@ -1,4 +0,0 @@
import { ThemeButtonWithMenu } from "./ThemeButtonWithMenu";
export default ThemeButtonWithMenu;
export { ThemeButtonWithMenu };
@@ -1,266 +0,0 @@
import clsx from "clsx";
import { orderBy } from "lodash";
import React from "react";
import { v4 as uuidv4 } from "uuid";
import { Checkbox } from "~/components/Checkbox";
import { HtmlContent } from "~/components/HtmlContent";
import { Accordion } from "../../../../../../components/Accordion";
import styles from "./styles.module.css";
import { CheckboxInfo } from "./typings";
export enum CheckboxVariant {
BUILDER = "builder",
SIDEBAR = "sidebar",
DEFAULT = "default",
}
interface Props {
checkboxes: Array<CheckboxInfo>;
heading: React.ReactNode;
description: React.ReactNode;
checkUncheckAllEnabled?: boolean;
onCheckUncheckAll?: CheckboxInfo;
showAccordion?: boolean;
accordionHeading?: string;
themed?: boolean;
darkMode?: boolean;
variant?: "builder" | "sidebar" | "default";
allText?: string;
}
export const FormCheckBoxesField: React.FC<Props> = ({
heading,
description,
checkboxes,
showAccordion,
checkUncheckAllEnabled,
onCheckUncheckAll,
themed,
darkMode,
accordionHeading = "Options",
variant = CheckboxVariant.DEFAULT,
allText = "All",
}) => {
const sortedChecks = orderBy(checkboxes, "sortOrder");
const allChecksEnabled = sortedChecks.every(
(checkbox) => checkbox.initiallyEnabled
);
if (!checkboxes.length) {
return null;
}
const renderSingleCheckbox = () => {
const [checkbox] = sortedChecks;
return (
<div
className={clsx([
styles.checkboxesContainer,
variant === CheckboxVariant.SIDEBAR &&
styles.checkboxesContainerSidebar,
])}
>
<div className={styles.summary}>
<div
className={clsx([
styles.summaryHeadingGroup,
styles.summaryHeadingGroupSingle,
])}
>
<div
className={clsx([
styles.summaryHeading,
variant === CheckboxVariant.SIDEBAR &&
styles.summaryHeadingSidebar,
darkMode && styles.summaryHeadingSidebarDark,
])}
>
{heading}
</div>
<div className={styles.checkbox}>
<Checkbox
id={uuidv4()}
checked={checkbox.initiallyEnabled}
aria-label={checkbox.label}
onClick={checkbox.onChange}
onChangePromise={checkbox.onChangePromise}
themed={themed}
darkMode={darkMode}
disabled={checkbox.enabled === false}
variant={variant}
/>
</div>
</div>
{description && (
<div
className={clsx([
styles.summaryDescription,
variant === CheckboxVariant.SIDEBAR &&
styles.summaryDescriptionSidebar,
darkMode && styles.darkMode,
])}
>
{description}
</div>
)}
</div>
</div>
);
};
const renderCheckboxes = () => {
return (
<div className={styles.container}>
{sortedChecks.map((checkbox, idx) => (
<div
className={styles.checkboxWrapper}
key={idx}
data-testid="content-preference-checkbox"
>
<div className={styles.group} key={idx}>
<div className={styles.checkbox}>
<Checkbox
id={uuidv4()}
checked={checkbox.initiallyEnabled}
aria-label={checkbox.label}
onClick={checkbox.onChange}
onChangePromise={checkbox.onChangePromise}
themed={themed}
darkMode={darkMode}
disabled={checkbox.enabled === false}
variant={variant}
/>
</div>
<div
className={clsx([
styles.groupLabel,
variant === CheckboxVariant.DEFAULT &&
styles.groupLabelDefault,
])}
>
{checkbox.label}
</div>
</div>
{checkbox.description && (
<HtmlContent
html={checkbox.description}
className={clsx([
styles.description,
variant === CheckboxVariant.DEFAULT &&
styles.descriptionDefault,
])}
/>
)}
</div>
))}
</div>
);
};
return checkboxes.length > 1 ||
variant === CheckboxVariant.BUILDER ||
variant === CheckboxVariant.DEFAULT ? (
<div
className={clsx(
[styles.checkboxesContainer],
variant === CheckboxVariant.SIDEBAR && styles.checkboxesContainerSidebar
)}
>
{((variant === CheckboxVariant.SIDEBAR && !showAccordion) ||
variant === CheckboxVariant.BUILDER ||
variant === CheckboxVariant.DEFAULT) && (
<div className={styles.summary}>
<div className={styles.summaryHeadingGroup}>
{checkUncheckAllEnabled &&
onCheckUncheckAll &&
variant === CheckboxVariant.DEFAULT && (
<Checkbox
id={uuidv4()}
checked={allChecksEnabled}
aria-label="Check/Uncheck all"
onClick={onCheckUncheckAll.onChange}
themed={themed}
darkMode={darkMode}
variant={variant}
/>
)}
<div
className={clsx([
styles.summaryHeading,
variant === CheckboxVariant.SIDEBAR &&
styles.summaryHeadingSidebar,
darkMode && styles.summaryHeadingSidebarDark,
variant === CheckboxVariant.DEFAULT &&
styles.summaryHeadingDefault,
])}
>
{heading}
</div>
</div>
{description && (
<div
className={clsx([
styles.summaryDescription,
variant === CheckboxVariant.SIDEBAR &&
styles.summaryDescriptionSidebar,
darkMode && styles.darkMode,
])}
>
{description}
</div>
)}
</div>
)}
{variant === CheckboxVariant.BUILDER && checkUncheckAllEnabled ? (
<div className={styles.summaryBuilder}>
<div className={styles.summaryHeadingGroup}>
{checkUncheckAllEnabled && onCheckUncheckAll && (
<Checkbox
id={uuidv4()}
checked={allChecksEnabled}
aria-label="Check/Uncheck all"
onClick={onCheckUncheckAll.onChange}
themed={themed}
darkMode={darkMode}
variant={variant}
/>
)}
<div className={styles.summaryHeadingAll}>{allText}</div>
</div>
</div>
) : null}
{showAccordion ? (
<Accordion
summary={
variant !== CheckboxVariant.SIDEBAR ? accordionHeading : heading
}
variant="text"
resetOpen={!allChecksEnabled}
className={clsx([
styles.accordion,
variant === CheckboxVariant.BUILDER && styles.accordionBuilder,
variant === CheckboxVariant.SIDEBAR && styles.accordionSidebar,
variant === CheckboxVariant.SIDEBAR &&
darkMode &&
styles.accordionHeadingDark,
])}
>
{variant === CheckboxVariant.SIDEBAR && (
<p className={clsx([styles.accordionSidebarDescription])}>
{description}
</p>
)}
{renderCheckboxes()}
</Accordion>
) : (
renderCheckboxes()
)}
</div>
) : (
renderSingleCheckbox()
);
};
@@ -1,134 +0,0 @@
import uniqueId from "lodash/uniqueId";
import React from "react";
import { Select } from "@dndbeyond/character-components/es";
import styles from "./styles.module.css";
interface Props {
initialValue: string | null;
onChange?: (value: string) => void;
onChangePromise?: (
newValue: string,
oldValue: string,
accept: () => void,
reject: () => void
) => void;
heading: React.ReactNode;
clsNames: Array<string>;
initialOptionRemoved: boolean;
options: Array<any>;
description?: React.ReactNode;
block: boolean;
placeholder?: string;
}
interface State {
value: string | null;
}
export default class FormSelectField extends React.PureComponent<Props, State> {
static defaultProps = {
initialValue: null,
clsNames: [],
initialOptionRemoved: false,
block: false,
};
constructor(props: Props) {
super(props);
this.state = {
value: props.initialValue,
};
}
componentDidUpdate(
prevProps: Readonly<Props>,
prevState: Readonly<State>,
snapshot?: any
): void {
const { initialValue } = this.props;
if (initialValue !== prevProps.initialValue) {
this.setState({
value: initialValue,
});
}
}
handleChange = (value: string): void => {
const { onChange } = this.props;
if (onChange) {
onChange(value);
}
this.setState({
value,
});
};
//TODO test that removing newValue from state still works in builder choices
handleChangePromise = (
newValue: string,
oldValue: string,
accept: () => void,
reject: () => void
): void => {
const { onChangePromise } = this.props;
if (onChangePromise) {
onChangePromise(newValue, oldValue, accept, reject);
}
};
render() {
const { value } = this.state;
const {
onChangePromise,
onChange,
block,
heading,
options,
clsNames,
initialOptionRemoved,
description,
placeholder,
} = this.props;
const uId: string = uniqueId("qry-");
let conClsNames: Array<string> = ["builder-field", "builder-field-select"];
if (clsNames.length) {
conClsNames = [...conClsNames, ...clsNames];
}
if (block) {
conClsNames.push("builder-field-select-block");
}
return (
<div className={conClsNames.join(" ")}>
<div className="builder-field-select-summary">
<label
className="builder-field-heading form-input-field-label"
htmlFor={uId}
>
{heading}
</label>
<div className={styles.summaryDescription}>{description}</div>
</div>
<div className="builder-field-select-input">
<Select
options={options}
onChange={onChange ? this.handleChange : undefined}
onChangePromise={
onChangePromise ? this.handleChangePromise : undefined
}
value={value}
initialOptionRemoved={initialOptionRemoved}
placeholder={placeholder}
/>
</div>
</div>
);
}
}
@@ -1,4 +0,0 @@
import FormSelectField from "./FormSelectField";
export default FormSelectField;
export { FormSelectField };
@@ -1,57 +0,0 @@
import React from "react";
import { Toggle } from "~/components/Toggle";
interface Props {
initiallyEnabled: boolean;
onChange?: (isEnabled?: boolean) => void;
onChangePromise?: (
newIsEnabled: boolean,
accept: () => void,
reject: () => void
) => void;
heading: React.ReactNode;
description?: React.ReactNode;
toggleLabel: string;
isReadOnly?: boolean;
}
export default class FormToggleField extends React.PureComponent<Props, {}> {
static defaultProps = {
initiallyEnabled: false,
};
render() {
const {
heading,
description,
initiallyEnabled,
onChange,
onChangePromise,
toggleLabel,
isReadOnly,
} = this.props;
return (
<div className="builder-field builder-field-toggle">
<div className="builder-field-toggle-summary">
<div className="builder-field-toggle-heading">{heading}</div>
{description && (
<div className="builder-field-toggle-description">
{description}
</div>
)}
</div>
<div className="builder-field-toggle-input">
{onChange && (
<Toggle
checked={initiallyEnabled}
onClick={onChange}
color="secondary"
aria-label={toggleLabel}
/>
)}
</div>
</div>
);
}
}
@@ -1,4 +0,0 @@
import FormToggleField from "./FormToggleField";
export default FormToggleField;
export { FormToggleField };
@@ -7,13 +7,13 @@ interface Props {
export default function InlineSeparatedNodes({ nodes, sep = ", " }: Props) {
return (
<React.Fragment>
<>
{nodes.map((node, idx) => (
<React.Fragment key={idx}>
{node}
{idx < nodes.length - 1 && sep}
</React.Fragment>
))}
</React.Fragment>
</>
);
}
@@ -1,17 +1,18 @@
import * as React from "react";
import { PureComponent, PropsWithChildren } from "react";
import LinkButton from "../LinkButton";
interface Props {
interface Props extends PropsWithChildren {
url: string;
className?: string;
block?: boolean;
size: "oversized" | "large" | "medium" | "small";
disabled?: boolean;
download?: string | boolean;
target?: string;
onClick?: (isDisabled: boolean) => void;
}
export default class BuilderLinkButton extends React.PureComponent<Props, {}> {
export default class BuilderLinkButton extends PureComponent<Props, {}> {
static defaultProps = {
className: "",
};
@@ -1,6 +1,6 @@
import React from "react";
import { MouseEvent, PureComponent, PropsWithChildren } from "react";
interface Props {
interface Props extends PropsWithChildren {
url: string;
className: string;
block: boolean;
@@ -10,7 +10,7 @@ interface Props {
onClick?: (isDisabled: boolean) => void;
target?: string;
}
export default class LinkButton extends React.PureComponent<Props, {}> {
export default class LinkButton extends PureComponent<Props, {}> {
static defaultProps = {
className: "",
clsNames: [],
@@ -18,7 +18,7 @@ export default class LinkButton extends React.PureComponent<Props, {}> {
disabled: false,
};
handleClick = (evt: React.MouseEvent) => {
handleClick = (evt: MouseEvent) => {
const { onClick, disabled } = this.props;
if (onClick) {
@@ -1,8 +1,8 @@
import * as React from "react";
import { PureComponent, PropsWithChildren } from "react";
import LinkButton from "../LinkButton";
interface Props {
interface Props extends PropsWithChildren {
url: string;
className?: string;
block?: boolean;
@@ -12,7 +12,7 @@ interface Props {
download?: string | boolean;
target?: string;
}
export default class ThemeLinkButton extends React.PureComponent<Props, {}> {
export default class ThemeLinkButton extends PureComponent<Props, {}> {
static defaultProps = {
className: "",
style: "filled",