Доработки по главной странице, итерация 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";
export default function Hero() {
const [location, setLocation] = useState("");
const [guests, setGuests] = useState("");
return (
@ -54,37 +53,15 @@ export default function Hero() {
<div className="flex flex-col md:flex-row gap-4">
{/* Локация */}
<div className="flex-1">
<Select
value={location}
onValueChange={setLocation}
>
<SelectTrigger className="w-full h-[64px] px-4">
<div className="flex items-center">
<Icon
name="map"
className="w-5 h-5 text-brand mr-3"
/>
<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>
<Button variant="outline" className="w-full h-[64px] px-4 justify-start">
<div className="flex items-center">
<Icon
name="map"
className="w-5 h-5 text-brand mr-3"
/>
<span className="font-normal">Балаклава</span>
</div>
</Button>
</div>
{/* Дата и время */}
@ -143,7 +120,7 @@ export default function Hero() {
</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>
</div>

View File

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

View File

@ -17,7 +17,7 @@ const yachts = [
length: "12 метров",
price: "от 12 500 ₽ / час",
feet: "7 Футов",
img: "/images/yacht.jpg",
img: "/images/yachts/yacht1.jpg",
badge: "Быстрая бронь",
},
{
@ -25,7 +25,7 @@ const yachts = [
length: "14 метров",
price: "от 26 400 ₽ / час",
feet: "7 Футов",
img: "/images/yacht.jpg",
img: "/images/yachts/yacht2.jpg",
badge: "По запросу",
},
{
@ -33,7 +33,7 @@ const yachts = [
length: "22 метра",
price: "от 48 000 ₽ / час",
feet: "7 Футов",
img: "/images/yacht.jpg",
img: "/images/yachts/yacht3.jpg",
badge: "По запросу",
},
{
@ -41,7 +41,7 @@ const yachts = [
length: "13.6 метров",
price: "от 17 400 ₽ / час",
feet: "7 Футов",
img: "/images/yacht.jpg",
img: "/images/yachts/yacht4.jpg",
badge: "По запросу",
},
{
@ -49,7 +49,7 @@ const yachts = [
length: "13 метров",
price: "от 14 400 ₽ / час",
feet: "7 Футов",
img: "/images/yacht.jpg",
img: "/images/yachts/yacht5.jpg",
badge: "По запросу",
},
{
@ -57,7 +57,7 @@ const yachts = [
length: "12 метров",
price: "от 12 480 ₽ / час",
feet: "7 Футов",
img: "/images/yacht.jpg",
img: "/images/yachts/yacht6.jpg",
badge: "По запросу",
},
];
@ -238,13 +238,18 @@ export default function YachtGrid() {
</div>
{/* 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>
{/* Total price */}
<div className="flex justify-between items-center text-l font-bold text-gray-800">
<span className="font-normal">Итого:</span>
<div className="flex justify-between items-center text-l mt-6 font-bold text-gray-800">
<span className="font-normal">
Итого:
</span>
<span>
{featuredYacht.totalPrice}
</span>
@ -276,14 +281,7 @@ export default function YachtGrid() {
{/* Badge Overlay */}
<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">
<Icon
size={16}
name={
idx === 0
? "logo"
: "restart"
}
/>
<Icon size={16} name="restart" />
<span>{yacht.badge}</span>
</div>
</div>

View File

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

View File

@ -1,57 +1,65 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
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",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground border hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground border hover:bg-destructive/90",
outline:
"border border-input bg-background border hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground border hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-full px-3 text-xs",
lg: "h-10 rounded-full px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
"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: {
variant: {
default:
"bg-primary text-primary-foreground border hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground border hover:bg-destructive/90",
outline:
"border border-input bg-background border hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground border hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
gradient: "text-white border-0 hover:opacity-90",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-full px-3 text-xs",
lg: "h-10 rounded-full px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
// Inline стили для градиента
const gradientStyle = variant === "gradient" ? {
background: "linear-gradient(90deg, #0072A8 0%, #0598DE 100%)",
} : {};
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
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 { format } from "date-fns";
import { ru } from "date-fns/locale";
import { ChevronDownIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
@ -14,6 +16,13 @@ import Icon from "./icon";
export function DatePicker() {
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 (
<Popover>
@ -21,21 +30,93 @@ export function DatePicker() {
<Button
variant="outline"
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
name="calendar"
className="w-4 h-4 text-brand mr-2"
/>
<Icon name="calendar" className="w-4 h-4 text-brand mr-2" />
{date ? (
format(date, "dd.MM.yyyy")
format(date, `dd.MM, ${departureTime} - ${arrivalTime}`)
) : (
<span>Выберите дату</span>
<span>Выберите дату и время</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar mode="single" selected={date} onSelect={setDate} />
<PopoverContent className="w-[360px] p-0 bg-white rounded-[20px] shadow-lg">
<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>
</Popover>
);