refactor: adjust dropdown typings and provide events for button onClick events

This commit is contained in:
Lukas Obermann
2024-02-29 18:58:01 +01:00
parent 739264f725
commit 80c2d22c78
12 changed files with 68 additions and 40 deletions
@@ -10,6 +10,8 @@ import { selectAttributeAdjustmentId } from "../../../../slices/characterSlice.t
import { changeAttributeAdjustmentId } from "../../../../slices/raceSlice.ts"
import "./AttributeAdjustment.scss"
const getKey = (option: DropdownOption<number>) => option.id
/**
* Returns a widget for changing the attribute adjustment set for the race.
*/
@@ -48,6 +50,7 @@ export const AttributesAdjustment: FC = () => {
disabled={
adjustmentOptions.length === 1 && adjustmentOptions[0]!.id === currentAdjustmentId
}
getKey={getKey}
/>
</div>
)
@@ -12,6 +12,8 @@ type Props = {
action: ActionCreatorWithPayload<number>
}
const getKey = (option: DropdownOption<number>) => option.id
/**
* Returns a dropdown for use with personal data.
*/
@@ -36,6 +38,7 @@ export const PersonalDataDropdown: FC<Props> = props => {
onChange={handleSetValue}
options={options}
disabled={options.length === 1 && value === options[0]!.id}
getKey={getKey}
/>
</div>
)
@@ -16,6 +16,8 @@ type Props = {
onReroll: () => void
}
const getKey = (option: DropdownOption<number>) => option.id
/**
* Returns a dropdown with a reroll option for use with personal data.
*/
@@ -40,6 +42,7 @@ export const PersonalDataDropdownWithReroll: FC<Props> = props => {
onChange={handleSetValue}
options={options}
disabled={options.length === 1 && value === options[0]!.id}
getKey={getKey}
/>
<IconButton
icon="&#xE913;"
@@ -229,6 +229,11 @@ export const ProfileOverview: FC = () => {
>
{translate("Finish Character Creation")}
</Button>
<p className="help">
{translate(
"You can save up no more than 10 AP to use later during the game, and you cannot begin the game with a negative AP balance.",
)}
</p>
</GridItem>
) : null}
{
@@ -5,6 +5,7 @@ import {
import { FC, useCallback } from "react"
import { Checkbox } from "../../../../../shared/components/checkbox/Checkbox.tsx"
import { Dropdown } from "../../../../../shared/components/dropdown/Dropdown.tsx"
import { DropdownOption } from "../../../../../shared/components/dropdown/DropdownItem.tsx"
import { IconButton } from "../../../../../shared/components/iconButton/IconButton.tsx"
import { OptionalRuleIdentifier } from "../../../../../shared/domain/identifier.ts"
import { isOptionalRuleActive } from "../../../../../shared/domain/rules/optionalRule.ts"
@@ -21,6 +22,8 @@ type Props = {
}
}
const getKey = (option: DropdownOption<number>) => option.id
/**
* Returns a single optional rule item.
*/
@@ -79,6 +82,7 @@ export const OptionalRulesItem: FC<Props> = props => {
value={getDynamicOptionalRuleById(optionalRule.id)?.options?.[0] ?? 2}
onChange={handleChangeOption}
disabled={!isActive}
getKey={getKey}
/>
) : null}
<IconButton
+4
View File
@@ -20,6 +20,8 @@ type Props = {
initialSettings: GlobalSettings
}
const getLocaleKey = (option: DropdownOption<string | undefined>) => option.id
/**
* Root component for the settings window.
*/
@@ -79,6 +81,7 @@ export const Root: React.FC<Props> = props => {
value={locale}
label={translate("Main Language")}
onChange={setLocale}
getKey={getLocaleKey}
/>
</GridItem>
<GridItem width="1/2">
@@ -93,6 +96,7 @@ export const Root: React.FC<Props> = props => {
value={fallbackLocale}
label={translate("Fallback Language")}
onChange={setFallbackLocale}
getKey={getLocaleKey}
/>
</GridItem>
<GridItem width="1/1">
+2 -2
View File
@@ -1,4 +1,4 @@
import { forwardRef } from "react"
import { MouseEvent, forwardRef } from "react"
import { classList } from "../../utils/classList.ts"
import { FRRFC } from "../../utils/react.js"
import "./Button.scss"
@@ -13,7 +13,7 @@ interface Props {
hint?: string
primary?: boolean
round?: boolean
onClick?(): void
onClick?(event: MouseEvent<HTMLButtonElement>): void
}
const Button: FRRFC<HTMLButtonElement, Props> = (props, ref) => {
+9 -4
View File
@@ -5,9 +5,9 @@ import { FCC } from "../../utils/react.ts"
import { Label } from "../label/Label.tsx"
import { Scroll } from "../scroll/Scroll.tsx"
import "./Dropdown.scss"
import { DropdownItem, DropdownKey, DropdownOption } from "./DropdownItem.tsx"
import { DropdownItem, DropdownOption } from "./DropdownItem.tsx"
type Props<A extends DropdownKey> = {
type Props<A> = {
className?: string
disabled?: boolean
fullWidth?: boolean
@@ -19,6 +19,8 @@ type Props<A extends DropdownKey> = {
values?: A[]
onChange?(option: A): void
onChangeList?(selected: A[]): void
equals?(a: A, b: A): boolean
getKey(option: DropdownOption<A>): string | number | undefined
}
/**
@@ -32,7 +34,7 @@ export const optionFromIndexed = (name: string, index: number): DropdownOption<n
/**
* A dropdown with a list of options.
*/
export const Dropdown = <A extends DropdownKey>(props: Props<A>): ReturnType<FCC<Props<A>>> => {
export function Dropdown<A>(props: Props<A>): ReturnType<FCC<Props<A>>> {
const {
className,
disabled,
@@ -45,6 +47,8 @@ export const Dropdown = <A extends DropdownKey>(props: Props<A>): ReturnType<FCC
required,
value,
values,
equals,
getKey,
} = props
const [isOpen, setOpen] = useState(false)
@@ -125,11 +129,12 @@ export const Dropdown = <A extends DropdownKey>(props: Props<A>): ReturnType<FCC
<ul className="dropdown-options">
{options.map(option => (
<DropdownItem
key={option.id ?? "__DEFAULT__"}
key={getKey(option) ?? "__DEFAULT__"}
active={value}
disabled={normalizedDisabled}
onChange={handleChange}
option={option}
equals={equals}
/>
))}
</ul>
@@ -1,32 +1,29 @@
import { FC, useCallback } from "react"
import { classList } from "../../utils/classList.ts"
/**
* The identifier of a dropdown option.
*/
export type DropdownKey = string | number | undefined
import { deepEqual } from "../../utils/compare.ts"
/**
* Configuration for a dropdown option.
*/
export type DropdownOption<A extends DropdownKey = DropdownKey> = {
export type DropdownOption<A> = {
id: A
name: string
disabled?: boolean
}
type Props<A extends DropdownKey> = {
type Props<A> = {
active: A
disabled: boolean
option: DropdownOption<A>
onChange(option: A): void
equals?(a: A, b: A): boolean
}
/**
* An item in the list of options of a dropdown.
*/
export const DropdownItem = <A extends DropdownKey>(props: Props<A>): ReturnType<FC<Props<A>>> => {
const { active, disabled, onChange, option } = props
export function DropdownItem<A>(props: Props<A>): ReturnType<FC<Props<A>>> {
const { active, disabled, onChange, option, equals = deepEqual } = props
const handleClick = useCallback(
() => (disabled || option.disabled === true ? undefined : onChange(option.id)),
@@ -36,7 +33,7 @@ export const DropdownItem = <A extends DropdownKey>(props: Props<A>): ReturnType
return (
<li
className={classList({
active: option.id === active,
active: equals(option.id, active),
disabled: option.disabled === true,
})}
onClick={handleClick}
@@ -1,4 +1,4 @@
import { forwardRef } from "react"
import { MouseEvent, forwardRef } from "react"
import { FRRFC } from "../../utils/react.ts"
import { Button } from "../button/Button.tsx"
import { Icon } from "../icon/Icon.tsx"
@@ -14,7 +14,7 @@ type Props = {
icon: string
label: string
primary?: boolean
onClick?(): void
onClick?(event: MouseEvent<HTMLButtonElement>): void
}
const IconButton: FRRFC<HTMLButtonElement, Props> = (props, ref) => {
+23 -19
View File
@@ -5,7 +5,7 @@ ul.list-wrapper {
margin: 0;
list-style: none;
li {
> li {
flex: none;
position: relative;
display: flex;
@@ -143,7 +143,7 @@ ul.list-wrapper {
> div {
flex: 1 1 auto;
&:not(.name) {
&:not(:first-child) {
margin: 0 0 0 10px;
}
@@ -152,7 +152,7 @@ ul.list-wrapper {
width: 200px;
}
&.dropdown.tiers {
&.dropdown.level {
width: 65px;
}
}
@@ -193,7 +193,7 @@ ul.list-wrapper {
}
}
p {
span {
font: 500 12px/16px Alegreya Sans;
letter-spacing: 0.05em;
margin-top: 2px;
@@ -208,6 +208,10 @@ ul.list-wrapper {
color: var(--headings-color);
margin: 0;
}
&.level {
margin-left: 0.3em;
}
}
.group {
@@ -254,7 +258,7 @@ ul.list-wrapper {
width: 200px;
}
&.dropdown.tiers {
&.dropdown.level {
width: 65px;
}
}
@@ -406,7 +410,7 @@ ul.list-wrapper {
border-color: var(--important-dark-color);
}
p.title,
.title,
.values .sr {
color: var(--important-bright-color);
}
@@ -421,7 +425,7 @@ ul.list-wrapper {
border-color: var(--common-dark-color);
}
p.title,
.title,
.values .sr {
color: var(--common-bright-color);
}
@@ -436,26 +440,26 @@ ul.list-wrapper {
border-color: var(--uncommon-dark-color);
}
p.title,
.title,
.values .sr {
color: var(--uncommon-bright-color);
}
}
&.disabled {
pointer-events: none;
.title {
color: var(--separator-color-60);
}
p.title {
color: var(--separator-color-30);
height: 32px;
line-height: 32px;
.values > div {
color: var(--separator-color-60);
}
}
&.placeholder {
pointer-events: none;
p.title {
.title {
position: relative;
&:after {
@@ -479,7 +483,7 @@ ul.list-wrapper {
}
&:first-child {
p.title {
.title {
width: 148px;
}
@@ -491,7 +495,7 @@ ul.list-wrapper {
&:nth-child(2) {
opacity: 0.8;
p.title {
.title {
width: 111px;
}
@@ -503,7 +507,7 @@ ul.list-wrapper {
&:nth-child(3) {
opacity: 0.6;
p.title {
.title {
width: 200px;
}
@@ -515,7 +519,7 @@ ul.list-wrapper {
&:nth-child(4) {
opacity: 0.4;
p.title {
.title {
width: 138px;
}
@@ -527,7 +531,7 @@ ul.list-wrapper {
&:nth-child(5) {
opacity: 0.2;
p.title {
.title {
width: 222px;
}
+3 -3
View File
@@ -17,12 +17,12 @@ export const ListItemName: FCC<Props> = props => {
return (
<div className={classList("name", { large: large !== undefined })} onClick={onClick}>
{addName === undefined ? (
<p className="title">{name}</p>
<span className="title">{name}</span>
) : (
<p className="title">
<span className="title">
<span>{name}</span>
<span className="add">{addName}</span>
</p>
</span>
)}
{children}
</div>