Dialog Components
Modal dialogs, confirmation prompts, and dialog utilities
Modal and dialog components for user interactions.
Prerequisites
- Complete the installation
- Import styles:
import '@tetherto/mdk-react-devkit/styles.css'
Components
@tetherto/mdk-react-devkit
Add a spare part
import { AddSparePartModal } from '@tetherto/mdk-react-devkit'Modal for registering a new spare part. Presents a part-type tab strip (Controller, PSU, Hashboard, …) and a form for miner model, part model, serial number, MAC address, status, location, tags, and a comment. Validation is controller-aware — the MAC address field appears and is required only when a controller part type is selected; other part types require a serial number.
When to use
Use this in an inventory "Spare Parts" view when an operator needs to register a single new part.
Pair it with SparePartSubTypesModal (via the subTypes* props) so the user can manage the
allowed part models without leaving the dialog.
Notes
- Select fields default to
""(empty), which renders the placeholder. Switching part type clears the part model and any serial/MAC validation errors. - MAC validation uses the format
00:1A:2B:3C:4D:5E(case-insensitive,:or-separators).
Example
import { useState } from "react";
import {
AddSparePartModal,
Button,
SPARE_PART_LOCATION_LABELS,
SPARE_PART_LOCATIONS,
SPARE_PART_STATUS_NAMES,
SPARE_PART_STATUSES,
SparePartNames,
SparePartTypes,
} from "@tetherto/mdk-react-devkit";
const PART_TYPES = Object.entries(SparePartTypes).map(([, value]) => ({
value,
label: SparePartNames[value as keyof typeof SparePartNames] ?? value,
}));
const MINER_MODEL_OPTIONS = [
{ value: "antminer-s19", label: "Antminer S19" },
{ value: "antminer-s19j", label: "Antminer S19j" },
{ value: "whatsminer-m30s", label: "Whatsminer M30S" },
];
const MODEL_OPTIONS_MAP: Record<string, Array<{ value: string; label: string }>> = {
[SparePartTypes.CONTROLLER]: [
{ value: "CT-S19", label: "CT-S19" },
{ value: "CT-S19j", label: "CT-S19j" },
],
[SparePartTypes.PSU]: [{ value: "PSU-3000W", label: "PSU-3000W" }],
[SparePartTypes.HASHBOARD]: [
{ value: "HB-S19", label: "HB-S19" },
{ value: "HB-S19j", label: "HB-S19j" },
],
};
const STATUS_OPTIONS = Object.values(SPARE_PART_STATUSES).map((value) => ({
value,
label: SPARE_PART_STATUS_NAMES[value as keyof typeof SPARE_PART_STATUS_NAMES] ?? value,
}));
const LOCATION_OPTIONS = Object.values(SPARE_PART_LOCATIONS).map((value) => ({
value,
label: SPARE_PART_LOCATION_LABELS[value] ?? value,
}));
export const AddSparePartModalExample = () => {
const [isOpen, setIsOpen] = useState(false);
const [activePartType, setActivePartType] = useState(PART_TYPES[0]?.value ?? "");
const modelOptions = MODEL_OPTIONS_MAP[activePartType] ?? [];
const isController = activePartType === SparePartTypes.CONTROLLER;
return (
<div>
<Button variant="primary" onClick={() => setIsOpen(true)}>
Register Part
</Button>
<AddSparePartModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
partTypes={PART_TYPES}
defaultPartTypeId={activePartType}
modelOptions={modelOptions}
minerModelOptions={MINER_MODEL_OPTIONS}
statusOptions={STATUS_OPTIONS}
locationOptions={LOCATION_OPTIONS}
isControllerPartTypeSelected={isController}
onPartTypeChange={(id) => setActivePartType(id)}
onSubmit={async () => {
setIsOpen(false);
}}
/>
</div>
);
};Related API
@tetherto/mdk-react-devkit
Manage spare part subtypes
import { SparePartSubTypesModal } from '@tetherto/mdk-react-devkit'Modal for viewing and adding spare part subtypes (part models) per part type. Presents a part-type tab strip, a table of existing subtypes for the active type, and an inline add form.
When to use
Use this to manage the list of allowed part models for each part type. It can be opened standalone
or embedded from AddSparePartModal's "View Subtypes" button so users can add a missing model
without losing their in-progress form.
Notes
- The active part type is controlled by the parent: change
activePartTypeIdand supply the matchingsubTypesinonPartTypeChange. - The add form validates a non-empty name and clears on successful add.
Example
import { useState } from "react";
import { Button, SparePartSubTypesModal } from "@tetherto/mdk-react-devkit";
const PART_TYPES = [
{ value: "inventory-miner_part-controller", label: "Controller" },
{ value: "inventory-miner_part-psu", label: "PSU" },
{ value: "inventory-miner_part-hashboard", label: "Hashboard" },
];
const INITIAL_SUB_TYPES: Record<string, string[]> = {
"inventory-miner_part-controller": ["CT-S19", "CT-S19j"],
"inventory-miner_part-psu": ["PSU-3000W"],
"inventory-miner_part-hashboard": ["HB-S19", "HB-S19j", "HB-S19XP"],
};
export const SparePartSubTypesModalExample = () => {
const [isOpen, setIsOpen] = useState(false);
const [activeId, setActiveId] = useState(PART_TYPES[0]?.value ?? "");
const [subTypesMap, setSubTypesMap] = useState(INITIAL_SUB_TYPES);
const handleAddSubType = async (name: string): Promise<{ error: string } | void> => {
const existing = subTypesMap[activeId] ?? [];
if (existing.includes(name)) return { error: "Subtype already exists" };
setSubTypesMap((prev) => ({ ...prev, [activeId]: [...existing, name] }));
};
return (
<div>
<Button variant="outline" onClick={() => setIsOpen(true)}>
View Subtypes
</Button>
<SparePartSubTypesModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
partTypes={PART_TYPES}
activePartTypeId={activeId}
onPartTypeChange={setActiveId}
subTypes={subTypesMap[activeId] ?? []}
onAddSubType={handleAddSubType}
/>
</div>
);
};Related API
@tetherto/mdk-react-devkit
Bulk-add spare parts
import { BulkAddSparePartsModal } from '@tetherto/mdk-react-devkit'Modal for bulk-adding spare parts from a CSV file. Provides a CSV template download, file selection
with client-side parsing, and submits the parsed records. CSV parsing and validation helpers
(parseCsvText, validateCSVRecords, mapRawRowToRecord, downloadCsvTemplate, CSVRecord) are
exported alongside the component for use in the consuming submit handler.
When to use
Use this when operators need to register many spare parts at once. Validate the parsed records in
your onSubmit with validateCSVRecords (location/status/model checks, duplicate detection) and
return an { error } to surface a message in the modal.
CSV format
Template headers (downloadable from the modal): part, model, miner model, serial num, mac, status, location, comment. Max MAX_CSV_ITEMS (50) rows per upload. validateCSVRecords enforces valid
part types, miner models, statuses, locations, per-part-type model subtypes, and duplicate
serial/MAC detection (the latter only for controllers).
Example
import { useState } from "react";
import { BulkAddSparePartsModal, Button } from "@tetherto/mdk-react-devkit";
export const BulkAddSparePartsModalExample = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<Button variant="primary" onClick={() => setIsOpen(true)}>
Bulk Add Parts
</Button>
<BulkAddSparePartsModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
onSubmit={async () => {
setIsOpen(false);
}}
/>
</div>
);
};@tetherto/mdk-react-devkit
Move a spare part
import { MoveSparePartModal } from '@tetherto/mdk-react-devkit'Two-step modal for moving a single spare part. Step one shows the part details (SparePartDetails)
alongside its current location and status, and lets the user pick a new location, status, and an
observation. Step two previews the before → after transition with color-coded badges for
confirmation before submitting.
When to use
Use this from a spare-parts inventory row action when an operator moves one part and you want an
explicit confirm step showing exactly what will change. For moving many parts at once, use
BatchMoveSparePartsModal instead.
Data shape
const sparePart: MoveSparePartModalSparePart = {
id: 'sp-001',
code: 'CB-AM-CB5_V10-01', // shown via SparePartDetails
type: 'CB5_V10',
site: 'Site A',
serialNum: 'test-miner',
macAddress: 'aa:bb:cc:dd:ee:ff',
location: 'site.warehouse', // dot-separated location key
status: 'ok_repaired', // spare part status key
}Label & color resolution
- Location/status labels are resolved from the passed option lists (
getOptionLabel). - The current/new badges are colored from
SPARE_PART_LOCATION_BG_COLORS/SPARE_PART_STATUS_BG_COLORS; an unknown location key renders with no background. - The footer shows "No Changes made" until the target location or status differs from the current values, at which point "Save Changes" advances to the confirmation step.
Example
import { useState } from "react";
import {
Button,
MoveSparePartModal,
SPARE_PART_LOCATION_LABELS,
SPARE_PART_LOCATIONS,
SPARE_PART_STATUS_NAMES,
SPARE_PART_STATUSES,
} from "@tetherto/mdk-react-devkit";
const locationOptions = Object.values(SPARE_PART_LOCATIONS)
.filter((value) => value !== SPARE_PART_LOCATIONS.SITE_CONTAINER)
.map((value) => ({ value, label: SPARE_PART_LOCATION_LABELS[value] ?? value }));
const statusOptions = Object.values(SPARE_PART_STATUSES).map((value) => ({
value,
label: SPARE_PART_STATUS_NAMES[value as keyof typeof SPARE_PART_STATUS_NAMES] ?? value,
}));
const mockSparePart = {
id: "sp-001",
code: "HB-A001",
type: "HB-S19",
site: "Site A",
serialNum: "SN123456",
location: SPARE_PART_LOCATIONS.SITE_WAREHOUSE,
status: SPARE_PART_STATUSES.OK_BRAND_NEW,
};
export const MoveSparePartModalExample = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<Button variant="outline" onClick={() => setIsOpen(true)}>
Move Spare Part
</Button>
<MoveSparePartModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
sparePart={mockSparePart}
locationOptions={locationOptions}
statusOptions={statusOptions}
onSubmit={async () => {
setIsOpen(false);
}}
/>
</div>
);
};Related API
@tetherto/mdk-react-devkit
Batch-move spare parts
import { BatchMoveSparePartsModal } from '@tetherto/mdk-react-devkit'Modal for moving multiple spare parts at once. Shows the selected parts in a table (code, current location, current status) and lets the user choose a new location and/or status plus an observation, applied to every selected part in one submit.
When to use
Use this when an operator multi-selects spare-part rows and wants to relocate or re-status them
together. At least one of location or status must be chosen. For a single part with a confirm
step, use MoveSparePartModal.
Data shape
const spareParts: BatchMoveSparePart[] = [
{ id: 'sp-001', code: 'HB-A001', location: 'site.warehouse', status: 'ok_brand_new' },
{ id: 'sp-002', code: 'HB-A002', location: 'workshop.lab', status: 'faulty' },
]Notes
- The form validates that either a location or a status is selected before submit.
- Table location/status cells are resolved to display labels from the passed option lists.
Example
import { useState } from "react";
import {
BatchMoveSparePartsModal,
Button,
SPARE_PART_LOCATION_LABELS,
SPARE_PART_LOCATIONS,
SPARE_PART_STATUS_NAMES,
SPARE_PART_STATUSES,
} from "@tetherto/mdk-react-devkit";
const locationOptions = Object.values(SPARE_PART_LOCATIONS)
.filter((value) => value !== SPARE_PART_LOCATIONS.SITE_CONTAINER)
.map((value) => ({ value, label: SPARE_PART_LOCATION_LABELS[value] ?? value }));
const statusOptions = Object.values(SPARE_PART_STATUSES).map((value) => ({
value,
label: SPARE_PART_STATUS_NAMES[value as keyof typeof SPARE_PART_STATUS_NAMES] ?? value,
}));
const mockSpareParts = [
{ id: "sp-001", code: "HB-A001", location: SPARE_PART_LOCATIONS.SITE_WAREHOUSE, status: SPARE_PART_STATUSES.OK_BRAND_NEW },
{ id: "sp-002", code: "HB-A002", location: SPARE_PART_LOCATIONS.WORKSHOP_LAB, status: SPARE_PART_STATUSES.FAULTY },
{ id: "sp-003", code: "CB-B001", location: SPARE_PART_LOCATIONS.SITE_WAREHOUSE, status: SPARE_PART_STATUSES.OK_RECOVERED },
];
export const BatchMoveSparePartsModalExample = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<Button variant="outline" onClick={() => setIsOpen(true)}>
Batch Move ({mockSpareParts.length} parts)
</Button>
<BatchMoveSparePartsModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
spareParts={mockSpareParts}
locationOptions={locationOptions}
statusOptions={statusOptions}
onSubmit={async () => {
setIsOpen(false);
}}
/>
</div>
);
};Related API
@tetherto/mdk-react-devkit
View a device movement
import { MovementDetailsModal } from '@tetherto/mdk-react-devkit'Modal that shows the details of a single historical device movement: a device summary (code, model, site, container, serial number, MAC) and the origin → destination transition of both location and status, with color-coded badges.
When to use
Use this in an inventory "Historical Movements" view when a user selects a movement row and you want to show the full before/after detail of where a device moved and how its status changed.
Data shape
const movement: MovementData = {
origin: 'site.warehouse', // dot-separated location key
destination: 'workshop.lab',
previousStatus: 'ok_brand_new', // miner status key
newStatus: 'faulty',
device: { // raw device record (miner / spare part)
code: 'M-123',
tags: ['code-M-123'],
type: 'antminer',
info: { site: 'Site A', container: 'C1', serialNum: 'SN-9', macAddress: 'AA:BB' },
},
comments: 'Moved for repair', // optional ReactNode
}Label & color resolution
The component renders data only — all resolution happens in buildMovementDetailsViewModel:
- Location labels come from
getLocationLabel('site.warehouse'→Site Warehouse). - Location/status badge colors come from
MINER_LOCATION_*/MINER_STATUS_*constants, falling back to a neutral border when a key is unknown. - Status labels come from
MINER_STATUS_NAMES('ok_brand_new'→Brand New). - The device
codeis resolved withgetMinerShortCode, andmodelfalls back frominfo.subType→type→-.
Example
/**
* Runnable example for MovementDetailsModal.
*/
import { type MovementData, MovementDetailsModal } from '@tetherto/mdk-react-devkit'
const exampleMovement: MovementData = {
origin: 'site.warehouse',
destination: 'workshop.lab',
previousStatus: 'ok_brand_new',
newStatus: 'faulty',
device: {
code: 'M-1042',
tags: ['code-M-1042'],
type: 'antminer',
info: {
site: 'Site A',
container: 'C-12',
serialNum: 'SN-9981',
macAddress: 'AA:BB:CC:DD:EE:FF',
},
},
comments: 'Moved to workshop lab for diagnostics.',
}
export const MovementDetailsModalExample = () => (
<div className="mdk-example-row">
<MovementDetailsModal
isOpen
movement={exampleMovement}
onClose={() => {
console.warn('modal closed')
}}
/>
</div>
)@tetherto/mdk-react-devkit
Delete a spare part
import { ConfirmDeleteSparePartModal } from '@tetherto/mdk-react-devkit'Confirmation modal for deleting a spare part. Warns that the action is irreversible and surfaces the part code so the user can verify before confirming.
When to use
Use this as the confirm step for a destructive "Delete" row action in a spare-parts inventory view.
Example
import { useState } from "react";
import { Button, ConfirmDeleteSparePartModal } from "@tetherto/mdk-react-devkit";
export const ConfirmDeleteSparePartModalExample = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<Button variant="danger" onClick={() => setIsOpen(true)}>
Delete Spare Part
</Button>
<ConfirmDeleteSparePartModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
onConfirm={async () => setIsOpen(false)}
sparePart={{ id: "sp-001", code: "HB-A001" }}
/>
</div>
);
};@tetherto/mdk-react-devkit
Import the public APIs on this page from @tetherto/mdk-react-devkit.
AddSparePartModal
Modal for registering a new spare part. Presents a part-type tab strip and a form for miner model, part model, serial number, MAC address (controllers only), status, location, tags, and a comment. Validation is controller-aware: controllers require a MAC address, other parts require a serial number. Receives option lists and the submit handler as props.
agent-ready
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
isOpen | boolean | ✓ | - | - |
onClose | VoidFunction | ✓ | - | - |
partTypes | SparePartSubTypesModalPartType[] | ✓ | - | - |
defaultPartTypeId | string | undefined | - | - | |
modelOptions | FormSelectOption[] | ✓ | - | - |
isModelOptionsLoading | boolean | undefined | - | - | |
minerModelOptions | FormSelectOption[] | ✓ | - | - |
statusOptions | FormSelectOption[] | ✓ | - | - |
locationOptions | FormSelectOption[] | ✓ | - | - |
isControllerPartTypeSelected | boolean | undefined | - | - | |
onPartTypeChange | (partTypeId: string) => void | ✓ | - | - |
onSubmit | (values: AddSparePartFormValues) => Promise<void | { fieldErrors?: { field: string; message: string; }[] | undefined; }> | ✓ | - | - |
isLoading | boolean | undefined | - | - | |
subTypesPartTypes | SparePartSubTypesModalPartType[] | undefined | - | - | |
subTypesActivePartTypeId | string | undefined | - | - | |
subTypes | string[] | undefined | - | - | |
onSubTypesPartTypeChange | ((id: string) => void) | undefined | - | - | |
onAddSubType | ((name: string) => Promise<void | { error?: string | undefined; }>) | undefined | - | - | |
isSubTypesLoading | boolean | undefined | - | - |
AlertDialogAction
Primary confirmation button inside an <AlertDialog>; clicking dismisses the dialog and runs the action handler.
agent-ready
AlertDialogCancel
Secondary dismiss button inside an <AlertDialog>; closes the dialog without invoking the destructive action.
agent-ready
AlertDialogContent
Modal content surface for an <AlertDialog> — renders the centered panel above the overlay with focus trap.
agent-ready
AlertDialogDescription
Supporting body text inside an <AlertDialog>; conveys the consequences of the action being confirmed.
agent-ready
AlertDialogFooter
Right-aligned action row inside an <AlertDialog> — hosts the cancel and confirm buttons.
agent-ready
AlertDialogHeader
Top section of an <AlertDialog> that groups the title and description above the action row.
agent-ready
AlertDialogOverlay
Full-viewport scrim rendered behind an <AlertDialog> to block interaction with the page.
agent-ready
AlertDialogTitle
Prominent title text inside an <AlertDialog> summarising the action that requires confirmation.
agent-ready
AssignPoolModal
Modal dialog for bulk-assigning a set of selected miners to a pool config. Displays the miner list, a pool selector with metadata (unit/miner counts, last-updated time), an endpoints preview, and an optional credential template preview. Submission is async; the modal stays open with a loading state until the parent resolves.
agent-ready
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
isOpen | boolean | ✓ | - | - |
onClose | () => void | ✓ | - | - |
onSubmit | (values: { pool: PoolSummary; }) => Promise<void> | ✓ | - | - |
miners | Device[] | ✓ | - | - |
poolConfig | PoolConfigEntry[] | ✓ | - | - |
BatchMoveSparePartsModal
Modal for moving multiple spare parts at once. Shows the selected parts in a table and lets the user choose a new location and/or status plus an observation, applied to every part. Receives the parts, option lists, and submit handler as props.
agent-ready
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
isOpen | boolean | ✓ | - | - |
onClose | VoidFunction | ✓ | - | - |
spareParts | BatchMoveSparePart[] | ✓ | - | - |
locationOptions | FormSelectOption[] | ✓ | - | - |
statusOptions | FormSelectOption[] | ✓ | - | - |
onSubmit | (values: { location: string | null; status: string | null; observation: string | null; }) => void | Promise<void> | ✓ | - | - |
BulkAddSparePartsModal
Modal for bulk-adding spare parts from a CSV file. Provides a CSV template download, file selection with client-side parsing, and submits the parsed records. Receives the submit handler as a prop; CSV parsing and validation helpers are exported alongside the component.
agent-ready
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
isOpen | boolean | ✓ | - | - |
onClose | VoidFunction | ✓ | - | - |
onSubmit | (records: CSVRecord[]) => Promise<void | { error?: string | undefined; }> | ✓ | - | - |
isLoading | boolean | undefined | - | - |
ConfirmDeleteSparePartModal
Confirmation modal for deleting a spare part. Warns that the action is irreversible and surfaces the part code so the user can verify before confirming. Receives the part and confirm handler as props.
agent-ready
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
isOpen | boolean | undefined | - | - | |
onClose | VoidFunction | undefined | - | - | |
onConfirm | ((sparePart: ConfirmDeleteSparePartModalSparePart) => void | Promise<void>) | undefined | - | - | |
sparePart | ConfirmDeleteSparePartModalSparePart | undefined | - | - | |
isLoading | boolean | undefined | - | - |
DialogContent
Centered modal surface for a <Dialog> — renders above the overlay with focus trap and Escape-to-close.
agent-ready
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
title | string | undefined | - | - | |
description | string | undefined | - | - | |
closeOnClickOutside | boolean | undefined | - | - | |
closeOnEscape | boolean | undefined | - | - | |
bare | boolean | undefined | - | - | |
closable | boolean | undefined | - | - | |
onClose | VoidFunction | undefined | - | - |
DialogDescription
Supporting body copy inside a <Dialog> rendered below the title.
agent-ready
DialogFooter
Action row at the bottom of a <Dialog> — typically primary/secondary buttons.
agent-ready
DialogHeader
Top region of a <Dialog> that groups the title and description.
agent-ready
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
bare | boolean | undefined | - | - | |
closable | boolean | undefined | - | - | |
onClose | VoidFunction | undefined | - | - |
DialogOverlay
Full-viewport scrim rendered behind an open <Dialog> to block background interaction.
agent-ready
DialogTitle
Prominent title text at the top of a <Dialog> summarising the modal's purpose.
agent-ready
MovementDetailsModal
Modal showing the details of a historical device movement — the device summary plus the origin → destination transition of location and status. Receives the selected movement as a prop — no internal data fetching.
agent-ready
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
isOpen | boolean | - | - | |
onClose | () => void | - | - | |
movement | MovementData | - | - |
MoveSparePartModal
Two-step modal for moving a single spare part. Step one shows the part details with its current location and status and lets the user pick a new location, status, and observation; step two previews the before → after transition for confirmation. Receives the part, option lists, and submit handler as props.
agent-ready
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
isOpen | boolean | undefined | - | - | |
onClose | VoidFunction | undefined | - | - | |
sparePart | MoveSparePartModalSparePart | undefined | - | - | |
requestedValues | { location?: string | undefined; status?: string | undefined; } | undefined | - | - | |
locationOptions | FormSelectOption[] | ✓ | - | - |
statusOptions | FormSelectOption[] | ✓ | - | - |
onSubmit | (values: { location: string; status: string; observation: string; }, sparePart: MoveSparePartModalSparePart) => void | Promise<void> | ✓ | - | - |
SparePartSubTypesModal
Modal for viewing and adding spare part subtypes per part type. Presents a part-type tab strip, a table of existing subtypes for the active type, and an inline add form. Receives the active part type, subtype list, and add handler as props.
agent-ready
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
isOpen | boolean | ✓ | - | - |
onClose | VoidFunction | ✓ | - | - |
partTypes | SparePartSubTypesModalPartType[] | ✓ | - | - |
activePartTypeId | string | ✓ | - | - |
onPartTypeChange | (id: string) => void | ✓ | - | - |
subTypes | string[] | ✓ | - | - |
onAddSubType | (name: string) => Promise<void | { error?: string | undefined; }> | ✓ | - | - |
isLoading | boolean | undefined | - | - |