Grabbed dndbeyond's source code

```
~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js
```
This commit is contained in:
2025-05-28 11:50:03 -07:00
commit 8df9031d27
3592 changed files with 319051 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
"use client";
import clsx from "clsx";
import type {
AllHTMLAttributes,
ButtonHTMLAttributes,
FC,
PropsWithChildren,
} from "react";
import styles from "./styles/Button.module.css";
import sizeStyles from "./styles/ButtonSizes.module.css";
import variantStyles from "./styles/ButtonVariants.module.css";
export interface ButtonProps
extends PropsWithChildren,
Omit<
AllHTMLAttributes<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement>,
"size" | "type"
>,
Pick<ButtonHTMLAttributes<HTMLButtonElement>, "type"> {
className?: string;
color?: "primary" | "secondary" | "success" | "info" | "warning" | "error";
variant?: "solid" | "outline" | "text";
size?: "x-small" | "small" | "medium" | "large" | "x-large";
isDiv?: boolean;
}
export const Button: FC<ButtonProps> = ({
className,
color = "primary",
variant = "solid",
size = "medium",
isDiv,
...props
}) => {
const Element = props.href ? "a" : isDiv ? "div" : "button";
return (
<Element
className={clsx([
styles.button,
color && variantStyles[color],
variant && variantStyles[variant],
size && sizeStyles[size],
className,
])}
aria-disabled={props.disabled}
{...props}
/>
);
};
+64
View File
@@ -0,0 +1,64 @@
import clsx from "clsx";
import type { ChangeEvent, FC, ReactNode } from "react";
import styles from "./Checkbox.module.css";
export interface CheckboxProps {
id?: string;
label?: ReactNode;
defaultChecked?: boolean;
checked?: boolean;
value?: string | number | readonly string[];
name?: string;
disabled?: boolean;
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
className?: string;
}
export const Checkbox: FC<CheckboxProps> = ({
id,
label,
defaultChecked,
checked,
value,
name,
disabled,
onChange,
className,
}) => (
<div className={clsx([styles.container, className])}>
<span className={clsx(styles.inputContainer)}>
<input
className={styles.checkbox}
id={id}
type="checkbox"
defaultChecked={defaultChecked}
checked={checked}
value={value}
name={name}
aria-disabled={disabled}
onChange={onChange ? (e) => !disabled && onChange?.(e) : undefined}
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
d="M3.5 7.47 6.833 11 12.5 5"
/>
</svg>
</span>
{label && (
<label
className={clsx([styles.label, disabled && styles.disabled])}
htmlFor={id}
>
{label}
</label>
)}
</div>
);
+98
View File
@@ -0,0 +1,98 @@
import clsx from "clsx";
import { useEffect, useRef, type HTMLAttributes, type MouseEvent } from "react";
import styles from "./Dialog.module.css";
/**
* A generic dialog component to display content in a dialog (non-blocking) or
* a modal (blocking) way. This has built-in functionality like backdrop
* clicking and escape key handling. It also accepts a trigger element which
* will open the dialog when clicked. This is to allow focus to be returned to
* the trigger when the element is closed (for accessibility).
*/
export interface DialogProps extends HTMLAttributes<HTMLDialogElement> {
open: boolean;
onClose: (e?: MouseEvent) => void;
modal?: boolean;
}
export const Dialog: React.FC<DialogProps> = ({
open,
onClose,
modal,
children,
className,
...props
}) => {
const dialogRef = useRef<HTMLDialogElement>(null);
const triggerRef = useRef<Element | null>(null);
const handleClose = () => {
onClose();
dialogRef.current?.close();
// Returns focus to trigger after close for accessibility
(triggerRef.current as HTMLButtonElement)?.focus();
};
const handleClickBackdrop = (e?: MouseEvent) => {
if (!e) return;
const rect = dialogRef.current?.getBoundingClientRect();
const isClickInDialog =
rect &&
rect.top <= e.clientY &&
e.clientY <= rect.top + rect.height &&
rect.left <= e.clientX &&
e.clientX <= rect.left + rect.width;
if (!isClickInDialog) handleClose();
};
const handleEsc = (e: KeyboardEvent) => {
if (e.key === "Escape" && open) {
handleClose();
}
};
useEffect(
() => {
// Get the element that was clicked to open the dialog
triggerRef.current = document.activeElement;
if (modal && open) dialogRef.current?.showModal();
if (open) dialogRef.current?.focus();
if (!open) {
handleClose();
}
},
//eslint-disable-next-line react-hooks/exhaustive-deps
[dialogRef.current, open, modal],
);
useEffect(
() => {
document.addEventListener("keydown", handleEsc);
return () => {
document.removeEventListener("keydown", handleEsc);
};
},
//eslint-disable-next-line react-hooks/exhaustive-deps
[modal, open],
);
return (
<dialog
className={clsx([styles.dialog, className])}
ref={dialogRef}
onClick={modal ? handleClickBackdrop : undefined}
open={modal ? undefined : open}
aria-modal={modal}
{...props}
>
{children}
</dialog>
);
};
+49
View File
@@ -0,0 +1,49 @@
import clsx from "clsx";
import type { FC, HTMLAttributes } from "react";
import styles from "./InfoItem.module.css";
interface InfoItemProps extends HTMLAttributes<HTMLDivElement> {
center?: boolean;
color?: "primary" | "secondary" | "success" | "info" | "warning" | "error";
label: string;
inline?: boolean;
italic?: boolean;
}
export const InfoItem: FC<InfoItemProps> = ({
center,
color,
className,
inline,
italic,
label,
children,
...props
}) => (
<div
className={clsx([
center && styles.center,
inline && styles.inline,
className,
])}
{...props}
>
<p
className={clsx([
styles.label,
color && styles[color],
italic && styles.italic,
])}
>
{label}
</p>
{typeof children === "string" ? (
<p
className={clsx([styles.value, color && styles[color]])}
dangerouslySetInnerHTML={{ __html: children }}
/>
) : (
<p className={clsx([styles.value, color && styles[color]])}>{children}</p>
)}
</div>
);
@@ -0,0 +1,28 @@
import clsx from "clsx";
import type { HTMLAttributes, ReactNode } from "react";
import { Tooltip } from "../Tooltip";
import styles from "./LabelChip.module.css";
interface LabelChipProps extends HTMLAttributes<HTMLSpanElement> {
tooltipContent?: ReactNode;
tooltipClickable?: boolean;
}
export const LabelChip = ({
className = "",
children,
tooltipContent,
tooltipClickable,
...props
}: LabelChipProps) => (
<>
<span className={clsx([styles.labelChip, className])} {...props}>
{children}
</span>
{props["data-tooltip-id"] && (
<Tooltip id={props["data-tooltip-id"]} clickable={tooltipClickable}>
{tooltipContent}
</Tooltip>
)}
</>
);
@@ -0,0 +1,45 @@
const ddbImageBase = "https://media.dndbeyond.com/mega-menu";
export const cardList = [
{
label: "Classes",
imageUrl: `${ddbImageBase}/aef3e4b2ac24457aa251c761d41ff07b.jpg`,
link: "https://www.dndbeyond.com/classes",
},
{
label: "Backgrounds",
imageUrl: `${ddbImageBase}/6e31ff6ea1f04ca2af9896f3f6d1b383.jpg`,
link: "https://www.dndbeyond.com/backgrounds",
},
{
label: "Species",
imageUrl: `${ddbImageBase}/8b551f9a1c2c4db09d65cdd7c139f9d5.jpg`,
link: `https://www.dndbeyond.com/species`,
},
{
label: "Feats",
imageUrl: `${ddbImageBase}/a69ab5bf67b03308893b582dbef700e9.jpg`,
link: "https://www.dndbeyond.com/feats",
},
{
label: "Spells",
imageUrl: `${ddbImageBase}/a9eac9081b6240ca8d6325ca4766345e.png`,
link: "https://www.dndbeyond.com/spells",
},
{
label: "Equipment",
imageUrl: `${ddbImageBase}/b778ff3ca3f18e5f75ad4b348615cab5.jpg`,
link: "https://www.dndbeyond.com/equipment",
},
{
label: "Magic Items",
imageUrl: `${ddbImageBase}/c06b79eae8ee234d1cea4688e117152b.jpg`,
link: "https://www.dndbeyond.com/magic-items",
},
{
label: "Monsters",
imageUrl: `${ddbImageBase}/36ee49066331fc36e3b37147d123463a.jpg`,
imagePosition: "center 11%",
link: "https://www.dndbeyond.com/monsters",
},
];
@@ -0,0 +1,26 @@
import { twitchUrl, youtubeUrl } from "../../../shared/constants";
const ddbImageBase = "https://media.dndbeyond.com/mega-menu";
export const primaryItems = [
{
label: "Community Update",
imageUrl: `${ddbImageBase}/c2979f81837d8ee7c113a874da2909db.png`,
link: "https://www.dndbeyond.com/community-update",
},
{
label: "Twitch",
imageUrl: `${ddbImageBase}/443085c3173d345e90d29881d8442a59.png`,
link: twitchUrl,
},
{
label: "Youtube",
imageUrl: `${ddbImageBase}/d5e1b6d07482834ebfcbc1f04bd6476d.png`,
link: youtubeUrl,
},
{
label: "Changelog",
imageUrl: `${ddbImageBase}/4af3d4c196428ab0809cf71d332d540d.png`,
link: "https://www.dndbeyond.com/changelog",
},
];
@@ -0,0 +1,37 @@
import type { MegaMenuCardProps } from "../MegaMenuCard";
const ddbImageBase = "https://media.dndbeyond.com/mega-menu";
export const characterBuilder: MegaMenuCardProps = {
label: "Character Builder",
imageUrl: `${ddbImageBase}/323a928e32eff87dee85dfbe0793ce12.jpg`,
link: "https://www.dndbeyond.com/characters/builder",
};
export const primaryItems: MegaMenuCardProps[] = [
{
label: "Maps",
imageUrl: `${ddbImageBase}/049ddb9085342521d25c5230451cfd45.jpg`,
link: "https://www.dndbeyond.com/games",
flags: [{ label: "Beta", variant: "info" }],
},
{
label: "Encounters",
imageUrl: `${ddbImageBase}/e434a8385f9619f0d52480c3c3987059.jpg`,
link: "https://www.dndbeyond.com/encounter-builder",
flags: [{ label: "Beta", variant: "info" }],
},
];
export const secondaryItems: MegaMenuCardProps[] = [
{
label: "Mobile App",
imageUrl: `${ddbImageBase}/3aa58aac2d02bb52d62204e158b48ce6.jpg`,
link: "https://www.dndbeyond.com/player-app",
},
{
label: "Avrae Discord Bot",
imageUrl: `${ddbImageBase}/28d923cd45ad209411ef1ff07993721b.jpg`,
link: "https://avrae.io/",
},
];
+129
View File
@@ -0,0 +1,129 @@
"use client";
import AngleDown from "@dndbeyond/fontawesome-cache/svgs/regular/angle-down.svg";
import XMark from "@dndbeyond/fontawesome-cache/svgs/regular/xmark.svg";
import clsx from "clsx";
import type { FC, MouseEvent, SelectHTMLAttributes } from "react";
import { useIsVisible } from "../../hooks/useIsVisible/useIsVisible";
import { Checkbox } from "../Checkbox";
import styles from "./Select.module.css";
interface Option {
value: string | number;
label: string;
}
interface SelectProps
extends Omit<SelectHTMLAttributes<HTMLDivElement>, "value"> {
label: string;
options: Array<Option>;
value: string | string[] | number[];
placeholder?: string;
onDelete?: (e: MouseEvent) => void;
error?: string;
}
export const Select: FC<SelectProps> = ({
className = "",
placeholder,
name,
label,
onChange,
onDelete,
options,
value,
disabled,
error,
...props
}) => {
const { ref, isVisible, setIsVisible } = useIsVisible(false);
const multiple = Array.isArray(value) || false;
const handleDelete = (e: MouseEvent) => {
if (onDelete) {
e.stopPropagation();
onDelete(e);
}
};
const getDisplay = () => {
// If value is an array, show tags
if (multiple && Array.isArray(value) && value.length) {
return (
<span className={styles.tags}>
{value.map((v) => (
<span
className={styles.tag}
onClick={(e: MouseEvent) => handleDelete(e)}
key={v}
>
{v}
<XMark />
</span>
))}
</span>
);
}
// If value is string, show string
else if (!multiple && value) {
return value;
}
// If there is no value, show the placeholder
else {
return <span className={styles.placeholder}>{placeholder}</span>;
}
};
return (
<>
<div ref={ref} className={clsx([styles.container, className])} {...props}>
<label id={`${name}Label`}>{label}</label>
<button
className={clsx([styles.button, error && styles.buttonError])}
id={`${name}Button`}
onClick={() => setIsVisible(!isVisible)}
role="combobox"
aria-haspopup="listbox"
aria-controls={`${name}Dropdown`}
aria-labelledby={`${name}Label`}
aria-expanded={isVisible}
tabIndex={0}
disabled={disabled || false}
>
{getDisplay()}
<AngleDown />
</button>
<div
className={styles.options}
role="group"
id={`${name}Dropdown`}
tabIndex={-1}
>
{options.map((option) =>
multiple ? (
<Checkbox
id={`checkbox-${option.value}`}
label={option.label}
key={option.value}
name={name}
value={option.value}
onChange={(e) => onChange?.(e)}
checked={value.includes(option.value as never)}
/>
) : (
<label className={styles.option} key={option.value}>
<input
type="radio"
name={name}
value={option.value}
onChange={(e) => onChange?.(e)}
checked={value === option.value}
/>
{option.label}
</label>
),
)}
</div>
</div>
{error && <p className={styles.error}>{error}</p>}
</>
);
};
+66
View File
@@ -0,0 +1,66 @@
import clsx from "clsx";
import { useEffect, useRef, type DialogHTMLAttributes, type FC } from "react";
import styles from "./Toast.module.css";
/**
* Toast Usage
* const [open, setOpen] = useState(false);
*
* const onClick = () => {
* setOpen(true);
* };
*
* const onClose = () => {
* setOpen(false);
* };
*
* <Button onClick={onClick}>Toast</Button>
* <Toast
* open={open}
* autoHideDuration={6000}
* onClose={onClose}
* message="Note Archived"
* />
**/
interface ToastProps extends DialogHTMLAttributes<HTMLDialogElement> {
align?: "left" | "right";
autoHideDuration?: number;
onClose: () => void;
}
let timer: NodeJS.Timeout | undefined;
export const Toast: FC<ToastProps> = ({
align = "left",
autoHideDuration = 6000,
children,
className,
onClose,
open,
...props
}) => {
const ref = useRef<HTMLDialogElement | null>(null);
useEffect(() => {
if (open) {
// Prevent the Toast from grabbing focus
ref.current?.setAttribute("open", "open");
timer = setTimeout(onClose, autoHideDuration);
} else {
ref.current?.close?.();
timer = undefined;
}
return () => clearTimeout(timer);
}, [autoHideDuration, onClose, open]);
return (
<dialog
className={clsx([styles.toast, styles[align], className])}
ref={ref}
{...props}
>
{children}
</dialog>
);
};
+28
View File
@@ -0,0 +1,28 @@
"use client";
import type { FC } from "react";
import { Tooltip as ReactTooltip, type ITooltip } from "react-tooltip";
import styles from "./Tooltip.module.css";
export interface TooltipProps extends ITooltip {
id: string;
"data-testid"?: string;
}
export const Tooltip: FC<TooltipProps> = ({
id,
"data-testid": testId,
children,
...props
}) => {
return (
<div
className={styles.container}
{...(testId && { "data-testid": testId })}
>
<ReactTooltip id={id} className={styles.tooltip} {...props}>
{children}
</ReactTooltip>
</div>
);
};
@@ -0,0 +1,46 @@
import { useEffect, useRef, useState, type MutableRefObject } from "react";
/**
* A hook to use for determining open and closed states
* @param initialValue Whether the component should start visible or not
*
* @returns {ref} A ref to be given to parent object
* @returns {isVisible} Boolean value to decide whether to show
* element or not
* @returns {setIsVisible} Dispatch function to update the value of
* isVisible
*/
interface Return<T> {
ref: MutableRefObject<T | null>;
isVisible: boolean;
setIsVisible: (value: boolean) => void;
}
export const useIsVisible = <T = HTMLDivElement>(
initialValue: boolean,
): Return<T> => {
const [isVisible, setIsVisible] = useState(initialValue);
const ref = useRef<T | null>(null);
// Handles clicks outside the parent ref
const handleClickOutside = (e: MouseEvent) => {
const containsElement =
e.composedPath().indexOf(ref.current as EventTarget) !== -1;
if (ref.current && !containsElement) setIsVisible(false);
};
useEffect(
() => {
document.addEventListener("click", handleClickOutside, true);
return () => {
document.removeEventListener("click", handleClickOutside, true);
};
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
return { ref, isVisible, setIsVisible };
};
+77
View File
@@ -0,0 +1,77 @@
import { ThemeOptions } from "@mui/material";
export const components: ThemeOptions["components"] = {
MuiButtonBase: {
defaultProps: {
disableRipple: true,
},
},
MuiDialog: {
styleOverrides: {
paper: ({ theme }) => ({
minWidth: 320,
"&.MuiPaper-rounded": {
borderRadius: "8px",
},
"&.MuiPaper-outlined": {
borderColor: theme.palette.primary.main,
},
}),
},
},
MuiGrid: {
styleOverrides: {
root: {
/**
* n.b. this is a workaround for bad types on the DialogTitle component,
* what I really want there is a Grid, but I don't want to enforce these
* styles on any other Grid components that might be passed in, so I add
* the className "DdbDialogTitle-root" to target this specific component
*/
"&.DdbDialogTitle-root": {
padding: "16px 20px 8px",
"& .MuiIconButton-root": {
padding: "4px",
marginRight: "-8px",
},
},
},
},
},
MuiIconButton: {
styleOverrides: {
root: {
transition: "none",
"&:hover": {
transition: "none",
borderRadius: "3px",
},
},
},
},
MuiDialogContent: {
styleOverrides: {
root: {
padding: "16px 20px 0",
},
},
},
MuiDialogContentText: {
styleOverrides: {
root: ({ theme }) => ({
color: theme.palette.text.primary,
letterSpacing: "0.15px",
marginBottom: "12px",
}),
},
},
MuiButton: {
styleOverrides: {
root: {
fontSize: "15px",
lineHeight: 26 / 15,
letterSpacing: "0.45px",
},
},
},
};
+95
View File
@@ -0,0 +1,95 @@
import { ThemeOptions } from "@mui/material";
export const palette: ThemeOptions["palette"] = {
mode: "dark",
text: {
primary: "#ecedeeff",
secondary: "#ecedeea3",
disabled: "#ecedee5c",
},
primary: {
main: "#dcdfe1ff",
dark: "#75838bff",
light: "#f9fafaff",
contrastText: "#000000ff",
},
secondary: {
main: "#00daa6ff",
dark: "#00b674ff",
light: "#b3efd9ff",
contrastText: "#000000ff",
},
action: {
active: "#dcdfe1a3",
hover: "#dcdfe11f",
selected: "#dcdfe12e",
disabled: "#dcdfe166",
disabledBackground: "#dcdfe133",
focus: "#dcdfe133",
},
error: {
main: "#ed6c02ff",
dark: "#c77700ff",
light: "#ffb547ff",
contrastText: "#ffffffff",
},
warning: {
main: "#ed6c02ff",
dark: "#c77700ff",
light: "#ffb547ff",
contrastText: "#ffffffff",
},
info: {
main: "#2196f3ff",
dark: "#0b79d0ff",
light: "#64b6f7ff",
contrastText: "#ffffffff",
},
success: {
main: "#4caf50ff",
dark: "#3b873eff",
light: "#7bc67eff",
contrastText: "#ffffffff",
},
background: {
paper: "#12181cff",
default: "#232b2fff",
pane: "#12181cdb",
},
common: { white: "#ffffffff", black: "#000000ff" },
grey: {
"50": "#f4f5f5ff",
"100": "#ecedeeff",
"200": "#dcdfe1ff",
"300": "#c4cbceff",
"400": "#a2acb2ff",
"500": "#75838bff",
"600": "#525c63ff",
"700": "#374045ff",
"800": "#232b2fff",
"900": "#12181cff",
},
rarity: {
uncommon: "#7ebe15ff",
rare: "#41a9f2ff",
veryRare: "#c364e7ff",
legendary: "#ffb62aff",
artifact: "#f77558ff",
contrastText: "#000000ff",
},
reference: {
magicItem: "#41a9f2ff",
monster: "#f77558ff",
skill: "#7ebe15ff",
spell: "#c364e7ff",
contrastText: "#000000ff",
},
message: {
check: "#c364e7ff",
custom: "#ecedeea3",
damage: "#f77558ff",
healSave: "#7ebe15ff",
initiative: "#ffb62aff",
toHit: "#41a9f2ff",
},
};
@@ -0,0 +1,89 @@
import { ThemeOptions } from "@mui/material";
export const palette: ThemeOptions["palette"] = {
mode: "light",
text: {
primary: "#12181cff",
secondary: "#12181ca3",
disabled: "#12181c5c",
},
primary: {
main: "#374045ff",
dark: "#12181cff",
light: "#75838bff",
contrastText: "#ffffffff",
},
secondary: {
main: "#e40712ff",
dark: "#9f271dff",
light: "#f77558ff",
contrastText: "#ffffffff",
},
action: {
active: "#12181ca3",
hover: "#12181c14",
selected: "#12181c1f",
disabled: "#12181c5c",
disabledBackground: "#12181c29",
focus: "#12181c29",
},
error: {
main: "#ed6c02ff",
dark: "#c77700ff",
light: "#ffb547ff",
contrastText: "#ffffffff",
},
warning: {
main: "#ed6c02ff",
dark: "#c77700ff",
light: "#ffb547ff",
contrastText: "#ffffffff",
},
info: {
main: "#2196f3ff",
dark: "#0b79d0ff",
light: "#64b6f7ff",
contrastText: "#ffffffff",
},
success: {
main: "#4caf50ff",
dark: "#3b873eff",
light: "#7bc67eff",
contrastText: "#ffffffff",
},
background: {
paper: "#ffffffff",
default: "#f4f5f5ff",
parchment: "#f9f6efff",
scroll: "#eee8dbff",
},
common: { white: "#ffffffff", black: "#000000ff" },
grey: {
"50": "#f4f5f5ff",
"100": "#ecedeeff",
"200": "#dcdfe1ff",
"300": "#c4cbceff",
"400": "#a2acb2ff",
"500": "#75838bff",
"600": "#525c63ff",
"700": "#374045ff",
"800": "#232b2fff",
"900": "#12181cff",
},
rarity: {
uncommon: "#009158ff",
rare: "#077bd0ff",
veryRare: "#9605d4ff",
legendary: "#ae5e21ff",
artifact: "#c81e18ff",
contrastText: "#ffffffff",
},
reference: {
character: "#ee8600ff",
magicItem: "#077bd0ff",
monster: "#c60000ff",
skill: "#009158ff",
spell: "#9605d4ff",
contrastText: "#ffffffff",
},
};
+113
View File
@@ -0,0 +1,113 @@
import { ThemeOptions } from "@mui/material";
export const typography: ThemeOptions["typography"] = {
h1: {
fontSize: "64px",
fontFamily: "Tiamat Condensed SC",
fontWeight: 400,
fontStyle: "normal",
fontStretch: "normal",
letterSpacing: 1,
lineHeight: "64px",
},
h2: {
fontSize: "48px",
fontFamily: "Roboto",
fontWeight: 400,
fontStyle: "normal",
fontStretch: "normal",
letterSpacing: "normal",
lineHeight: "52.8px",
},
h3: {
fontSize: "36px",
fontFamily: "Roboto",
fontWeight: 400,
fontStyle: "normal",
fontStretch: "normal",
letterSpacing: "normal",
lineHeight: "43.2px",
},
h4: {
fontSize: "26px",
fontFamily: "Roboto",
fontWeight: 400,
fontStyle: "normal",
fontStretch: "normal",
letterSpacing: "normal",
lineHeight: "32.5px",
},
h5: {
fontSize: "20px",
fontFamily: "Roboto",
fontWeight: 500,
fontStyle: "normal",
fontStretch: "normal",
letterSpacing: "normal",
lineHeight: "25px",
},
h6: {
fontSize: "16px",
fontFamily: "Roboto",
fontWeight: 500,
fontStyle: "normal",
fontStretch: "normal",
letterSpacing: "normal",
lineHeight: "22.4px",
},
body1: {
fontSize: "16px",
fontFamily: "Roboto",
fontWeight: 400,
fontStyle: "normal",
fontStretch: "normal",
letterSpacing: 0.15,
lineHeight: "24px",
},
body2: {
fontSize: "14px",
fontFamily: "Roboto",
fontWeight: 400,
fontStyle: "normal",
fontStretch: "normal",
letterSpacing: 0.15,
lineHeight: "20.02px",
},
subtitle1: {
fontSize: "16px",
fontFamily: "Roboto",
fontWeight: 400,
fontStyle: "normal",
fontStretch: "normal",
letterSpacing: 0.15,
lineHeight: "28px",
},
subtitle2: {
fontSize: "14px",
fontFamily: "Roboto",
fontWeight: 500,
fontStyle: "normal",
fontStretch: "normal",
letterSpacing: 0.1,
lineHeight: "21.98px",
},
overline: {
fontSize: "12px",
fontFamily: "Roboto",
fontWeight: 400,
fontStyle: "normal",
fontStretch: "normal",
letterSpacing: 1,
lineHeight: "31.92px",
textTransform: "uppercase",
},
caption: {
fontSize: "12px",
fontFamily: "Roboto",
fontWeight: 400,
fontStyle: "normal",
fontStretch: "normal",
letterSpacing: 0.4,
lineHeight: "19.92px",
},
};
+30
View File
@@ -0,0 +1,30 @@
import { createTheme, ThemeOptions } from "@mui/material";
import { deepmerge } from "@mui/utils";
import { components } from "./config/components";
import { palette as darkThemePalette } from "./config/palette-dark";
import { palette as lightThemePalette } from "./config/palette-light";
import { typography } from "./config/typography";
export const createLightTheme = (options: ThemeOptions = {}) =>
createTheme(
deepmerge(
{
components,
palette: lightThemePalette,
typography,
},
options,
),
);
export const createDarkTheme = (options: ThemeOptions = {}) =>
createTheme(
deepmerge(
{
components,
palette: darkThemePalette,
typography,
},
options,
),
);
+17
View File
@@ -0,0 +1,17 @@
export const signInLabel = "Sign In";
export const signUpLabel = "Sign Up";
export const signOutLabel = "Sign Out";
// Social Media Links
export const discordUrl = "https://www.dndbeyond.com/discord";
export const facebookUrl = "https://www.facebook.com/dndbeyond";
export const instagramUrl = "https://www.instagram.com/dndbeyond";
export const tiktokUrl = "https://www.tiktok.com/@dnd_beyond";
export const twitchUrl = "https://www.twitch.tv/dndbeyond";
export const twitterUrl = "https://twitter.com/dndbeyond";
export const youtubeUrl =
"https://www.youtube.com/channel/UCPy-338BEVgDkQade0qJmkw";
export const googleStoreUrl =
"https://play.google.com/store/apps/details?id=com.fandom.playercompanion";
export const appleStoreUrl =
"https://apps.apple.com/us/app/d-d-beyond/id1501810129";
+75
View File
@@ -0,0 +1,75 @@
// this implementation is temporary until we can natively dynamically import css files
// ref: https://react.dev/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022#asset-loading
const wayOfLight = /* css */ `
:root {
--ttui_color-primary--main: #374045;
--ttui_color-primary--dark: #12181c;
--ttui_color-primary--light: #75838b;
--ttui_color-primary--contrast: #ffffff;
--ttui_color-secondary--main: var(--ttui_red-500);
--ttui_color-secondary--dark: var(--ttui_red-700);
--ttui_color-secondary--light: var(--ttui_red-300);
--ttui_color-secondary--contrast: var(--ttui_common-0);
--ttui_color-error--main: #ed6c02;
--ttui_color-error--dark: #c77700;
--ttui_color-error--light: #ffb547;
--ttui_color-error--contrast: #ffffff;
--ttui_color-warning--main: #ed6c02;
--ttui_color-warning--dark: #c77700;
--ttui_color-warning--light: #ffb547;
--ttui_color-warning--contrast: #ffffff;
--ttui_color-info--main: #2196f3;
--ttui_color-info--dark: #0b79d0;
--ttui_color-info--light: #64b6f7;
--ttui_color-info--contrast: #ffffff;
--ttui_color-success--main: #4caf50;
--ttui_color-success--dark: #3b873e;
--ttui_color-success--light: #7bc67e;
--ttui_color-success--contrast: #ffffff;
--ttui_color-bg--paper: #ffffff;
--ttui_color-bg--default: #f4f5f5;
--ttui_color-bg--pane: #f9f6ef;
--ttui_color-bg--scroll: #eee8db;
--ttui_color-rarity--uncommon: #7ebe15;
--ttui_color-rarity--rare: #41a9f2;
--ttui_color-rarity--veryRare: #c364e7;
--ttui_color-rarity--legendary: #ffb62a;
--ttui_color-rarity--artifact: #f77558;
--ttui_color-rarity--contrast: #000000;
--ttui_color-reference--character: #ee8600;
--ttui_color-reference--magicItem: #41a9f2;
--ttui_color-reference--monster: #c60000;
--ttui_color-reference--skill: #7ebe15;
--ttui_color-reference--spell: #c364e7;
--ttui_color-reference--contrast: #000000;
--ttui_color-message--check: #c364e7;
--ttui_color-message--custom: #ecedee;
--ttui_color-message--damage: #f77558;
--ttui_color-message--healSave: #7ebe15;
--ttui_color-message--initiative: #ffb62a;
--ttui_color-message--toHit: #41a9f2;
--ttui_color-text--primary: #12181cff;
--ttui_color-text--secondary: #12181ca3;
--ttui_color-text--disabled: #12181c5c;
--ttui_color-action--hover: var(--ttui_grey-100);
--ttui_color-action--active: var(--ttui_grey-200);
--ttui_color-action--selected: var(--ttui_grey-200);
--ttui_color-megamenu--main: #26282a;
--ttui_color-megamenu--dark: #1c1d1e;
--ttui_color-megamenu--contrast: var(--ttui_common-0);
}
`;
export default wayOfLight;