Доработки по главной странице, итерация 2

This commit is contained in:
Sergey Bolshakov 2025-10-26 14:09:46 +03:00
parent 3b2a71bace
commit 6ee8a8f624
12 changed files with 174 additions and 108 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -15,7 +15,6 @@ import { DatePicker } from "@/components/ui/date-picker";
import Icon from "@/components/ui/icon"; import Icon from "@/components/ui/icon";
export default function Hero() { export default function Hero() {
const [location, setLocation] = useState("");
const [guests, setGuests] = useState(""); const [guests, setGuests] = useState("");
return ( return (
@ -54,37 +53,15 @@ export default function Hero() {
<div className="flex flex-col md:flex-row gap-4"> <div className="flex flex-col md:flex-row gap-4">
{/* Локация */} {/* Локация */}
<div className="flex-1"> <div className="flex-1">
<Select <Button variant="outline" className="w-full h-[64px] px-4 justify-start">
value={location} <div className="flex items-center">
onValueChange={setLocation} <Icon
> name="map"
<SelectTrigger className="w-full h-[64px] px-4"> className="w-5 h-5 text-brand mr-3"
<div className="flex items-center"> />
<Icon <span className="font-normal">Балаклава</span>
name="map" </div>
className="w-5 h-5 text-brand mr-3" </Button>
/>
<SelectValue placeholder="Выберите локацию" />
</div>
</SelectTrigger>
<SelectContent>
<SelectItem value="balaklava">
Балаклава
</SelectItem>
<SelectItem value="sevastopol">
Севастополь
</SelectItem>
<SelectItem value="yalta">
Ялта
</SelectItem>
<SelectItem value="sudak">
Судак
</SelectItem>
<SelectItem value="kerch">
Керчь
</SelectItem>
</SelectContent>
</Select>
</div> </div>
{/* Дата и время */} {/* Дата и время */}
@ -143,7 +120,7 @@ export default function Hero() {
</div> </div>
{/* Кнопка поиска */} {/* Кнопка поиска */}
<Button className="bg-brand hover:bg-brand-hover active:bg-brand-active font-bold text-white h-[64px] w-[176px] px-8"> <Button variant="gradient" className="font-bold text-white h-[64px] w-[176px] px-8">
Найти Найти
</Button> </Button>
</div> </div>

View File

@ -22,7 +22,7 @@ export default function VideoSection() {
alt="Яхта SALVADOR на море" alt="Яхта SALVADOR на море"
width={800} width={800}
height={600} height={600}
className="w-full h-auto" className="w-full h-auto cursor-pointer"
/> />
</div> </div>
</div> </div>

View File

@ -17,7 +17,7 @@ const yachts = [
length: "12 метров", length: "12 метров",
price: "от 12 500 ₽ / час", price: "от 12 500 ₽ / час",
feet: "7 Футов", feet: "7 Футов",
img: "/images/yacht.jpg", img: "/images/yachts/yacht1.jpg",
badge: "Быстрая бронь", badge: "Быстрая бронь",
}, },
{ {
@ -25,7 +25,7 @@ const yachts = [
length: "14 метров", length: "14 метров",
price: "от 26 400 ₽ / час", price: "от 26 400 ₽ / час",
feet: "7 Футов", feet: "7 Футов",
img: "/images/yacht.jpg", img: "/images/yachts/yacht2.jpg",
badge: "По запросу", badge: "По запросу",
}, },
{ {
@ -33,7 +33,7 @@ const yachts = [
length: "22 метра", length: "22 метра",
price: "от 48 000 ₽ / час", price: "от 48 000 ₽ / час",
feet: "7 Футов", feet: "7 Футов",
img: "/images/yacht.jpg", img: "/images/yachts/yacht3.jpg",
badge: "По запросу", badge: "По запросу",
}, },
{ {
@ -41,7 +41,7 @@ const yachts = [
length: "13.6 метров", length: "13.6 метров",
price: "от 17 400 ₽ / час", price: "от 17 400 ₽ / час",
feet: "7 Футов", feet: "7 Футов",
img: "/images/yacht.jpg", img: "/images/yachts/yacht4.jpg",
badge: "По запросу", badge: "По запросу",
}, },
{ {
@ -49,7 +49,7 @@ const yachts = [
length: "13 метров", length: "13 метров",
price: "от 14 400 ₽ / час", price: "от 14 400 ₽ / час",
feet: "7 Футов", feet: "7 Футов",
img: "/images/yacht.jpg", img: "/images/yachts/yacht5.jpg",
badge: "По запросу", badge: "По запросу",
}, },
{ {
@ -57,7 +57,7 @@ const yachts = [
length: "12 метров", length: "12 метров",
price: "от 12 480 ₽ / час", price: "от 12 480 ₽ / час",
feet: "7 Футов", feet: "7 Футов",
img: "/images/yacht.jpg", img: "/images/yachts/yacht6.jpg",
badge: "По запросу", badge: "По запросу",
}, },
]; ];
@ -238,13 +238,18 @@ export default function YachtGrid() {
</div> </div>
{/* Book button */} {/* Book button */}
<Button className="w-full text-white py-4 text-lg font-bold mb-6 rounded-lg shadow-md"> <Button
variant="gradient"
className="font-bold text-white h-[64px] w-full px-8"
>
Забронировать Забронировать
</Button> </Button>
{/* Total price */} {/* Total price */}
<div className="flex justify-between items-center text-l font-bold text-gray-800"> <div className="flex justify-between items-center text-l mt-6 font-bold text-gray-800">
<span className="font-normal">Итого:</span> <span className="font-normal">
Итого:
</span>
<span> <span>
{featuredYacht.totalPrice} {featuredYacht.totalPrice}
</span> </span>
@ -276,14 +281,7 @@ export default function YachtGrid() {
{/* Badge Overlay */} {/* Badge Overlay */}
<div className="absolute top-3 left-3"> <div className="absolute top-3 left-3">
<div className="flex items-center justify-center bg-black/40 text-white px-3 py-1 rounded-lg text-sm flex items-center gap-1"> <div className="flex items-center justify-center bg-black/40 text-white px-3 py-1 rounded-lg text-sm flex items-center gap-1">
<Icon <Icon size={16} name="restart" />
size={16}
name={
idx === 0
? "logo"
: "restart"
}
/>
<span>{yacht.badge}</span> <span>{yacht.badge}</span>
</div> </div>
</div> </div>

View File

@ -45,6 +45,7 @@
--color-brand: var(--brand); --color-brand: var(--brand);
--color-brand-hover: var(--brand-hover); --color-brand-hover: var(--brand-hover);
--color-brand-active: var(--brand-active); --color-brand-active: var(--brand-active);
--color-brand-gradient: var(--brand-gradient);
--radius-sm: calc(var(--radius) - 4px); --radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px); --radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius); --radius-lg: var(--radius);
@ -87,6 +88,7 @@
--brand: oklch(0.5588 0.0992 215.93); --brand: oklch(0.5588 0.0992 215.93);
--brand-hover: oklch(0.4588 0.0992 215.93); /* #006B7A */ --brand-hover: oklch(0.4588 0.0992 215.93); /* #006B7A */
--brand-active: oklch(0.3588 0.0992 215.93); --brand-active: oklch(0.3588 0.0992 215.93);
--brand-gradient: linear-gradient(90deg, #0072A8 0%, #0598DE 100%);
} }
.dark { .dark {

View File

@ -1,57 +1,65 @@
import * as React from "react" import * as React from "react";
import { Slot } from "@radix-ui/react-slot" import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{ {
variants: { variants: {
variant: { variant: {
default: default:
"bg-primary text-primary-foreground border hover:bg-primary/90", "bg-primary text-primary-foreground border hover:bg-primary/90",
destructive: destructive:
"bg-destructive text-destructive-foreground border hover:bg-destructive/90", "bg-destructive text-destructive-foreground border hover:bg-destructive/90",
outline: outline:
"border border-input bg-background border hover:bg-accent hover:text-accent-foreground", "border border-input bg-background border hover:bg-accent hover:text-accent-foreground",
secondary: secondary:
"bg-secondary text-secondary-foreground border hover:bg-secondary/80", "bg-secondary text-secondary-foreground border hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, gradient: "text-white border-0 hover:opacity-90",
size: { },
default: "h-9 px-4 py-2", size: {
sm: "h-8 rounded-full px-3 text-xs", default: "h-9 px-4 py-2",
lg: "h-10 rounded-full px-8", sm: "h-8 rounded-full px-3 text-xs",
icon: "h-9 w-9", lg: "h-10 rounded-full px-8",
}, icon: "h-9 w-9",
}, },
defaultVariants: { },
variant: "default", defaultVariants: {
size: "default", variant: "default",
}, size: "default",
} },
) }
);
export interface ButtonProps export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>, extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> { VariantProps<typeof buttonVariants> {
asChild?: boolean asChild?: boolean;
} }
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => { ({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : "button";
return (
<Comp // Inline стили для градиента
className={cn(buttonVariants({ variant, size, className }))} const gradientStyle = variant === "gradient" ? {
ref={ref} background: "linear-gradient(90deg, #0072A8 0%, #0598DE 100%)",
{...props} } : {};
/>
) return (
} <Comp
) className={cn(buttonVariants({ variant, size, className }))}
Button.displayName = "Button" style={gradientStyle}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = "Button";
export { Button, buttonVariants } export { Button, buttonVariants };

View File

@ -2,6 +2,8 @@
import * as React from "react"; import * as React from "react";
import { format } from "date-fns"; import { format } from "date-fns";
import { ru } from "date-fns/locale";
import { ChevronDownIcon } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar"; import { Calendar } from "@/components/ui/calendar";
@ -14,6 +16,13 @@ import Icon from "./icon";
export function DatePicker() { export function DatePicker() {
const [date, setDate] = React.useState<Date>(); const [date, setDate] = React.useState<Date>();
const [departureTime, setDepartureTime] = React.useState("12:00");
const [arrivalTime, setArrivalTime] = React.useState("13:00");
const handleApply = () => {
// Логика применения выбранных даты и времени
console.log("Применено:", { date, departureTime, arrivalTime });
};
return ( return (
<Popover> <Popover>
@ -21,21 +30,93 @@ export function DatePicker() {
<Button <Button
variant="outline" variant="outline"
data-empty={!date} data-empty={!date}
className="data-[empty=true]:text-muted-foreground w-full h-[64px] justify-start text-left font-normal" className="w-full h-[64px] justify-start text-left font-normal"
> >
<Icon <Icon name="calendar" className="w-4 h-4 text-brand mr-2" />
name="calendar"
className="w-4 h-4 text-brand mr-2"
/>
{date ? ( {date ? (
format(date, "dd.MM.yyyy") format(date, `dd.MM, ${departureTime} - ${arrivalTime}`)
) : ( ) : (
<span>Выберите дату</span> <span>Выберите дату и время</span>
)} )}
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-auto p-0"> <PopoverContent className="w-[360px] p-0 bg-white rounded-[20px] shadow-lg">
<Calendar mode="single" selected={date} onSelect={setDate} /> <div className="p-4 w-full">
{/* Календарь */}
<Calendar
mode="single"
selected={date}
onSelect={setDate}
className="mb-4 "
locale={ru}
disabled={(date) =>
date < new Date(new Date().setHours(0, 0, 0, 0))
}
classNames={{
root: "w-full",
month: "flex w-full flex-col gap-4",
button_previous:
"h-8 w-8 flex items-center justify-center hover:bg-gray-100 rounded-md",
button_next:
"h-8 w-8 flex items-center justify-center hover:bg-gray-100 rounded-md",
month_caption:
"flex h-8 w-full items-center justify-center px-8 text-gray-700 font-semibold",
table: "w-full border-collapse",
weekdays: "flex",
weekday:
"flex-1 text-gray-500 text-xs font-normal p-2 text-center",
week: "mt-2 flex w-full",
today: "bg-gray-100 text-gray-900",
outside: "text-gray-300",
disabled: "text-gray-400 cursor-not-allowed",
}}
/>
{/* Поля времени */}
<div className="flex gap-3 mb-4">
<div className="flex-1">
<label className="block text-xs text-gray-500 mb-1">
Выход
</label>
<div className="relative">
<input
type="time"
value={departureTime}
onChange={(e) =>
setDepartureTime(e.target.value)
}
className="w-full h-10 px-3 border border-gray-300 rounded-md text-gray-700 font-medium focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent"
/>
<ChevronDownIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500 pointer-events-none" />
</div>
</div>
<div className="flex-1">
<label className="block text-xs text-gray-500 mb-1">
Заход
</label>
<div className="relative">
<input
type="time"
value={arrivalTime}
onChange={(e) =>
setArrivalTime(e.target.value)
}
className="w-full h-10 px-3 border border-gray-300 rounded-md text-gray-700 font-medium focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent"
/>
<ChevronDownIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500 pointer-events-none" />
</div>
</div>
</div>
{/* Кнопка Применить */}
<Button
onClick={handleApply}
variant="gradient"
className="font-bold text-white h-[44px] w-full px-8"
>
Применить
</Button>
</div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
); );