fix: Mobile height inconsistencies (#2540)

Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Johannes
2024-04-26 18:04:03 +02:00
committed by GitHub
parent 460c046f0a
commit a6568ea539
38 changed files with 171 additions and 142 deletions
+36 -14
View File
@@ -1,9 +1,29 @@
const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
// Helper function to calculate difference in days between two dates
export const diffInDays = (date1: Date, date2: Date) => {
const diffTime = Math.abs(date2.getTime() - date1.getTime());
return Math.floor(diffTime / (1000 * 60 * 60 * 24));
};
// Helper function to get the month name
export const getMonthName = (monthIndex: number) => {
return monthNames[monthIndex];
};
export const formatDateWithOrdinal = (date: Date): string => {
const getOrdinalSuffix = (day: number) => {
const suffixes = ["th", "st", "nd", "rd"];
@@ -12,20 +32,6 @@ export const formatDateWithOrdinal = (date: Date): string => {
};
const dayOfWeekNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const dayOfWeek = dayOfWeekNames[date.getDay()];
const day = date.getDate();
@@ -35,6 +41,22 @@ export const formatDateWithOrdinal = (date: Date): string => {
return `${dayOfWeek}, ${monthNames[monthIndex]} ${day}${getOrdinalSuffix(day)}, ${year}`;
};
// Helper function to format the date with an ordinal suffix
export const getOrdinalDate = (date: number) => {
const j = date % 10,
k = date % 100;
if (j === 1 && k !== 11) {
return date + "st";
}
if (j === 2 && k !== 12) {
return date + "nd";
}
if (j === 3 && k !== 13) {
return date + "rd";
}
return date + "th";
};
export function isValidDateString(value: string) {
const regex = /^(?:\d{4}-\d{2}-\d{2}|\d{2}-\d{2}-\d{4})$/;
@@ -49,7 +49,7 @@ export default function CalEmbed({ question, onSuccessfulBooking }: CalEmbedProp
}, [cal, question.calUserName]);
return (
<div className="relative mt-4 max-h-[33vh] overflow-auto">
<div className="relative mt-4 overflow-auto">
<div id="fb-cal-embed" className={cn("border-border rounded-lg border")} />
</div>
);
@@ -209,7 +209,7 @@ export default function FileInput({
return (
<div
className={`items-left bg-input-bg hover:bg-input-bg-selected border-border relative mt-3 flex w-full flex-col justify-center rounded-lg border-2 border-dashed dark:border-slate-600 dark:bg-slate-700 dark:hover:border-slate-500 dark:hover:bg-slate-800`}>
<div className="max-h-[30vh] overflow-auto">
<div>
{fileUrls &&
fileUrls?.map((file, index) => {
const fileName = getOriginalFileNameFromUrl(file);
@@ -63,42 +63,44 @@ export const ConsentQuestion = ({
htmlString={getLocalizedValue(question.html, languageCode) || ""}
questionId={question.id}
/>
<label
tabIndex={1}
id={`${question.id}-label`}
onKeyDown={(e) => {
// Accessibility: if spacebar was pressed pass this down to the input
if (e.key === " ") {
e.preventDefault();
document.getElementById(question.id)?.click();
document.getElementById(`${question.id}-label`)?.focus();
}
}}
className="border-border bg-input-bg text-heading hover:bg-input-bg-selected focus:bg-input-bg-selected focus:ring-brand rounded-custom relative z-10 mt-4 flex w-full cursor-pointer items-center border p-4 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2">
<input
type="checkbox"
id={question.id}
name={question.id}
value={getLocalizedValue(question.label, languageCode)}
onChange={(e) => {
if (e.target instanceof HTMLInputElement && e.target.checked) {
onChange({ [question.id]: "accepted" });
} else {
onChange({ [question.id]: "dismissed" });
<div className="bg-survey-bg sticky -bottom-2 z-10 w-full px-1 py-1">
<label
tabIndex={1}
id={`${question.id}-label`}
onKeyDown={(e) => {
// Accessibility: if spacebar was pressed pass this down to the input
if (e.key === " ") {
e.preventDefault();
document.getElementById(question.id)?.click();
document.getElementById(`${question.id}-label`)?.focus();
}
}}
checked={value === "accepted"}
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
aria-labelledby={`${question.id}-label`}
required={question.required}
/>
<span id={`${question.id}-label`} className="ml-3 font-medium">
{getLocalizedValue(question.label, languageCode)}
</span>
</label>
className="border-border bg-input-bg text-heading hover:bg-input-bg-selected focus:bg-input-bg-selected focus:ring-brand rounded-custom relative z-10 my-2 flex w-full cursor-pointer items-center border p-4 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2">
<input
type="checkbox"
id={question.id}
name={question.id}
value={getLocalizedValue(question.label, languageCode)}
onChange={(e) => {
if (e.target instanceof HTMLInputElement && e.target.checked) {
onChange({ [question.id]: "accepted" });
} else {
onChange({ [question.id]: "dismissed" });
}
}}
checked={value === "accepted"}
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
aria-labelledby={`${question.id}-label`}
required={question.required}
/>
<span id={`${question.id}-label`} className="ml-3 font-medium">
{getLocalizedValue(question.label, languageCode)}
</span>
</label>
</div>
</div>
</ScrollableContainer>
<div className="flex w-full justify-between px-6 py-4">
{!isFirstQuestion && (
<BackButton
@@ -112,10 +112,10 @@ export const DateQuestion = ({
<span>{errorMessage}</span>
</div>
<div
className={cn("mt-4", errorMessage && "rounded-lg border-2 border-red-500")}
className={cn("mt-4 w-full", errorMessage && "rounded-lg border-2 border-red-500")}
id="date-picker-root">
{loading && (
<div className="bg-survey-bg border-border text-placeholder relative flex h-12 w-full cursor-pointer appearance-none items-center justify-center rounded-lg border text-left text-base font-normal focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-1">
<div className="bg-survey-bg border-border text-placeholder relative flex h-16 w-full cursor-pointer appearance-none items-center justify-center rounded-lg border text-left text-base font-normal focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-1">
<span
className="h-6 w-6 animate-spin rounded-full border-b-2 border-neutral-900"
style={{ borderTopColor: "transparent" }}></span>
@@ -104,7 +104,7 @@ export const MatrixQuestion = ({
subheader={getLocalizedValue(question.subheader, languageCode)}
questionId={question.id}
/>
<div className="overflow-x-auto">
<div className="overflow-x-auto py-4">
<table className="no-scrollbar min-w-full table-auto border-collapse text-sm">
<thead>
<tr>
@@ -154,7 +154,7 @@ export const MatrixQuestion = ({
</div>
</div>
</ScrollableContainer>
<div className="flex w-full justify-between px-6">
<div className="flex w-full justify-between px-6 py-4">
{!isFirstQuestion && (
<BackButton
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
@@ -109,7 +109,7 @@ export const PictureSelectionQuestion = ({
<div className="mt-4">
<fieldset>
<legend className="sr-only">Options</legend>
<div className="rounded-m bg-survey-bg relative grid max-h-[33vh] grid-cols-2 gap-x-5 gap-y-4 overflow-y-auto">
<div className="bg-survey-bg relative grid grid-cols-2 gap-x-5 gap-y-4">
{questionChoices.map((choice, idx) => (
<label
key={choice.id}
@@ -128,7 +128,7 @@ export const PictureSelectionQuestion = ({
Array.isArray(value) && value.includes(choice.id)
? `border-brand text-brand z-10 border-4 shadow-xl`
: "",
"focus:border-brand group/image relative inline-block h-28 w-full cursor-pointer overflow-hidden rounded-xl border focus:border-4 focus:outline-none"
"focus:border-brand group/image rounded-custom relative inline-block h-28 w-full cursor-pointer overflow-hidden border focus:border-4 focus:outline-none"
)}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
@@ -170,7 +170,7 @@ export const PictureSelectionQuestion = ({
tabIndex={-1}
checked={value.includes(choice.id)}
className={cn(
"border-border pointer-events-none absolute right-2 top-2 z-20 h-5 w-5 rounded border",
"border-border rounded-custom pointer-events-none absolute right-2 top-2 z-20 h-5 w-5 border",
value.includes(choice.id) ? "border-brand text-brand" : ""
)}
required={question.required && value.length ? false : question.required}
@@ -24,7 +24,7 @@ export const ScrollableContainer = ({ children }: ScrollableContainerProps) => {
const toggleOverflow = (hide: boolean) => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
if (hide) {
timeoutRef.current = setTimeout(() => setIsOverflowHidden(true), 1500);
timeoutRef.current = setTimeout(() => setIsOverflowHidden(true), 1000);
} else {
setIsOverflowHidden(false);
checkScroll();
@@ -55,10 +55,10 @@ export const ScrollableContainer = ({ children }: ScrollableContainerProps) => {
<div
ref={containerRef}
style={{
scrollbarGutter: "stable",
maxHeight: isSurveyPreview ? "41vh" : "60vh",
scrollbarGutter: "stable both-edges",
maxHeight: isSurveyPreview ? "40dvh" : "60dvh",
}}
className={`overflow-${isOverflowHidden ? "hidden" : "auto"} pb-1 pl-6 pr-4`}
className={`overflow-${isOverflowHidden ? "hidden" : "auto"} px-4 pb-1`}
onMouseEnter={() => toggleOverflow(false)}
onTouchStart={() => toggleOverflow(false)}
onTouchEnd={() => toggleOverflow(true)}
@@ -1,6 +1,8 @@
import { useEffect, useMemo, useState } from "preact/hooks";
import DatePicker from "react-date-picker";
import { getMonthName, getOrdinalDate } from "@formbricks/lib/utils/datetime";
const CalendarIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -72,23 +74,19 @@ export default function Question({ defaultDate, format }: { defaultDate?: Date;
const formattedDate = useMemo(() => {
if (!selectedDate) return "";
if (format === "M-d-y") {
return `${selectedDate?.getMonth() + 1}-${selectedDate?.getDate()}-${selectedDate?.getFullYear()}`;
}
const day = selectedDate.getDate();
const monthIndex = selectedDate.getMonth();
const year = selectedDate.getFullYear();
if (format === "d-M-y") {
return `${selectedDate?.getDate()}-${selectedDate?.getMonth() + 1}-${selectedDate?.getFullYear()}`;
}
return `${selectedDate?.getFullYear()}-${selectedDate?.getMonth() + 1}-${selectedDate?.getDate()}`;
}, [format, selectedDate]);
return `${getOrdinalDate(day)} of ${getMonthName(monthIndex)}, ${year}`;
}, [selectedDate]);
return (
<div className="relative h-40">
<div className="relative">
{!datePickerOpen && (
<div
onClick={() => setDatePickerOpen(true)}
className="bg-input-bg hover:bg-input-bg-selected border-border text-placeholder relative flex h-40 w-full cursor-pointer appearance-none items-center justify-center rounded-lg border text-left text-base font-normal focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-1">
className="bg-input-bg hover:bg-input-bg-selected border-border text-heading rounded-custom relative flex h-[12dvh] w-full cursor-pointer appearance-none items-center justify-center border text-left text-base font-normal focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-1">
<div className="flex items-center gap-2">
{selectedDate ? (
<div className="flex items-center gap-2">
@@ -119,10 +117,10 @@ export default function Question({ defaultDate, format }: { defaultDate?: Date;
monthPlaceholder="MM"
yearPlaceholder="YYYY"
format={format ?? "M-d-y"}
className={`dp-input-root rounded-custom ${!datePickerOpen ? "wrapper-hide" : ""}
className={`dp-input-root rounded-custom wrapper-hide ${!datePickerOpen ? "" : "h-[46dvh] sm:h-[34dvh]"}
${hideInvalid ? "hide-invalid" : ""}
`}
calendarClassName="calendar-root w-80 rounded-lg border border-[#e5e7eb] p-3 shadow-md h-40 overflow-auto"
calendarClassName="calendar-root !bg-input-bg border border-border rounded-custom p-3 h-[46dvh] sm:h-[33dvh] overflow-auto"
clearIcon={null}
onCalendarOpen={() => {
setDatePickerOpen(true);
@@ -136,14 +134,14 @@ export default function Question({ defaultDate, format }: { defaultDate?: Date;
calendarIcon={<CalendarIcon />}
tileClassName={({ date }) => {
const baseClass =
"hover:bg-slate-200 rounded-md h-9 p-0 mt-1 font-normal text-slate-900 aria-selected:opacity-100";
"hover:bg-input-bg-selected rounded-custom h-9 p-0 mt-1 font-normal text-heading aria-selected:opacity-100";
// today's date class
if (
date.getDate() === new Date().getDate() &&
date.getMonth() === new Date().getMonth() &&
date.getFullYear() === new Date().getFullYear()
) {
return `${baseClass} bg-slate-100`;
return `${baseClass} border border-input-border`;
}
// active date class
if (
@@ -151,7 +149,7 @@ export default function Question({ defaultDate, format }: { defaultDate?: Date;
date.getMonth() === selectedDate?.getMonth() &&
date.getFullYear() === selectedDate?.getFullYear()
) {
return `${baseClass} !bg-slate-900 !text-slate-100`;
return `${baseClass} !bg-accent-selected-bg !border-border-highlight !text-heading`;
}
return baseClass;
@@ -159,6 +157,7 @@ export default function Question({ defaultDate, format }: { defaultDate?: Date;
formatShortWeekday={(_, date) => {
return date.toLocaleDateString("en-US", { weekday: "short" }).slice(0, 2);
}}
navi
showNeighboringMonth={false}
showLeadingZeros={false}
/>
@@ -50,12 +50,14 @@
.react-date-picker__calendar--open {
position: absolute !important;
top: 0 !important;
width: 100% !important;
}
.calendar-root {
position: absolute !important;
top: 0 !important;
background: var(--fb-survey-background-color) !important;
width: 100% !important;
}
.calendar-root [class$="navigation"] {
+8 -2
View File
@@ -11,8 +11,8 @@
/* Chrome, Edge, and Safari */
#fbjs *::-webkit-scrollbar {
width: 8px ;
background: transparent ;
width: 6px;
background: transparent;
}
#fbjs *::-webkit-scrollbar-track {
@@ -25,6 +25,12 @@
border-radius: 10px
}
/* Firefox */
#fbjs * {
scrollbar-width: thin;
scrollbar-color: var(--fb-brand-color) transparent;
}
/* this is for styling the HtmlBody component */
.fb-htmlbody {
+1 -1
View File
@@ -16,7 +16,7 @@ interface ClientLogoProps {
export const ClientLogo = ({ environmentId, product, previewSurvey = false }: ClientLogoProps) => {
return (
<div
className={cn(previewSurvey ? "" : "left-5 top-5 md:left-7 md:top-7", "group fixed z-0 rounded-lg")}
className={cn(previewSurvey ? "" : "left-3 top-3 md:left-7 md:top-7", "group fixed z-0 rounded-lg")}
style={{ backgroundColor: product.logo?.bgColor }}>
{previewSurvey && environmentId && (
<Link
-1
View File
@@ -34,7 +34,6 @@ const CodeBlock: React.FC<CodeBlockProps> = ({
{showCopyToClipboard && (
<div className="absolute right-2 top-2 z-20 h-8 w-8 cursor-pointer rounded-md bg-slate-100 p-1.5 text-slate-600 hover:bg-slate-200">
<CopyIcon
className=""
onClick={() => {
const childText = children?.toString() || "";
navigator.clipboard.writeText(childText);
+2 -2
View File
@@ -203,11 +203,11 @@ export const FileInput: React.FC<FileInputProps> = ({
return (
<div className="w-full cursor-default">
<div className="">
<div>
{isVideoAllowed && (
<TabBar tabs={tabs} activeId={activeTab} setActiveId={setActiveTab} tabStyle="button" />
)}
<div className="">
<div>
{activeTab === "video" && (
<div className={cn(isVideoAllowed && "rounded-b-lg border-x border-b border-slate-200 p-4")}>
<VideoSettings