Commit f6123576 by Syed Abdul Rahman

implemented pagination and header component

parent 5f4cad48
......@@ -39,3 +39,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
*storybook.log
storybook-static
import type { StorybookConfig } from '@storybook/nextjs';
const config: StorybookConfig = {
"stories": [
"../src/**/*.mdx",
// "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)",
"../src/components/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [],
"framework": {
"name": "@storybook/nextjs",
"options": {}
},
"staticDirs": [
"..\\public"
]
};
export default config;
\ No newline at end of file
import type { Preview } from '@storybook/nextjs'
import '../src/app/fonts.css'
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;
\ No newline at end of file
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
import storybook from "eslint-plugin-storybook";
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
......@@ -11,6 +14,7 @@ const compat = new FlatCompat({
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
...storybook.configs["flat/recommended"]
];
export default eslintConfig;
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -6,20 +6,25 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"next": "15.3.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next": "15.3.4"
"react-dom": "^19.0.0"
},
"devDependencies": {
"typescript": "^5",
"@eslint/eslintrc": "^3",
"@storybook/nextjs": "^9.0.12",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.3.4",
"@eslint/eslintrc": "^3"
"eslint-plugin-storybook": "^9.0.12",
"storybook": "^9.0.12",
"typescript": "^5"
}
}
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path fill-rule="evenodd" clip-rule="evenodd" d="M10.9866 2.81264C11.1759 3.25273 11.0265 3.76528 10.6304 4.03473C9.04069 5.11602 8 6.93663 8 8.99999C8 12.3137 10.6863 15 14 15C16.9001 15 19.3217 12.9413 19.8791 10.205C19.9749 9.7348 20.3912 9.39893 20.871 9.40466C21.3508 9.4104 21.7589 9.75612 21.8434 10.2285C21.9464 10.8042 22 11.3962 22 12C22 17.5229 17.5229 22 12 22C6.47713 22 2 17.5229 2 12C2 7.21279 5.36283 3.21342 9.85431 2.23097C10.3223 2.1286 10.7972 2.37255 10.9866 2.81264ZM6.51162 6.17934C4.96517 7.6383 4 9.70684 4 12C4 16.4183 7.5817 20 12 20C15.4257 20 18.3485 17.8468 19.4889 14.8199C18.0565 16.1715 16.1254 17 14 17C9.58175 17 6 13.4182 6 8.99999C6 8.00706 6.18101 7.05645 6.51162 6.17934Z" fill="#949494"/> </g>
</svg>
\ No newline at end of file
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="64px" height="64px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="0.384"/>
<g id="SVGRepo_iconCarrier"> <path d="M15.7955 15.8111L21 21M18 10.5C18 14.6421 14.6421 18 10.5 18C6.35786 18 3 14.6421 3 10.5C3 6.35786 6.35786 3 10.5 3C14.6421 3 18 6.35786 18 10.5Z" stroke="#787373" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </g>
</svg>
\ No newline at end of file
import App from './page'
export default {
title: "components/app",
component: App,
tags: ["autodocs"]
}
export const Default = {
}
\ No newline at end of file
@font-face {
font-family: "Inter-Bold";
src: url("../../public/fonts/Inter_28pt-Bold.ttf");
}
@font-face {
font-family: "Inter-Medium";
src: url("../../public/fonts/Inter_28pt-Medium.ttf");
}
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import "./fonts.css";
import Header from "@/components/Layout/Header/Header";
import Pagination from "@/components/Shared/Pagination/Pagination";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
......@@ -25,7 +27,9 @@ export default function RootLayout({
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable}`}>
<Header />
{children}
<Pagination currentPage={1} totalPages={12} />
</body>
</html>
);
......
......@@ -14,155 +14,5 @@
padding: 80px;
gap: 64px;
font-family: var(--font-geist-sans);
}
@media (prefers-color-scheme: dark) {
.page {
--gray-rgb: 255, 255, 255;
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);
--button-primary-hover: #ccc;
--button-secondary-hover: #1a1a1a;
}
}
.main {
display: flex;
flex-direction: column;
gap: 32px;
grid-row-start: 2;
}
.main ol {
font-family: var(--font-geist-mono);
padding-left: 0;
margin: 0;
font-size: 14px;
line-height: 24px;
letter-spacing: -0.01em;
list-style-position: inside;
}
.main li:not(:last-of-type) {
margin-bottom: 8px;
}
.main code {
font-family: inherit;
background: var(--gray-alpha-100);
padding: 2px 4px;
border-radius: 4px;
font-weight: 600;
}
.ctas {
display: flex;
gap: 16px;
}
.ctas a {
appearance: none;
border-radius: 128px;
height: 48px;
padding: 0 20px;
border: none;
border: 1px solid transparent;
transition:
background 0.2s,
color 0.2s,
border-color 0.2s;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
line-height: 20px;
font-weight: 500;
}
a.primary {
background: var(--foreground);
color: var(--background);
gap: 8px;
}
a.secondary {
border-color: var(--gray-alpha-200);
min-width: 158px;
}
.footer {
grid-row-start: 3;
display: flex;
gap: 24px;
}
.footer a {
display: flex;
align-items: center;
gap: 8px;
}
.footer img {
flex-shrink: 0;
}
/* Enable hover only on non-touch devices */
@media (hover: hover) and (pointer: fine) {
a.primary:hover {
background: var(--button-primary-hover);
border-color: transparent;
}
a.secondary:hover {
background: var(--button-secondary-hover);
border-color: transparent;
}
.footer a:hover {
text-decoration: underline;
text-underline-offset: 4px;
}
}
@media (max-width: 600px) {
.page {
padding: 32px;
padding-bottom: 80px;
}
.main {
align-items: center;
}
.main ol {
text-align: center;
}
.ctas {
flex-direction: column;
}
.ctas a {
font-size: 14px;
height: 40px;
padding: 0 16px;
}
a.secondary {
min-width: auto;
}
.footer {
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
}
@media (prefers-color-scheme: dark) {
.logo {
filter: invert();
}
background-color: rgb(228, 223, 248);
}
import Image from "next/image";
import styles from "./page.module.css";
import Header from "@/components/Layout/Header/Header";
export default function Home() {
return (
<div className={styles.page}>
<main className={styles.main}>
<Image
className={styles.logo}
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol>
<li>
Get started by editing <code>src/app/page.tsx</code>.
</li>
<li>Save and see your changes instantly.</li>
</ol>
<div className={styles.ctas}>
<a
className={styles.primary}
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className={styles.logo}
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
className={styles.secondary}
>
Read our docs
</a>
</div>
</main>
<footer className={styles.footer}>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org →
</a>
</footer>
</div>
);
return <div className={styles.page}></div>;
}
.inputWrapper {
display: flex;
align-items: center;
border-radius: 5px;
background-color: #cccccc43;
/* padding: 1rem 0.5rem; */
}
.inputWrapper input {
border: none;
outline: none;
outline: none;
border: none;
height: 12px;
width: 100%;
font-size: 15px;
color: black;
background-color: #cccccc10;
padding: 1.5rem 0;
font-family: "Inter-Medium";
}
.svgWrapper {
height: 12px;
padding: 0 1rem;
/* padding: 1rem 0.5rem; */
}
import Input from "./Input";
export default {
title: "Components/Base/Input",
component: Input,
tags: ["autodocs"],
};
export const Default = {
args: {
placeholder: "Discover news, articles and more..",
},
};
import searchIcon from "../../../../public/imgaes/search-svgrepo-com.svg";
import styles from "./Input.module.css";
export default function Input({ label, placeholder }: any) {
return (
<>
{label && <div>{label}</div>}
<div className={styles.inputWrapper}>
<div className={styles.svgWrapper}>
<svg
width="20px"
height="15px"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
<g
id="SVGRepo_tracerCarrier"
strokeLinecap="round"
strokeLinejoin="round"
stroke="#CCCCCC"
strokeWidth="0.384"
></g>
<g id="SVGRepo_iconCarrier">
{" "}
<path
d="M15.7955 15.8111L21 21M18 10.5C18 14.6421 14.6421 18 10.5 18C6.35786 18 3 14.6421 3 10.5C3 6.35786 6.35786 3 10.5 3C14.6421 3 18 6.35786 18 10.5Z"
stroke="#787373"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
></path>{" "}
</g>
</svg>
</div>
<input
className={styles.input}
name="input"
placeholder={placeholder}
/>
</div>
</>
);
}
.header-wrapper {
background-color: white;
font-family: "Inter-Bold";
display: flex;
justify-content: space-around;
align-items: center;
box-shadow: rgba(0, 0, 0, 0.08) 0px 4px 12px;
padding: 0 1rem;
box-sizing: border-box;
width: 100%;
height: 85px;
}
.input-container {
width: 30%;
}
.headerTitle {
color: rgba(41, 41, 41, 0.818);
font-family: "Inter-Bold";
font-size: 22px;
}
import Header from "./Header";
export default {
title: "Components/Layout/Header",
component: Header,
tags: ["autodocs"],
};
export const Default = {};
import Input from "@/components/Base/Input/Input";
import themeLogo from "../../../../public/imgaes/moon-svgrepo-com.svg";
import styles from "./Header.module.css";
import Image from "next/image";
export default function Header() {
return (
<header className={styles["header-wrapper"]}>
<div className={styles.headerTitle}>NewsBlog</div>
<div className={styles["input-container"]}>
<Input placeholder={"Discover news, articles and more..."} />
</div>
<div>
<Image src={themeLogo} alt="image" width={25} />
</div>
</header>
);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin-top: 20px;
flex-wrap: wrap;
}
.button {
padding: 6px 12px;
border: 1px solid #ccc;
border-radius: 4px;
color: #333;
text-decoration: none;
font-size: 14px;
transition: background-color 0.2s ease;
background: white;
}
.button:hover {
background-color: #f0f0f0;
}
.active {
background-color: #0070f3;
color: white;
border-color: #0070f3;
pointer-events: none;
}
.dots {
padding: 6px 12px;
border: none;
background: none;
font-size: 16px;
cursor: pointer;
color: #666;
}
.dots:hover {
color: #000;
}
import Pagination from "./Pagination";
export default {
title: "Components/Shared/Pagination",
component: Pagination,
};
export const Default = {
args: {
currentPage: 1,
totalPages: 12,
},
};
"use client";
import Link from "next/link";
import { useState, useEffect } from "react";
import styles from "./Pagination.module.css";
type PaginationProps = {
currentPage: number;
totalPages: number;
};
export default function Pagination({
currentPage,
totalPages,
}: PaginationProps) {
const visiblePageCount = 4;
const [groupStart, setGroupStart] = useState(1);
// Update group when currentPage changes (like when URL param changes)
useEffect(() => {
const newStart = Math.max(2, currentPage - 1);
setGroupStart(newStart);
}, [currentPage]);
const groupEnd = Math.min(groupStart + visiblePageCount - 1, totalPages - 1);
const pages: (number | "prevDots" | "nextDots")[] = [1];
if (groupStart > 2) pages.push("prevDots");
for (let i = groupStart; i <= groupEnd; i++) {
pages.push(i);
}
if (groupEnd < totalPages - 1) pages.push("nextDots");
pages.push(totalPages);
const handleDotsClick = (direction: "prev" | "next") => {
setGroupStart((prev) => {
if (direction === "prev") return Math.max(2, prev - visiblePageCount);
if (direction === "next")
return Math.min(totalPages - visiblePageCount, prev + visiblePageCount);
return prev;
});
};
return (
<div className={styles.wrapper}>
{currentPage > 1 && (
<Link href={`?page=${currentPage - 1}`} className={styles.button}>
« Prev
</Link>
)}
{pages.map((page, index) => {
if (page === "prevDots") {
return (
<button
key={`dots-prev-${index}`}
className={styles.dots}
onClick={() => handleDotsClick("prev")}
>
...
</button>
);
}
if (page === "nextDots") {
return (
<button
key={`dots-next-${index}`}
className={styles.dots}
onClick={() => handleDotsClick("next")}
>
...
</button>
);
}
return (
<Link
key={`page-${page}`}
href={`?page=${page}`}
className={`${styles.button} ${
currentPage === page ? styles.active : ""
}`}
>
{page}
</Link>
);
})}
{currentPage < totalPages && (
<Link href={`?page=${currentPage + 1}`} className={styles.button}>
Next »
</Link>
)}
</div>
);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment