1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
4import React from 'react';
12} from '@mui/material';
13import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
14import { open } from '@tauri-apps/plugin-dialog';
16export const Section = ({
21 children: React.ReactNode;
29 borderColor: 'divider',
38 expandIcon={<ExpandMoreIcon />}
40 bgcolor: 'action.hover',
43 '& .MuiAccordionSummary-content': {
46 '& .MuiAccordionSummary-expandIconWrapper': {
47 color: 'text.secondary',
51 <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
59 flexDirection: 'column',
62 borderColor: 'divider',
70export const NumberField = ({
77 onChange: (val: number | null) => void;
79 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
80 const strValue = e.target.value;
82 // If the field is cleared, represent it as null.
83 if (strValue === '') {
88 const numValue = parseFloat(strValue);
90 // Only update the parent state if the parsed value is a valid number.
91 // This prevents NaN from being stored and allows the user to type
92 // intermediate invalid states (e.g., "1.2.3", "-") without
93 // corrupting the application state.
94 if (!isNaN(numValue)) {
107 onChange={handleChange}
108 // Allow floating point numbers in the number input's spinners.
109 inputProps={{ step: 'any' }}
114export const FileInput = ({
122 onChange: (val: string) => void;
123 filters: { name: string; extensions: string[] }[];
125 const handleOpenFile = async () => {
127 const selected = await open({
131 if (typeof selected === 'string') {
135 console.error('Error opening file dialog:', err);
141 <Typography variant="caption" color="text.secondary">
148 value={value?.split(/[/\\]/).pop() ?? 'No file selected.'}
149 onClick={handleOpenFile}
150 sx={{ cursor: 'pointer' }}
154 style: { cursor: 'pointer' },