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:
+129
@@ -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>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user