Skip to content

Instantly share code, notes, and snippets.

@EvanMarie
Last active May 6, 2024 21:51
Show Gist options
  • Save EvanMarie/242c224364817f449113a049fadf3a98 to your computer and use it in GitHub Desktop.
Save EvanMarie/242c224364817f449113a049fadf3a98 to your computer and use it in GitHub Desktop.
airbnb
type IconBarType = {
icon: React.ComponentType<{ className?: string }>;
label: string;
};
const iconBar = [
{ icon: MdOutlinePark, label: "National Park" },
{ icon: MdOutlinePool, label: "Beautiful Pools" },
{ icon: MdOutlinePhotoCamera, label: "Amazing Views" },
{ icon: GiSydneyOperaHouse, label: "Top Cities" },
{ icon: TbCategoryFilled, label: "Off-The-Grid" },
{ icon: TbBeach, label: "Beachfront" },
{ icon: MdOutlineCabin, label: "Cabins" },
{ icon: FaWater, label: "Lakefront" },
{ icon: GiCampingTent, label: "Camping" },
{ icon: GiMushroomHouse, label: "OMG!" },
{ icon: GiTreehouse, label: "Treehouse" },
{ icon: GiIsland, label: "Tropical" },
{ icon: TbBuildingStore, label: "Tiny Homes" },
{ icon: GiFamilyHouse, label: "Mansions" },
{ icon: LuTrees, label: "Countryside" },
{ icon: GiFarmTractor, label: "Farms" },
{ icon: LuCastle, label: "Castles" },
{ icon: GiMountainCave, label: "Caves" },
];
export default function AirBnBMockup() {
const [category, setCategory] = useState<String | undefined>(undefined);
const [properties, setProperties] = useState(airbnbProperties);
const [selectedDateOne, setSelectedDateOne] = useState<Date | null>(null);
const [selectedDateTwo, setSelectedDateTwo] = useState<Date | null>(null);
const [when, setWhen] = useState("Anytime");
useEffect(() => {
setProperties(generateAirbnbProperties(70));
}, [category]);
return (
<VStackFull
className="w-screen h-[100svh] bg-white text-slate-900 fixed inset-0 pb-[3vh] rounded-none"
gap="gap-[0px]"
>
<HStackFull className="fixed top-0 left-0 right-0 h-[16vh] justify-center border-b-[0.2vh] border-b-slate-900/30 rounded-b-none">
<VStackFull
className=" h-full justify-between pt-[1.5vh]"
gap="gap-[0px]"
>
<FlexFull className="flex md:hidden">
<AirbnbSearchSmallScreens
when={when}
setWhen={setWhen}
selectedDateOne={selectedDateOne}
setSelectedDateOne={setSelectedDateOne}
selectedDateTwo={selectedDateTwo}
setSelectedDateTwo={setSelectedDateTwo}
/>
</FlexFull>
<FlexFull className="hidden md:flex">
<AirbnbSearchLargeScreens
when={when}
setWhen={setWhen}
selectedDateOne={selectedDateOne}
setSelectedDateOne={setSelectedDateOne}
selectedDateTwo={selectedDateTwo}
setSelectedDateTwo={setSelectedDateTwo}
/>
</FlexFull>
<FlexFull className="xl:w-[75vw]">
<IconBar
iconBar={iconBar}
setCategory={setCategory}
category={category}
/>
</FlexFull>
</VStackFull>
</HStackFull>
<FlexFull className="h-[97.5svh] overflow-hidden pt-[16vh]">
<FlexFull className="h-[81svh] overflow-y-auto">
<VStackFull>
<Wrap className="w-full p-[1vh] justify-evenly">
{" "}
{properties.map((property, index) => (
<AirBnBPropertyCard
property={property}
key={index}
when={when}
/>
))}
</Wrap>
<AirbnbFooter />
</VStackFull>
</FlexFull>
</FlexFull>
</VStackFull>
);
}
export type AirbnbProperties = {
id: string;
location: string;
description: string;
nightlyPrice: number;
weeklyPrice: number;
monthlyPrice: number;
rating: number;
badgeLabel: string;
};
function GetRandomNightlyPrice(min = 100) {
return Math.floor(Math.random() * (700 - min + 1)) + min;
}
function GetRandomWeeklyPrice(min = 1500) {
return Math.floor(Math.random() * (5000 - min + 1)) + min;
}
function GetRandomMonthlyPrice(min = 4000) {
return Math.floor(Math.random() * (10000 - min + 1)) + min;
}
function GetRandomRating() {
return Math.random() * (5.0 - 3.1) + 3.1;
}
function GetRandomBadgeLabel() {
const badgeLabelOptions = [
"Guest Favorite",
"Superhost",
"Plus",
"Great Value",
"",
];
const randomIndex = Math.floor(Math.random() * badgeLabelOptions.length);
return badgeLabelOptions[randomIndex];
}
function GetRandomLocation() {
const locations = [
"New York",
"Los Angeles",
"San Francisco",
"Chicago",
"Miami",
"Austin",
"Seattle",
"Portland",
"Denver",
"Boston",
"New Orleans",
"Nashville",
"San Diego",
"Las Vegas",
"Orlando",
"Washington D.C.",
"Philadelphia",
"Atlanta",
"Dallas",
"Houston",
"Phoenix",
"Minneapolis",
"Detroit",
"St. Louis",
"Kansas City",
"Salt Lake City",
"Albuquerque",
"Santa Fe",
"Honolulu",
"Anchorage",
"Seattle",
"Portland",
"Denver",
"Boston",
"New Orleans",
"Nashville",
"San Diego",
"Las Vegas",
"Orlando",
"Washington D.C.",
"Philadelphia",
"Atlanta",
"Dallas",
"Houston",
"Phoenix",
"Minneapolis",
"Detroit",
"St. Louis",
"Kansas City",
"Salt Lake City",
"Albuquerque",
"Santa Fe",
"Honolulu",
"Anchorage",
];
const randomIndex = Math.floor(Math.random() * locations.length);
return locations[randomIndex];
}
function GetRandomDescription() {
const randomDescriptions = [
"In the heart of the city.",
"Beautiful views!",
"Great location!",
"Superior service!",
"Top-rated!",
"Highly recommended!",
"Great value!",
"Extended stay discounts!",
"Pet-friendly!",
"Family-friendly!",
"Luxury accommodations!",
"Great for business travelers!",
"Great for couples!",
"Great for families!",
"Great for solo travelers!",
"Great for groups!",
];
const randomIndex = Math.floor(Math.random() * randomDescriptions.length);
return randomDescriptions[randomIndex];
}
export function GetRandomNumbers() {
const numbers = [];
for (let i = 0; i < 5; i++) {
numbers.push(Math.floor(Math.random() * 129) + 1);
}
return numbers;
}
export function generateAirbnbProperties(count: number) {
const properties = [];
for (let i = 1; i <= count; i++) {
properties.push({
id: i.toString(),
location: GetRandomLocation(),
description: GetRandomDescription(),
nightlyPrice: GetRandomNightlyPrice(),
weeklyPrice: GetRandomWeeklyPrice(),
monthlyPrice: GetRandomMonthlyPrice(),
rating: GetRandomRating(),
badgeLabel: GetRandomBadgeLabel(),
});
}
return properties;
}
export const airbnbProperties = generateAirbnbProperties(70);
export default function DateSelector({
selectedDateOne,
selectedDateTwo,
setSelectedDateOne,
setSelectedDateTwo,
when,
setWhen,
}: {
selectedDateOne: Date | null;
selectedDateTwo: Date | null;
setSelectedDateOne: (date: Date | null) => void;
setSelectedDateTwo: (date: Date | null) => void;
when: string;
setWhen: (when: string) => void;
}) {
const [currentMonth, setCurrentMonth] = useState(new Date());
const [mode, setMode] = useState<"dates" | "flexible">("dates");
const [flexibleTime, setFlexibleTime] = useState("weekend");
useEffect(() => {
if (mode === "dates" && selectedDateOne && selectedDateTwo) {
const formattedStart = format(selectedDateOne, "MMM d");
const formattedEnd = format(selectedDateTwo, "MMM d");
setWhen(`from ${formattedStart} to ${formattedEnd}`);
}
}, [selectedDateOne, selectedDateTwo, setWhen, mode]);
const handleDateClick = (date: Date) => {
if (!selectedDateOne || (selectedDateOne && selectedDateTwo)) {
setSelectedDateOne(date);
setSelectedDateTwo(null);
} else if (selectedDateOne && !selectedDateTwo && date >= selectedDateOne) {
setSelectedDateTwo(date);
}
};
const isWithinRange = (day: Date) => {
if (selectedDateOne && selectedDateTwo) {
return day >= selectedDateOne && day <= selectedDateTwo;
}
return false;
};
const renderDays = () => {
const start = startOfMonth(currentMonth);
const end = endOfMonth(currentMonth);
const days = eachDayOfInterval({ start, end });
const dayOffset = getDay(start);
const paddingDays = Array.from({ length: dayOffset }, (_, i) => (
<div
key={`padding-${i}`}
className="w-[10vw] h-[10vw] md:w-[5.5vh] md:h-[5.5vh] p-[1vh]"
></div> // Render empty divs for padding
));
const dateButtons = days.map((day, index) => {
const isSelectedStartOrEnd =
selectedDateOne?.toDateString() === day.toDateString() ||
selectedDateTwo?.toDateString() === day.toDateString();
const isInRange = isWithinRange(day);
return (
<button
key={index}
className={`w-[10vw] h-[10vw] md:w-[5.5vh] md:h-[5.5vh] p-[1vh] text-md sm:text-lg md:text-md leading-tight border-[0.2vh] border-transparent rounded-full hover:border-slate-500 transition-400 ${
isSelectedStartOrEnd ? "bg-pink-700 text-white textShadow" : ""
} ${
isInRange
? "border-pink-900 bg-pink-400/80 text-white textShadow"
: ""
}`}
onClick={() => handleDateClick(day)}
>
{format(day, "d")}
</button>
);
});
return <>{[...paddingDays, ...dateButtons]}</>; // Combine padding days with actual date buttons
};
const handleNextMonth = () => {
setCurrentMonth(addMonths(currentMonth, 1));
};
const handlePrevMonth = () => {
setCurrentMonth(subMonths(currentMonth, 1));
};
return (
<Box className="w-full p-[1vh] bg-white rounded-[3vh] shadowNarrowTight space-y-[1vh]">
<HStackFull className=" justify-evenly items-center py-[1vh] px-[2vh] bg-slate-300/40 rounded-[3vh]">
<button
onClick={() => setMode("dates")}
className={`${
mode === "dates"
? "text-slate-900 bg-white shadowNarrowTight"
: "text-slate-500"
} rounded-[2vh] px-[1vh] py-[0.5vh] transition-400`}
>
Dates
</button>
<button
onClick={() => {
setSelectedDateOne(null);
setSelectedDateTwo(null);
setMode("flexible");
}}
className={`${
mode === "flexible"
? "text-slate-900 bg-white shadowNarrowTight"
: "text-slate-500"
} rounded-[2vh] px-[1vh] py-[0.5vh] transition-400`}
>
Flexible
</button>
</HStackFull>
{mode === "dates" && (
<>
<HStack className="justify-between items-center py-[1vh]">
<button onClick={handlePrevMonth} className="text-xl">
&#9664;
</button>
<Text className="flex-1 text-center">
{format(currentMonth, "MMMM yyyy")}
</Text>
<button onClick={handleNextMonth} className="text-xl">
&#9654;
</button>
</HStack>
<div className="grid grid-cols-7 gap-1 text-center font-semibold text-pink-700">
<div>S</div>
<div>M</div>
<div>T</div>
<div>W</div>
<div>T</div>
<div>F</div>
<div>S</div>
</div>
<div className="grid grid-cols-7 gap-1">{renderDays()}</div>
</>
)}
{mode === "flexible" && (
<VStackFull className="overflow-hidden">
<FlexibleOptions
flexibleTime={flexibleTime}
setFlexibleTime={setFlexibleTime}
when={when}
setWhen={setWhen}
/>
</VStackFull>
)}
</Box>
);
}
export default function FlexibleOptions({
flexibleTime,
setFlexibleTime,
when,
setWhen,
}: {
flexibleTime: string;
setFlexibleTime: (time: string) => void;
when: string;
setWhen: (when: string) => void;
}) {
const [selectedDuration, setSelectedDuration] = useState<string>("weekend");
const [selectedMonth, setSelectedMonth] = useState<Date>(new Date());
const months: Date[] = Array.from({ length: 12 }, (_, i) =>
addMonths(new Date(), i)
);
useEffect(() => {
const formattedMonth = format(selectedMonth, "MMMM");
let newTimeDescription = "";
switch (selectedDuration) {
case "weekend":
newTimeDescription = `a weekend in ${formattedMonth}`;
break;
case "week":
newTimeDescription = `a week in ${formattedMonth}`;
break;
case "month":
newTimeDescription = `the month of ${formattedMonth}`;
break;
default:
newTimeDescription = `${selectedDuration} in ${formattedMonth}`;
}
setFlexibleTime(newTimeDescription);
setWhen(newTimeDescription); // Set 'when' to the same value as 'flexibleTime'
}, [selectedDuration, selectedMonth, setFlexibleTime, setWhen]);
const handleSelectMonth = (month: Date) => {
setSelectedMonth(month);
};
const baseStyles = "px-4 py-2 rounded-[2vh] border-[0.2vh] transition-300";
const selectedStyles =
"border-black bg-slate-400 text-white subtleTextShadow";
const unselectedStyles = "border-transparent bg-gray-200";
return (
<VStackFull className="gap-y-[1vh] h-full">
<Text className="text-md">Stay for a</Text>
<HStackFull className="gap-x-[1vh] justify-between px-[1vh]">
<button
onClick={() => setSelectedDuration("weekend")}
className={`${baseStyles} ${
selectedDuration === "weekend" ? selectedStyles : unselectedStyles
}`}
>
Weekend
</button>
<button
onClick={() => setSelectedDuration("week")}
className={`${baseStyles} ${
selectedDuration === "week" ? selectedStyles : unselectedStyles
}`}
>
Week
</button>
<button
onClick={() => setSelectedDuration("month")}
className={`${baseStyles} ${
selectedDuration === "month" ? selectedStyles : unselectedStyles
}`}
>
Month
</button>
</HStackFull>
<Box className="w-full overflow-hidden">
<FlexFull className="overflow-x-auto py-[2vh]">
<HStack className="w-fit py-2">
{months.map((month, index) => (
<button
key={index}
onClick={() => handleSelectMonth(month)}
className={`px-6 py-2 rounded-lg border-2 ${
format(month, "MMMM yyyy") ===
format(selectedMonth, "MMMM yyyy")
? "border-black"
: "border-transparent"
}`}
>
{format(month, "MMMM yyyy")}
</button>
))}
</HStack>
</FlexFull>
</Box>
<CenterHorizontalFull>{when && <Text>{when}</Text>}</CenterHorizontalFull>
</VStackFull>
);
}
type MenuSectionProps = {
title: string;
items: { name: string; url: string }[];
};
function MenuSection({ title, items }: MenuSectionProps) {
return (
<FlexFull className="px-[1vh] sm:px-[2vh]">
<VStack align="items-start w-full py-[1vh] border-b-[0.2vh] border-b-slate-800/30 rounded-b-none lg:border-b-transparent">
<h5 className="font-semibold text-slate-800 pb-[0.5vh] text-sm">
{title}
</h5>
<ul>
{items.map((item, index) => (
<li key={index}>
<a
href={item.url}
className="hover:underline text-xs leading-tight"
>
{item.name}
</a>
</li>
))}
</ul>
</VStack>
</FlexFull>
);
}
export default function AirbnbFooter() {
const sections: MenuSectionProps[] = [
{
title: "Support",
items: [
{ name: "Help Center", url: "#" },
{ name: "AirCover", url: "#" },
{ name: "Anti-discrimination", url: "#" },
{ name: "Disability support", url: "#" },
{ name: "Cancellation options", url: "#" },
{ name: "Report neighborhood concern", url: "#" },
],
},
{
title: "Hosting",
items: [
{ name: "Airbnb your home", url: "#" },
{ name: "AirCover for Hosts", url: "#" },
{ name: "Hosting resources", url: "#" },
{ name: "Community forum", url: "#" },
{ name: "Hosting responsibly", url: "#" },
{ name: "Airbnb-friendly apartments", url: "#" },
{ name: "Join a free Hosting class", url: "#" },
],
},
{
title: "Airbnb",
items: [
{ name: "Newsroom", url: "#" },
{ name: "New features", url: "#" },
{ name: "Careers", url: "#" },
{ name: "Investors", url: "#" },
{ name: "Gift cards", url: "#" },
{ name: "Airbnb.org emergency stays", url: "#" },
],
},
];
return (
<VStackFull className="bg-slate-100 text-slate-600 text-sm mPlus-font lg:px-[1vh]">
<Inspiration />
<FlexFull className="flex-col gap-[2vh] lg:flex-row lg:border-b-[0.2vh] lg:border-b-slate-800/30 rounded-b-none px-[1vh] md:px-[2vh]">
{sections.map((section, index) => (
<MenuSection key={index} {...section} />
))}
</FlexFull>
<HStackFull className="px-[2vh] text-center text-xs">
© 2024 Airbnb, Inc. -{" "}
<a href="#" className="hover:underline">
Terms
</a>{" "}
-{" "}
<a href="#" className="hover:underline">
Sitemap
</a>{" "}
-{" "}
<a href="#" className="hover:underline">
Privacy
</a>{" "}
-{" "}
<a href="#" className="hover:underline">
Your Privacy Choices
</a>
</HStackFull>
</VStackFull>
);
}
function Inspiration() {
const [activeTab, setActiveTab] = useState<string>("Popular");
const handleTabClick = (tab: string) => {
setActiveTab(tab);
};
return (
<VStackFull className="px-[1vh] md:px-[2vh]">
<FlexFull>
<Text className="text-lg text-slate-800 font-semibold px-[1vh] pt-[1vh]">
Inspiration for future getaways
</Text>
</FlexFull>
<FlexFull>
<Box className="w-full overflow-x-auto hide-scrollbar">
<HStackFull className="w-fit" gap="gap-[0px]">
{inspirationObjects.map((tab, index) => (
<div
key={index}
className={`text-nowrap text-sm cursor-pointer rounded-b-none px-[2vh] py-[1vh] transition-400 ${
activeTab === tab.heading
? "text-pink-500 border-b-[0.3vh] border-pink-600"
: "text-gray-500 hover:text-gray-700 border-b-[0.1vh] border-slate-800/40"
}`}
onClick={() => handleTabClick(tab.heading)}
>
{tab.heading}
</div>
))}
</HStackFull>
</Box>
</FlexFull>
<Wrap className="w-full border-b-[0.2vh] rounded-b-none border-b-slate-800/40">
{inspirationObjects
.find((obj) => obj.heading === activeTab)
?.items.map((item, index) => (
<VStack
key={index}
gap="gap-[0px]"
className="text-xs text-slate-900 px-[2vh] py-[1vh]"
align="items-start"
>
<Text className="font-bold text-slate-500 leading-tight">
{item.name}
</Text>
{item.tagline && (
<Text className="text-xs text-slate-500 leading-tight">
{item.tagline}
</Text>
)}
</VStack>
))}
</Wrap>
</VStackFull>
);
}
const inspirationObjects = [
{
heading: "Popular",
items: [
{ name: "Canmore", tagline: "Condo rentals" },
{ name: "Benalmádena", tagline: "Vacation rentals" },
{ name: "Tucson", tagline: "Mansion rentals" },
{ name: "Jasper", tagline: "Cabin rentals" },
{ name: "Anaheim", tagline: "Apartment rentals" },
{ name: "Monterey", tagline: "Vacation rentals" },
{ name: "Marbella", tagline: "Vacation rentals" },
{ name: "Mijas", tagline: "Vacation rentals" },
{ name: "Mountain View", tagline: "House rentals" },
{ name: "Devonport", tagline: "Vacation rentals" },
{ name: "Paso Robles", tagline: "Cottage rentals" },
{ name: "Santa Barbara", tagline: "Condo rentals" },
{ name: "Sonoma", tagline: "Vacation rentals" },
{ name: "Prescott", tagline: "Vacation rentals" },
{ name: "Scottsdale", tagline: "House rentals" },
{ name: "Ibiza", tagline: "Apartment rentals" },
],
},
{
heading: "Arts & Culture",
items: [
{ name: "Phoenix", tagline: "Apartment rentals" },
{ name: "Hot Springs", tagline: "Condo rentals" },
{ name: "Prague", tagline: "Vacation rentals" },
{ name: "Washington", tagline: "House rentals" },
{ name: "York", tagline: "Cottage rentals" },
{ name: "Paris", tagline: "Cottage rentals" },
{ name: "Los Angeles", tagline: "Rentals with pools" },
{ name: "San Diego", tagline: "Vacation rentals" },
{ name: "San Francisco", tagline: "Vacation rentals" },
{ name: "Barcelona", tagline: "Apartment rentals" },
{ name: "Keswick", tagline: "Vacation rentals" },
{ name: "London", tagline: "House rentals" },
{ name: "Rhodes", tagline: "Cottage rentals" },
{ name: "Nashville", tagline: "Vacation rentals" },
{ name: "Dublin", tagline: "Apartment rentals" },
{ name: "Scarborough", tagline: "Vacation rentals" },
{ name: "Sherwood Forest", tagline: "Vacation rentals" },
],
},
{
heading: "Outdoors",
items: [
{ name: "Lake Martin", tagline: "Condo rentals" },
{ name: "Banff", tagline: "Condo rentals" },
{ name: "North Rim", tagline: "Vacation rentals" },
{ name: "Payson", tagline: "Cabin rentals" },
{ name: "Emerald Lake", tagline: "Vacation rentals" },
{ name: "Vancouver Island", tagline: "Vacation rentals" },
{ name: "Nerja", tagline: "Beach house rentals" },
{ name: "Pinetop-Lakeside", tagline: "Vacation rentals" },
{ name: "Victoria", tagline: "House rentals" },
{ name: "Greer", tagline: "Cabin rentals" },
{ name: "Red Rock", tagline: "Cabin rentals" },
{ name: "Dinner Plain", tagline: "Pet-friendly rentals" },
{ name: "Idyllwild-Pine Cove", tagline: "House rentals" },
{ name: "Mammoth Lakes", tagline: "Vacation rentals" },
{ name: "Lake Havasu City", tagline: "Vacation rentals" },
{ name: "Lake Powell", tagline: "Vacation rentals" },
{ name: "Streaky Bay", tagline: "Vacation rentals" },
],
},
{
heading: "Mountains",
items: [
{ name: "Mentone", tagline: "Vacation rentals" },
{ name: "Sedona", tagline: "Pet-friendly rentals" },
{ name: "Blue Mountains", tagline: "Vacation rentals" },
{ name: "Asheville", tagline: "Cabin rentals" },
{ name: "Townsend", tagline: "Cabin rentals" },
{ name: "Wears Valley", tagline: "Vacation rentals" },
{ name: "Helen", tagline: "Chalet rentals" },
{ name: "Blowing Rock", tagline: "Cottage rentals" },
{ name: "Cabins", tagline: "Vacation rentals" },
{ name: "Pine Mountain", tagline: "Cabin rentals" },
{ name: "Stone Mountain", tagline: "Cabin rentals" },
{ name: "Boone", tagline: "Vacation rentals" },
{ name: "Hochatown", tagline: "Cabin rentals" },
{ name: "Island Park", tagline: "Vacation rentals" },
{ name: "Pigeon Forge", tagline: "Vacation rentals" },
],
},
{
heading: "Beach",
items: [
{ name: "Dauphin Island", tagline: "Vacation rentals" },
{ name: "Fort Morgan", tagline: "Beach house rentals" },
{ name: "Hamilton Island", tagline: "Vacation rentals" },
{ name: "Lancelin", tagline: "Vacation rentals" },
{ name: "Big Sur", tagline: "Vacation rentals" },
{ name: "Bodega Bay", tagline: "Vacation rentals" },
{ name: "Gulf Shores", tagline: "Vacation rentals" },
{ name: "Melbourne Beach", tagline: "Beach house rentals" },
{ name: "Cambria", tagline: "Cabin rentals" },
{ name: "Cayucos", tagline: "Vacation rentals" },
{ name: "Bruny Island", tagline: "Vacation rentals" },
{ name: "Crescent Head", tagline: "Vacation rentals" },
{ name: "Moonta Bay", tagline: "Vacation rentals" },
{ name: "Ocean Grove", tagline: "Beach house rentals" },
{ name: "Huntington Beach", tagline: "Vacation rentals" },
{ name: "Gerringong", tagline: "Vacation rentals" },
{ name: "Majorca", tagline: "Rentals with pools" },
],
},
{
heading: "Unique Stays",
items: [
{ name: "Cabins", tagline: "United States" },
{ name: "Treehouses", tagline: "United States" },
{ name: "Lakehouses", tagline: "United States" },
{ name: "Yurt Rentals", tagline: "United States" },
{ name: "Private Island Rentals", tagline: "United States" },
{ name: "Farm Houses", tagline: "United States" },
{ name: "Glamping", tagline: "United States" },
{ name: "Tiny Houses", tagline: "United States" },
{ name: "Beach Houses", tagline: "United States" },
{ name: "Castle Rentals", tagline: "United States" },
{ name: "Houseboats", tagline: "United States" },
{ name: "Campers and RVs", tagline: "United States" },
{ name: "Yurt Rentals", tagline: "United Kingdom" },
{ name: "Farm Cottages", tagline: "United Kingdom" },
{ name: "Cabin Rentals", tagline: "Australia" },
{ name: "Luxury Cabins", tagline: "United Kingdom" },
{ name: "Holiday Caravans", tagline: "United Kingdom" },
],
},
{
heading: "Categories",
items: [
{ name: "Amazing pools", tagline: "" },
{ name: "Arctic", tagline: "" },
{ name: "Countryside", tagline: "" },
{ name: "Design", tagline: "" },
{ name: "OMG!", tagline: "" },
{ name: "Tiny homes", tagline: "" },
{ name: "Camping", tagline: "" },
{ name: "Earth homes", tagline: "" },
{ name: "Towers", tagline: "" },
{ name: "Campers", tagline: "" },
{ name: "Castles", tagline: "" },
{ name: "Farms", tagline: "" },
{ name: "National parks", tagline: "" },
{ name: "Windmills", tagline: "" },
{ name: "Luxe", tagline: "" },
{ name: "Containers", tagline: "" },
{ name: "Vineyards", tagline: "" },
],
},
{
heading: "Things to Do",
items: [
{ name: "London", tagline: "England" },
{ name: "Paris", tagline: "Île-de-France" },
{ name: "Amsterdam", tagline: "North Holland" },
{ name: "Miami", tagline: "Florida" },
{ name: "Tokyo", tagline: "Tokyo" },
{ name: "Vienna", tagline: "Vienna" },
{ name: "New York", tagline: "New York" },
{ name: "Madrid", tagline: "Community of Madrid" },
{ name: "Barcelona", tagline: "Catalonia" },
{ name: "Istanbul", tagline: "Istanbul" },
{ name: "Los Angeles", tagline: "California" },
{ name: "Rome", tagline: "Lazio" },
{ name: "Athens", tagline: "Greece" },
{ name: "Prague", tagline: "Czechia" },
{ name: "Orlando", tagline: "Florida" },
{ name: "Bali", tagline: "Indonesia" },
{ name: "Lisbon", tagline: "Lisbon" },
],
},
{
heading: "Travel Tips & Inspiration",
items: [
{ name: "Family travel hub", tagline: "Tips and inspiration" },
{ name: "Family budget travel", tagline: "Get there for less" },
{
name: "Vacation ideas for any budget",
tagline: "Make it special without making it spendy",
},
{
name: "Travel Europe on a budget",
tagline: "How to take the kids to Europe for less",
},
{ name: "Outdoor adventure", tagline: "Explore nature with the family" },
{
name: "Kid-friendly state parks",
tagline: "Check out these family-friendly hikes",
},
{
name: "Bucket list national parks",
tagline: "Must-see parks for family travel",
},
],
},
{
heading: "Airbnb-Friendly Apartments",
items: [
{ name: "Atlanta Metro", tagline: "Georgia" },
{ name: "Augusta", tagline: "Georgia" },
{ name: "Charlotte", tagline: "North Carolina" },
{ name: "Cincinnati", tagline: "Ohio" },
{ name: "Gainesville", tagline: "Florida" },
{ name: "Hoboken", tagline: "New Jersey" },
{ name: "Austin Metro", tagline: "Texas" },
{ name: "Cleveland", tagline: "Ohio" },
{ name: "Houston Metro", tagline: "Texas" },
{ name: "Birmingham", tagline: "Alabama" },
{ name: "Boston Metro", tagline: "Massachusetts" },
{ name: "Columbus", tagline: "Ohio" },
{ name: "Dallas", tagline: "Texas" },
{ name: "Indianapolis", tagline: "Indiana" },
{ name: "Jacksonville", tagline: "Florida" },
{ name: "Denver", tagline: "Colorado" },
{ name: "Boulder", tagline: "Colorado" },
],
},
];
type IconBarType = {
icon: React.ComponentType<{ className?: string }>;
label: string;
};
interface IconBarProps {
iconBar: IconBarType[];
category: String | undefined;
setCategory: (category: string) => void;
}
export default function IconBar({
iconBar,
category,
setCategory,
}: IconBarProps) {
const scrollContainerRef = useRef<HTMLDivElement>(null);
const [disableLeftArrow, setDisableLeftArrow] = useState(false);
const [disableRightArrow, setDisableRightArrow] = useState(false);
useEffect(() => {
const checkScrollArrows = () => {
if (scrollContainerRef.current) {
const {
scrollLeft = 0,
scrollWidth = 0,
clientWidth = 0,
} = scrollContainerRef.current;
setDisableLeftArrow(scrollLeft > 0);
setDisableRightArrow(scrollLeft < scrollWidth - clientWidth);
}
};
checkScrollArrows();
window.addEventListener("resize", checkScrollArrows);
return () => window.removeEventListener("resize", checkScrollArrows);
}, []);
const scroll = (direction: "left" | "right") => {
if (scrollContainerRef.current) {
const scrollAmount = direction === "left" ? -300 : 300;
scrollContainerRef.current.scrollBy({
left: scrollAmount,
behavior: "smooth",
});
}
};
return (
<HStackFull className="items-center md:px-[1.5vh] " gap="gap-[0px]">
<motion.div
className="hidden md:flex"
whileHover={{ scale: 1.08, transition: { duration: 0.3 } }}
onClick={() => scroll("left")}
>
<Icon icon={BiChevronLeftCircle} iconClassName="text-[3vh]" />
</motion.div>
<Box
className="w-full overflow-x-auto hide-scrollbar"
ref={scrollContainerRef}
>
<HStackFull className="w-fit items-center px-[1vh] md:px-[0px]">
{iconBar.map(({ icon: Icon, label }) => (
<Center
key={label} // Move the key prop here
className={`p-[0.5vh] hover:cursor-pointer `}
onClick={() => setCategory(label)}
>
<VStack
className={`flex-col items-center w-[9vh] h-[8vh] justify-center text-slate-800 border-b-[0.3vh] border-transparent transition-400 rounded-none
${category === label ? " border-b-slate-800 " : ""}
`}
gap="gap-[0px]"
>
<Icon className="text-3xl " />
<Text className="text-nowrap text-xs">{label}</Text>
</VStack>
</Center>
))}
</HStackFull>
</Box>
<motion.div
className="hidden md:inline"
whileHover={{ scale: 1.08, transition: { duration: 0.3 } }}
onClick={() => scroll("right")}
>
<Icon icon={BiChevronRightCircle} iconClassName="text-[3vh]" />
</motion.div>
</HStackFull>
);
}
export default function AirBnBPropertyCard({
property,
when,
}: {
property: AirbnbProperties;
when: string;
}) {
const [imageNumbers, setImageNumbers] = useState<number[]>([]);
const scrollRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setImageNumbers(GetRandomNumbers());
}, [property]);
const handleNext = () => {
scrollRef.current?.scrollBy({
left: scrollRef.current?.clientWidth,
behavior: "smooth",
});
};
const handlePrev = () => {
scrollRef.current?.scrollBy({
left: -scrollRef.current?.clientWidth,
behavior: "smooth",
});
};
const cardStyles =
"w-[90vw] sm:w-[45vw] md:w-[30vw] lg:w-[23vw] xxl:w-[18vw]";
const imageStyles = "";
return (
<Flex className="p-[1vh] hover:cursor-pointer">
<VStack key={property.id} className={`${cardStyles}`}>
<VStackFull className="relative group">
{property.badgeLabel !== "" && (
<Box
className="absolute top-[1vh] left-[1vh] z-10 bg-white px-[1vh] rounded-[2vh] font-semibold"
hoverCursor="hover:cursor-pointer"
>
{" "}
<Text className="text-sm">{property.badgeLabel}</Text>
</Box>
)}
<Box
className="absolute top-[2vh] right-[2vh] z-10"
hoverCursor="hover:cursor-pointer"
>
{" "}
<IoMdHeartEmpty className="text-[3.5vh] text-white bg-slate-900/50 p-[0.5vh] rounded-full" />
</Box>
<button
className="absolute left-[0.5vh] top-1/2 transform -translate-y-1/2 bg-white p-[0.3vh] rounded-full shadow-md z-10 opacity-0 group-hover:opacity-100 transition-400"
onClick={handlePrev}
>
<FaAngleLeft className="text-[2.3vh]" />
</button>
<button
className="absolute right-[0.5vh] top-1/2 transform -translate-y-1/2 bg-white p-[0.3vh] rounded-full shadow-md z-10 opacity-0 group-hover:opacity-100 transition-400"
onClick={handleNext}
>
<FaAngleRight className="text-[2.3vh]" />
</button>
<Box
hoverCursor="hover:cursor-pointer"
className={`w-full overflow-x-auto snap-x scroll-smooth snap-mandatory relative hide-scrollbar rounded-[2vh] ${imageStyles}`}
ref={scrollRef}
>
<HStack className="w-fit" hoverCursor="hover:cursor-pointer">
{imageNumbers.map((image, index) => (
<Flex
key={index}
className={`flex-shrink-0 rounded-[2vh] snap-start snap-always w-full`}
>
<Image
src={`https://mhejreuxaxxodkdlfcoq.supabase.co/storage/v1/render/image/public/darkVioletPublic/landing/mockups/airbnb_properties${String(
image
)}.png?width=900&resize=contain&quality=50`}
key={image}
alt={`airbnb ${index}`}
className="rounded-[2vh]"
/>
</Flex>
))}
</HStack>
</Box>
</VStackFull>
<VStackFull className="p-[1vh] gap-[0.5vh]" gap="gap-[0px]">
<HStackFull
hoverCursor="hover:cursor-pointer"
className="items-center justify-between"
>
<Text className="text-md leading-tight font-bold">
{property.location}
</Text>
<HStack
gap="gap-[0px]"
className="items-center"
hoverCursor="hover:cursor-pointer"
>
<FaStar className="text-black" />
<Text className="text-md leading-tight">
{Number(property.rating).toFixed(1)}
</Text>
</HStack>
</HStackFull>
<VStackFull align="items-start" gap="gap-[0px]">
<Text className="text-sm leading-tight">
{property.description}
</Text>
<Text className="text-sm leading-tight">{when}</Text>
{when.includes("week") ? (
<Text className="text-sm leading-tight">
${property.weeklyPrice.toLocaleString()} / week
</Text>
) : when.includes("month") ? (
<Text className="text-sm leading-tight">
${property.monthlyPrice.toLocaleString()} / month
</Text>
) : (
<Text className="text-sm leading-tight">
${property.nightlyPrice.toLocaleString()} / night
</Text>
)}
{/* <Text className="text-sm">
<span className="font-semibold">
${property.nightlyPrice.toLocaleString()}
</span>{" "}
/ night
</Text>
<Text className="text-sm">
<span className="font-semibold">
${property.weeklyPrice.toLocaleString()}
</span>{" "}
/ week
</Text>
<Text className="text-sm">
<span className="font-semibold">
${property.monthlyPrice.toLocaleString()}
</span>{" "}
/ month
</Text> */}
</VStackFull>
</VStackFull>
</VStack>
</Flex>
);
}
export type MenuStateType = "where" | "when" | "start" | "end" | "who" | null;
export const hoverBorder =
"border-[0.3vh] border-slate-800/20 hover:border-slate-800 transition-300";
const fadeInOut = {
initial: { opacity: 0, transition: { duration: 0.1 } },
animate: { opacity: 1, transition: { duration: 0.1 } },
exit: { opacity: 0, transition: { duration: 0.1 } },
};
export default function AirbnbSearchSmallScreens({
when,
setWhen,
selectedDateOne,
setSelectedDateOne,
selectedDateTwo,
setSelectedDateTwo,
}: {
when: string;
setWhen: (when: string) => void;
selectedDateOne: Date | null;
setSelectedDateOne: (date: Date | null) => void;
selectedDateTwo: Date | null;
setSelectedDateTwo: (date: Date | null) => void;
}) {
const [searchOpen, setSearchOpen] = useState(false);
const [where, setWhere] = useState("");
const [who, setWho] = useState("Add Guests");
const modalVariants = {
open: {
opacity: 1,
transition: { type: "spring", stiffness: 300, damping: 30 },
},
closed: {
opacity: 0,
transition: { type: "spring", stiffness: 300, damping: 30 },
},
};
const topAnimation = {
initial: { opacity: 0, y: "-30vh" },
whileInView: { opacity: 1, y: 0 },
transition: {
opacity: {
duration: 0.4,
delay: 0.1,
},
y: {
duration: 0.5,
delay: 0,
},
},
};
const bottomAnimation = {
initial: { opacity: 0, y: "8vh" },
whileInView: { opacity: 1, y: 0 },
transition: {
opacity: {
duration: 0.5,
delay: 0.1,
},
y: {
duration: 0.5,
delay: 0.2,
},
},
};
const [menuState, setMenuState] = useState<MenuStateType>("where");
const [adults, setAdults] = useState(0);
const [children, setChildren] = useState(0);
const [infants, setInfants] = useState(0);
const [pets, setPets] = useState(0);
return (
<>
{/* ****************** HEADER MAIN SEARCH ****************** */}
<CenterHorizontalFull className="px-[2vh] py-[1vh] h-fit">
<HStackFull className="items-center">
<motion.div
className="bg-zinc-100 shadowNarrowTight w-full h-[5.3vh] rounded-[3vh] flex items-center p-[1vh]"
onClick={() => setSearchOpen(true)}
>
<HStackFull
className="h-full items-center"
hoverCursor="hover:cursor-pointer"
>
<Icon
icon={SearchIcon}
iconClassName="text-[2.3vh]"
containerClassName="flex flex-shrink-0"
/>
<VStackFull align="items-start" gap="gap-[0px]">
<Text className="text-sm leading-tight">
{where ? where : "Where to?"}
</Text>
<HStackFull
className="text-xs leading-tight text-zinc-500 "
hoverCursor="hover:cursor-pointer"
>
<Text>{!where ? "Anywhere" : ""}</Text>
<Text>{when}</Text>
<Text>{who}</Text>
</HStackFull>
</VStackFull>{" "}
</HStackFull>
</motion.div>
<Center
className={`p-[1vh] h-fit rounded-full bg-zinc-100 flex-shrink-0 ${hoverBorder}`}
>
<Icon icon={FaSliders} iconClassName="text-[2vh]" />
</Center>
</HStackFull>
</CenterHorizontalFull>
{/* ****************** SEARCH MODAL ****************** */}
<Modal
isOpen={searchOpen}
setModalOpen={setSearchOpen}
onClose={() => setSearchOpen(false)}
showBottomClose={false}
showTopClose={false}
variants={modalVariants}
>
<VStackFull className="bg-slate-200 justify-between">
<motion.div
className="w-full"
initial={topAnimation.initial}
whileInView={topAnimation.whileInView}
transition={topAnimation.transition}
viewport={{
once: true,
}}
>
<FlexFull>
<VStackFull>
{/* ************* HEADER ************* */}
<HStackFull className="p-[1vh] justify-between">
<Center
className="bg-white rounded-full p-[0.2vh] border-960-md hover:border-transparent transition-400"
onClick={() => setSearchOpen(false)}
>
<Icon icon={MdClose} iconClassName="text-[2.3vh]" />{" "}
</Center>
{/* ************* STAYS EXPERIENCES ************* */}
<StaysExperiences />
<Text className="text-transparent">space</Text>
</HStackFull>
{/* ************* TOP COMPONENTS ************* */}
<VStackFull className="p-[1vh]">
{menuState === "where" ? (
<WhereTop
menuState={menuState}
where={where}
setWhere={setWhere}
setMenuState={setMenuState}
/>
) : (
<SectionCollapsed
label="Where"
onClick={() => setMenuState("where")}
status={where || "flexible"}
/>
)}
{menuState === "when" ? (
<WhenTop
menuState={menuState}
selectedDateOne={selectedDateOne}
selectedDateTwo={selectedDateTwo}
setSelectedDateOne={setSelectedDateOne}
setSelectedDateTwo={setSelectedDateTwo}
when={when}
setWhen={setWhen}
/>
) : (
<SectionCollapsed
label="When"
onClick={() => setMenuState("when")}
status={when}
/>
)}
{menuState === "who" ? (
<WhoTop
menuState={menuState}
who={who}
setWho={setWho}
adults={adults}
setAdults={setAdults}
children={children}
setChildren={setChildren}
infants={infants}
setInfants={setInfants}
pets={pets}
setPets={setPets}
/>
) : (
<SectionCollapsed
label="Who"
onClick={() => setMenuState("who")}
status={who}
/>
)}
</VStackFull>
</VStackFull>
</FlexFull>
</motion.div>
{/* ************* BOTTOM ************* */}
<motion.div
className="w-full flex h-[10vh]"
initial={bottomAnimation.initial}
whileInView={bottomAnimation.whileInView}
transition={bottomAnimation.transition}
viewport={{
once: true,
}}
>
<CenterHorizontalFull>
<button
className="bg-pink-500 px-[1.5vh] py-[0.5vh] text-white hover:bg-pink-700 transition-400 rounded-[2vh]"
onClick={() => setSearchOpen(false)}
>
<HStack
className="w-full items-center"
hoverCursor="hover:cursor-pointer"
>
<Icon icon={SearchIcon} />
<Text>Search</Text>
</HStack>
</button>
</CenterHorizontalFull>
</motion.div>
</VStackFull>
</Modal>
</>
);
}
export function AirbnbSearchLargeScreens({
when,
setWhen,
selectedDateOne,
setSelectedDateOne,
selectedDateTwo,
setSelectedDateTwo,
}: {
when: string;
setWhen: (when: string) => void;
selectedDateOne: Date | null;
setSelectedDateOne: (date: Date | null) => void;
selectedDateTwo: Date | null;
setSelectedDateTwo: (date: Date | null) => void;
}) {
return (
<HStackFull className="justify-between items-center px-[1.5vh] xl:px-[2.5vh] xxl:px-[3.5vh]">
<Logo />
<AirbnbSearchLargeConvertible
selectedDateOne={selectedDateOne}
selectedDateTwo={selectedDateTwo}
setSelectedDateOne={setSelectedDateOne}
setSelectedDateTwo={setSelectedDateTwo}
when={when}
setWhen={setWhen}
/>
<ExpandableSearchRightIcons />
</HStackFull>
);
}
export function AirbnbSearchLargeConvertible({
when,
setWhen,
selectedDateOne,
setSelectedDateOne,
selectedDateTwo,
setSelectedDateTwo,
}: {
when: string;
setWhen: (when: string) => void;
selectedDateOne: Date | null;
setSelectedDateOne: (date: Date | null) => void;
selectedDateTwo: Date | null;
setSelectedDateTwo: (date: Date | null) => void;
}) {
const [modalOpen, setModalOpen] = useState(false);
const modalVariants = {
open: {
opacity: 1,
// scale: 1,
y: 0,
transition: {
opacity: { duration: 0.3 }, // Slows down the opacity transition
scale: { type: "spring", stiffness: 300, damping: 30 },
y: { type: "spring", stiffness: 300, damping: 30 },
},
},
closed: {
opacity: 0,
// scale: 0,
y: "-1vh",
transition: {
opacity: { duration: 0.3 }, // Slows down the opacity transition
scale: { type: "spring", stiffness: 300, damping: 30 },
y: { type: "spring", stiffness: 300, damping: 30 },
},
},
};
const expandedVStacks =
"gap-[0px] leading-tight mPlus-font px-[2vh] py-[0.6vh] h-full rounded-[4vh] hover:bg-slate-100 transition-400 hover:cursor-pointer";
const expandedSelectedStyles =
"bg-slate-100 shadowNarrowTight transition-400";
const VStacksHeading = "leading-tight text-sm font-semibold text-nowrap";
const VStacksSubheading = "leading-tight text-xs text-nowrap";
const handleSelectExpanded = (selected: string) => {
return (e: React.MouseEvent) => {
e.stopPropagation();
setMenuState(selected as MenuStateType);
};
};
const [where, setWhere] = useState("");
const [who, setWho] = useState("Add Guests");
const [menuState, setMenuState] = useState<MenuStateType>("where");
const [adults, setAdults] = useState(0);
const [children, setChildren] = useState(0);
const [infants, setInfants] = useState(0);
const [pets, setPets] = useState(0);
return (
<>
<motion.div
className="bg-zinc-100 shadowNarrowTight w-fit h-[5.3vh] rounded-[3vh] flex items-center p-[1vh] hover:cursor-pointer "
variants={fadeInOut as Variants}
initial="initial"
animate="animate"
exit="exit"
onClick={() => setModalOpen(true)}
>
<HStackFull
className="text-sm leading-tight text-zinc-500 items-center justify-between"
gap="gap-[2vh]"
hoverCursor="hover:cursor-pointer"
>
<HStack
className="items-center px-[2vh] text-xs lg:text-sm leading-tight text-nowrap"
gap="gap-[1vh] lg:gap-[2vh]"
hoverCursor="hover:cursor-pointer"
>
<Text>{where || "Anywhere"}</Text>{" "}
<Text className="text-[3vh]">|</Text>
<Text>{when}</Text> <Text className="text-[3vh]">|</Text>
<Text>{who}</Text>
</HStack>
<Center
className={`p-[0.5vh] h-fit rounded-full bg-pink-500 flex-shrink-0 ${hoverBorder}`}
>
<Icon icon={SearchIcon} iconClassName="text-[2vh] text-white" />
</Center>
</HStackFull>
</motion.div>
<Modal
isOpen={modalOpen}
useModalContent={false}
setModalOpen={setModalOpen}
modalClassName="fixed top-0 right-0 left-0 "
onClose={() => setModalOpen(false)}
modalSize="w-screen h-fit"
overlayColor="bg-slate-700/20"
overlayBlur="blur-[1px]"
variants={modalVariants as Variants}
>
<motion.div
className="flex w-full h-full flex-col"
onClick={() => setMenuState(null)}
>
<VStackFull className="h-[16vh] justify-between ">
<VStackFull className="h-full justify-between bg-zinc-100 p-[1.5vh]">
<CenterHorizontalFull>
<HStackFull className="justify-between px-[1vh] xxl:px-[2vh] items-center">
<Logo /> <StaysExperiences />
<ExpandableSearchRightIcons />
</HStackFull>
</CenterHorizontalFull>
<CenterHorizontalFull>
<HStack className="w-90% xl:w-70% xxl:w-60% justify-evenly items-center rounded-[4vh] bg-slate-200 p-[0.4vh] shadowNarrowTight">
<HStackFull className="justify-evenly">
<VStack
className={`${expandedVStacks} ${
menuState === "where" && expandedSelectedStyles
}`}
align="items-start"
onClick={handleSelectExpanded("where")}
>
<Text className={VStacksHeading}>Where</Text>
<Text className={VStacksSubheading}>
{where || "Search Destinations"}
</Text>
</VStack>{" "}
<VStack
className={`${expandedVStacks} ${
menuState === "start" && expandedSelectedStyles
}`}
align="items-start"
onClick={handleSelectExpanded("start")}
>
{" "}
<Text className={VStacksHeading}>Check In</Text>
<Text className={VStacksSubheading}>
{selectedDateOne ? (
<FormatDate
inputDate={selectedDateOne}
format="text"
dateOnly
/>
) : (
"Add Dates"
)}
</Text>
</VStack>{" "}
<VStack
className={`${expandedVStacks} ${
menuState === "end" && expandedSelectedStyles
}`}
align="items-start"
onClick={handleSelectExpanded("end")}
>
{" "}
<Text className={VStacksHeading}>Check Out</Text>
<Text className={VStacksSubheading}>
{selectedDateTwo ? (
<FormatDate
inputDate={selectedDateTwo}
format="text"
dateOnly
/>
) : (
"Add Dates"
)}
</Text>
</VStack>{" "}
<VStack
className={`${expandedVStacks} ${
menuState === "who" && expandedSelectedStyles
}`}
align="items-start"
onClick={handleSelectExpanded("who")}
>
{" "}
<Text className={VStacksHeading}>Who</Text>
<Text className={VStacksSubheading}>
{who || "Guest Details"}
</Text>
</VStack>
</HStackFull>
<Flex className="px-[2vh]">
<Center
className={`p-[0.8vh] h-fit rounded-full bg-pink-500 flex-shrink-0 ${hoverBorder} flex-shrink-0 md:flex xl:hidden`}
>
<Icon
icon={SearchIcon}
iconClassName="text-[2.3vh] text-white"
/>
</Center>
<Center
className={`p-[0.8vh] h-fit rounded-[3vh] bg-pink-500 flex-shrink-0 ${hoverBorder} flex-shrink-0 md:hidden xl:flex`}
>
<HStack className="h-full items-center">
<Icon
icon={SearchIcon}
iconClassName="text-[2.3vh] text-white"
/>
<Text className="text-md text-white ">Search</Text>
</HStack>
</Center>
</Flex>
</HStack>
</CenterHorizontalFull>
</VStackFull>
</VStackFull>
{menuState === "where" && (
<FlexFull>
<Flex className="h-[60vh] w-[60vh] p-[1vh]">
<WhereTop
menuState={menuState}
where={where}
setWhere={setWhere}
setMenuState={setMenuState}
/>
</Flex>
</FlexFull>
)}
{menuState === "start" && (
<FlexFull className="justify-center">
<Flex className="h-[60vh] w-[60vh] p-[1vh]">
<WhenTop
menuState={menuState}
selectedDateOne={selectedDateOne}
selectedDateTwo={selectedDateTwo}
setSelectedDateOne={setSelectedDateOne}
setSelectedDateTwo={setSelectedDateTwo}
when={when}
setWhen={setWhen}
/>
</Flex>
</FlexFull>
)}
{menuState === "end" && (
<FlexFull className="justify-center">
<Flex className="h-[60vh] w-[60vh] p-[1vh]">
<WhenTop
menuState={menuState}
selectedDateOne={selectedDateOne}
selectedDateTwo={selectedDateTwo}
setSelectedDateOne={setSelectedDateOne}
setSelectedDateTwo={setSelectedDateTwo}
when={when}
setWhen={setWhen}
/>
</Flex>
</FlexFull>
)}
{menuState === "who" && (
<FlexFull className="justify-end">
<Flex className="h-[60vh] w-[60vh] p-[1vh]">
<WhoTop
menuState={menuState}
who={who}
setWho={setWho}
adults={adults}
setAdults={setAdults}
children={children}
setChildren={setChildren}
infants={infants}
setInfants={setInfants}
pets={pets}
setPets={setPets}
/>
</Flex>
</FlexFull>
)}
</motion.div>
</Modal>
</>
);
}
function SectionCollapsed({
label,
onClick,
status,
}: {
label: string;
onClick: () => void;
status: string;
}) {
return (
<TransitionFull className="p-[0.5vh]">
<div
className={`p-[1vh] hover:cursor-pointer flex w-full justify-between ${hoverBorder} bg-white`}
onClick={() => {
console.log(`Changing menu state to: ${label}`);
onClick();
}}
>
<Text className="font-semibold">{label}</Text>
<Text>{status}</Text>
</div>
</TransitionFull>
);
}
function WhereTop({
menuState,
where,
setWhere,
setMenuState,
}: {
menuState: MenuStateType;
where: string;
setWhere: (location: string) => void;
setMenuState: (menuState: MenuStateType) => void;
}) {
const locations = [
{ label: "flexible", fileName: "flexible" },
{ label: "Europe", fileName: "europe" },
{ label: "Mexico", fileName: "mexico" },
{ label: "Caribben", fileName: "caribbean" },
{ label: "Italy", fileName: "italy" },
{ label: "South America", fileName: "south_america" },
{ label: "Columbia", fileName: "columbia" },
{ label: "Central America", fileName: "central_america" },
{ label: "United Kingdom", fileName: "united_kingdom" },
];
return (
<AnimatePresence mode="wait">
<motion.div
key={menuState}
className="w-full flex flex-col"
variants={fadeInOut}
initial="initial"
animate="animate"
exit="exit"
onClick={(e) => e.stopPropagation()}
>
<VStackFull className="rounded-[2vh] shadowNarrowTight bg-white py-[1vh]">
<VStackFull className="p-[3vh] " gap="gap-[2vh]">
<FlexFull className="text-lg mPlus-font font-semibold">
Where to?
</FlexFull>
<FlexFull className="relative">
<Icon
icon={SearchIcon}
iconClassName="text-[2vh]"
containerClassName="absolute top-[1.3vh] left-[1vh]"
/>
<input
className="w-full h-[4.5vh] pl-[4vh] border-860-md text-md"
placeholder={where}
/>
</FlexFull>
</VStackFull>
<Box className="w-full overflow-x-auto hide-scrollbar">
<HStackFull className="w-fit px-[1vh]" gap="gap-[2vh]">
{locations.map((location) => (
<Flex className="px-[1vh]">
<VStack key={location.label} gap="gap-[0px]">
<Box
className={`w-[15vh] h-[15vh] rounded-[1vh] ${hoverBorder} hover:cursor-pointer`}
onClick={() => {
setWhere(location.label);
setMenuState("when");
}}
>
<Image
src={`https://mhejreuxaxxodkdlfcoq.supabase.co/storage/v1/render/image/public/darkVioletPublic/landing/mockups/airbnb_locations_${location.fileName}.png?width=700&resize=contain&quality=60`}
alt={location.label}
className=" rounded-[0.8vh] w-full h-full"
/>
</Box>
<Text>{location.label}</Text>
</VStack>
</Flex>
))}
</HStackFull>
</Box>
</VStackFull>
</motion.div>
</AnimatePresence>
);
}
function WhenTop({
menuState,
selectedDateOne,
selectedDateTwo,
setSelectedDateOne,
setSelectedDateTwo,
when,
setWhen,
}: {
menuState: MenuStateType;
selectedDateOne: Date | null;
selectedDateTwo: Date | null;
setSelectedDateOne: (date: Date | null) => void;
setSelectedDateTwo: (date: Date | null) => void;
when: string;
setWhen: (when: string) => void;
}) {
return (
<AnimatePresence mode="wait">
<motion.div
key={menuState}
className="w-full flex flex-col"
variants={fadeInOut}
initial="initial"
animate="animate"
exit="exit"
onClick={(e) => e.stopPropagation()}
>
<VStackFull className="rounded-[2vh] shadowNarrowTight bg-white py-[1vh]">
<VStackFull className="p-[3vh] " gap="gap-[2vh]">
<FlexFull className="text-lg mPlus-font font-semibold">
When is your trip?
</FlexFull>
<DateSelector
selectedDateOne={selectedDateOne}
setSelectedDateTwo={setSelectedDateTwo}
selectedDateTwo={selectedDateTwo}
setSelectedDateOne={setSelectedDateOne}
when={when}
setWhen={setWhen}
/>
</VStackFull>
</VStackFull>
</motion.div>
</AnimatePresence>
);
}
function WhoTop({
menuState,
who,
setWho,
adults,
setAdults,
children,
setChildren,
infants,
setInfants,
pets,
setPets,
}: {
menuState: MenuStateType;
who: string;
setWho: (who: string) => void;
adults: number;
setAdults: (adults: number) => void;
children: number;
setChildren: (children: number) => void;
infants: number;
setInfants: (infants: number) => void;
pets: number;
setPets: (pets: number) => void;
}) {
return (
<AnimatePresence mode="wait">
<motion.div
key={menuState}
className="w-full flex flex-col"
variants={fadeInOut}
initial="initial"
animate="animate"
exit="exit"
>
<VStackFull className="rounded-[2vh] shadowNarrowTight bg-white py-[1vh]">
<VStackFull className="p-[3vh] " gap="gap-[2vh]">
<FlexFull className="text-lg mPlus-font font-semibold">
<WhoTopContent
menuState={menuState}
who={who}
setWho={setWho}
adults={adults}
setAdults={setAdults}
children={children}
setChildren={setChildren}
infants={infants}
setInfants={setInfants}
pets={pets}
setPets={setPets}
/>
</FlexFull>
</VStackFull>
</VStackFull>
</motion.div>
</AnimatePresence>
);
}
function StaysExperiences() {
const [stays, setStays] = useState("stays");
return (
<motion.div className="flex gap-[3vh] mPlus-font" layoutId="border-bottom">
<motion.div
onClick={() => setStays("stays")}
className={`text-md hover:cursor-pointer ${
stays === "stays"
? "font-bold border-b-[0.3vh] border-b-slate-800 rounded-none transition-300"
: "border-b-[0.3vh] border-b-transparent"
}`}
>
Stays
</motion.div>
<motion.div
onClick={() => setStays("experiences")}
className={`text-md hover:cursor-pointer ${
stays === "experiences"
? "font-bold border-b-[0.3vh] border-b-slate-800 rounded-none transition-300"
: "border-b-[0.3vh] border-b-transparent"
}`}
>
Experiences
</motion.div>
<motion.div
onClick={() => setStays("online experiences")}
className={`hidden md:flex text-md hover:cursor-pointer ${
stays === "online experiences"
? "font-bold border-b-[0.3vh] border-b-slate-800 rounded-none transition-300"
: "border-b-[0.3vh] border-b-transparent"
}`}
>
Online Experiences
</motion.div>
</motion.div>
);
}
function ExpandableSearchRightIcons() {
return (
<HStack className="flex-shrink-0 items-center" gap="gap-[2vh]">
<Flex className="rounded-[3vh] hover:bg-slate-800/10 transition-400 px-[1vh] py-[0.5vh] hover:cursor-pointer">
List yours
</Flex>
<Flex className="rounded-[3vh] hover:bg-slate-800/10 transition-400 px-[1vh] py-[0.5vh]">
<Icon icon={CiGlobe} iconClassName="text-[2.5vh]" />
</Flex>
<HStack
className="py-[0.5vh] px-[1vh] border-[0.2vh] border-slate-800/40 rounded-[3vh] text-slate-800/80 hover:shadowNarrowTight transition-400"
hoverCursor="hover:cursor-pointer"
>
<Flex>
<Icon icon={IoMenu} iconClassName="text-[3vh]" />
</Flex>
<Flex>
{" "}
<Icon icon={FaUserCircle} iconClassName="text-[3vh]" />
</Flex>
</HStack>
</HStack>
);
}
function Logo() {
return (
<HStack className="items-center" gap="gap-[0vh]">
<Icon
icon={TbBrandAirbnb}
iconClassName="text-[4vh] xl:text-[5vh] text-pink-500"
/>
<Text className="text-lg hidden lg:flex mPlus-font font-semibold text-pink-500">
airbnb
</Text>
</HStack>
);
}
import { motion, AnimatePresence } from "framer-motion";
import { MenuStateType, hoverBorder } from "./airbnbSearch";
import VStackFull from "~/components/buildingBlocks/vStackFull";
import FlexFull from "~/components/buildingBlocks/flexFull";
import { SearchIcon } from "styles";
import Icon from "~/components/buildingBlocks/icon";
import Box from "~/components/buildingBlocks/box";
import HStackFull from "~/components/buildingBlocks/hStackFull";
import Flex from "~/components/buildingBlocks/flex";
import VStack from "~/components/buildingBlocks/vStack";
import Image from "~/components/buildingBlocks/image";
import Text from "~/components/buildingBlocks/text";
import { useEffect, useState } from "react";
import { FiMinusCircle, FiPlusCircle } from "react-icons/fi";
import DateSelector from "./airbnbDateSelector";
const fadeInOut = {
initial: { opacity: 0, transition: { duration: 0.5 } },
animate: { opacity: 1, transition: { duration: 0.5 } },
exit: { opacity: 0, transition: { duration: 0.5 } },
};
export function WhereTop({
menuState,
where,
setWhere,
setMenuState,
}: {
menuState: MenuStateType;
where: string;
setWhere: (location: string) => void;
setMenuState: (menuState: MenuStateType) => void;
}) {
const locations = [
{ label: "flexible", fileName: "flexible" },
{ label: "Europe", fileName: "europe" },
{ label: "Mexico", fileName: "mexico" },
{ label: "Caribben", fileName: "caribbean" },
{ label: "Italy", fileName: "italy" },
{ label: "South America", fileName: "south_america" },
{ label: "Columbia", fileName: "columbia" },
{ label: "Central America", fileName: "central_america" },
{ label: "United Kingdom", fileName: "united_kingdom" },
];
return (
<AnimatePresence mode="wait">
<motion.div
key={menuState}
className="w-full flex flex-col"
variants={fadeInOut}
initial="initial"
animate="animate"
exit="exit"
onClick={(e) => e.stopPropagation()}
>
<VStackFull className="rounded-[2vh] shadowNarrowTight bg-white py-[1vh]">
<VStackFull className="p-[3vh] " gap="gap-[2vh]">
<FlexFull className="text-lg mPlus-font font-semibold">
Where to?
</FlexFull>
<FlexFull className="relative">
<Icon
icon={SearchIcon}
iconClassName="text-[2vh]"
containerClassName="absolute top-[1.3vh] left-[1vh]"
/>
<input
className="w-full h-[4.5vh] pl-[4vh] border-860-md text-md"
placeholder={where}
/>
</FlexFull>
</VStackFull>
<Box className="w-full overflow-x-auto hide-scrollbar">
<HStackFull className="w-fit px-[1vh]" gap="gap-[2vh]">
{locations.map((location) => (
<Flex className="px-[1vh]">
<VStack key={location.label} gap="gap-[0px]">
<Box
className={`w-[15vh] h-[15vh] rounded-[1vh] ${hoverBorder} hover:cursor-pointer`}
onClick={() => {
setWhere(location.label);
setMenuState("when");
}}
>
<Image
src={`https://mhejreuxaxxodkdlfcoq.supabase.co/storage/v1/render/image/public/darkVioletPublic/landing/mockups/airbnb_locations_${location.fileName}.png?width=700&resize=contain&quality=60`}
alt={location.label}
className=" rounded-[0.8vh] w-full h-full"
/>
</Box>
<Text>{location.label}</Text>
</VStack>
</Flex>
))}
</HStackFull>
</Box>
</VStackFull>
</motion.div>
</AnimatePresence>
);
}
export function WhenTop({
menuState,
selectedDateOne,
selectedDateTwo,
setSelectedDateOne,
setSelectedDateTwo,
}: {
menuState: MenuStateType;
selectedDateOne: Date | null;
selectedDateTwo: Date | null;
setSelectedDateOne: (date: Date | null) => void;
setSelectedDateTwo: (date: Date | null) => void;
}) {
return (
<AnimatePresence mode="wait">
<motion.div
key={menuState}
className="w-full flex flex-col"
variants={fadeInOut}
initial="initial"
animate="animate"
exit="exit"
onClick={(e) => e.stopPropagation()}
>
<VStackFull className="rounded-[2vh] shadowNarrowTight bg-white py-[1vh]">
<VStackFull className="p-[3vh] " gap="gap-[2vh]">
<FlexFull className="text-lg mPlus-font font-semibold">
When is your trip?
</FlexFull>
<DateSelector
selectedDateOne={selectedDateOne}
setSelectedDateTwo={setSelectedDateTwo}
selectedDateTwo={selectedDateTwo}
setSelectedDateOne={setSelectedDateOne}
/>
</VStackFull>
</VStackFull>
</motion.div>
</AnimatePresence>
);
}
type CategoryProps = {
label: string;
description: string;
count: number;
setCount: (count: number) => void;
};
const Category = ({ label, description, count, setCount }: CategoryProps) => {
const handleIncrement = () => setCount(count + 1);
const handleDecrement = () => {
if (count > 0) setCount(count - 1);
};
return (
<div
className="flex justify-between items-center p-[2vh] border-b border-gray-300 mPlus-font"
onClick={(e) => e.stopPropagation()}
>
<div>
<div className="text-lg font-semibold">{label}</div>
<div className="text-gray-500">{description}</div>
</div>
<div className="flex items-center">
<button
className="text-xl p-[1vh] leading-tight"
onClick={handleDecrement}
disabled={count === 0}
>
<Icon
icon={FiMinusCircle}
iconClassName={`text-xl ${count < 1 && "text-slate-900/30"}`}
/>
</button>
<span className="mx-4">{count}</span>
<button className="text-xl" onClick={handleIncrement}>
<Icon icon={FiPlusCircle} iconClassName="text-xl" />
</button>
</div>
</div>
);
};
export function WhoTopContent({
menuState,
who,
setWho,
adults,
setAdults,
children,
setChildren,
infants,
setInfants,
pets,
setPets,
}: {
menuState: MenuStateType;
who: string;
setWho: (who: string) => void;
adults: number;
setAdults: (adults: number) => void;
children: number;
setChildren: (children: number) => void;
infants: number;
setInfants: (infants: number) => void;
pets: number;
setPets: (pets: number) => void;
}) {
useEffect(() => {
const parts = [];
const totalPeople = adults + children;
if (totalPeople > 0) {
parts.push(`${totalPeople} ${totalPeople === 1 ? "guest" : "guests"}`);
}
if (infants > 0) {
parts.push(`${infants} ${infants === 1 ? "infant" : "infants"}`);
}
if (pets > 0) {
parts.push(`${pets} ${pets === 1 ? "pet" : "pets"}`);
}
setWho(parts.join(", "));
}, [adults, children, infants, pets]);
return (
<AnimatePresence mode="wait">
<motion.div
key={menuState}
className="w-full flex flex-col"
variants={fadeInOut}
initial="initial"
animate="animate"
exit="exit"
>
<VStackFull className="rounded-[2vh] shadowNarrowTight bg-white py-[1vh]">
<VStackFull className="p-[3vh] " gap="gap-[2vh]">
<FlexFull className="text-lg mPlus-font font-semibold">
<div className="w-full mx-auto bg-white shadow-md rounded-lg">
<Category
label="Adults"
description="Ages 13 or above"
count={adults}
setCount={setAdults}
/>
<Category
label="Children"
description="Ages 2 – 12"
count={children}
setCount={setChildren}
/>
<Category
label="Infants"
description="Under 2"
count={infants}
setCount={setInfants}
/>
<Category
label="Pets"
description="Bringing a service animal?"
count={pets}
setCount={setPets}
/>
</div>
</FlexFull>
<div className="text-center font-medium">{who}</div>
</VStackFull>
</VStackFull>
</motion.div>
</AnimatePresence>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment