feat(ui): split submit tab into actions and review
This commit is contained in:
@@ -9,6 +9,8 @@ export const UI_DIMENSIONS = {
|
||||
previewLeftMinWidth: 22,
|
||||
previewLeftMaxWidth: 34,
|
||||
previewLeftRatio: 0.34,
|
||||
submitWideMinWidth: 64,
|
||||
submitActionsWidth: 24,
|
||||
} as const;
|
||||
|
||||
export const UI_TEXT = {
|
||||
|
||||
+58
-4
@@ -2,8 +2,8 @@ import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
||||
import { SUBMIT_CHOICES } from "../constants.ts";
|
||||
import { serializeAnswer } from "../state/answers.ts";
|
||||
import type { AskResult, AskState, AskStateAnswer } from "../types.ts";
|
||||
import { UI_TEXT } from "./constants.ts";
|
||||
import { pushWrappedText } from "./render-helpers.ts";
|
||||
import { UI_DIMENSIONS, UI_TEXT } from "./constants.ts";
|
||||
import { mergeColumns, pushWrappedText } from "./render-helpers.ts";
|
||||
import type { Theme } from "./render-types.ts";
|
||||
|
||||
type ReviewAnswer = AskResult["answers"][string] & {
|
||||
@@ -19,10 +19,51 @@ export function renderSubmitScreen(
|
||||
theme: Theme,
|
||||
width: number
|
||||
) {
|
||||
const showAllNotes = state.activeSubmitActionIndex === 1;
|
||||
|
||||
if (shouldUseWideSubmitLayout(width)) {
|
||||
const leftWidth = UI_DIMENSIONS.submitActionsWidth;
|
||||
const rightWidth = Math.max(1, width - leftWidth - 2);
|
||||
const reviewLines = renderSubmitReviewLines(
|
||||
state,
|
||||
theme,
|
||||
rightWidth,
|
||||
showAllNotes
|
||||
);
|
||||
const actionLines = renderSubmitActionLines(state, theme, leftWidth);
|
||||
for (const line of mergeColumns(
|
||||
actionLines,
|
||||
reviewLines,
|
||||
leftWidth,
|
||||
width
|
||||
)) {
|
||||
lines.push(line);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const reviewLines = renderSubmitReviewLines(
|
||||
state,
|
||||
theme,
|
||||
width,
|
||||
showAllNotes
|
||||
);
|
||||
const actionLines = renderSubmitActionLines(state, theme, width);
|
||||
lines.push(...reviewLines);
|
||||
lines.push("");
|
||||
lines.push(...actionLines);
|
||||
}
|
||||
|
||||
function renderSubmitReviewLines(
|
||||
state: AskState,
|
||||
theme: Theme,
|
||||
width: number,
|
||||
showAllNotes: boolean
|
||||
): string[] {
|
||||
const lines: string[] = [];
|
||||
pushWrappedText(lines, UI_TEXT.reviewTitle, width, theme, "accent", " ", " ");
|
||||
lines.push("");
|
||||
|
||||
const showAllNotes = state.activeSubmitActionIndex === 1;
|
||||
for (let index = 0; index < state.questions.length; index++) {
|
||||
const question = state.questions[index];
|
||||
renderSubmittedQuestion(
|
||||
@@ -37,8 +78,21 @@ export function renderSubmitScreen(
|
||||
}
|
||||
}
|
||||
|
||||
lines.push("");
|
||||
return lines;
|
||||
}
|
||||
|
||||
function renderSubmitActionLines(
|
||||
state: AskState,
|
||||
theme: Theme,
|
||||
width: number
|
||||
): string[] {
|
||||
const lines: string[] = [];
|
||||
renderSubmitActions(lines, state, theme, width);
|
||||
return lines;
|
||||
}
|
||||
|
||||
function shouldUseWideSubmitLayout(width: number): boolean {
|
||||
return width >= UI_DIMENSIONS.submitWideMinWidth;
|
||||
}
|
||||
|
||||
function renderSubmittedQuestion(
|
||||
|
||||
@@ -45,6 +45,61 @@ test("submit screen uses shorter action labels without extra prompt text", () =>
|
||||
assert(!lines.some((line) => line.includes("1. Submit answers")));
|
||||
});
|
||||
|
||||
test("submit screen uses side-by-side layout on wide screens", () => {
|
||||
const state = createInitialState({
|
||||
questions: [
|
||||
{
|
||||
id: "q1",
|
||||
label: "Color",
|
||||
prompt: "Pick one color.",
|
||||
options: [{ value: "blue", label: "Blue" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const lines: string[] = [];
|
||||
renderSubmitScreen(lines, state, mockTheme(), 120);
|
||||
|
||||
assert(
|
||||
lines.some(
|
||||
(line) => line.includes("1. Submit") && line.includes("Review answers")
|
||||
)
|
||||
);
|
||||
assert(
|
||||
lines.some((line) => line.includes("3. Cancel") && line.includes("Color"))
|
||||
);
|
||||
});
|
||||
|
||||
test("submit screen stacks review above actions on narrow screens", () => {
|
||||
const state = createInitialState({
|
||||
questions: [
|
||||
{
|
||||
id: "q1",
|
||||
label: "Color",
|
||||
prompt: "Pick one color.",
|
||||
options: [{ value: "blue", label: "Blue" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const lines: string[] = [];
|
||||
renderSubmitScreen(lines, state, mockTheme(), 50);
|
||||
|
||||
const reviewIndex = lines.findIndex((line) =>
|
||||
line.includes("Review answers")
|
||||
);
|
||||
const actionIndex = lines.findIndex((line) => line.includes("1. Submit"));
|
||||
|
||||
assert.notEqual(reviewIndex, -1);
|
||||
assert.notEqual(actionIndex, -1);
|
||||
assert(reviewIndex < actionIndex);
|
||||
assert(
|
||||
!lines.some(
|
||||
(line) => line.includes("1. Submit") && line.includes("Review answers")
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test("submit screen shows notes only for answered questions in submit mode", () => {
|
||||
let state = createInitialState({
|
||||
questions: [
|
||||
@@ -72,7 +127,7 @@ test("submit screen shows notes only for answered questions in submit mode", ()
|
||||
state = saveNote(state, "Second note");
|
||||
|
||||
const lines: string[] = [];
|
||||
renderSubmitScreen(lines, state, mockTheme(), 80);
|
||||
renderSubmitScreen(lines, state, mockTheme(), 140);
|
||||
|
||||
const firstQuestionIndex = lines.findIndex((line) =>
|
||||
line.includes("<text>Single</text>")
|
||||
@@ -99,7 +154,7 @@ test("submit screen shows notes only for answered questions in submit mode", ()
|
||||
assert(optionNoteIndex > firstAnswerIndex);
|
||||
assert(lines[firstQuestionNoteIndex]?.startsWith(" "));
|
||||
assert(lines[optionNoteIndex]?.startsWith(" "));
|
||||
assert.equal(lines[secondQuestionIndex - 1], "");
|
||||
assert.equal(lines[secondQuestionIndex - 1]?.trim(), "");
|
||||
assert(
|
||||
lines.some((line) => line.includes("<syntaxString>Note:</syntaxString>"))
|
||||
);
|
||||
@@ -178,7 +233,7 @@ test("submit screen renders multi-select option notes under their related answer
|
||||
state = saveNote(state, "Second note");
|
||||
|
||||
const lines: string[] = [];
|
||||
renderSubmitScreen(lines, state, mockTheme(), 80);
|
||||
renderSubmitScreen(lines, state, mockTheme(), 140);
|
||||
|
||||
const firstAnswerIndex = lines.findIndex((line) =>
|
||||
line.includes("→ Follow-up action")
|
||||
|
||||
Reference in New Issue
Block a user