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:
parent
b95d687904
commit
f214b0ac4d
10 changed files with 71 additions and 7 deletions
19
src/features/guides/components/FileUpload.css
Normal file
19
src/features/guides/components/FileUpload.css
Normal 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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
0
src/features/guides/components/Frame.css
Normal file
0
src/features/guides/components/Frame.css
Normal 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>
|
||||
|
|
|
|||
0
src/features/guides/components/Step.css
Normal file
0
src/features/guides/components/Step.css
Normal file
|
|
@ -1,3 +1,4 @@
|
|||
import "./Step.css";
|
||||
import type { StepType } from "../../../lib/schemas";
|
||||
|
||||
export default function Step({
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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} />}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,6 +136,6 @@
|
|||
}
|
||||
|
||||
:focus-visible {
|
||||
outline-offset: 0.2rem;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue