Skip to content

Commit

Permalink
Merge pull request #17 from ivalshamkya/feat/accordion
Browse files Browse the repository at this point in the history
Feat/accordion
  • Loading branch information
ivalshamkya authored Jan 27, 2024
2 parents 3b3ebb2 + 2c72691 commit ae231eb
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 66 deletions.
2 changes: 1 addition & 1 deletion src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,6 @@

.square-pattern {
opacity: 1;
background-image: linear-gradient(#fff6f4 1px, transparent 1px), linear-gradient(to right, #fff6f4 1px, #fff 1px);
background-image: linear-gradient(#fff6f4 3px, transparent 3px), linear-gradient(to right, #fff6f4 3px, #fff 1px);
background-size: 40px 40px;
}
36 changes: 19 additions & 17 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,20 @@

import Button from '@/components/neobruu/Button'
import Image from 'next/image'
import { IoIosRocket, IoLogoGithub } from 'react-icons/io'
import { IoLogoGithub } from 'react-icons/io'
import { motion } from 'framer-motion'
import Link from 'next/link'

export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center">
<div className='relative flex flex-col justify-center items-center w-full min-h-screen bg-[#ffffff] square-pattern p-20 md:p-24'>
<div className='relative flex flex-col justify-center items-center w-full min-h-screen square-pattern bg-gradient-to-br from-green-500 to-red-300 p-20 md:p-24'>
<div className='w-full max-w-2xl grid place-items-center gap-5'>
<div className="relative">
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
className='absolute -top-10 md:-top-16 -right-10 md:-right-16 w-20 md:w-32'
>
<img
src="/spin.svg"
alt="Spinning Image"
/>
</motion.div>
<h1 className='text-xl md:text-6xl text-center text-white font-black bg-[#5e42ff] py-2 px-5 shadow-[7px_7px_0px_0px_rgba(0,0,0,1)]'>Neo-Brutalism UI</h1>
<h1 className='text-xl md:text-6xl text-center text-white font-black bg-[#4b42ff] py-2 px-5 shadow-[7px_7px_0px_0px_rgba(0,0,0,1)]'>Neo-Brutalism UI</h1>
</div>
<p className='text-sm md:text-lg text-center tracking-tighter'>
Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
Discover bold and raw aesthetics components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
</p>
<div className='flex gap-5'>
<Link
Expand All @@ -44,17 +34,29 @@ export default function Home() {
</Link>
</div>
</div>
<img src="/star.svg" alt="star" className='absolute bottom-36 md:bottom-52 right-5 w-7 md:w-10 rotate-45' />
<img src="/star.svg" alt="star" className='absolute bottom-20 right-5 w-16 md:w-32 rotate-6' />
<div className='absolute bottom-36 md:bottom-52 right-5'>
<div className='relative'>
<Image src="/star.svg" alt="star" fill={true} className='w-7 md:w-10 rotate-45' />
</div>
</div>
<div className='absolute bottom-20 right-5 '>
<div className='relative'>
<Image src="/star.svg" alt="star" fill={true} className='w-16 md:w-32 rotate-6' />
</div>
</div>
<motion.div
animate={{ scale: 1.1 }}
transition={{ duration: 1.5, repeat: Infinity, ease: 'easeIn' }}
className='absolute top-24 left-5 w-16 md:w-32'
>
<img
<div className='relative'>

<Image
src="/heart.svg"
alt="Spinning Image"
fill={true}
/>
</div>
</motion.div>
</div>
</main>
Expand Down
26 changes: 7 additions & 19 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,6 @@ import { AiFillGithub } from "react-icons/ai";
import SearchBar from "./SearchBar";

function Navbar() {
const [theme, setTheme] = useState<string>("");

useEffect(() => {
setTheme((prev) => localStorage.getItem("theme") ?? "light");
if (theme === "dark") {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
localStorage.setItem("theme", theme);
}, [theme]);

return (
<nav className="fixed left-0 top-0 z-10 mx-auto flex h-16 md:h-20 w-full items-center border-b border-black bg-white px-5 dark:bg-zinc-900">
<div className="mx-auto flex w-[1300px] max-w-full items-center justify-between">
Expand All @@ -26,13 +14,13 @@ function Navbar() {
</Link>
<div className="flex gap-3">
<SearchBar />
<a
target="_blank"
href="https://github.com/ivalshamkya/NeoBruu"
className="flex items-center justify-center rounded-md bg-zinc-900 border border-zinc-600 p-[5px] md:p-2 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] shadow-black transition-all hover:translate-x-[3px] hover:translate-y-[3px] hover:shadow-none dark:shadow-zinc-400"
>
<AiFillGithub className="h-6 w-6 text-white" />
</a>
<Link
target="_blank"
href="https://github.com/ivalshamkya/NeoBruu"
className="flex items-center justify-center rounded-md bg-zinc-900 border border-zinc-600 p-[5px] md:p-2 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] shadow-black transition-all focus:translate-x-[3px] focus:translate-y-[3px] focus:shadow-none"
>
<AiFillGithub className="h-6 w-6 text-white" />
</Link>
</div>
</div>
</nav>
Expand Down
30 changes: 30 additions & 0 deletions src/components/example/AccordionExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'
import Accordion from "@/components/neobruu/Accordion";

export default function AccordionExample() {
return (
<div className="flex gap-3">
<Accordion>
<Accordion.Item value="1">
<Accordion.Trigger>Question 1</Accordion.Trigger>
<Accordion.Content>
<h1 className="text-xl">Lorem</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Possimus atque error aperiam consectetur excepturi. Quod, eaque. Libero, non illo vero accusantium consectetur, iure tempora commodi quo quibusdam, fuga dolore in?</p>
</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="2">
<Accordion.Trigger>Question 2</Accordion.Trigger>
<Accordion.Content>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Iusto accusantium impedit corporis magnam esse pariatur libero sint mollitia veniam, sed quos hic voluptatibus molestias porro ab. Possimus ratione commodi dolorem. Lorem ipsum dolor sit amet consectetur, adipisicing elit. Doloribus corporis ipsam aperiam assumenda molestiae minima, ipsum dolore adipisci, quod doloremque nam deserunt porro nostrum ut iste tenetur placeat nulla numquam!
</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="3">
<Accordion.Trigger>Question 3</Accordion.Trigger>
<Accordion.Content>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Iusto accusantium impedit corporis magnam esse pariatur libero sint mollitia veniam, sed quos hic voluptatibus molestias porro ab. Possimus ratione commodi dolorem. Lorem ipsum dolor sit amet consectetur, adipisicing elit. Doloribus corporis ipsam aperiam assumenda molestiae minima, ipsum dolore adipisci, quod doloremque nam deserunt porro nostrum ut iste tenetur placeat nulla numquam!
</Accordion.Content>
</Accordion.Item>
</Accordion>
</div>
)
}
6 changes: 3 additions & 3 deletions src/components/example/AvatarExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import Avatar from "@/components/neobruu/Avatar";
export default function AvatarExample() {
return (
<div className="flex gap-3">
<Avatar src="https://picsum.photos/200" alt="avatar" rounded="none" />
<Avatar src="https://picsum.photos/200" alt="avatar" rounded="lg" />
<Avatar src="https://picsum.photos/200" alt="avatar" rounded="full" />
<Avatar src="/aiko.jpeg" alt="avatar" rounded="none" />
<Avatar src="/aiko.jpeg" alt="avatar" rounded="lg" />
<Avatar src="/aiko.jpeg" alt="avatar" rounded="full" />
</div>
)
}
155 changes: 155 additions & 0 deletions src/components/neobruu/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
'use client'
import React, { ReactElement, createRef, useEffect, useRef, useState } from 'react';
import { FiChevronDown, FiPlus } from 'react-icons/fi';

type Props = {
children: React.ReactNode;
};

type AccordionTriggerProps = {
children: React.ReactNode;
showContent?: boolean;
setShowContent?: React.Dispatch<React.SetStateAction<boolean>>;
};

type AccordionContentProps = {
children: React.ReactNode;
showContent?: boolean;
contentHeight?: string;
contentRef?: React.RefObject<HTMLDivElement>;
};

type AccordionItemProps = {
children: React.ReactNode;
value: string;
};

export default function Accordion({ children }: Props) {
const [contentStates, setContentStates] = useState<{ [key: string]: boolean }>({});
const [contentHeights, setContentHeights] = useState<{ [key: string]: string }>({});
const contentRefs: { [key: string]: React.RefObject<HTMLDivElement> } = {};

useEffect(() => {
const updatedContentStates: { [key: string]: boolean } = {};
const updatedContentHeights: { [key: string]: string } = {};

Object.keys(contentRefs).forEach((key) => {
if (contentRefs[key].current) {
updatedContentHeights[key] = (contentRefs[key].current?.scrollHeight || 0) + 'px';
updatedContentStates[key] = contentStates[key] || false;
}
});

setContentHeights(updatedContentHeights);
setContentStates(updatedContentStates);
}, []);

const handleToggleContent = (key: string) => {
setContentStates((prevContentStates) => {
const newContentStates: { [key: string]: boolean } = {};
Object.keys(prevContentStates).forEach((contentKey) => {
if (contentKey === key) {
newContentStates[contentKey] = !prevContentStates[contentKey];
} else {
newContentStates[contentKey] = false;
}
});

return newContentStates;
});
};


return (
<div className="w-[500px] border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
{React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
if (child.type === Accordion.Item) {
const { children, value } = child.props as AccordionItemProps;
contentRefs[value] = createRef<HTMLDivElement>();

return React.cloneElement(child as ReactElement<AccordionItemProps & AccordionContentProps & AccordionTriggerProps>, {
key: value,
contentRef: contentRefs[value],
showContent: contentStates[value] || false,
contentHeight: contentHeights[value] || '0px',
setShowContent: () => handleToggleContent(value),
});
}
}
return null;
})}
</div>
);
}

Accordion.Item = function AccordionItem({
children,
value,
showContent,
contentHeight,
contentRef,
setShowContent,
}: AccordionItemProps & AccordionContentProps & AccordionTriggerProps) {
return (
<div>
{React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
if (child.type === Accordion.Trigger) {
return React.cloneElement(child as ReactElement<AccordionTriggerProps>, {
showContent,
setShowContent,
});
} else if (child.type === Accordion.Content) {
return React.cloneElement(child as ReactElement<AccordionContentProps>, {
showContent,
contentHeight,
contentRef,
});
}
}
return null;
})}
</div>
);
};

Accordion.Trigger = function AccordionTrigger({ children, showContent, setShowContent }: AccordionTriggerProps) {
return (
<button
role="button"
aria-expanded={showContent}
className="flex w-full items-center justify-between border-b border-black bg-orange-500/80 p-5 font-bold"
onClick={() => {
if (setShowContent) {
setShowContent(!showContent);
}
}}
>
{children}
<FiChevronDown
style={{ transform: `rotate(${showContent ? '180deg' : '0'})` }}
className="ml-4 min-h-[24px] min-w-[24px] transition-transform ease-in-out"
/>
</button>
);
};

Accordion.Content = function AccordionContent({
children,
showContent,
contentHeight,
contentRef,
}: AccordionContentProps) {
return (
<div
ref={contentRef}
style={{ height: showContent ? contentHeight : '0px', transition: 'height 0.3s ease-in-out' }}
className="max-h-[500px] overflow-x-hidden overflow-y-auto rounded-[5px] bg-white font-bold"
>
<div className='p-5'>
{children}
</div>
</div>
);
};
5 changes: 4 additions & 1 deletion src/components/neobruu/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
'use client'

import Image from "next/image";

type Props = {
src: string;
alt: string;
Expand All @@ -12,6 +15,6 @@ export default function Avatar({
}: Props) {

return (
<img src={src} alt={alt} className={`h-16 w-16 rounded-${rounded} border-2 border-black bg-cover bg-center shadow-[1px_1px_0px_0px_rgba(0,0,0,1)] duration-300`}/>
<Image src={src} alt={alt} className={`h-16 w-16 rounded-${rounded} border-2 border-black bg-cover bg-center shadow-[1px_1px_0px_0px_rgba(0,0,0,1)] duration-300`}/>
);
}
2 changes: 1 addition & 1 deletion src/components/neobruu/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function Button({
case 'light':
return 'border-black bg-slate-50';
case 'dark':
return 'border-black bg-zinc-900 text-white';
return 'border-white/10 bg-zinc-900 text-white';
case 'blue':
return 'border-black bg-blue-500';
case 'yellow':
Expand Down
Loading

0 comments on commit ae231eb

Please sign in to comment.