diff --git a/src/assets/styles/_variables.scss b/src/assets/styles/_variables.scss index 557283fe..be0068c5 100644 --- a/src/assets/styles/_variables.scss +++ b/src/assets/styles/_variables.scss @@ -32,3 +32,4 @@ $color-base-60: rgb($color-base, 60%); $color-base-70: rgb($color-base, 70%); $color-base-80: rgb($color-base, 80%); $color-base-90: rgb($color-base, 90%); +$color-light-bg-hover: rgb($color-light-bg, 0.8); diff --git a/src/assets/styles/misc.scss b/src/assets/styles/misc.scss index 98b32801..dedd53de 100644 --- a/src/assets/styles/misc.scss +++ b/src/assets/styles/misc.scss @@ -8,6 +8,7 @@ --main-black: #{v.$color-black-main}; --second-black: #{v.$color-black-secondary}; --light-bg: #{v.$color-light-bg}; + --light-bg-hover: #{v.$color-light-bg-hover}; --color-1: #{v.$color-1}; --color-2: #{v.$color-2}; --color-3: #{v.$color-3}; diff --git a/src/components/atoms/AccordionItem/AccordionItem.scss b/src/components/atoms/AccordionItem/AccordionItem.scss new file mode 100644 index 00000000..ce2fd06b --- /dev/null +++ b/src/components/atoms/AccordionItem/AccordionItem.scss @@ -0,0 +1,64 @@ +$color-background: var(--light-bg); +$color-background-hover: var(--light-bg-hover); +$color-background-open: linear-gradient(270deg, var(--color-1), var(--color-2)); +$border-radius: 0.5rem; + +.accordion-item { + border-radius: $border-radius; + width: 100%; + background-color: $color-background; + transition: background-color 0.3s ease-in-out; + + &:hover { + background-color: $color-background-hover; + } + + &__header { + padding: 1rem; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + width: 100%; + box-sizing: border-box; + border: none; + + h5 { + font-size: var(--font-size-h5); + margin: 0; + } + + .chevron { + transition: transform 0.3s ease-in-out; + } + } + + &__content { + padding: 1rem; + animation: open 0.3s ease-in-out; + } + + &--open { + background: $color-background-open; + /* stylelint-disable-next-line selector-class-pattern */ + .accordion-item__header { + border-bottom: 1px solid var(--color-base-20); + + .chevron { + transform: rotate(-90deg); + } + } + } +} +@keyframes open { + 0% { + opacity: 0; + transform: translateY(-1rem); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +} diff --git a/src/components/atoms/AccordionItem/AccordionItem.stories.tsx b/src/components/atoms/AccordionItem/AccordionItem.stories.tsx new file mode 100644 index 00000000..70d76243 --- /dev/null +++ b/src/components/atoms/AccordionItem/AccordionItem.stories.tsx @@ -0,0 +1,18 @@ +import { Meta, Story } from '@storybook/react'; +import { AccordionItem, AccordionItemProps } from './AccordionItem'; + +export default { + title: 'Components/Atoms/AccordionItem', + component: AccordionItem, + parameters: { actions: { argTypesRegex: null } }, +} as Meta; + +const Template: Story = (args) => ( + +); + +export const Primary: { args: AccordionItemProps } = Template.bind({}); +Primary.args = { + title: 'AccordionItem Title', + children: 'AccordionItem Content', +}; diff --git a/src/components/atoms/AccordionItem/AccordionItem.tsx b/src/components/atoms/AccordionItem/AccordionItem.tsx new file mode 100644 index 00000000..7dfdcfd1 --- /dev/null +++ b/src/components/atoms/AccordionItem/AccordionItem.tsx @@ -0,0 +1,52 @@ +import { HTMLAttributes, useState } from 'react'; +import classnames from 'classnames'; +import { DropdownChevron } from '../Icons'; +import './AccordionItem.scss'; + +export interface AccordionItemProps + extends Pick, 'className' | 'style'> { + title: string; + iconSize?: number; + open?: boolean; + onClick?: () => void; + children?: React.ReactNode; +} + +export const AccordionItem = ({ + title, + className, + style, + iconSize, + onClick, + open = false, + children, +}: AccordionItemProps) => { + const [isOpen, setIsOpen] = useState(open); + const isExpanded = open ?? isOpen; + + const handleToggle = () => { + setIsOpen((prevState) => !prevState); + }; + return ( +
+ + {isExpanded &&
{children}
} +
+ ); +}; diff --git a/src/components/atoms/AccordionItem/index.ts b/src/components/atoms/AccordionItem/index.ts new file mode 100644 index 00000000..52849382 --- /dev/null +++ b/src/components/atoms/AccordionItem/index.ts @@ -0,0 +1 @@ +export * from './AccordionItem'; diff --git a/src/components/atoms/Icons/Components/DropdownChevron.tsx b/src/components/atoms/Icons/Components/DropdownChevron.tsx new file mode 100644 index 00000000..c62a0482 --- /dev/null +++ b/src/components/atoms/Icons/Components/DropdownChevron.tsx @@ -0,0 +1,25 @@ +import cn from 'classnames'; +import { IconProps } from '../types'; + +export const DropdownChevron = ({ + width = 32, + height = 32, + className, + color = '#FFFBFF', + ...props +}: IconProps) => ( + + + +); diff --git a/src/components/atoms/Icons/Components/index.ts b/src/components/atoms/Icons/Components/index.ts new file mode 100644 index 00000000..8b2456cf --- /dev/null +++ b/src/components/atoms/Icons/Components/index.ts @@ -0,0 +1 @@ +export * from './DropdownChevron'; diff --git a/src/components/atoms/Icons/index.ts b/src/components/atoms/Icons/index.ts new file mode 100644 index 00000000..18fedba2 --- /dev/null +++ b/src/components/atoms/Icons/index.ts @@ -0,0 +1 @@ +export * from './Components'; diff --git a/src/components/atoms/Icons/types.ts b/src/components/atoms/Icons/types.ts new file mode 100644 index 00000000..b5eae803 --- /dev/null +++ b/src/components/atoms/Icons/types.ts @@ -0,0 +1,3 @@ +import { SVGAttributes } from 'react'; + +export interface IconProps extends SVGAttributes {} diff --git a/src/components/atoms/Navigation/Navigation.scss b/src/components/atoms/Navigation/Navigation.scss index cd1183d0..1d05c85e 100644 --- a/src/components/atoms/Navigation/Navigation.scss +++ b/src/components/atoms/Navigation/Navigation.scss @@ -4,15 +4,17 @@ $color-font-hover: linear-gradient(270deg, var(--color-1), var(--color-2)); .nav { margin: 0; - padding: 0; + padding: 1rem; list-style: none; + width: fit-content; &-item { - a { - @include u.font($size: var(--font-size-small)); + padding: 0.5rem; + color: var(--base-color); + @include u.font($size: var(--font-size-small)); + a { position: relative; - width: max-content; &:hover { background: $color-font-hover; @@ -20,5 +22,43 @@ $color-font-hover: linear-gradient(270deg, var(--color-1), var(--color-2)); -webkit-text-fill-color: transparent; } } + + &--menu { + position: relative; + + .chevron { + transform: rotate(90deg); + } + } + } + + &-submenu { + position: absolute; + inset-block-start: 2rem; + width: max-content; + padding: 0.5rem !important; + list-style: none; + background: var(--main-black); + border-radius: 0 0 8px 8px; + display: flex; + flex-direction: column; + z-index: 10; + + .chevron { + transform: none; + } + + & & { + inset-block-start: 0; + inset-inline-start: 100%; + } + + .nav-item { + a { + display: flex; + align-items: center; + gap: 0.5rem; + } + } } } diff --git a/src/components/atoms/Navigation/Navigation.stories.tsx b/src/components/atoms/Navigation/Navigation.stories.tsx index 96f2b87e..cb0f6d3f 100644 --- a/src/components/atoms/Navigation/Navigation.stories.tsx +++ b/src/components/atoms/Navigation/Navigation.stories.tsx @@ -1,5 +1,6 @@ import { Meta, Story } from '@storybook/react'; import { Navigation } from './Navigation'; +import { NavigationItem } from './NavigationItem'; export default { title: 'Components/Atoms/Navigation', @@ -7,34 +8,60 @@ export default { } as Meta; const Template: Story = (args) => ( - + + Contact Us + + ); export const Primary = Template.bind({}); diff --git a/src/components/atoms/Navigation/Navigation.tsx b/src/components/atoms/Navigation/Navigation.tsx index 980a96be..0b60dd55 100644 --- a/src/components/atoms/Navigation/Navigation.tsx +++ b/src/components/atoms/Navigation/Navigation.tsx @@ -1,24 +1,41 @@ /* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ -import React, { FC } from 'react'; +import classNames from 'classnames'; +import React, { FC, useState } from 'react'; +import { DropdownChevron } from '../Icons'; import './Navigation.scss'; interface NavigationProps { - onItemClick: () => void; + isSubmenu?: boolean; + title?: string; } export const Navigation: FC> = ({ children, - onItemClick, + isSubmenu, + title, }) => { - const childrenModified = React.Children.map(children, (child) => { - if (!React.isValidElement(child)) { - return null; - } - return ( -
  • - {child} -
  • - ); - }); - return
      {childrenModified}
    ; + const [isOpen, setIsOpen] = useState(false); + + if (isSubmenu) { + if (title) + return ( +
  • setIsOpen(true)} + onMouseLeave={() => setIsOpen(false)} + > + {title} + {isOpen && ( +
      + {children} +
    + )} +
  • + ); + } + return
      {children}
    ; }; diff --git a/src/components/atoms/Navigation/NavigationItem.tsx b/src/components/atoms/Navigation/NavigationItem.tsx new file mode 100644 index 00000000..bd2c0439 --- /dev/null +++ b/src/components/atoms/Navigation/NavigationItem.tsx @@ -0,0 +1,21 @@ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ +interface NavigationItemProps { + onClick?: () => void; + isMenu?: boolean; + children: React.ReactNode; +} +export const NavigationItem = ({ + onClick, + isMenu, + children, +}: NavigationItemProps) => { + if (isMenu) { + return
      {children}
    ; + } + return ( +
  • + {children} +
  • + ); +}; diff --git a/src/components/atoms/Navigation/index.ts b/src/components/atoms/Navigation/index.ts index 95e14a93..a86f73e0 100644 --- a/src/components/atoms/Navigation/index.ts +++ b/src/components/atoms/Navigation/index.ts @@ -1 +1,2 @@ export * from './Navigation'; +export * from './NavigationItem'; diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts index 64b7767a..9df219cc 100644 --- a/src/components/atoms/index.ts +++ b/src/components/atoms/index.ts @@ -1,5 +1,5 @@ export { Button } from './Button'; -export { Navigation } from './Navigation'; +export { Navigation, NavigationItem } from './Navigation'; export { Input } from './Input'; export { CelebritiesItem, type CelebritiesItemProps } from './CelebritiesItem'; export { Dropdown } from './Dropdown'; diff --git a/src/components/molecules/Accordion/Accordion.scss b/src/components/molecules/Accordion/Accordion.scss new file mode 100644 index 00000000..c3a09dcf --- /dev/null +++ b/src/components/molecules/Accordion/Accordion.scss @@ -0,0 +1,5 @@ +.accordion { + display: flex; + flex-direction: column; + gap: 1rem; +} diff --git a/src/components/molecules/Accordion/Accordion.stories.tsx b/src/components/molecules/Accordion/Accordion.stories.tsx new file mode 100644 index 00000000..71c0ed08 --- /dev/null +++ b/src/components/molecules/Accordion/Accordion.stories.tsx @@ -0,0 +1,41 @@ +import { Meta, Story } from '@storybook/react'; +import { AccordionItem } from '../../atoms/AccordionItem/AccordionItem'; +import { Accordion, AccordionProps } from './Accordion'; + +export default { + title: 'Components/Molecules/Accordion', + component: Accordion, +} as Meta; + +const Template: Story = (args) => ( + + +

    + Bonuz is a community focused and creator oriented blockchain-based + turnkey solution for building and leveraging closer connections by + rewarding all participants for their engagement (engage to earn). +

    +
    + +

    + A percentage of all the gains in the Bonuz Ecosystem will be donated to + impactful charities. Bonuz will announce a livestream event hosted by + our team and celebrities for the official launch of the Bonuz + Foundation. +

    +

    + Every $BONUZ token holder has the opportunity to votewhich charity + organization receives donations from the Bonuz foundation. +

    +
    + +

    + We at Bonuz are building essential parts of the next digital era. + Bonuz.Land is our metaverse - a virtual and augmented reality + application hosting all types of users from our ecosystem. +

    +
    +
    +); + +export const Primary: { args: AccordionProps } = Template.bind({}); diff --git a/src/components/molecules/Accordion/Accordion.tsx b/src/components/molecules/Accordion/Accordion.tsx new file mode 100644 index 00000000..ca77b30b --- /dev/null +++ b/src/components/molecules/Accordion/Accordion.tsx @@ -0,0 +1,73 @@ +import { + HTMLAttributes, + useState, + Children, + cloneElement, + isValidElement, + useMemo, + useCallback, +} from 'react'; +import cn from 'classnames'; +import './Accordion.scss'; + +export interface AccordionProps + extends Pick, 'className' | 'style'> { + children?: React.ReactNode; + isMultiOpen?: boolean; +} + +export const Accordion = ({ + children: originalChildren, + isMultiOpen, + className, + style, +}: AccordionProps) => { + const children = Children.toArray(originalChildren); + const [openItems, setOpenItems] = useState([]); + + const isOpen = useCallback( + (index: number) => openItems.includes(index), + [openItems], + ); + + const onChildClick = useCallback( + (itemIndex: number) => { + if (isMultiOpen) { + const newOpenItems = [...openItems]; + if (isOpen(itemIndex)) { + const index = newOpenItems.indexOf(itemIndex); + if (index > -1) { + newOpenItems.splice(index, 1); + } + } else { + newOpenItems.push(itemIndex); + } + setOpenItems(newOpenItems); + return; + } + + setOpenItems([itemIndex]); + }, + [isOpen, openItems, isMultiOpen], + ); + const childrenToRender = useMemo( + () => + Children.map(children, (child, index) => { + const orignalProps = isValidElement(child) && child.props; + return cloneElement(child as React.ReactElement, { + ...orignalProps, + open: isOpen(index), + onClick: () => onChildClick(index), + // eslint-disable-next-line react/no-array-index-key + key: index, + }); + }), + [children, isOpen, onChildClick], + ); + + return ( +
    + {children && childrenToRender} +
    + ); +}; diff --git a/src/components/molecules/Accordion/index.ts b/src/components/molecules/Accordion/index.ts new file mode 100644 index 00000000..63f62bc6 --- /dev/null +++ b/src/components/molecules/Accordion/index.ts @@ -0,0 +1 @@ +export * from './Accordion'; diff --git a/src/components/molecules/Header/Header.scss b/src/components/molecules/Header/Header.scss index 2bb77b27..a7ca0858 100644 --- a/src/components/molecules/Header/Header.scss +++ b/src/components/molecules/Header/Header.scss @@ -67,37 +67,16 @@ header { } &:nth-child(2) { - margin-left: 60px; - - [dir='rtl'] & { - margin-left: 0; - margin-right: 60px; - } + margin-inline-start: 60px; .menu { width: max-content; - ul { + > ul { list-style: none; - padding-left: 0; + padding-inline-start: 0; display: flex; gap: 20px; - - [dir='rtl'] & { - padding-left: unset; - padding-right: 0; - } - } - - li { - a { - @include u.font( - $weight: var(--font-weight-medium), - $size: var(--font-size-small) - ); - - text-decoration: none; - } } } } @@ -105,15 +84,9 @@ header { &:nth-child(3) { display: flex; justify-content: space-between; - margin-left: auto; - margin-right: 30px; + margin-inline-start: auto; gap: 20px; - [dir='rtl'] & { - margin-left: 30px; - margin-right: auto; - } - .dropdown { &__list > li { @include u.font($size: var(--font-size-medium)); @@ -216,17 +189,11 @@ header { } &:nth-child(2) { - margin-left: 0; - - [dir='rtl'] & { - margin-left: auto; - margin-right: 0; - } + margin-inline-start: 0; .menu { position: absolute; - left: 0; - top: 0; + inset: 0; width: 100%; height: 100vh; background: $color-background-nav-mobile; @@ -238,12 +205,7 @@ header { transform: translateY(50px); overflow: hidden; - [dir='rtl'] & { - left: unset; - right: 0; - } - - ul { + > ul { flex-direction: column; width: max-content; position: absolute; @@ -253,12 +215,6 @@ header { z-index: 11; gap: 2rem; } - - li { - a { - @include u.font($size: 20px); - } - } } } diff --git a/src/components/molecules/Header/Header.stories.tsx b/src/components/molecules/Header/Header.stories.tsx index 4f7ab86d..acb1019b 100644 --- a/src/components/molecules/Header/Header.stories.tsx +++ b/src/components/molecules/Header/Header.stories.tsx @@ -2,7 +2,7 @@ import { Meta, Story } from '@storybook/react'; import { Dropdown } from '../../atoms'; import { Header } from './Header'; import mockData from '../../../mock/mock-data.json'; - +import { Navigation, NavigationItem } from '../../atoms'; export default { title: 'Components/Layout/Header/Primary', component: Header, @@ -21,24 +21,58 @@ const Template: Story = (args) => ( } NavigationElements={[ - - How it works - , - - How it works - , - - How it works - , - - How it works - , - - How it works - , - - How it works - , + + About + , + + Services + , + + Use Cases + , + + + Crypto + + + + Web3 + + + + + + + + + + + + + Find Us + + + + , + + Contact Us + , ]} SignInElement={