feat(ux): Sticky Save/Cancel buttons on forms (#38)

- Add sticky positioning to form action buttons (always visible at bottom)
- Add Ctrl+S / Cmd+S keyboard shortcut to save forms
- Create reusable useFormShortcuts hook
- Apply to all create forms: spool, filament, vendor

Fixes: https://github.com/Donkie/Spoolman/issues/274

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-16 00:09:06 -06:00
parent 88747307e5
commit 94612a6dd3
5 changed files with 62 additions and 3 deletions

View File

@@ -5,7 +5,7 @@ import { Alert, Button, DatePicker, Divider, Form, Input, InputNumber, Radio, Se
import TextArea from "antd/es/input/TextArea";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields";
import { FilamentCreateModal } from "../../components/filamentCreateModal";
import { useSpoolmanLocations } from "../../components/otherModels";
@@ -14,6 +14,7 @@ import "../../utils/overrides.css";
import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { EntityType, useGetFields } from "../../utils/queryFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
import { useFormShortcuts } from "../../utils/useFormShortcuts";
import { createFilamentFromExternal } from "../filaments/functions";
import { IFilament } from "../filaments/model";
import { useGetFilamentSelectOptions } from "./functions";
@@ -140,6 +141,12 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
redirect(redirectTo);
};
// Keyboard shortcut: Ctrl+S / Cmd+S to save
const handleSaveShortcut = useCallback(() => {
handleSubmit("list");
}, []);
useFormShortcuts(handleSaveShortcut);
// Use useEffect to update the form's initialValues when the extra fields are loaded
// This is necessary because the form is rendered before the extra fields are loaded
useEffect(() => {