Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0518675622 | |||
| e0257ff6fa | |||
| 3149358d53 | |||
| d7569122bb | |||
| 8b6b156d74 | |||
| 3b9bdb232f | |||
| 6747ba4d83 | |||
| 861015f03f | |||
| 301c6d04bb | |||
| b741446e29 |
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
||||||
|
import ReactLenis from "lenis/react";
|
||||||
import ContactCTA from '@/components/sections/contact/ContactCTA';
|
import ContactCTA from '@/components/sections/contact/ContactCTA';
|
||||||
import FaqDouble from '@/components/sections/faq/FaqDouble';
|
import FaqDouble from '@/components/sections/faq/FaqDouble';
|
||||||
import FeatureCardTwentyThree from '@/components/sections/feature/FeatureCardTwentyThree';
|
import FeatureCardTwentyThree from '@/components/sections/feature/FeatureCardTwentyThree';
|
||||||
@@ -25,7 +26,7 @@ export default function LandingPage() {
|
|||||||
secondaryButtonStyle="layered"
|
secondaryButtonStyle="layered"
|
||||||
headingFontWeight="bold"
|
headingFontWeight="bold"
|
||||||
>
|
>
|
||||||
|
<ReactLenis root>
|
||||||
<div id="nav" data-section="nav">
|
<div id="nav" data-section="nav">
|
||||||
<NavbarLayoutFloatingInline
|
<NavbarLayoutFloatingInline
|
||||||
navItems={[
|
navItems={[
|
||||||
@@ -55,7 +56,7 @@ export default function LandingPage() {
|
|||||||
{
|
{
|
||||||
text: "Get My Free Roof Inspection", href: "#quote"},
|
text: "Get My Free Roof Inspection", href: "#quote"},
|
||||||
{
|
{
|
||||||
text: "See Why Dallas Trusts Us", href: "#reviews"},
|
text: "Call Now", href: "tel:+12145550199"},
|
||||||
]}
|
]}
|
||||||
layoutOrder="default"
|
layoutOrder="default"
|
||||||
imageSrc="http://img.b2bpic.net/free-photo/chisinau-arena-sunset-moldova_1268-16015.jpg"
|
imageSrc="http://img.b2bpic.net/free-photo/chisinau-arena-sunset-moldova_1268-16015.jpg"
|
||||||
@@ -172,7 +173,7 @@ export default function LandingPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="contact-cta" data-section="contact-cta">
|
<div id="quote" data-section="quote">
|
||||||
<ContactCTA
|
<ContactCTA
|
||||||
useInvertedBackground={false}
|
useInvertedBackground={false}
|
||||||
background={{
|
background={{
|
||||||
@@ -230,7 +231,7 @@ export default function LandingPage() {
|
|||||||
copyrightText="© 2024 | Results Roofing. All rights reserved."
|
copyrightText="© 2024 | Results Roofing. All rights reserved."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</ReactLenis>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,57 +1,74 @@
|
|||||||
import React, { forwardRef } from "react";
|
"use client";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { useButtonClick } from "@/components/button/useButtonClick";
|
|
||||||
|
|
||||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
import { useRef, memo } from "react";
|
||||||
|
import { useCharAnimation } from "../useCharAnimation";
|
||||||
|
import { useButtonClick } from "../useButtonClick";
|
||||||
|
import { cls } from "@/lib/utils";
|
||||||
|
import "./BounceButton.css";
|
||||||
|
|
||||||
|
interface ButtonBounceEffectProps {
|
||||||
text: string;
|
text: string;
|
||||||
|
onClick?: () => void;
|
||||||
href?: string;
|
href?: string;
|
||||||
|
className?: string;
|
||||||
bgClassName?: string;
|
bgClassName?: string;
|
||||||
textClassName?: string;
|
textClassName?: string;
|
||||||
iconClassName?: string;
|
disabled?: boolean;
|
||||||
newTab?: boolean;
|
ariaLabel?: string;
|
||||||
|
type?: "button" | "submit" | "reset";
|
||||||
|
scrollToSection?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ButtonBounceEffect = forwardRef<HTMLButtonElement, ButtonProps>(
|
const ButtonBounceEffect = ({
|
||||||
(
|
text,
|
||||||
{
|
onClick,
|
||||||
text,
|
href,
|
||||||
onClick,
|
className = "",
|
||||||
href,
|
bgClassName = "",
|
||||||
className,
|
textClassName = "",
|
||||||
bgClassName,
|
disabled = false,
|
||||||
textClassName,
|
ariaLabel,
|
||||||
iconClassName,
|
type = "button",
|
||||||
disabled = false,
|
scrollToSection,
|
||||||
ariaLabel,
|
}: ButtonBounceEffectProps) => {
|
||||||
type = "button", newTab,
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
...props
|
const handleClick = useButtonClick(href, onClick, scrollToSection);
|
||||||
},
|
|
||||||
ref
|
|
||||||
) => {
|
|
||||||
const clickHandler = useButtonClick(href, onClick, newTab);
|
|
||||||
|
|
||||||
return (
|
useCharAnimation(buttonRef, text);
|
||||||
<button
|
|
||||||
className={cn(
|
return (
|
||||||
"group relative flex h-12 w-full items-center justify-center rounded-lg bg-primary-cta p-3 text-sm font-medium text-primary-cta-foreground transition-all duration-300 ease-out active:scale-95", className
|
<button
|
||||||
|
ref={buttonRef}
|
||||||
|
type={type}
|
||||||
|
onClick={handleClick}
|
||||||
|
data-href={href}
|
||||||
|
disabled={disabled}
|
||||||
|
aria-label={ariaLabel || text}
|
||||||
|
className={cls(
|
||||||
|
"bounce-button relative cursor-pointer flex items-center justify-center bg-transparent border-none leading-none no-underline h-9 px-6 min-w-0 w-fit max-w-full rounded-theme text-primary-cta-text",
|
||||||
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cls(
|
||||||
|
"bounce-button-bg absolute! inset-0 rounded-theme primary-button",
|
||||||
|
bgClassName
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
<span
|
||||||
|
data-button-animate-chars=""
|
||||||
|
className={cls(
|
||||||
|
"bounce-button-text relative text-sm inline-block overflow-hidden truncate whitespace-nowrap",
|
||||||
|
textClassName
|
||||||
)}
|
)}
|
||||||
onClick={clickHandler}
|
|
||||||
aria-label={ariaLabel}
|
|
||||||
disabled={disabled}
|
|
||||||
type={type}
|
|
||||||
ref={ref}
|
|
||||||
{...props}
|
|
||||||
>
|
>
|
||||||
<span
|
{text}
|
||||||
className={cn(
|
</span>
|
||||||
"relative flex items-center gap-2 translate-y-0 group-hover:-translate-y-1 group-active:translate-y-0 transition-transform duration-300 ease-out", textClassName
|
</button>
|
||||||
)}
|
);
|
||||||
>
|
};
|
||||||
{text}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
ButtonBounceEffect.displayName = "ButtonBounceEffect";
|
ButtonBounceEffect.displayName = "ButtonBounceEffect";
|
||||||
|
|
||||||
|
export default memo(ButtonBounceEffect);
|
||||||
|
|||||||
@@ -1,33 +1,74 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback } from "react";
|
import { useLenis } from "lenis/react";
|
||||||
// No import for 'lenis' or 'lenis/react'
|
import { useRouter, usePathname } from "next/navigation";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
/**
|
export const useButtonClick = (
|
||||||
* A hook to provide a consistent click handler for buttons, especially for internal hash navigation.
|
href?: string,
|
||||||
* It ensures smooth scrolling without relying on external libraries like Lenis or problematic querySelector patterns.
|
onClick?: () => void,
|
||||||
*/
|
scrollToSection?: boolean
|
||||||
export function useButtonClick() {
|
) => {
|
||||||
const handleButtonClick = useCallback((event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>, href?: string) => {
|
const lenis = useLenis();
|
||||||
if (href && href.startsWith("#")) {
|
const router = useRouter();
|
||||||
const id = href.substring(1); // Remove the '#'
|
const pathname = usePathname();
|
||||||
// Use getElementById directly to avoid querySelector issues with complex IDs (like '##quote')
|
|
||||||
const targetElement = document.getElementById(id);
|
|
||||||
|
|
||||||
if (targetElement) {
|
const scrollToElement = (sectionId: string, delay: number = 100) => {
|
||||||
event.preventDefault(); // Prevent default browser jump
|
setTimeout(() => {
|
||||||
targetElement.scrollIntoView({ behavior: "smooth" });
|
if (lenis) {
|
||||||
// Optionally update the URL hash without a full page reload or additional scroll
|
lenis.scrollTo(`#${sectionId}`, { offset: 0 });
|
||||||
// history.pushState(null, '', href);
|
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Attempted to scroll to element with ID '${id}', but it was not found on the page.`);
|
const element = document.getElementById(sectionId);
|
||||||
// If the element is not found, we let the default behavior happen (which might lead to a page refresh to the root)
|
if (element) {
|
||||||
// or prevent it and do nothing. Given it's a "fix" for a bug, a silent failure with a console warning is safer than unexpected navigation.
|
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (href) {
|
||||||
|
const isExternalLink = /^(https?:\/\/|www\.)/.test(href);
|
||||||
|
|
||||||
|
if (isExternalLink) {
|
||||||
|
window.open(
|
||||||
|
href.startsWith("www.") ? `https://${href}` : href,
|
||||||
|
"_blank",
|
||||||
|
"noopener,noreferrer"
|
||||||
|
);
|
||||||
|
} else if (href.startsWith("/")) {
|
||||||
|
const [path, hash] = href.split("#");
|
||||||
|
|
||||||
|
if (path !== pathname) {
|
||||||
|
router.push(path);
|
||||||
|
if (hash) {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.hash = hash;
|
||||||
|
scrollToElement(hash, 100);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hash) {
|
||||||
|
window.location.hash = hash;
|
||||||
|
scrollToElement(hash, 50);
|
||||||
|
} else if (scrollToSection) {
|
||||||
|
const sectionId = path.replace(/^\//, "").replace(/\//g, "-");
|
||||||
|
scrollToElement(sectionId, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scrollToElement(href, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// For non-hash hrefs (external links, full paths), let the browser handle it
|
onClick?.();
|
||||||
// unless there's a specific programmatic override needed (which isn't requested here).
|
};
|
||||||
}, []);
|
|
||||||
|
|
||||||
return { handleButtonClick };
|
useEffect(() => {
|
||||||
}
|
if (typeof window !== "undefined" && window.location.hash) {
|
||||||
|
const hash = window.location.hash.replace("#", "");
|
||||||
|
scrollToElement(hash, 300);
|
||||||
|
}
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
return handleClick;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user