The settings panel element.
// Standalone settings view (React v19 useActionState-based).
<SettingsPanel />
export default function SettingsPanel() {
// React v19's use() hook simplifies context access
const appContext = useAppContext();
if (!appContext) {
return <div>Loading settings...</div>;
}
// React v19's useActionState for form management
const [formState, updateSetting, isPending] = useActionState(
(currentSettings: UserSettings, action: SettingAction): UserSettings => {
console.debug("Settings action", { action, currentSettings });
let newSettings: UserSettings;
switch (action.type) {
case ACTION_TYPE.SWITCH_CHANGE:
newSettings = {
...currentSettings,
[action.name]: action.checked,
};
break;
case ACTION_TYPE.INPUT_CHANGE:
newSettings = {
...currentSettings,
[action.name]: action.value,
};
break;
case ACTION_TYPE.BUTTON_CLICK:
newSettings = {
...currentSettings,
[action.name]: action.value,
};
break;
case ACTION_TYPE.RESTORE_DEFAULTS:
// Restore to default settings
newSettings = {
...(defaultSettings as UserSettings),
...currentSettings,
};
break;
default:
return currentSettings;
}
// Handle async operations with startTransition
startTransition(() => {
try {
// Update the app context - this will handle Chrome storage automatically
appContext.setUserSettings(newSettings);
} catch (error) {
console.error("Failed to update settings:", error);
}
});
return newSettings;
},
appContext.userSettings,
);
// Unified event handlers using the action dispatcher
const handleSwitchChange = (event: ChangeEvent<HTMLInputElement>) => {
updateSetting({
type: ACTION_TYPE.SWITCH_CHANGE,
name: event.target.name,
checked: event.target.checked,
});
};
const handleInputChange = (
event: SelectChangeEvent | ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
updateSetting({
type: ACTION_TYPE.INPUT_CHANGE,
name: event.target.name,
value: event.target.value,
});
};
const handleButtonClick = (event: MouseEvent<HTMLDivElement>) => {
if (!isButtonElement(event.target)) return;
const { name, value } = event.target;
if (name && value) {
updateSetting({ type: ACTION_TYPE.BUTTON_CLICK, name, value });
}
};
const handleRestoreDefaults = () => {
updateSetting({ type: ACTION_TYPE.RESTORE_DEFAULTS });
};
// Use formState for current values, falling back to appContext
const currentSettings = formState || appContext.userSettings;
return (
<FormGroup>
{/* Loading indicator when settings are updating */}
{isPending && (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: 2,
backgroundColor: "#1976d2",
animation: "pulse 1s infinite",
zIndex: 1000,
}}
/>
)}
<List
sx={{
width: "100%",
bgcolor: "background.paper",
color: "text.primary",
opacity: isPending ? 0.7 : 1,
transition: "opacity 0.2s ease",
}}
component="nav"
aria-labelledby="nested-list-subheader"
subheader={
<ListSubheader component="label" id="nested-list-subheader">
Behavior
</ListSubheader>
}
>
<ListItem sx={displayHelperOnHover}>
<ListItemText primary="Cache Search Results" />
{/* <FormHelperText>Improves performance</FormHelperText> */}
<FormControlLabel
control={
<Switch
checked={currentSettings.caching}
onChange={handleSwitchChange}
name="caching"
disabled={isPending}
/>
}
labelPlacement="start"
label=""
/>
</ListItem>
<ListItem sx={displayHelperOnHover}>
<ListItemText primary="Currency" />
{/*<FormHelperText>Convert all currency to this</FormHelperText>*/}
<FormControl>
<InputLabel id="currency-select-label">Currency</InputLabel>
<Select
labelId="currency-select-label"
value={currentSettings.currency}
onChange={handleInputChange}
name="currency"
label="currency"
size="small"
sx={{ ...inputStyle }}
disabled={isPending}
>
{Object.entries(currencies).map(([currencyId, { symbol }]) => (
<MenuItem key={currencyId} value={currencyId}>
{currencyId.toUpperCase()} ({symbol})
</MenuItem>
))}
</Select>
</FormControl>
</ListItem>
<ListItem sx={displayHelperOnHover}>
<ListItemText primary="Location" />
{/*<FormHelperText>Your country</FormHelperText>*/}
<FormControl>
<InputLabel id="location-select-label">Location</InputLabel>
<Select
labelId="location-select-label"
value={currentSettings.location}
onChange={handleInputChange}
name="location"
label="location"
size="small"
sx={{ ...inputStyle }}
disabled={isPending}
>
<MenuItem value="">
<i>None</i>
</MenuItem>
{Object.entries(locations).map(([locationId, { name }]) => (
<MenuItem key={locationId} value={locationId}>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</ListItem>
<ListItem sx={displayHelperOnHover}>
<ListItemText primary="Currency Rate" />
{/*<FormHelperText>Just an input example</FormHelperText>*/}
<FormControl>
<TextField
value={currentSettings.currencyRate}
label="Currency Rate"
name="currencyRate"
onChange={handleInputChange}
variant="filled"
size="small"
sx={{ ...inputStyle }}
disabled={isPending}
/>
</FormControl>
</ListItem>
<Divider variant="middle" component="li" />
<ListSubheader component="label" id="nested-list-subheader">
Display
</ListSubheader>
<ListItem sx={displayHelperOnHover}>
<ListItemText primary="Font Size" />
{/*<FormHelperText>Popup size</FormHelperText>*/}
<FormControl>
<ButtonGroup
variant="contained"
aria-label="Font size"
onClick={handleButtonClick}
disabled={isPending}
>
<Button
name="fontSize"
value="small"
size="small"
aria-label="Small"
title="Small"
variant={currentSettings.fontSize === "small" ? "contained" : "text"}
disabled={isPending}
>
<TextDecreaseIcon fontSize="small" sx={{ pointerEvents: "none" }} />
</Button>
<Button
name="fontSize"
value="medium"
size="small"
aria-label="Medium"
title="Medium"
variant={currentSettings.fontSize === "medium" ? "contained" : "text"}
disabled={isPending}
>
<TextFormatIcon fontSize="small" sx={{ pointerEvents: "none" }} />
</Button>
<Button
name="fontSize"
value="large"
size="small"
aria-label="Large"
title="Large"
variant={currentSettings.fontSize === "large" ? "contained" : "text"}
disabled={isPending}
>
<TextIncreaseIcon fontSize="small" sx={{ pointerEvents: "none" }} />
</Button>
</ButtonGroup>
</FormControl>
</ListItem>
<ListItem sx={displayHelperOnHover}>
<ListItemText primary="Show Helpful Tips" />
{/*<FormHelperText id="some-setting-helper-text">Show help in tooltips</FormHelperText>*/}
<Switch
checked={currentSettings.showHelp}
onChange={handleSwitchChange}
name="showHelp"
disabled={isPending}
/>
</ListItem>
<Divider component="li" />
<ListItem>
<Stack
spacing={2}
direction="row"
sx={{ display: "block", marginLeft: "auto", marginRight: "auto" }}
>
<Button variant="outlined" onClick={handleRestoreDefaults} disabled={isPending}>
{isPending ? "Restoring..." : "Restore Defaults"}
</Button>
</Stack>
</ListItem>
</List>
</FormGroup>
);
}
Enhanced SettingsPanel component using React v19 features for improved form management.
Key improvements over original SettingsPanel.tsx:
COMPARISON WITH ORIGINAL:
Original (multiple separate handlers):
React v19 Version:
BENEFITS: