feat: add frame reordering and improve file upload accessibility

- Add Up/Down reorder controls to Frame component
- Add handleReOrderFrames hook using splice swap
- Improve FileUpload: keyboard support, aria attributes, fix accept=image/* typo
- Extract component-level CSS for Frame, Step, FileUpload
- Update focus-visible styles; scope border style to .file-upload
This commit is contained in:
johannes-hernehult 2026-06-15 19:49:00 +02:00
parent b95d687904
commit f214b0ac4d
10 changed files with 71 additions and 7 deletions

View file

@ -0,0 +1,19 @@
.file-upload {
display: grid;
place-items: center;
cursor: pointer;
border-style: dashed;
padding: 2rem;
font-weight: bold;
&:hover,
&:focus-visible {
border-color: #ff770080;
color: #ff7700;
background-color: #ff770010;
}
}
.file-upload input {
display: none;
}

View file

@ -1,17 +1,31 @@
import "./FileUpload.css";
export default function FileUpload({
onChange,
}: {
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}) {
return (
<label>
<span>File</span>
<label
className="file-upload"
role="button"
tabIndex={0}
aria-label="Upload images"
aria-describedby="upload-hint"
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
e.currentTarget.querySelector("input")?.click();
}
}}
>
<span aria-hidden="true">Click to upload images</span>
<input
type="file"
name="file"
accept="images/*"
accept="image/*"
multiple
onChange={(e) => onChange(e)}
onChange={onChange}
/>
</label>
);

View file

View file

@ -1,23 +1,35 @@
import "./Frame.css";
import type { FrameType, StepType } from "../../../lib/schemas";
import Step from "./Step";
export default function Frame({
frame,
frameIndex,
onRemove,
addStep,
removeStep,
updateStep,
updateTitle,
reOrder,
}: {
frame: FrameType;
frameIndex: number;
onRemove: (frameId: string) => void;
addStep: (frameId: string) => void;
removeStep: (stepId: string) => void;
updateStep: (stepId: string, text: string) => void;
updateTitle: (frameId: string, title: string) => void;
reOrder: (startIndex: number, endIndex: number) => void;
}) {
return (
<div>
<button type="button" onClick={() => reOrder(frameIndex, frameIndex - 1)}>
Up
</button>
<button type="button" onClick={() => reOrder(frameIndex, frameIndex + 1)}>
Down
</button>
<button type="button" onClick={() => onRemove(frame.id)}>
Remove Frame
</button>

View file

View file

@ -1,3 +1,4 @@
import "./Step.css";
import type { StepType } from "../../../lib/schemas";
export default function Step({

View file

@ -54,6 +54,15 @@ export default function useNewGuide() {
setFrames((prev) => prev.filter((frame) => frame.id !== frameId));
};
const handleReOrderFrames = (startIndex: number, endIndex: number) => {
setFrames((prev) => {
const newOrder = [...prev];
const [movedFrame] = newOrder.splice(startIndex, 1);
newOrder.splice(endIndex, 0, movedFrame);
return newOrder;
});
};
const handleAddSteps = (frameId: string) => {
setFrames((prev) =>
prev.map((frame) =>
@ -123,6 +132,7 @@ export default function useNewGuide() {
handleUpdateDescription,
handleAddFrames,
handleUpdateFrameTitle,
handleReOrderFrames,
handleAddSteps,
handleRemoveFrame,
handleUpdateStep,

View file

@ -12,6 +12,7 @@ export default function NewGuide() {
handleUpdateFrameTitle,
handleAddSteps,
handleRemoveFrame,
handleReOrderFrames,
handleRemoveStep,
handleUpdateStep,
handleSaveGuide,
@ -42,16 +43,18 @@ export default function NewGuide() {
frames.map((frame, index) => (
<Frame
key={index}
frameIndex={index}
frame={frame}
onRemove={handleRemoveFrame}
addStep={handleAddSteps}
updateStep={handleUpdateStep}
removeStep={handleRemoveStep}
updateTitle={handleUpdateFrameTitle}
reOrder={handleReOrderFrames}
/>
))}
{frames.length === 0 && <p>No frames added yet.</p>}
{frames.length === 0 && <p>No images added yet.</p>}
{frames.length > 0 && <FileUpload onChange={handleAddFrames} />}

View file

@ -70,7 +70,8 @@ textarea {
}
input,
textarea {
textarea,
.file-upload {
border: 3px solid var(--color-border);
border-radius: 4px;
padding: 0.25rem;
@ -80,3 +81,7 @@ textarea {
resize: none;
height: 5rem;
}
:focus-visible {
border-color: #ff7700 !important;
}

View file

@ -136,6 +136,6 @@
}
:focus-visible {
outline-offset: 0.2rem;
outline: none;
}
}