mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-08 06:41:45 -05:00
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:
@@ -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"] {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user