mortezaei 1 week ago
parent
commit
bab927154a
  1. 4
      src/app/candidate-contact/page.tsx
  2. 2
      src/app/finding-match/page.tsx
  3. 35
      src/app/globals.css
  4. 2
      src/app/intro/page.tsx
  5. 1
      src/app/questions-list/[slug]/question-detail-client.test.tsx
  6. 8
      src/app/questions-list/page.tsx
  7. 2
      src/app/request-accepted/page.tsx
  8. 2
      src/app/request-sent/page.tsx
  9. 4
      src/components/Componentes/page-loading-skeleton.tsx
  10. 9
      src/components/Componentes/question-snap-list.tsx
  11. 8
      src/components/Componentes/required-steps-card.test.tsx
  12. 2
      src/components/Componentes/slider-page.tsx
  13. 37
      src/components/Componentes/use-sheet-scroll-lock.ts

4
src/app/candidate-contact/page.tsx

@ -116,7 +116,7 @@ export default function CandidateContactPage() {
/>
) : null}
<main className="-mx-[17px] flex min-h-screen flex-col px-[23px] pt-[max(16px,calc(var(--safe-top)+8px))] pb-[calc(20px+var(--safe-bottom))]">
<main className="-mx-[17px] flex min-h-screen flex-col px-[23px] pt-[max(12px,calc(var(--safe-top)+4px))] pb-[calc(20px+var(--safe-bottom))]">
<PageHeader
className="-mx-[6px]"
leftButton={
@ -332,7 +332,7 @@ export default function CandidateContactPage() {
/>
) : null}
<main className="-mx-[17px] flex min-h-screen flex-col px-[17px] pt-[max(16px,calc(var(--safe-top)+8px))] pb-36">
<main className="-mx-[17px] flex min-h-screen flex-col px-[17px] pt-[max(12px,calc(var(--safe-top)+4px))] pb-36">
<PageHeader />
<section className="flex flex-1 items-center justify-center">

2
src/app/finding-match/page.tsx

@ -78,7 +78,7 @@ export default function FindingMatchPage() {
<main
style={{ paddingBottom: "calc(100px + var(--safe-bottom))" }}
className="-mx-[17px] flex min-h-screen flex-col px-[23px] pt-[max(16px,calc(var(--safe-top)+8px))] text-center"
className="-mx-[17px] flex min-h-screen flex-col px-[23px] pt-[max(12px,calc(var(--safe-top)+4px))] text-center"
>
<PageHeader
className="-mx-[6px]"

35
src/app/globals.css

@ -249,7 +249,8 @@ body.dropdown-open .app-shell {
transform 300ms ease-in-out,
opacity 220ms ease-out,
max-height 300ms ease-in-out,
padding 300ms ease-in-out;
padding 300ms ease-in-out,
margin 300ms ease-in-out;
}
body.question-sheet-open .app-shell .question-detail-header {
@ -273,24 +274,48 @@ body.question-sheet-open .app-shell .question-snap-list {
padding-bottom: 0;
}
body.question-sheet-open .app-shell .question-snap-item[aria-current="step"] {
body.question-keyboard-open .app-shell .question-snap-list {
padding-top: 0;
padding-bottom: 0;
}
body.question-sheet-open .app-shell .question-snap-item[aria-current="step"],
body.question-keyboard-open .app-shell .question-snap-item[aria-current="step"] {
top: 0;
bottom: 0;
}
body.question-sheet-open
.app-shell
.question-snap-item[aria-current="step"]
.question-snap-content,
body.question-keyboard-open
.app-shell
.question-snap-item[aria-current="step"]
.question-snap-content {
margin-top: 0;
margin-bottom: 0;
transform: translateY(calc(var(--question-keyboard-shift, 0px) * -1));
}
/* Keep the question centered normally; when Flutter reports the keyboard,
place it in the viewport area that remains directly below progress. */
body.question-keyboard-open
.app-shell
.question-snap-item[aria-current="step"]
.question-snap-content {
transform: translateY(-48px);
margin-top: 8px;
margin-bottom: auto;
transform: none;
}
body.question-sheet-open
.app-shell
.question-snap-item[aria-current="step"]
.question-snap-spacer,
body.question-keyboard-open
.app-shell
.question-snap-item[aria-current="step"]
.question-snap-spacer {
display: none;
}
.page-background-none,

2
src/app/intro/page.tsx

@ -70,7 +70,7 @@ export default function Intro() {
};
return (
<div className="pt-[max(16px,calc(var(--safe-top)+8px))]">
<div className="pt-[max(12px,calc(var(--safe-top)+4px))]">
{isReportSheetOpen && (
<ReportActionsSheet onClose={() => setIsReportSheetOpen(false)} />
)}

1
src/app/questions-list/[slug]/question-detail-client.test.tsx

@ -422,6 +422,7 @@ describe("QuestionDetailClient Validation", () => {
);
expect(screen.getByRole("textbox")).toBeDefined();
expect(screen.getByRole("textbox")).not.toHaveFocus();
expect(screen.queryByRole("status")).toBeNull();
});

8
src/app/questions-list/page.tsx

@ -473,7 +473,9 @@ export default function QuestionsListPage() {
<div className="pointer-events-none absolute -top-16 left-1/2 h-[220px] w-[220px] -translate-x-1/2 rounded-full bg-white/70 blur-3xl" />
<header
style={{ paddingTop: "calc(var(--safe-top) + 16px)" }}
style={{
paddingTop: "max(12px, calc(var(--safe-top) + 4px))",
}}
className="sticky top-0 z-20 -mx-[17px] flex items-center justify-between bg-[#F5F5F5]/90 px-[17px] pb-3 backdrop-blur-md"
>
<NavigationButton
@ -619,7 +621,9 @@ export default function QuestionsListPage() {
<div className="pointer-events-none absolute -top-16 left-1/2 h-[220px] w-[220px] -translate-x-1/2 rounded-full bg-white/70 blur-3xl" />
<header
style={{ paddingTop: "calc(var(--safe-top) + 16px)" }}
style={{
paddingTop: "max(12px, calc(var(--safe-top) + 4px))",
}}
className="sticky top-0 z-20 -mx-[17px] flex items-center justify-between bg-[#F5F5F5]/90 px-[17px] pb-3 backdrop-blur-md"
>
<NavigationButton

2
src/app/request-accepted/page.tsx

@ -491,7 +491,7 @@ export default function RequestAcceptedPage() {
/>
) : null}
<main className="-mx-[17px] flex min-h-screen flex-col px-[23px] pt-[max(16px,calc(var(--safe-top)+8px))] pb-[calc(20px+var(--safe-bottom))] text-center">
<main className="-mx-[17px] flex min-h-screen flex-col px-[23px] pt-[max(12px,calc(var(--safe-top)+4px))] pb-[calc(20px+var(--safe-bottom))] text-center">
<PageHeader className="-mx-[6px]" />
<div className="flex flex-1 flex-col justify-between gap-6 pt-4">

2
src/app/request-sent/page.tsx

@ -69,7 +69,7 @@ export default function RequestSentPage() {
<main
style={{ paddingBottom: "calc(40px + var(--safe-bottom))" }}
className="-mx-[17px] flex min-h-screen flex-col px-[23px] pt-[max(16px,calc(var(--safe-top)+8px))] text-center"
className="-mx-[17px] flex min-h-screen flex-col px-[23px] pt-[max(12px,calc(var(--safe-top)+4px))] text-center"
>
<PageHeader className="-mx-[6px]" />

4
src/components/Componentes/page-loading-skeleton.tsx

@ -17,7 +17,7 @@ export function PageLoadingSkeleton({
<PageBackground disabled />
<main
aria-busy="true"
className="-mx-[17px] flex min-h-svh flex-col px-[17px] pt-[max(16px,calc(var(--safe-top)+8px))] pb-[calc(24px+var(--safe-bottom))] bg-[#F7F1F0]"
className="-mx-[17px] flex min-h-svh flex-col px-[17px] pt-[max(12px,calc(var(--safe-top)+4px))] pb-[calc(24px+var(--safe-bottom))] bg-[#F7F1F0]"
>
{/* Header */}
<header className="flex h-11 items-center justify-between shrink-0 mb-4">
@ -68,7 +68,7 @@ export function PageLoadingSkeleton({
<PageBackground disabled />
<main
aria-busy="true"
className="-mx-[17px] flex min-h-svh flex-col px-[23px] pt-[max(16px,calc(var(--safe-top)+8px))] pb-[calc(24px+var(--safe-bottom))]"
className="-mx-[17px] flex min-h-svh flex-col px-[23px] pt-[max(12px,calc(var(--safe-top)+4px))] pb-[calc(24px+var(--safe-bottom))]"
>
<header className="flex h-11 items-center justify-between">
<LoadingSkeleton className="size-10 rounded-full" />

9
src/components/Componentes/question-snap-list.tsx

@ -219,6 +219,10 @@ export function QuestionSnapList({
}, [activeIndex, onQuestionTransition]);
useEffect(() => {
if (activeIndex === 0) {
return;
}
const activeQuestion = questionRefs.current[activeIndex];
if (!activeQuestion) {
@ -654,7 +658,10 @@ export function QuestionSnapList({
{question}
</div>
{isActive && (
<div className="h-[20%] shrink min-h-0" aria-hidden="true" />
<div
className="question-snap-spacer h-[20%] shrink min-h-0"
aria-hidden="true"
/>
)}
</div>
);

8
src/components/Componentes/required-steps-card.test.tsx

@ -14,7 +14,8 @@ describe("RequiredStepsCard", () => {
expect(screen.getByText("0/9")).toBeDefined();
expect(container.querySelector(".shimmer-white-bg")).toBeNull();
expect(container.querySelectorAll("circle")[1].getAttribute("stroke-dashoffset")).toBe(
const circles = container.querySelectorAll("svg")[1].querySelectorAll("circle");
expect(circles[1].getAttribute("stroke-dashoffset")).toBe(
String(2 * Math.PI * 25),
);
});
@ -23,9 +24,8 @@ describe("RequiredStepsCard", () => {
const { container } = render(<RequiredStepsCard completed={4} total={9} />);
expect(screen.getByText("4/9")).toBeDefined();
const offset = Number(
container.querySelectorAll("circle")[1].getAttribute("stroke-dashoffset"),
);
const circles = container.querySelectorAll("svg")[1].querySelectorAll("circle");
const offset = Number(circles[1].getAttribute("stroke-dashoffset"));
expect(offset).toBeCloseTo(2 * Math.PI * 25 * (5 / 9));
});
});

2
src/components/Componentes/slider-page.tsx

@ -147,7 +147,7 @@ export default function SliderPage() {
return (
<div className="relative flex h-[100dvh] max-h-[100dvh] flex-col overflow-hidden">
<header className="relative -mx-[17px] rounded-b-[32px] px-[17px] pt-[max(16px,calc(var(--safe-top)+8px))] pb-3.5">
<header className="relative -mx-[17px] rounded-b-[32px] px-[17px] pt-[max(12px,calc(var(--safe-top)+4px))] pb-3">
<div className="relative flex items-center justify-between">
<NavigationButton
icon="back"

37
src/components/Componentes/use-sheet-scroll-lock.ts

@ -5,6 +5,7 @@ import { viewPaddingsBridge } from "@/lib/view-paddings";
let activeSheetCount = 0;
let hasFocusedKeyboardInput = false;
let keyboardHeight = 0;
let bodyHadDropdownClass = false;
let initialBodyOverflow = "";
let initialHtmlOverflow = "";
@ -86,13 +87,36 @@ function isKeyboardInputTarget(target: EventTarget | null): boolean {
}
function syncQuestionInputOpenClass() {
if (hasFocusedKeyboardInput) {
if (hasFocusedKeyboardInput && keyboardHeight > 0) {
document.body.classList.add("question-keyboard-open");
} else {
document.body.classList.remove("question-keyboard-open");
}
}
function syncKeyboardShift(height: number) {
const root = document.documentElement;
if (height <= 0) {
root.style.setProperty("--question-keyboard-shift", "0px");
return;
}
const activeContent = document.querySelector<HTMLElement>(
'.question-snap-item[aria-current="step"] .question-snap-content',
);
if (!activeContent) return;
const visibleBottom = window.innerHeight - height;
const requiredShift = Math.max(
0,
activeContent.getBoundingClientRect().bottom - visibleBottom + 16,
);
root.style.setProperty(
"--question-keyboard-shift",
`${Math.min(requiredShift, height)}px`,
);
}
/**
* Moves the active question up (same animation the dropdown sheet uses) while
* a keyboard input is focused, so the mobile keyboard cannot cover it.
@ -113,13 +137,13 @@ export function useQuestionInputFocusSync() {
};
const unsubscribeConfig = viewPaddingsBridge.subscribeConfig((config) => {
if (config.keyboardHeight > 0) {
hasFocusedKeyboardInput = isKeyboardInputTarget(document.activeElement);
} else {
keyboardHeight = config.keyboardHeight;
if (keyboardHeight === 0) {
// Android Back can hide Flutter's keyboard without dispatching blur.
hasFocusedKeyboardInput = false;
}
syncQuestionInputOpenClass();
window.requestAnimationFrame(() => syncKeyboardShift(keyboardHeight));
});
const initialViewportHeight = window.visualViewport?.height ?? 0;
@ -146,6 +170,11 @@ export function useQuestionInputFocusSync() {
);
unsubscribeConfig();
hasFocusedKeyboardInput = false;
keyboardHeight = 0;
document.documentElement.style.setProperty(
"--question-keyboard-shift",
"0px",
);
document.body.classList.remove("question-keyboard-open");
};
}, []);

Loading…
Cancel
Save