Commit fe4624c5 by Syed Abdul Rahman

Initial commit

parent f6123576
import type { Preview } from '@storybook/nextjs'
import '../src/app/fonts.css'
const preview: Preview = {
const preview = {
parameters: {
backgrounds: {
default: 'myCustomBlue',
options: {
myCustomBlue: { name: 'cutom-color', value: '#f8f9fa' }, // Example
},
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
color: /(background|color)$/i,
date: /Date$/i,
},
},
a11y: {
test: "todo"
}
},
initialGlobals: {
backgrounds: { value: 'myCustomBlue' },
},
};
export default preview;
\ No newline at end of file
export default preview;
......@@ -7,3 +7,23 @@
font-family: "Inter-Medium";
src: url("../../public/fonts/Inter_28pt-Medium.ttf");
}
@font-face {
font-family: "Inter-Regular";
src: url("../../public/fonts/Inter_24pt-Regular.ttf");
}
@font-face {
font-family: "Playfair-Bold";
src: url("../../public/fonts/PlayfairDisplay-Bold.ttf");
}
@font-face {
font-family: "Playfair-Medium";
src: url("../../public/fonts/PlayfairDisplay-Medium.ttf");
}
@font-face {
font-family: "Playfair-Regular";
src: url("../../public/fonts//PlayfairDisplay-Regular.ttf");
}
.btn-wrapper {
display: flex;
gap: 2rem;
flex-wrap: wrap;
align-items: center;
}
.button {
border: 0;
font-family: "Inter-Medium";
cursor: pointer;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
box-sizing: border-box;
font-size: 1rem;
}
.primary {
background-color: white;
color: rgb(98, 97, 97);
}
.primary:hover {
color: white;
background-color: rgb(115, 40, 255) !important;
}
.secondary {
background-color: rgb(115, 40, 255);
color: white;
border: 1px solid rgb(115, 40, 255);
}
.secondary:hover {
color: rgb(98, 97, 97);
background-color: white !important;
border: 1px solid rgb(115, 40, 255);
}
.sm {
padding: 0.5rem 1rem;
font-size: 15px;
}
.md {
padding: 0.8rem 1.5rem;
font-size: 18px;
}
.lg {
padding: 1.2rem 2.2rem;
font-size: 15px;
}
.button-active {
color: white;
background-color: rgb(115, 40, 255);
}
.loader {
width: 20px;
height: 20px;
border-radius: 50%;
border: 1px solid gray;
border-top: 2px solid blue;
display: inline-block;
animation: rotate 0.5s linear infinite;
}
.loader-sm {
width: 10px;
height: 10px;
}
.loader-md {
width: 15px;
height: 15px;
}
.loader-lg {
width: 20px;
height: 20px;
}
.disabled {
background-color: rgb(172, 167, 167);
pointer-events: none;
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
import Button from "./Button";
import styles from "./Button.module.css";
export default {
title: "Components/Base/Button",
component: Button,
};
export const Primary = {
args: {},
render: () => {
return (
<>
<h2 style={{ fontFamily: "Inter-Medium", color: "gray" }}>Size</h2>
<section className={styles["btn-wrapper"]}>
<Button variant="primary" size="sm">
Small
</Button>
<Button variant="primary" size="md">
Medium
</Button>
<Button variant="primary" size="lg">
Large
</Button>
</section>
<h2 style={{ fontFamily: "Inter-Medium", color: "gray" }}>Loader</h2>
<section className={styles["btn-wrapper"]}>
<Button variant="primary" size="sm" loading={true}>
Button
</Button>
<Button variant="primary" size="md" loading={true}>
Button
</Button>
<Button variant="primary" size="lg" loading={true}>
Button
</Button>
</section>
<h2 style={{ fontFamily: "Inter-Medium", color: "gray" }}>Disabled</h2>
<section className={styles["btn-wrapper"]}>
<Button variant="primary" size="sm" disabled={true}>
Button
</Button>
<Button variant="primary" size="md" disabled={true}>
Button
</Button>
<Button variant="primary" size="lg" disabled={true}>
Button
</Button>
</section>
<h2 style={{ fontFamily: "Inter-Medium", color: "gray" }}>
Loader and Disabled
</h2>
<section className={styles["btn-wrapper"]}>
<Button variant="primary" size="sm" loading={true} disabled={true}>
Button
</Button>
<Button variant="primary" size="md" loading={true} disabled={true}>
Button
</Button>
<Button variant="primary" size="lg" loading={true} disabled={true}>
Button
</Button>
</section>
<h2 style={{ fontFamily: "Inter-Medium", color: "gray" }}>Active</h2>
<section className={styles["btn-wrapper"]}>
<Button variant="primary" size="sm" active={true}>
Button
</Button>
<Button variant="primary" size="md" active={true}>
Button
</Button>
<Button variant="primary" size="lg" active={true}>
Button
</Button>
</section>
</>
);
},
};
export const Secondary = {
args: {},
render: () => {
return (
<>
<h2 style={{ fontFamily: "Inter-Medium", color: "gray" }}>Size</h2>
<section className={styles["btn-wrapper"]}>
<Button variant="secondary" size="sm">
Small
</Button>
<Button variant="secondary" size="md">
Medium
</Button>
<Button variant="secondary" size="lg">
Large
</Button>
</section>
<h2 style={{ fontFamily: "Inter-Medium", color: "gray" }}>Loader</h2>
<section className={styles["btn-wrapper"]}>
<Button variant="secondary" size="sm" loading={true}>
Button
</Button>
<Button variant="secondary" size="md" loading={true}>
Button
</Button>
<Button variant="secondary" size="lg" loading={true}>
Button
</Button>
</section>
<h2 style={{ fontFamily: "Inter-Medium", color: "gray" }}>Disabled</h2>
<section className={styles["btn-wrapper"]}>
<Button variant="secondary" size="sm" disabled={true}>
Button
</Button>
<Button variant="secondary" size="md" disabled={true}>
Button
</Button>
<Button variant="secondary" size="lg" disabled={true}>
Button
</Button>
</section>
<h2 style={{ fontFamily: "Inter-Medium", color: "gray" }}>
Loader and Disabled
</h2>
<section className={styles["btn-wrapper"]}>
<Button variant="secondary" size="sm" loading={true} disabled={true}>
Button
</Button>
<Button variant="secondary" size="md" loading={true} disabled={true}>
Button
</Button>
<Button variant="secondary" size="lg" loading={true} disabled={true}>
Button
</Button>
</section>
<h2 style={{ fontFamily: "Inter-Medium", color: "gray" }}>Active</h2>
<section className={styles["btn-wrapper"]}>
<Button variant="secondary" size="sm" active={true}>
Button
</Button>
<Button variant="secondary" size="md" active={true}>
Button
</Button>
<Button variant="secondary" size="lg" active={true}>
Button
</Button>
</section>
</>
);
},
};
import styles from "./Button.module.css";
const Button = ({
variant,
size,
active,
loading,
disabled,
children,
...buttonProps
}: any) => {
return (
<button
className={`${styles["button"]} ${styles[variant]} ${styles[size]} ${
active ? styles["button-active"] : ""
} ${disabled ? styles["disabled"] : ""}`}
{...buttonProps}
>
{loading && (
<>
<div
className={`${styles[`loader-${size}`]} ${styles["loader"]}`}
></div>
<span>Loading</span>
</>
)}
{!loading && children}
</button>
);
};
export default Button;
......@@ -2,8 +2,6 @@
display: flex;
align-items: center;
border-radius: 5px;
background-color: #cccccc43;
/* padding: 1rem 0.5rem; */
}
.inputWrapper input {
......@@ -15,13 +13,21 @@
width: 100%;
font-size: 15px;
color: black;
background-color: #cccccc10;
padding: 1.5rem 0;
background-color: #cccccc43;
padding: 1.1rem 0;
font-family: "Inter-Medium";
}
input::placeholder {
color: rgba(128, 128, 128, 0.605);
}
.label {
font-family: "Inter-Medium";
padding: 0.5rem 0;
}
.svgWrapper {
height: 12px;
padding: 0 1rem;
/* padding: 1rem 0.5rem; */
padding: 1.1rem 1rem;
background-color: #cccccc43;
}
import Input from "./Input";
export default {
title: "Components/Base/Input",
component: Input,
tags: ["autodocs"],
argTypes: {
onChange: { action: "On Input Change" },
},
};
export const Default = {
......
import searchIcon from "../../../../public/imgaes/search-svgrepo-com.svg";
import styles from "./Input.module.css";
export default function Input({ label, placeholder }: any) {
type InputProps = {
label?: string;
placeholder?: string;
onChange?: (e: any) => void;
} & React.InputHTMLAttributes<HTMLInputElement>;
export default function Input({
label,
onChange,
placeholder,
...props
}: InputProps) {
return (
<>
{label && <div>{label}</div>}
{label && <div className={styles.label}>{label}</div>}
<div className={styles.inputWrapper}>
<div className={styles.svgWrapper}>
<svg
......@@ -37,6 +47,8 @@ export default function Input({ label, placeholder }: any) {
className={styles.input}
name="input"
placeholder={placeholder}
onChange={(e) => onChange?.(e.target.value)}
{...props}
/>
</div>
</>
......
.typograpy {
font-family: "Inter-Medium";
color: rgb(111, 111, 111);
}
import styles from "./Typography.module.css";
export default {
tags: ["autodocs"],
title: "Components/Base/Typography",
};
export const Default = {
render: () => {
return (
<div className={styles.typograpy}>
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas nec
nulla dignissim, malesuada diam vel, vulputate quam. Nunc nulla dui,
eleifend vel massa ac, imperdiet tincidunt arcu.Lorem ipsum dolor sit
amet, consectetur adipiscing elit. Maecenas nec nulla dignissim,
malesuada diam vel, vulputate quam. Nunc nulla dui, eleifend vel massa
ac, imperdiet tincidunt arcu.Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Maecenas nec nulla dignissim, malesuada diam vel,
vulputate quam. Nunc nulla dui, eleifend vel massa ac, imperdiet
tincidunt arcu.
</p>
</div>
);
},
};
......@@ -8,7 +8,7 @@
padding: 0 1rem;
box-sizing: border-box;
width: 100%;
height: 85px;
padding: 1rem 0;
}
.input-container {
......@@ -20,3 +20,7 @@
font-family: "Inter-Bold";
font-size: 22px;
}
.theme-switcher {
cursor: pointer;
}
......@@ -9,7 +9,7 @@ export default function Header() {
<div className={styles["input-container"]}>
<Input placeholder={"Discover news, articles and more..."} />
</div>
<div>
<div className={styles["theme-switcher"]}>
<Image src={themeLogo} alt="image" width={25} />
</div>
</header>
......
......@@ -5,6 +5,7 @@
gap: 8px;
margin-top: 20px;
flex-wrap: wrap;
font-family: "Inter-Medium";
}
.button {
......
......@@ -3,11 +3,16 @@ import Pagination from "./Pagination";
export default {
title: "Components/Shared/Pagination",
component: Pagination,
argTypes: {
onPageChange: {
action: "PageNo.",
},
},
};
export const Default = {
args: {
currentPage: 1,
currentPage: 9,
totalPages: 12,
},
};
"use client";
import Link from "next/link";
import { useState, useEffect } from "react";
import { useState, useEffect, useMemo } from "react";
import styles from "./Pagination.module.css";
import Button from "@/components/Base/Button/Button";
type PaginationProps = {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
};
export default function Pagination({
currentPage,
totalPages,
onPageChange,
}: PaginationProps) {
const visiblePageCount = 4;
const [groupStart, setGroupStart] = useState(1);
const visiblePageCount = 3;
const maxGroupStart = Math.max(2, totalPages - visiblePageCount);
const [groupStart, setGroupStart] = useState(Math.max(2, currentPage - 1));
// Update group when currentPage changes (like when URL param changes)
useEffect(() => {
const newStart = Math.max(2, currentPage - 1);
const newStart = Math.max(2, Math.min(currentPage - 1, maxGroupStart));
setGroupStart(newStart);
}, [currentPage]);
}, [currentPage, maxGroupStart]);
const groupEnd = Math.min(groupStart + visiblePageCount - 1, totalPages - 1);
const pages: (number | "prevDots" | "nextDots")[] = [1];
const pages = useMemo<(number | "prevDots" | "nextDots")[]>(() => {
const tempPages: (number | "prevDots" | "nextDots")[] = [1];
if (groupStart > 2) tempPages.push("prevDots");
if (groupStart > 2) pages.push("prevDots");
for (let i = groupStart; i <= groupEnd; i++) {
tempPages.push(i);
}
for (let i = groupStart; i <= groupEnd; i++) {
pages.push(i);
}
if (groupEnd < totalPages - 1) tempPages.push("nextDots");
if (groupEnd < totalPages - 1) pages.push("nextDots");
if (totalPages > 1) tempPages.push(totalPages);
pages.push(totalPages);
return tempPages;
}, [groupStart, groupEnd, 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;
if (direction === "prev") {
return Math.max(2, prev - visiblePageCount);
} else {
return Math.min(maxGroupStart, prev + visiblePageCount);
}
});
};
return (
<div className={styles.wrapper}>
{currentPage > 1 && (
<Link href={`?page=${currentPage - 1}`} className={styles.button}>
<button
onClick={() => onPageChange(currentPage - 1)}
className={styles.button}
>
« Prev
</Link>
</button>
)}
{pages.map((page, index) => {
if (page === "prevDots") {
return (
<button
key={`dots-prev-${index}`}
className={styles.dots}
onClick={() => handleDotsClick("prev")}
>
...
</button>
);
}
if (page === "nextDots") {
if (page === "prevDots" || page === "nextDots") {
return (
<button
key={`dots-next-${index}`}
key={`dots-${index}`}
className={styles.dots}
onClick={() => handleDotsClick("next")}
onClick={() =>
handleDotsClick(page === "prevDots" ? "prev" : "next")
}
>
...
</button>
......@@ -78,22 +79,25 @@ export default function Pagination({
}
return (
<Link
<Button
key={`page-${page}`}
href={`?page=${page}`}
className={`${styles.button} ${
currentPage === page ? styles.active : ""
}`}
variant="primary"
size="sm"
active={currentPage === page}
onClick={() => onPageChange(page)}
>
{page}
</Link>
</Button>
);
})}
{currentPage < totalPages && (
<Link href={`?page=${currentPage + 1}`} className={styles.button}>
<button
onClick={() => onPageChange(currentPage + 1)}
className={styles.button}
>
Next »
</Link>
</button>
)}
</div>
);
......
article {
width: 85%;
margin: 0 auto;
}
.title-section {
font-family: "Playfair-Medium";
width: 90%;
font-size: 26px;
margin: 0 auto;
}
.meta-container {
display: flex;
font-family: "Inter-Regular";
font-size: 15px;
gap: 2rem;
}
.img-section {
border-radius: 10px;
padding: 2rem 0;
}
.img-section img {
border-radius: 10px;
}
.markdown-section {
padding: 2rem;
font-family: "Inter-Medium";
border-radius: 10px;
background-color: white;
}
import BlogDetail from "./BlogDetail";
export default {
title: "Components/TopLevel/BlogDetail",
component: BlogDetail,
};
export const Default = {
args: {
title:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas eget venenatis leo. Donec mattis tortor in nisl lacinia, ",
postedOn: "july 11, 2020",
postedBy: "Charlotte Maria",
readTime: "1",
tag: "fashion",
img: "https://picsum.photos/1000/300",
content: [
{
type: "heading",
children: [
{
type: "text",
text: "Lorem ipsum dolor sit amet: ",
},
],
level: 1,
},
{
type: "paragraph",
children: [
{
type: "text",
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus efficitur posuere sodales. Ut et lectus volutpat",
},
],
},
{
type: "list",
format: "unordered",
children: [
{
type: "list-item",
children: [
{
type: "text",
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
},
],
},
{
type: "list-item",
children: [
{
type: "text",
text: "Lorem ipsum dolor sit amet.",
},
],
},
{
type: "list-item",
children: [
{
type: "text",
text: "Lorem ipsum dolor sit amet.",
},
],
},
{
type: "list-item",
children: [
{
type: "text",
text: "Lorem ipsum dolor sit amet.",
},
],
},
],
},
{
type: "paragraph",
children: [
{
type: "text",
text: "",
},
],
},
{
type: "paragraph",
children: [
{
text: "Lorem ipsum dolor sit amet.",
type: "text",
bold: true,
},
],
},
{
type: "heading",
children: [
{
type: "text",
text: "Lorem ipsum dolor sit amet.",
},
],
level: 3,
},
],
},
};
import styles from "./BlogDetail.module.css";
function BlogDetail({
title,
postedOn,
postedBy,
tag,
readTime,
img,
content,
}: any) {
const renderText = (node: any) => {
let text = node.text;
if (node.bold) text = <strong>{text}</strong>;
if (node.italic) text = <em>{text}</em>;
if (node.underline) text = <u>{text}</u>;
return text;
};
const renderChildren = (children: any[]) =>
children.map((child, idx) => {
if (child.type === "text") {
return <span key={idx}>{renderText(child)}</span>;
}
return null;
});
const renderNode = (node: any, idx: number) => {
switch (node.type) {
case "heading":
const HeadingTag: any = `h${node.level}`;
return (
<HeadingTag key={idx}>{renderChildren(node.children)}</HeadingTag>
);
case "paragraph":
return <p key={idx}>{renderChildren(node.children)}</p>;
case "list":
const ListTag = node.format === "unordered" ? "ul" : "ol";
return (
<ListTag key={idx}>
{node.children.map((item: any, i: number) => (
<li key={i}>{renderChildren(item.children)}</li>
))}
</ListTag>
);
default:
return null;
}
};
return (
<article>
<section className={styles["title-section"]}>
<p>{title}</p>
<div className={styles["meta-container"]}>
<div>
Posted on <span>{postedOn}</span>
</div>
<div>
By <span>{postedBy}</span>
</div>
<div>Published in {tag}</div>
<div>
<span>{readTime} min read</span>
</div>
</div>
</section>
<section className={styles["img-section"]}>
<img src={img || "https://picsum.photos/1000/400"} alt={title} />
</section>
<section className={styles["markdown-section"]}>
{content?.map((node: any, idx: number) => renderNode(node, idx))}
</section>
</article>
);
}
export default BlogDetail;
.card {
width: 300px;
border-radius: 10px;
background-color: rgb(252, 252, 252);
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
}
.card-avatar {
width: 100%;
height: 200px;
object-fit: cover;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
/* display: block; */
}
.card-content {
padding: 0 1rem;
font-family: "Inter-Medium";
}
.card figure {
border: 1px solid black;
}
.card-title {
color: rgb(80, 73, 73);
}
.card-description {
color: gray;
}
.card-footer {
padding: 0 1rem;
}
.author-details {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem 0;
}
.author-name {
color: rgb(95, 89, 89);
font-family: "Inter-Bold";
}
.post-info {
display: flex;
gap: 1rem;
font-family: "Inter-Regular";
color: rgba(128, 128, 128, 0.53);
font-size: 14px;
padding: 2px 0;
}
.author-avatar {
border-radius: 50%;
}
import Card from "./Card";
export default {
title: "Components/TopLevel/CardList/Card",
tags: ["autodocs"],
component: Card,
};
export const Default = {
args: {},
};
import styles from "./Card.module.css";
function Card({ img, cardTitle, cardDescription, author }: any) {
return (
<article className={styles.card}>
<img
className={styles["card-avatar"]}
src="https://picsum.photos/300/200"
alt="Author photo"
/>
<section className={styles["card-content"]}>
<h3 className={styles["card-title"]}>Card Title </h3>
<p className={styles["card-description"]}>
This is a short description inside the This is a short description
inside the
</p>
</section>
<footer className={styles["card-footer"]}>
<div className={styles["author-details"]}>
<div>
<img
src="https://picsum.photos/40/40"
alt="Author photo"
className={styles["author-avatar"]}
/>
</div>
<div className="author-info">
<div className={styles["author-name"]}>Syed Abdul Rahman</div>
<div className={styles["post-info"]}>
<div>July 13, 2020</div>
<div>1 min</div>
</div>
</div>
</div>
</footer>
</article>
);
}
export default Card;
.wrapperContainer {
display: grid;
grid-template-columns: repeat(3, 300px);
column-gap: 1rem;
row-gap: 1rem;
}
import CardWrapper from "./CardWrapper";
export default {
title: "Components/TopLevel/CardList/CardWrapper",
component: CardWrapper,
};
export const Default = {
args: {},
};
import Card from "../Card/Card";
import styles from "./CardWrapper.module.css";
const data = [
{
id: 1,
img: "https://placehold.co/300x200",
title: "Lorem ipsum dolor",
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
author: {
name: "John Doe",
postedDate: "12/07/2025",
readTime: "2",
},
},
{
id: 2,
img: "https://placehold.co/300x200",
title: "Understanding React Hooks",
content:
"Hooks let you use state and other React features without writing a class.",
author: {
name: "Jane Smith",
postedDate: "14/07/2025",
readTime: "4",
},
},
{
id: 3,
img: "https://placehold.co/300x200",
title: "Mastering JavaScript Closures",
content:
"Closures are a fundamental concept that every JavaScript developer should know.",
author: {
name: "Alex Johnson",
postedDate: "16/07/2025",
readTime: "3",
},
},
{
id: 4,
img: "https://placehold.co/300x200",
title: "CSS Grid vs Flexbox",
content:
"Both CSS Grid and Flexbox are powerful layout systems. Learn when to use each.",
author: {
name: "Nina Brown",
postedDate: "18/07/2025",
readTime: "5",
},
},
{
id: 5,
img: "https://placehold.co/300x200",
title: "Building Accessible Web Apps",
content:
"Accessibility should be a key focus when building modern web applications.",
author: {
name: "Mohammed Ali",
postedDate: "20/07/2025",
readTime: "6",
},
},
];
function CardWrapper() {
return (
<div className={styles.wrapperContainer}>
{data.map((ele) => (
<>
<Card />
</>
))}
</div>
);
}
export default CardWrapper;
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