Add new "SF-BN B203" product description and implement product details page
The new product file "b203.json" was added to the product description containing detailed descriptions, features and product aspects. In the "prod.astro" file, a product details page was implemented, which fetches data from the JSON file and presents it to the user. The changes also included the development of a "ProductTabBtn" component to improve code modularity and the user interface.
This commit is contained in:
parent
539837b6d9
commit
885c0f9754
6 changed files with 435 additions and 0 deletions
31
src/components/ui/buttons/ProductTabBtn.astro
Normal file
31
src/components/ui/buttons/ProductTabBtn.astro
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
const { id, dataTab, title, first } = Astro.props;
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
dataTab: string;
|
||||
title: string;
|
||||
first?: boolean;
|
||||
}
|
||||
|
||||
const BUTTON_CLASS =
|
||||
"flex w-full justify-center rounded-xl border border-transparent p-3 outline-none ring-zinc-500 transition duration-300 hover:bg-neutral-100 focus-visible:ring dark:ring-zinc-200 dark:hover:bg-neutral-700 dark:focus:outline-none md:p-5";
|
||||
|
||||
const HEADING_CLASS = "block text-center font-semibold";
|
||||
const INACTIVE_HEADING_CLASS = "text-neutral-800 dark:text-neutral-200";
|
||||
---
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class={`${BUTTON_CLASS} ${first ? "active bg-neutral-100 hover:border-transparent dark:bg-white/[.05]" : ""}`}
|
||||
id={id}
|
||||
data-tab-target={dataTab}
|
||||
role="tab"
|
||||
>
|
||||
<h2
|
||||
class={`${HEADING_CLASS} ${first ? "text-[#fa5a15] dark:text-[#fb713b]" : INACTIVE_HEADING_CLASS}`}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
</button>
|
||||
|
63
src/content/products/b203.json
Normal file
63
src/content/products/b203.json
Normal file
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"main": {
|
||||
"title": "SF-BN B203",
|
||||
"subTitle": "Tap Bolts and Nuts Set",
|
||||
"content": "Meet the SF-BN B203 – your reliable companion for professional-grade fastening. This comprehensive box set comes with a versatile selection of tap bolts and nuts, meticulously crafted to provide the strongest hold for your construction and assembly projects.",
|
||||
"img": "../images/product-main.avif",
|
||||
"imgAlt": "Mockup boxes of a tap bolts and nuts set"
|
||||
},
|
||||
"tabs": [
|
||||
{
|
||||
"id": "tabs-with-card-item-1",
|
||||
"dataTab": "#tabs-with-card-1",
|
||||
"title": "Product Description"
|
||||
},
|
||||
{
|
||||
"id": "tabs-with-card-item-2",
|
||||
"dataTab": "#tabs-with-card-2",
|
||||
"title": "Specifications"
|
||||
},
|
||||
{
|
||||
"id": "tabs-with-card-item-3",
|
||||
"dataTab": "#tabs-with-card-3",
|
||||
"title": "Blueprints"
|
||||
}
|
||||
],
|
||||
"description": {
|
||||
"title": "Strength Meets Precision",
|
||||
"subTitle": "The SF-BN B203 Tap Bolts and Nuts Set offers robust durability and precision for construction professionals, ensuring reliable performance in every application, from house framing to machinery assembly.",
|
||||
"btnTitle": "Contact sales to learn more",
|
||||
"btnURL": "#"
|
||||
},
|
||||
"descriptionList": [
|
||||
{
|
||||
"title": "Corrosion Resistance",
|
||||
"subTitle": "Zinc coating not only provides a polished look but also shields against corrosion, ensuring longevity."
|
||||
},
|
||||
{
|
||||
"title": "Improved Safety",
|
||||
"subTitle": "A secure fitting translates to safer structures with reduced risk of component failure."
|
||||
},
|
||||
{
|
||||
"title": "Convenience",
|
||||
"subTitle": "This all-in-one set means you have the right size on hand, cutting down on project delays and additional trips to the hardware store."
|
||||
}
|
||||
],
|
||||
"specifications": [
|
||||
{
|
||||
"id": "tabs-with-card-item-1",
|
||||
"dataTab": "#tabs-with-card-1",
|
||||
"title": "Product Description"
|
||||
},
|
||||
{
|
||||
"id": "tabs-with-card-item-2",
|
||||
"dataTab": "#tabs-with-card-2",
|
||||
"title": "Specifications"
|
||||
},
|
||||
{
|
||||
"id": "tabs-with-card-item-3",
|
||||
"dataTab": "#tabs-with-card-3",
|
||||
"title": "Blueprints"
|
||||
}
|
||||
]
|
||||
}
|
BIN
src/images/blueprint-1.avif
Normal file
BIN
src/images/blueprint-1.avif
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
src/images/blueprint-2.avif
Normal file
BIN
src/images/blueprint-2.avif
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
src/images/product-main.avif
Normal file
BIN
src/images/product-main.avif
Normal file
Binary file not shown.
After Width: | Height: | Size: 53 KiB |
341
src/pages/prod.astro
Normal file
341
src/pages/prod.astro
Normal file
|
@ -0,0 +1,341 @@
|
|||
---
|
||||
// Import section components
|
||||
import MainLayout from "../layouts/MainLayout.astro";
|
||||
import productData from "../content/products/b203.json";
|
||||
import ProductTabBtn from "../components/ui/buttons/ProductTabBtn.astro";
|
||||
import PrimaryCTA from "../components/ui/buttons/PrimaryCTA.astro";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
gsap: any;
|
||||
}
|
||||
}
|
||||
import { Image } from "astro:assets";
|
||||
---
|
||||
|
||||
<MainLayout>
|
||||
<div id="overlay" class="fixed inset-0 bg-neutral-200 dark:bg-neutral-800">
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mx-auto flex max-w-[85rem] flex-col px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
id="fadeText"
|
||||
class="mb-8 max-w-prose text-pretty font-light text-neutral-700 dark:text-neutral-300 sm:text-xl"
|
||||
>
|
||||
{productData.main.content}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div id="fadeInUp">
|
||||
<h1
|
||||
class="block text-4xl font-bold tracking-tighter text-neutral-800 dark:text-neutral-200 sm:text-5xl md:text-6xl lg:text-7xl"
|
||||
>
|
||||
{productData.main.title}
|
||||
</h1>
|
||||
<p class="text-lg text-neutral-600 dark:text-neutral-400">
|
||||
{productData.main.subTitle}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<img
|
||||
id="fadeInMoveRight"
|
||||
src="src/images/product-main.avif"
|
||||
class="w-[600px]"
|
||||
alt={productData.main.imgAlt}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Features -->
|
||||
<div class="mx-auto max-w-[85rem] px-4 pt-10 sm:px-6 lg:px-8 lg:pt-14">
|
||||
<!-- Tab Nav -->
|
||||
<nav
|
||||
class="mx-auto grid max-w-6xl gap-y-px sm:flex sm:gap-x-4 sm:gap-y-0"
|
||||
aria-label="Tabs"
|
||||
role="tablist"
|
||||
>
|
||||
{
|
||||
productData.tabs.map((tab, index) => (
|
||||
<ProductTabBtn
|
||||
title={tab.title}
|
||||
id={tab.id}
|
||||
dataTab={tab.dataTab}
|
||||
first={index === 0}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
<!-- End Tab Nav -->
|
||||
|
||||
<div class="mt-12 md:mt-16">
|
||||
<div id="tabs-with-card-1" role="tabpanel">
|
||||
<div class="mx-auto max-w-[85rem] px-4 pb-10 sm:px-6 lg:px-8 lg:pb-14">
|
||||
<div class="grid gap-12 md:grid-cols-2">
|
||||
<div class="lg:w-3/4">
|
||||
<h2
|
||||
class="text-balance text-3xl font-bold tracking-tight text-neutral-800 dark:text-neutral-200 md:leading-tight lg:text-4xl"
|
||||
>
|
||||
{productData.description.title}
|
||||
</h2>
|
||||
<p
|
||||
class="mt-3 text-pretty text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
{productData.description.subTitle}
|
||||
</p>
|
||||
<p class="mt-5">
|
||||
<PrimaryCTA
|
||||
title={productData.description.btnTitle}
|
||||
url={productData.description.btnURL}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<!-- End Col -->
|
||||
|
||||
<div class="space-y-6 lg:space-y-10">
|
||||
<!-- Icon Block -->
|
||||
{
|
||||
productData.descriptionList.map((list) => (
|
||||
<div class="flex">
|
||||
<div class="ms-5 sm:ms-8">
|
||||
<h3 class="text-base font-semibold text-gray-800 dark:text-gray-200 sm:text-lg">
|
||||
{list.title}
|
||||
</h3>
|
||||
<p class="mt-1 text-gray-600 dark:text-gray-400">
|
||||
{list.subTitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tabs-with-card-2" class="hidden" role="tabpanel">
|
||||
<!-- Icon Blocks -->
|
||||
<div class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<div class="grid grid-cols-2 place-content-evenly gap-x-8">
|
||||
<div>
|
||||
<div>
|
||||
<h3 class="block font-bold text-gray-800 dark:text-white">
|
||||
Build your portfolio
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
The simplest way to keep your portfolio always up-to-date.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="block font-bold text-gray-800 dark:text-white">
|
||||
Build your portfolio
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
The simplest way to keep your portfolio always up-to-date.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="block font-bold text-gray-800 dark:text-white">
|
||||
Build your portfolio
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
The simplest way to keep your portfolio always up-to-date.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="block font-bold text-gray-800 dark:text-white">
|
||||
Build your portfolio
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
The simplest way to keep your portfolio always up-to-date.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="block font-bold text-gray-800 dark:text-white">
|
||||
Build your portfolio
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
The simplest way to keep your portfolio always up-to-date.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="block font-bold text-gray-800 dark:text-white">
|
||||
Build your portfolio
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
The simplest way to keep your portfolio always up-to-date.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="block font-bold text-gray-800 dark:text-white">
|
||||
Build your portfolio
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
The simplest way to keep your portfolio always up-to-date.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="block font-bold text-gray-800 dark:text-white">
|
||||
Build your portfolio
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
The simplest way to keep your portfolio always up-to-date.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Grid -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Icon Blocks -->
|
||||
|
||||
<div id="tabs-with-card-3" class="hidden" role="tabpanel">
|
||||
<div class="mx-auto mb-12 flex w-full md:mb-28">
|
||||
<div
|
||||
class="relative left-12 top-12 z-10 -ml-12 overflow-hidden rounded-xl shadow-lg md:left-12 md:top-16 lg:ml-0"
|
||||
>
|
||||
<img
|
||||
src="src/images/blueprint-1.avif"
|
||||
loading="lazy"
|
||||
alt="Photo by Kaung Htet"
|
||||
class="h-full w-full object-cover object-center"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="relative right-12 overflow-hidden rounded-lg shadow-xl">
|
||||
<img
|
||||
src="src/images/blueprint-2.avif"
|
||||
loading="lazy"
|
||||
alt="Photo by Manny Moreno"
|
||||
class="h-full w-full object-cover object-center"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script
|
||||
is:inline
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"
|
||||
></script>
|
||||
<script>
|
||||
window.addEventListener("load", (event) => {
|
||||
if (window.gsap) {
|
||||
const gsap = window.gsap;
|
||||
gsap.set("#fadeText", { autoAlpha: 0, y: 50 });
|
||||
gsap.set("#fadeInUp", { autoAlpha: 0, y: 50 });
|
||||
gsap.set("#fadeInMoveRight", { autoAlpha: 0, x: 300 });
|
||||
|
||||
let timeline = gsap.timeline();
|
||||
|
||||
timeline.to("#fadeText", {
|
||||
duration: 2,
|
||||
autoAlpha: 1,
|
||||
y: 0,
|
||||
delay: 1,
|
||||
ease: "power2.out",
|
||||
});
|
||||
|
||||
timeline.to(
|
||||
"#fadeInUp",
|
||||
{ duration: 2, autoAlpha: 1, y: 0, ease: "power2.out" },
|
||||
"-=1",
|
||||
);
|
||||
|
||||
timeline.to(
|
||||
"#fadeInMoveRight",
|
||||
{ duration: 2, autoAlpha: 1, x: 0, ease: "power2.out" },
|
||||
"-=1.5",
|
||||
);
|
||||
|
||||
timeline.to("#overlay", { duration: 1, autoAlpha: 0, delay: 0.5 });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function setButtonInactive(btn: any, activeButton: any) {
|
||||
if (btn !== activeButton) {
|
||||
btn.classList.remove(
|
||||
"active",
|
||||
"bg-neutral-100",
|
||||
"hover:border-transparent",
|
||||
"dark:bg-white/[.05]",
|
||||
);
|
||||
|
||||
const contentId = btn.getAttribute("data-tab-target");
|
||||
if (contentId) {
|
||||
const contentElement = document.querySelector(contentId);
|
||||
if (contentElement) {
|
||||
contentElement.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
changeHeadingStyle(
|
||||
btn,
|
||||
["text-neutral-800", "dark:text-neutral-200"],
|
||||
["text-[#fa5a15]", "dark:text-[#fb713b]"],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function activateButton(button: any) {
|
||||
button.classList.add(
|
||||
"active",
|
||||
"bg-neutral-100",
|
||||
",hover:border-transparent",
|
||||
"dark:bg-white/[.05]",
|
||||
);
|
||||
|
||||
const tabId = button.getAttribute("data-tab-target");
|
||||
if (tabId) {
|
||||
const contentElementToShow = document.querySelector(tabId);
|
||||
if (contentElementToShow) {
|
||||
contentElementToShow.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
changeHeadingStyle(
|
||||
button,
|
||||
["text-[#fa5a15]", "dark:text-[#fb713b]"],
|
||||
["text-neutral-800", "dark:text-neutral-200"],
|
||||
);
|
||||
}
|
||||
|
||||
function changeHeadingStyle(
|
||||
button: any,
|
||||
addClasses: any,
|
||||
removeClasses: any,
|
||||
) {
|
||||
let heading = button.querySelector("h2");
|
||||
if (heading) {
|
||||
heading.classList.remove(...removeClasses);
|
||||
heading.classList.add(...addClasses);
|
||||
}
|
||||
}
|
||||
|
||||
const tabButtons = document.querySelectorAll("[data-tab-target]");
|
||||
|
||||
if (tabButtons.length > 0) {
|
||||
changeHeadingStyle(
|
||||
tabButtons[0],
|
||||
["text-[#fa5a15]", "dark:text-[#fb713b]"],
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
tabButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
tabButtons.forEach((btn) => setButtonInactive(btn, button));
|
||||
activateButton(button);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</MainLayout>
|
Loading…
Add table
Reference in a new issue