diff --git a/components/forms/MemberAttributes.hooks.ts b/components/forms/MemberAttributes.hooks.ts new file mode 100644 index 0000000..3cc36e2 --- /dev/null +++ b/components/forms/MemberAttributes.hooks.ts @@ -0,0 +1,33 @@ +import { useMemo, useState } from 'react' +import { uid } from 'utils/random' + +import type { Attribute } from './MemberAttributes' + +export function useMemberAttributesState() { + const [record, setRecord] = useState>(() => ({})) + + const entries = useMemo(() => Object.entries(record), [record]) + const values = useMemo(() => Object.values(record), [record]) + + function add(attribute: Attribute = { address: '', weight: 0 }) { + setRecord((prev) => ({ ...prev, [uid()]: attribute })) + } + + function update(key: string, attribute = record[key]) { + setRecord((prev) => ({ ...prev, [key]: attribute })) + } + + function remove(key: string) { + return setRecord((prev) => { + const latest = { ...prev } + delete latest[key] + return latest + }) + } + + function reset() { + setRecord({}) + } + + return { entries, values, add, update, remove, reset } +} diff --git a/components/forms/MemberAttributes.tsx b/components/forms/MemberAttributes.tsx new file mode 100644 index 0000000..e36ed94 --- /dev/null +++ b/components/forms/MemberAttributes.tsx @@ -0,0 +1,94 @@ +import { FormControl } from 'components/FormControl' +import { AddressInput, NumberInput } from 'components/forms/FormInput' +import { useEffect, useId, useMemo } from 'react' +import { FaMinus, FaPlus } from 'react-icons/fa' + +import { useInputState, useNumberInputState } from './FormInput.hooks' + +export interface Attribute { + address: string + weight: number +} + +export interface MemberAttributesProps { + title: string + subtitle?: string + isRequired?: boolean + attributes: [string, Attribute][] + onAdd: () => void + onChange: (key: string, attribute: Attribute) => void + onRemove: (key: string) => void +} + +export function MemberAttributes(props: MemberAttributesProps) { + const { title, subtitle, isRequired, attributes, onAdd, onChange, onRemove } = props + + return ( + + {attributes.map(([id], i) => ( + + ))} + + ) +} + +export interface MemberAttributeProps { + id: string + isLast: boolean + onAdd: MemberAttributesProps['onAdd'] + onChange: MemberAttributesProps['onChange'] + onRemove: MemberAttributesProps['onRemove'] + defaultAttribute: Attribute +} + +export function MemberAttribute({ id, isLast, onAdd, onChange, onRemove, defaultAttribute }: MemberAttributeProps) { + const Icon = useMemo(() => (isLast ? FaPlus : FaMinus), [isLast]) + + const htmlId = useId() + + const addressState = useInputState({ + id: `ma-address-${htmlId}`, + name: `ma-address-${htmlId}`, + title: `Address`, + defaultValue: defaultAttribute.address, + }) + + const weightState = useNumberInputState({ + id: `ma-weight-${htmlId}`, + name: `ma-weight-${htmlId}`, + title: `Weight`, + defaultValue: defaultAttribute.weight, + }) + + useEffect(() => { + onChange(id, { address: addressState.value, weight: weightState.value }) + }, [addressState.value, weightState.value, id]) + + return ( +
+ + + +
+ +
+
+ ) +}