From fc8441fb92353553bfc06b5d0212d14d099fc420 Mon Sep 17 00:00:00 2001 From: "murashkin.i" Date: Mon, 6 Mar 2023 20:48:34 +0200 Subject: [PATCH 01/17] feat: adding header component to ui --- packages/ui/package.json | 1 + packages/ui/src/base/header/Header.tsx | 161 +++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 packages/ui/src/base/header/Header.tsx diff --git a/packages/ui/package.json b/packages/ui/package.json index 7196676d..bb406984 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -18,6 +18,7 @@ "test:unit": "jest --config ./jest.config.js" }, "dependencies": { + "@headlessui/react": "^1.7.12", "@heroicons/react": "2.0.16", "@swc/helpers": "0.4.14", "clsx": "1.2.1" diff --git a/packages/ui/src/base/header/Header.tsx b/packages/ui/src/base/header/Header.tsx new file mode 100644 index 00000000..96fbc315 --- /dev/null +++ b/packages/ui/src/base/header/Header.tsx @@ -0,0 +1,161 @@ +import { Dialog } from '@headlessui/react' +import { XMarkIcon, Bars3Icon } from '@heroicons/react/24/outline' +import clsx from 'clsx' +import { useState } from 'react' +import type { FC, ReactNode, MouseEventHandler } from 'react' + +type NavigationItemType = { title: string; href: string } +type AuthBtnType = NavigationItemType & { onClick?: MouseEventHandler } + +type Props = { + className?: string + logo: ReactNode + navigation?: NavigationItemType[] + userNavigation: NavigationItemType[] + authBlock: ReactNode + signInConfig?: AuthBtnType + signUpConfig?: AuthBtnType + logoutConfig?: AuthBtnType +} + +export const Header: FC = props => { + const { + className, + logo, + navigation, + userNavigation, + signInConfig, + signUpConfig, + logoutConfig, + authBlock, + } = props + + const [mobileMenuOpen, setMobileMenuOpen] = useState(false) + + const unAuthBlock = () => + !!signInConfig || !!signUpConfig ? ( +
+ {!!signInConfig && ( + + {signInConfig.title} + + )} + {!!signUpConfig && ( + + {signUpConfig.title} + + )} +
+ ) : null + + return ( +
+ + + +
+ +
+ {!!logo && <>{logo}} + + {!authBlock && unAuthBlock()} + + +
+ +
+
+ {navigation && ( +
+ {navigation.map(item => ( + + {item.title} + + ))} +
+ )} + +
+ {!authBlock && userNavigation && ( +
+ {userNavigation.map(item => ( + + {item.title} + + ))} + + {!!logoutConfig && ( + + {logoutConfig.title} + + )} +
+ )} +
+
+
+
+
+
+ ) +} From 527eaf19c4b2290b977afa31ee88fac938326f30 Mon Sep 17 00:00:00 2001 From: "murashkin.i" Date: Mon, 6 Mar 2023 22:52:30 +0200 Subject: [PATCH 02/17] feat: adding navLinks to Header ui --- packages/ui/src/base/header/Header.tsx | 97 ++++++++++++++------------ 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/packages/ui/src/base/header/Header.tsx b/packages/ui/src/base/header/Header.tsx index 96fbc315..ebe0290e 100644 --- a/packages/ui/src/base/header/Header.tsx +++ b/packages/ui/src/base/header/Header.tsx @@ -1,14 +1,31 @@ import { Dialog } from '@headlessui/react' import { XMarkIcon, Bars3Icon } from '@heroicons/react/24/outline' import clsx from 'clsx' +import Link from 'next/link' import { useState } from 'react' import type { FC, ReactNode, MouseEventHandler } from 'react' +import { Button, Mode, Size } from '../button/Button' + +const linkClasses = { + main: 'inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 focus:outline-none transition duration-150 ease-in-out', + active: 'border-indigo-400 text-gray-900 focus:border-indigo-700', + inactive: + 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300', +} + +const linkClassesResponsive = { + main: 'inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 focus:outline-none transition duration-150 ease-in-out', + active: 'border-indigo-400 text-gray-900 focus:border-indigo-700', + inactive: + 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300', +} type NavigationItemType = { title: string; href: string } -type AuthBtnType = NavigationItemType & { onClick?: MouseEventHandler } +type AuthBtnType = { label: string; onClick?: MouseEventHandler } type Props = { className?: string + activePath?: string logo: ReactNode navigation?: NavigationItemType[] userNavigation: NavigationItemType[] @@ -21,6 +38,7 @@ type Props = { export const Header: FC = props => { const { className, + activePath, logo, navigation, userNavigation, @@ -32,26 +50,43 @@ export const Header: FC = props => { const [mobileMenuOpen, setMobileMenuOpen] = useState(false) + const navLinkRenderer = (params: NavigationItemType, isResponsive = false) => { + const classes = isResponsive ? linkClassesResponsive : linkClasses + const classNames = clsx( + classes.main, + { [classes.active]: params.href === activePath }, + { [classes.inactive]: params.href !== activePath } + ) + + return ( + + {params.title} + + ) + } + const unAuthBlock = () => !!signInConfig || !!signUpConfig ? (
{!!signInConfig && ( - - {signInConfig.title} - + {signInConfig.label} + )} {!!signUpConfig && ( - - {signUpConfig.title} - + {signUpConfig.label} + )}
) : null @@ -66,15 +101,7 @@ export const Header: FC = props => { {navigation && (
- {navigation.map(item => ( - - {item.title} - - ))} + {navigation.map(item => navLinkRenderer(item))}
)} @@ -114,40 +141,22 @@ export const Header: FC = props => {
{navigation && (
- {navigation.map(item => ( - - {item.title} - - ))} + {navigation.map(item => navLinkRenderer(item, true))}
)}
{!authBlock && userNavigation && (
- {userNavigation.map(item => ( - - {item.title} - - ))} + {userNavigation.map(item => navLinkRenderer(item, true))} {!!logoutConfig && ( - - {logoutConfig.title} - + {logoutConfig.label} + )}
)} From 8ebc493906dd8ae1258acc48630ff8c87979f31d Mon Sep 17 00:00:00 2001 From: "murashkin.i" Date: Tue, 7 Mar 2023 19:34:04 +0200 Subject: [PATCH 03/17] chore: update lock file --- pnpm-lock.yaml | 58 ++++---------------------------------------------- 1 file changed, 4 insertions(+), 54 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd1aeefc..7e9e33d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -371,6 +371,7 @@ importers: packages/ui: specifiers: + '@headlessui/react': ^1.7.12 '@heroicons/react': 2.0.16 '@swc/helpers': 0.4.14 '@tailwindcss/aspect-ratio': 0.4.2 @@ -407,6 +408,7 @@ importers: tsup: 6.6.3 typescript: 4.9.5 dependencies: + '@headlessui/react': 1.7.13_biqbaboplfbrettd7655fr4n2y '@heroicons/react': 2.0.16_react@18.2.0 '@swc/helpers': 0.4.14 '@wayofdev/lint-staged-config': 2.0.2_lint-staged@13.1.2 @@ -6605,7 +6607,7 @@ packages: prettier: 2.8.4 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - tailwindcss: 3.2.7_aesdjsunmf4wiehhujt67my7tu + tailwindcss: 3.2.7_postcss@8.4.21 typescript: 4.9.5 transitivePeerDependencies: - eslint-import-resolver-webpack @@ -9844,7 +9846,7 @@ packages: dependencies: fast-glob: 3.2.12 postcss: 8.4.21 - tailwindcss: 3.2.7_aesdjsunmf4wiehhujt67my7tu + tailwindcss: 3.2.7_postcss@8.4.21 dev: true /eslint-plugin-testing-library/5.10.2_vgl77cfdswitgr47lm5swmv43m: @@ -15290,24 +15292,6 @@ packages: postcss-value-parser: 4.2.0 dev: true - /postcss-load-config/3.1.4_aesdjsunmf4wiehhujt67my7tu: - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - dependencies: - lilconfig: 2.1.0 - postcss: 8.4.21 - ts-node: 10.9.1_alpjt73dvgv6kni625hu7f2l4m - yaml: 1.10.2 - dev: true - /postcss-load-config/3.1.4_postcss@8.4.21: resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} @@ -17652,40 +17636,6 @@ packages: strip-ansi: 6.0.1 dev: true - /tailwindcss/3.2.7_aesdjsunmf4wiehhujt67my7tu: - resolution: {integrity: sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==} - engines: {node: '>=12.13.0'} - hasBin: true - peerDependencies: - postcss: ^8.0.9 - dependencies: - arg: 5.0.2 - chokidar: 3.5.3 - color-name: 1.1.4 - detective: 5.2.1 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.2.12 - glob-parent: 6.0.2 - is-glob: 4.0.3 - lilconfig: 2.1.0 - micromatch: 4.0.5 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.0.0 - postcss: 8.4.21 - postcss-import: 14.1.0_postcss@8.4.21 - postcss-js: 4.0.1_postcss@8.4.21 - postcss-load-config: 3.1.4_aesdjsunmf4wiehhujt67my7tu - postcss-nested: 6.0.0_postcss@8.4.21 - postcss-selector-parser: 6.0.11 - postcss-value-parser: 4.2.0 - quick-lru: 5.1.1 - resolve: 1.22.1 - transitivePeerDependencies: - - ts-node - dev: true - /tailwindcss/3.2.7_postcss@8.4.21: resolution: {integrity: sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==} engines: {node: '>=12.13.0'} From e256b987bea9bf9637aa0d2f6b2a1e777533a1be Mon Sep 17 00:00:00 2001 From: "murashkin.i" Date: Tue, 7 Mar 2023 21:34:38 +0200 Subject: [PATCH 04/17] feat: adding Dropdown component to the ui --- packages/ui/src/base/dropdown/Dropdown.tsx | 89 ++++++++++++++++++++++ packages/ui/src/base/header/Header.tsx | 6 +- packages/ui/src/base/index.ts | 1 + 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 packages/ui/src/base/dropdown/Dropdown.tsx diff --git a/packages/ui/src/base/dropdown/Dropdown.tsx b/packages/ui/src/base/dropdown/Dropdown.tsx new file mode 100644 index 00000000..662c2f7b --- /dev/null +++ b/packages/ui/src/base/dropdown/Dropdown.tsx @@ -0,0 +1,89 @@ +import { Menu, Transition } from '@headlessui/react' +import clsx from 'clsx' +import type { LinkProps } from 'next/link' +import Link from 'next/link' +import type { FC, ReactNode, ButtonHTMLAttributes } from 'react' +import React from 'react' + +const alignmentClasses = { + left: 'origin-top-left left-0', + top: 'origin-top', + right: 'origin-top-right right-0', +} + +type DropdownItemType = { + variant: 'button' | 'link' + children: ReactNode + props?: LinkProps | ButtonHTMLAttributes +} + +type Props = { + align?: 'left' | 'top' | 'right' + width?: number + contentClasses?: string + trigger: ReactNode + items: DropdownItemType[] +} + +const Dropdown: FC = ({ + align = 'right', + width = 48, + contentClasses = 'py-1 bg-white', + trigger, + items, +}) => { + const dropdownItemRenderer = (params: DropdownItemType, isActive = false) => { + const classNames = clsx( + 'w-full text-left block px-4 py-2 text-sm leading-5 text-gray-700 focus:outline-none transition duration-150 ease-in-out', + { 'bg-gray-100': isActive } + ) + + const linkProps = (params.props || { href: '#' }) as LinkProps + const btnProps = (params.props || {}) as ButtonHTMLAttributes + + return params.variant === 'link' ? ( + + {params.children} + + ) : ( + + ) + } + + return ( + + {({ open }) => ( + <> + {trigger} + +
+ + {items?.map((item, i) => ( + + {({ active }) => dropdownItemRenderer(item, active)} + + ))} + +
+
+ + )} +
+ ) +} +export default Dropdown diff --git a/packages/ui/src/base/header/Header.tsx b/packages/ui/src/base/header/Header.tsx index ebe0290e..9c681099 100644 --- a/packages/ui/src/base/header/Header.tsx +++ b/packages/ui/src/base/header/Header.tsx @@ -35,7 +35,7 @@ type Props = { logoutConfig?: AuthBtnType } -export const Header: FC = props => { +const Header: FC = props => { const { className, activePath, @@ -106,7 +106,7 @@ export const Header: FC = props => { )}
- {!authBlock ? unAuthBlock() : <>{authBlock}} + <>{!authBlock ? unAuthBlock() : authBlock}
@@ -168,3 +168,5 @@ export const Header: FC = props => { ) } + +export default Header diff --git a/packages/ui/src/base/index.ts b/packages/ui/src/base/index.ts index b1a37dc2..e7c8eca4 100644 --- a/packages/ui/src/base/index.ts +++ b/packages/ui/src/base/index.ts @@ -1 +1,2 @@ export { Button, type ButtonProps, Size, Mode } from './button/Button' +export { default as Header } from './header/Header' From bb2e869d6367efa31371bb30fd76ead445fcd506 Mon Sep 17 00:00:00 2001 From: "murashkin.i" Date: Sat, 11 Mar 2023 16:50:17 +0200 Subject: [PATCH 05/17] feat: adding header component to web MainNav --- apps/storybook/src/stories/Banner.stories.tsx | 3 +- apps/storybook/src/stories/Button.stories.tsx | 2 +- apps/storybook/src/stories/Header.tsx | 2 +- apps/web/src/components/nav/MainNav.tsx | 248 +++++++----------- apps/web/src/features/home/pages/HomePage.tsx | 3 +- .../features/system/pages/NotFoundPage.tsx | 2 +- packages/ui/src/base/banner/Banner.tsx | 4 +- .../src/base/banner/__tests__/Banner.test.tsx | 2 +- packages/ui/src/base/button/Button.tsx | 7 +- .../src/base/button/__tests__/Button.test.tsx | 2 +- packages/ui/src/base/dropdown/Dropdown.tsx | 41 ++- packages/ui/src/base/header/Header.tsx | 103 ++++---- packages/ui/src/base/index.ts | 11 +- 13 files changed, 193 insertions(+), 237 deletions(-) diff --git a/apps/storybook/src/stories/Banner.stories.tsx b/apps/storybook/src/stories/Banner.stories.tsx index bade6b73..721e5e99 100644 --- a/apps/storybook/src/stories/Banner.stories.tsx +++ b/apps/storybook/src/stories/Banner.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react' -import { Banner, type BannerProps } from '@wayofdev/ui/src/base/banner/Banner' +import type { BannerProps } from '@wayofdev/ui/src' +import { Banner } from '@wayofdev/ui/src' const meta = { title: 'Example/Banner', diff --git a/apps/storybook/src/stories/Button.stories.tsx b/apps/storybook/src/stories/Button.stories.tsx index 62a759c5..ac0807b5 100644 --- a/apps/storybook/src/stories/Button.stories.tsx +++ b/apps/storybook/src/stories/Button.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react' -import { Button, Size, Mode } from '@wayofdev/ui/src/base/button/Button' +import { Button, Size, Mode } from '@wayofdev/ui/src' // More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction const meta = { diff --git a/apps/storybook/src/stories/Header.tsx b/apps/storybook/src/stories/Header.tsx index ca9d0537..54663f34 100644 --- a/apps/storybook/src/stories/Header.tsx +++ b/apps/storybook/src/stories/Header.tsx @@ -1,4 +1,4 @@ -import { Button, Size, Mode } from '@wayofdev/ui/src/base/button/Button' +import { Button, Size, Mode } from '@wayofdev/ui/src' import './header.css' type User = { diff --git a/apps/web/src/components/nav/MainNav.tsx b/apps/web/src/components/nav/MainNav.tsx index bbd5af16..68c3d5c6 100644 --- a/apps/web/src/components/nav/MainNav.tsx +++ b/apps/web/src/components/nav/MainNav.tsx @@ -1,174 +1,116 @@ -import { Button, Size, Mode } from '@wayofdev/ui/src/base/button/Button' +import type { NavigationItemType, AuthBtnType, DropdownItemType } from '@wayofdev/ui' +import { Header, Button, Size, Mode, Dropdown, DropdownVariant } from '@wayofdev/ui' import { useRouter } from 'next/compat/router' import Link from 'next/link' import { signIn, signOut, useSession } from 'next-auth/react' import type { FC } from 'react' -import { useState } from 'react' import AppLogo from '@/components/app-logo/AppLogo' -import Dropdown from '@/components/dropdown/Dropdown' -import { DropdownButton } from '@/components/dropdown/DropdownLink' -import NavLink from '@/components/nav/NavLink' -import ResponsiveNavLink, { ResponsiveNavButton } from '@/components/nav/ResponsiveNavLink' export const MainNav: FC = () => { const router = useRouter() - const [open, setOpen] = useState(false) - const dropdownWidth = 48 - const { data: session } = useSession() + const isAuth = !!session?.user - return ( - + return ( +
) } diff --git a/apps/web/src/features/home/pages/HomePage.tsx b/apps/web/src/features/home/pages/HomePage.tsx index 8067e693..21045b65 100644 --- a/apps/web/src/features/home/pages/HomePage.tsx +++ b/apps/web/src/features/home/pages/HomePage.tsx @@ -1,6 +1,5 @@ import { event } from '@wayofdev/facebook-pixel/src/lib/fpixel' -import { Banner } from '@wayofdev/ui/src/base/banner/Banner' -import { Button } from '@wayofdev/ui/src/base/button/Button' +import { Banner, Button } from '@wayofdev/ui' import { useTranslation } from 'next-i18next' import { NextSeo } from 'next-seo' import type { FC } from 'react' diff --git a/apps/web/src/features/system/pages/NotFoundPage.tsx b/apps/web/src/features/system/pages/NotFoundPage.tsx index c875641f..05533df8 100644 --- a/apps/web/src/features/system/pages/NotFoundPage.tsx +++ b/apps/web/src/features/system/pages/NotFoundPage.tsx @@ -18,7 +18,7 @@ export const NotFoundPage: FC = properties => { {title}
-

+

{title}

diff --git a/packages/ui/src/base/banner/Banner.tsx b/packages/ui/src/base/banner/Banner.tsx index 77f546ee..cb569fc8 100644 --- a/packages/ui/src/base/banner/Banner.tsx +++ b/packages/ui/src/base/banner/Banner.tsx @@ -7,7 +7,7 @@ export interface BannerProps { children?: never } -export const Banner: FC = ({ message }) => { +const Banner: FC = ({ message }) => { return (

@@ -43,3 +43,5 @@ export const Banner: FC = ({ message }) => {
) } + +export default Banner diff --git a/packages/ui/src/base/banner/__tests__/Banner.test.tsx b/packages/ui/src/base/banner/__tests__/Banner.test.tsx index 25989450..2bbb49e2 100644 --- a/packages/ui/src/base/banner/__tests__/Banner.test.tsx +++ b/packages/ui/src/base/banner/__tests__/Banner.test.tsx @@ -1,5 +1,5 @@ import renderer from 'react-test-renderer' -import { Banner } from '../Banner' +import Banner from '../Banner' it('should match snapshot', () => { const tree = renderer diff --git a/packages/ui/src/base/button/Button.tsx b/packages/ui/src/base/button/Button.tsx index 078409c1..1810671d 100644 --- a/packages/ui/src/base/button/Button.tsx +++ b/packages/ui/src/base/button/Button.tsx @@ -1,3 +1,4 @@ +import clsx from 'clsx' import type { ButtonHTMLAttributes, FC } from 'react' export enum Size { @@ -32,7 +33,7 @@ export interface ButtonProps extends ButtonHTMLAttributes { label: string } -export const Button: FC = ({ +const Button: FC = ({ mode = Mode.Primary, size = Size.Base, backgroundColor, @@ -42,8 +43,10 @@ export const Button: FC = ({ const baseClasses = 'inline-flex items-center border focus:outline-none font-medium shadow-sm' return ( - ) } + +export default Button diff --git a/packages/ui/src/base/button/__tests__/Button.test.tsx b/packages/ui/src/base/button/__tests__/Button.test.tsx index 557e2766..518e68c4 100644 --- a/packages/ui/src/base/button/__tests__/Button.test.tsx +++ b/packages/ui/src/base/button/__tests__/Button.test.tsx @@ -1,5 +1,5 @@ import renderer from 'react-test-renderer' -import { Button, Size, Mode } from '../Button' +import Button, { Size, Mode } from '../Button' describe('Button', () => { const sizes = Object.values(Size) diff --git a/packages/ui/src/base/dropdown/Dropdown.tsx b/packages/ui/src/base/dropdown/Dropdown.tsx index 662c2f7b..91ebe35b 100644 --- a/packages/ui/src/base/dropdown/Dropdown.tsx +++ b/packages/ui/src/base/dropdown/Dropdown.tsx @@ -5,20 +5,31 @@ import Link from 'next/link' import type { FC, ReactNode, ButtonHTMLAttributes } from 'react' import React from 'react' -const alignmentClasses = { - left: 'origin-top-left left-0', - top: 'origin-top', - right: 'origin-top-right right-0', +export enum DropdownAlign { + Left = 'left', + Top = 'top', + Right = 'right', } -type DropdownItemType = { - variant: 'button' | 'link' - children: ReactNode +const alignmentClasses: Record = { + [DropdownAlign.Left]: 'origin-top-left left-0', + [DropdownAlign.Top]: 'origin-top', + [DropdownAlign.Right]: 'origin-top-right right-0', +} + +export enum DropdownVariant { + Button = 'button', + Link = 'link', +} + +export type DropdownItemType = { + variant?: DropdownVariant + element?: ReactNode props?: LinkProps | ButtonHTMLAttributes } type Props = { - align?: 'left' | 'top' | 'right' + align?: DropdownAlign width?: number contentClasses?: string trigger: ReactNode @@ -26,13 +37,17 @@ type Props = { } const Dropdown: FC = ({ - align = 'right', + align = DropdownAlign.Right, width = 48, contentClasses = 'py-1 bg-white', trigger, items, }) => { const dropdownItemRenderer = (params: DropdownItemType, isActive = false) => { + if (!params.variant) { + return <>{params.element} + } + const classNames = clsx( 'w-full text-left block px-4 py-2 text-sm leading-5 text-gray-700 focus:outline-none transition duration-150 ease-in-out', { 'bg-gray-100': isActive } @@ -41,13 +56,13 @@ const Dropdown: FC = ({ const linkProps = (params.props || { href: '#' }) as LinkProps const btnProps = (params.props || {}) as ButtonHTMLAttributes - return params.variant === 'link' ? ( + return params.variant === DropdownVariant.Link ? ( - {params.children} + {params.element} ) : ( ) } @@ -74,7 +89,7 @@ const Dropdown: FC = ({ static > {items?.map((item, i) => ( - + {({ active }) => dropdownItemRenderer(item, active)} ))} diff --git a/packages/ui/src/base/header/Header.tsx b/packages/ui/src/base/header/Header.tsx index 9c681099..81acf37b 100644 --- a/packages/ui/src/base/header/Header.tsx +++ b/packages/ui/src/base/header/Header.tsx @@ -4,7 +4,6 @@ import clsx from 'clsx' import Link from 'next/link' import { useState } from 'react' import type { FC, ReactNode, MouseEventHandler } from 'react' -import { Button, Mode, Size } from '../button/Button' const linkClasses = { main: 'inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 focus:outline-none transition duration-150 ease-in-out', @@ -14,25 +13,28 @@ const linkClasses = { } const linkClassesResponsive = { - main: 'inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 focus:outline-none transition duration-150 ease-in-out', - active: 'border-indigo-400 text-gray-900 focus:border-indigo-700', + main: 'block pl-3 pr-4 py-2 border-l-4 text-base font-medium leading-5 focus:outline-none transition duration-150 ease-in-out', + active: + 'border-indigo-400 text-indigo-700 bg-indigo-50 focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700', inactive: - 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300', + 'border-transparent text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300', } -type NavigationItemType = { title: string; href: string } -type AuthBtnType = { label: string; onClick?: MouseEventHandler } +export type NavigationItemType = { title: string; href: string } +export type AuthBtnType = { label: string; onClick?: MouseEventHandler } type Props = { className?: string activePath?: string - logo: ReactNode + isAuth?: boolean navigation?: NavigationItemType[] - userNavigation: NavigationItemType[] - authBlock: ReactNode - signInConfig?: AuthBtnType - signUpConfig?: AuthBtnType + userNavigation?: NavigationItemType[] logoutConfig?: AuthBtnType + logo: ReactNode + userBlock: ReactNode + triggerContent: ReactNode + authBlock: ReactNode + unAuthBlock: ReactNode } const Header: FC = props => { @@ -42,10 +44,12 @@ const Header: FC = props => { logo, navigation, userNavigation, - signInConfig, - signUpConfig, logoutConfig, + isAuth, + userBlock, + triggerContent, authBlock, + unAuthBlock, } = props const [mobileMenuOpen, setMobileMenuOpen] = useState(false) @@ -65,32 +69,6 @@ const Header: FC = props => { ) } - const unAuthBlock = () => - !!signInConfig || !!signUpConfig ? ( -
- {!!signInConfig && ( - - )} - {!!signUpConfig && ( - - )} -
- ) : null - return (
)} -
- <>{!authBlock ? unAuthBlock() : authBlock} -
+ {isAuth ? ( +
+ {authBlock} +
+ ) : ( +
{unAuthBlock}
+ )}
@@ -126,7 +108,9 @@ const Header: FC = props => {
{!!logo && <>{logo}} - {!authBlock && unAuthBlock()} +
+ {!isAuth && unAuthBlock} +
- )} -
+ {isAuth && userNavigation && ( + <> + {userBlock} +
+ {userNavigation.map(item => navLinkRenderer(item, true))} + + {!!logoutConfig && ( + + )} +
+ )}
diff --git a/packages/ui/src/base/index.ts b/packages/ui/src/base/index.ts index e7c8eca4..e0a869ee 100644 --- a/packages/ui/src/base/index.ts +++ b/packages/ui/src/base/index.ts @@ -1,2 +1,9 @@ -export { Button, type ButtonProps, Size, Mode } from './button/Button' -export { default as Header } from './header/Header' +export { default as Banner, type BannerProps } from './banner/Banner' +export { default as Button, type ButtonProps, Size, Mode } from './button/Button' +export { default as Header, type NavigationItemType, type AuthBtnType } from './header/Header' +export { + default as Dropdown, + type DropdownItemType, + DropdownVariant, + DropdownAlign, +} from './dropdown/Dropdown' From 133f6b09e380ace748ab84473a135bea137f0c63 Mon Sep 17 00:00:00 2001 From: "murashkin.i" Date: Sat, 11 Mar 2023 16:57:37 +0200 Subject: [PATCH 06/17] refactor: deleting unused files --- apps/web/src/components/dropdown/Dropdown.js | 58 ------------------- .../src/components/dropdown/DropdownLink.js | 34 ----------- apps/web/src/components/nav/NavLink.js | 16 ----- .../src/components/nav/ResponsiveNavLink.js | 23 -------- 4 files changed, 131 deletions(-) delete mode 100644 apps/web/src/components/dropdown/Dropdown.js delete mode 100644 apps/web/src/components/dropdown/DropdownLink.js delete mode 100644 apps/web/src/components/nav/NavLink.js delete mode 100644 apps/web/src/components/nav/ResponsiveNavLink.js diff --git a/apps/web/src/components/dropdown/Dropdown.js b/apps/web/src/components/dropdown/Dropdown.js deleted file mode 100644 index ca4179cb..00000000 --- a/apps/web/src/components/dropdown/Dropdown.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useState } from 'react' -import { Menu, Transition } from '@headlessui/react' - -const Dropdown = ({ - align = 'right', - width = 48, - contentClasses = 'py-1 bg-white', - trigger, - children, -}) => { - let alignmentClasses - width = 'w-48' - - switch (align) { - case 'left': - alignmentClasses = 'origin-top-left left-0' - break - case 'top': - alignmentClasses = 'origin-top' - break - case 'right': - default: - alignmentClasses = 'origin-top-right right-0' - break - } - - const [open, setOpen] = useState(false) - - return ( - - {({ open }) => ( - <> - {trigger} - -
- - {children} - -
-
- - )} -
- ) -} - -export default Dropdown diff --git a/apps/web/src/components/dropdown/DropdownLink.js b/apps/web/src/components/dropdown/DropdownLink.js deleted file mode 100644 index 349c1a58..00000000 --- a/apps/web/src/components/dropdown/DropdownLink.js +++ /dev/null @@ -1,34 +0,0 @@ -import { Link } from 'next/link' -import { Menu } from '@headlessui/react' - -const DropdownLink = ({ children, ...props }) => ( - - {({ active }) => ( - - {children} - - )} - -) - -export const DropdownButton = ({ children, ...props }) => ( - - {({ active }) => ( - - )} - -) - -export default DropdownLink diff --git a/apps/web/src/components/nav/NavLink.js b/apps/web/src/components/nav/NavLink.js deleted file mode 100644 index 68c7d495..00000000 --- a/apps/web/src/components/nav/NavLink.js +++ /dev/null @@ -1,16 +0,0 @@ -import Link from 'next/link' - -const NavLink = ({ active = false, children, ...props }) => ( - - {children} - -) - -export default NavLink diff --git a/apps/web/src/components/nav/ResponsiveNavLink.js b/apps/web/src/components/nav/ResponsiveNavLink.js deleted file mode 100644 index a7dde463..00000000 --- a/apps/web/src/components/nav/ResponsiveNavLink.js +++ /dev/null @@ -1,23 +0,0 @@ -import Link from 'next/link' - -const ResponsiveNavLink = ({ active = false, children, ...props }) => ( - - {children} - -) - -export const ResponsiveNavButton = props => ( -
-
- -) diff --git a/apps/storybook/src/stories/Page.stories.ts b/apps/storybook/src/stories/Page.stories.ts deleted file mode 100644 index 6b0040f5..00000000 --- a/apps/storybook/src/stories/Page.stories.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react' -import { within, userEvent } from '@storybook/testing-library' - -import { Page } from './Page' - -const meta = { - title: 'Example/Page', - component: Page, - parameters: { - // More on how to position stories at: https://storybook.js.org/docs/7.0/react/configure/story-layout - layout: 'fullscreen', - }, -} satisfies Meta - -export default meta -type Story = StoryObj - -export const LoggedOut: Story = {} - -// More on interaction testing: https://storybook.js.org/docs/7.0/react/writing-tests/interaction-testing -export const LoggedIn: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement) - const loginButton = await canvas.getByRole('button', { - name: /Log in/i, - }) - await userEvent.click(loginButton) - }, -} diff --git a/apps/storybook/src/stories/Page.tsx b/apps/storybook/src/stories/Page.tsx deleted file mode 100644 index 6bc6e588..00000000 --- a/apps/storybook/src/stories/Page.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react' - -import { Header } from './Header' -import './page.css' - -type User = { - name: string -} - -export const Page: React.FC = () => { - const [user, setUser] = React.useState() - - return ( -
-
setUser({ name: 'Jane Doe' })} - onLogout={() => setUser(undefined)} - onCreateAccount={() => setUser({ name: 'Jane Doe' })} - /> - -
-

Pages in Storybook

-

- We recommend building UIs with a{' '} - - component-driven - {' '} - process starting with atomic components and ending with pages. -

-

- Render pages with mock data. This makes it easy to build and review page states without - needing to navigate to them in your app. Here are some handy patterns for managing page - data in Storybook: -

-
    -
  • - Use a higher-level connected component. Storybook helps you compose such data from the - "args" of child component stories -
  • -
  • - Assemble data in the page component from your services. You can mock these services out - using Storybook. -
  • -
-

- Get a guided tutorial on component-driven development at{' '} - - Storybook tutorials - - . Read more in the{' '} - - docs - - . -

-
- Tip Adjust the width of the canvas with the{' '} - - - - - - Viewports addon in the toolbar -
-
-
- ) -} diff --git a/apps/storybook/src/stories/header.css b/apps/storybook/src/stories/header.css deleted file mode 100644 index 44c549da..00000000 --- a/apps/storybook/src/stories/header.css +++ /dev/null @@ -1,32 +0,0 @@ -.wrapper { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - padding: 15px 20px; - display: flex; - align-items: center; - justify-content: space-between; -} - -svg { - display: inline-block; - vertical-align: top; -} - -h1 { - font-weight: 700; - font-size: 20px; - line-height: 1; - margin: 6px 0 6px 10px; - display: inline-block; - vertical-align: top; -} - -button + button { - margin-left: 10px; -} - -.welcome { - color: #333; - font-size: 14px; - margin-right: 10px; -} diff --git a/apps/storybook/src/stories/page.css b/apps/storybook/src/stories/page.css deleted file mode 100644 index fb64fe46..00000000 --- a/apps/storybook/src/stories/page.css +++ /dev/null @@ -1,69 +0,0 @@ -section { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 24px; - padding: 48px 20px; - margin: 0 auto; - max-width: 600px; - color: #333; -} - -section h2 { - font-weight: 700; - font-size: 32px; - line-height: 1; - margin: 0 0 4px; - display: inline-block; - vertical-align: top; -} - -section p { - margin: 1em 0; -} - -section a { - text-decoration: none; - color: #1ea7fd; -} - -section ul { - padding-left: 30px; - margin: 1em 0; -} - -section li { - margin-bottom: 8px; -} - -section .tip { - display: inline-block; - border-radius: 1em; - font-size: 11px; - line-height: 12px; - font-weight: 700; - background: #e7fdd8; - color: #66bf3c; - padding: 4px 12px; - margin-right: 10px; - vertical-align: top; -} - -section .tip-wrapper { - font-size: 13px; - line-height: 20px; - margin-top: 40px; - margin-bottom: 40px; -} - -section .tip-wrapper svg { - display: inline-block; - height: 12px; - width: 12px; - margin-right: 4px; - vertical-align: top; - margin-top: 3px; -} - -section .tip-wrapper svg path { - fill: #1ea7fd; -} diff --git a/apps/web/src/components/nav/MainNav.tsx b/apps/web/src/components/nav/MainNav.tsx index 68c3d5c6..81ccc839 100644 --- a/apps/web/src/components/nav/MainNav.tsx +++ b/apps/web/src/components/nav/MainNav.tsx @@ -1,4 +1,4 @@ -import type { NavigationItemType, AuthBtnType, DropdownItemType } from '@wayofdev/ui' +import type { NavigationItemType, LogoutBtnType, DropdownItemType } from '@wayofdev/ui' import { Header, Button, Size, Mode, Dropdown, DropdownVariant } from '@wayofdev/ui' import { useRouter } from 'next/compat/router' import Link from 'next/link' @@ -21,7 +21,7 @@ export const MainNav: FC = () => { { title: 'My Orders', href: '/my-orders' }, ] - const logoutConfig: AuthBtnType = { label: 'Logout', onClick: () => signOut() } + const logoutConfig: LogoutBtnType = { label: 'Logout', onClick: () => signOut() } const logoBlock = ( diff --git a/packages/ui/src/base/header/Header.tsx b/packages/ui/src/base/header/Header.tsx index 81acf37b..0d5937f9 100644 --- a/packages/ui/src/base/header/Header.tsx +++ b/packages/ui/src/base/header/Header.tsx @@ -21,23 +21,23 @@ const linkClassesResponsive = { } export type NavigationItemType = { title: string; href: string } -export type AuthBtnType = { label: string; onClick?: MouseEventHandler } +export type LogoutBtnType = { label: string; onClick?: MouseEventHandler } -type Props = { +export type HeaderProps = { className?: string activePath?: string isAuth?: boolean navigation?: NavigationItemType[] userNavigation?: NavigationItemType[] - logoutConfig?: AuthBtnType - logo: ReactNode - userBlock: ReactNode - triggerContent: ReactNode - authBlock: ReactNode - unAuthBlock: ReactNode + logoutConfig?: LogoutBtnType + logo?: ReactNode + userBlock?: ReactNode + triggerContent?: ReactNode + authBlock?: ReactNode + unAuthBlock?: ReactNode } -const Header: FC = props => { +const Header: FC = props => { const { className, activePath, diff --git a/packages/ui/src/base/index.ts b/packages/ui/src/base/index.ts index e0a869ee..a6822866 100644 --- a/packages/ui/src/base/index.ts +++ b/packages/ui/src/base/index.ts @@ -1,6 +1,11 @@ export { default as Banner, type BannerProps } from './banner/Banner' export { default as Button, type ButtonProps, Size, Mode } from './button/Button' -export { default as Header, type NavigationItemType, type AuthBtnType } from './header/Header' +export { + default as Header, + type HeaderProps, + type NavigationItemType, + type LogoutBtnType, +} from './header/Header' export { default as Dropdown, type DropdownItemType, From 3d15c48c655142aa5c09b95a343a3362b2b6e218 Mon Sep 17 00:00:00 2001 From: "murashkin.i" Date: Sat, 11 Mar 2023 20:24:37 +0200 Subject: [PATCH 08/17] fix: components pathes --- apps/storybook/src/stories/Banner.stories.tsx | 2 +- apps/storybook/src/stories/Button.stories.tsx | 2 +- apps/storybook/src/stories/Header.stories.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/storybook/src/stories/Banner.stories.tsx b/apps/storybook/src/stories/Banner.stories.tsx index 721e5e99..453668ec 100644 --- a/apps/storybook/src/stories/Banner.stories.tsx +++ b/apps/storybook/src/stories/Banner.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react' import type { BannerProps } from '@wayofdev/ui/src' -import { Banner } from '@wayofdev/ui/src' +import Banner from '@wayofdev/ui/src/base/banner/Banner' const meta = { title: 'Example/Banner', diff --git a/apps/storybook/src/stories/Button.stories.tsx b/apps/storybook/src/stories/Button.stories.tsx index ac0807b5..6224ed27 100644 --- a/apps/storybook/src/stories/Button.stories.tsx +++ b/apps/storybook/src/stories/Button.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react' -import { Button, Size, Mode } from '@wayofdev/ui/src' +import Button, { Size, Mode } from '@wayofdev/ui/src/base/button/Button' // More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction const meta = { diff --git a/apps/storybook/src/stories/Header.stories.tsx b/apps/storybook/src/stories/Header.stories.tsx index 6e562bc7..55787e18 100644 --- a/apps/storybook/src/stories/Header.stories.tsx +++ b/apps/storybook/src/stories/Header.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react' import type { HeaderProps } from '@wayofdev/ui/src' -import { Header } from '@wayofdev/ui/src' +import Header from '@wayofdev/ui/src/base/header/Header' const meta = { title: 'Example/Header', From 81eb84a726f1ac1b9e38a91f0debbd16c155e495 Mon Sep 17 00:00:00 2001 From: "murashkin.i" Date: Sun, 12 Mar 2023 01:08:39 +0200 Subject: [PATCH 09/17] feat: adding Header stories examples --- apps/storybook/.eslintrc.js | 2 +- apps/storybook/.storybook/NextLink.tsx | 2 + apps/storybook/.storybook/main.ts | 4 + apps/storybook/src/stories/Banner.stories.tsx | 2 +- apps/storybook/src/stories/Button.stories.tsx | 2 +- apps/storybook/src/stories/Header.stories.tsx | 104 +++++++++++++++++- packages/ui/src/base/dropdown/Dropdown.tsx | 11 +- packages/ui/src/base/header/Header.tsx | 10 +- 8 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 apps/storybook/.storybook/NextLink.tsx diff --git a/apps/storybook/.eslintrc.js b/apps/storybook/.eslintrc.js index 3f5abe96..60e9012a 100644 --- a/apps/storybook/.eslintrc.js +++ b/apps/storybook/.eslintrc.js @@ -36,7 +36,7 @@ module.exports = { }, overrides: [ { - files: ['src/stories/*.ts'], + files: ['src/stories/*.{ts,tsx}'], rules: { '@typescript-eslint/naming-convention': 'off', }, diff --git a/apps/storybook/.storybook/NextLink.tsx b/apps/storybook/.storybook/NextLink.tsx new file mode 100644 index 00000000..ccdaa768 --- /dev/null +++ b/apps/storybook/.storybook/NextLink.tsx @@ -0,0 +1,2 @@ +const NextLink = props => +export default NextLink diff --git a/apps/storybook/.storybook/main.ts b/apps/storybook/.storybook/main.ts index c1558574..c1bef61b 100644 --- a/apps/storybook/.storybook/main.ts +++ b/apps/storybook/.storybook/main.ts @@ -23,5 +23,9 @@ const config: StorybookConfig = { docs: { autodocs: 'tag', }, + async viteFinal(config) { + config.resolve.alias['next/link'] = require.resolve('./NextLink.tsx') + return config + }, } export default config diff --git a/apps/storybook/src/stories/Banner.stories.tsx b/apps/storybook/src/stories/Banner.stories.tsx index 453668ec..721e5e99 100644 --- a/apps/storybook/src/stories/Banner.stories.tsx +++ b/apps/storybook/src/stories/Banner.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react' import type { BannerProps } from '@wayofdev/ui/src' -import Banner from '@wayofdev/ui/src/base/banner/Banner' +import { Banner } from '@wayofdev/ui/src' const meta = { title: 'Example/Banner', diff --git a/apps/storybook/src/stories/Button.stories.tsx b/apps/storybook/src/stories/Button.stories.tsx index 6224ed27..ac0807b5 100644 --- a/apps/storybook/src/stories/Button.stories.tsx +++ b/apps/storybook/src/stories/Button.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react' -import Button, { Size, Mode } from '@wayofdev/ui/src/base/button/Button' +import { Button, Size, Mode } from '@wayofdev/ui/src' // More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction const meta = { diff --git a/apps/storybook/src/stories/Header.stories.tsx b/apps/storybook/src/stories/Header.stories.tsx index 55787e18..3634c301 100644 --- a/apps/storybook/src/stories/Header.stories.tsx +++ b/apps/storybook/src/stories/Header.stories.tsx @@ -1,6 +1,29 @@ import type { Meta, StoryObj } from '@storybook/react' import type { HeaderProps } from '@wayofdev/ui/src' -import Header from '@wayofdev/ui/src/base/header/Header' +import { Button, Dropdown, DropdownVariant, Header } from '@wayofdev/ui/src' +import Img from './assets/colors.svg' + +const triggerContent = ( + <> +
john.doe@example.com
+ +
+ + + +
+ +) + +const dropdownItems = [ + { variant: DropdownVariant.Button, element: 'Settings', props: { href: '/#Settings' } }, + { variant: DropdownVariant.Button, element: 'My orders', props: { href: '/#MyOrders' } }, + { variant: DropdownVariant.Button, element: 'Logout' }, +] const meta = { title: 'Example/Header', @@ -11,16 +34,85 @@ const meta = { // More on how to position stories at: https://storybook.js.org/docs/7.0/react/configure/story-layout layout: 'fullscreen', }, + args: { + isAuth: false, + activePath: '/', + navigation: [ + { title: 'Home', href: '/' }, + { title: 'Products', href: '/#Products' }, + ], + userNavigation: [ + { title: 'Settings', href: '/#Settings' }, + { title: 'My orders', href: '/#MyOrders' }, + ], + logoutConfig: { label: 'Logout' }, + logo: logo, + unAuthBlock: + } + /> + ), + userBlock: ( +
+
+ + + +
+ +
+
John Doe
+
john.doe@example.com
+
+
+ ), + }, } satisfies Meta export default meta type Story = StoryObj -export const Default: Story = { - args: {}, +export const LoggedOut: Story = {} + +export const LoggedIn: Story = { + args: { + isAuth: true, + triggerContent: undefined, + authBlock: , + }, } -export const LoggedOut: Story = {} +export const CustomTriggerMenu: Story = { + args: { + isAuth: true, + }, +} -// More on interaction testing: https://storybook.js.org/docs/7.0/react/writing-tests/interaction-testing -export const LoggedIn: Story = {} +export const MobileMenuUserInfo: Story = { + args: { + isAuth: true, + }, + parameters: { + viewport: { + defaultViewport: 'iphonexsmax', + }, + }, +} diff --git a/packages/ui/src/base/dropdown/Dropdown.tsx b/packages/ui/src/base/dropdown/Dropdown.tsx index 91ebe35b..68fd7204 100644 --- a/packages/ui/src/base/dropdown/Dropdown.tsx +++ b/packages/ui/src/base/dropdown/Dropdown.tsx @@ -1,4 +1,5 @@ import { Menu, Transition } from '@headlessui/react' +import { Bars3Icon } from '@heroicons/react/24/outline' import clsx from 'clsx' import type { LinkProps } from 'next/link' import Link from 'next/link' @@ -32,7 +33,7 @@ type Props = { align?: DropdownAlign width?: number contentClasses?: string - trigger: ReactNode + trigger?: ReactNode items: DropdownItemType[] } @@ -71,7 +72,13 @@ const Dropdown: FC = ({
{({ open }) => ( <> - {trigger} + + {trigger || ( + + )} + = props => { > {!!logo &&
{logo}
} - {navigation && ( + {!!navigation && (
- {navigation.map(item => navLinkRenderer(item))} + {navigation?.map(item => navLinkRenderer(item))}
)} @@ -125,16 +125,16 @@ const Header: FC = props => {
{navigation && (
- {navigation.map(item => navLinkRenderer(item, true))} + {navigation?.map(item => navLinkRenderer(item, true))}
)}
- {isAuth && userNavigation && ( + {isAuth && !!userNavigation && ( <> {userBlock}
- {userNavigation.map(item => navLinkRenderer(item, true))} + {userNavigation?.map(item => navLinkRenderer(item, true))} {!!logoutConfig && ( + ), + }, +} + +export const DropdownCustomWidth: Story = { + args: { + widthClass: 'w-80', + }, +} + +export const DropdownCustomClass: Story = { + args: { + contentClasses: 'py-1.5 bg-purple-200', + }, +} diff --git a/apps/storybook/src/stories/Header.stories.tsx b/apps/storybook/src/stories/Header.stories.tsx index 3634c301..9a123072 100644 --- a/apps/storybook/src/stories/Header.stories.tsx +++ b/apps/storybook/src/stories/Header.stories.tsx @@ -106,6 +106,12 @@ export const CustomTriggerMenu: Story = { }, } +export const CustomHeaderClass: Story = { + args: { + className: 'bg-green-200', + }, +} + export const MobileMenuUserInfo: Story = { args: { isAuth: true, diff --git a/packages/ui/src/base/dropdown/Dropdown.tsx b/packages/ui/src/base/dropdown/Dropdown.tsx index 68fd7204..0fddce30 100644 --- a/packages/ui/src/base/dropdown/Dropdown.tsx +++ b/packages/ui/src/base/dropdown/Dropdown.tsx @@ -29,17 +29,17 @@ export type DropdownItemType = { props?: LinkProps | ButtonHTMLAttributes } -type Props = { +export type DropdownProps = { align?: DropdownAlign - width?: number + widthClass?: string contentClasses?: string trigger?: ReactNode items: DropdownItemType[] } -const Dropdown: FC = ({ +const Dropdown: FC = ({ align = DropdownAlign.Right, - width = 48, + widthClass = 'w-48', contentClasses = 'py-1 bg-white', trigger, items, @@ -89,7 +89,7 @@ const Dropdown: FC = ({ leaveTo="transform opacity-0 scale-95" >
= props => { const { - className, + className = 'bg-white', activePath, logo, navigation, @@ -70,7 +70,7 @@ const Header: FC = props => { } return ( -
+
} + unAuthBlock={
unAuthBlock
} + /> + ) + .toJSON() + expect(tree).toMatchSnapshot() + }) +}) diff --git a/packages/ui/src/base/header/__tests__/__snapshots__/Header.test.tsx.snap b/packages/ui/src/base/header/__tests__/__snapshots__/Header.test.tsx.snap new file mode 100644 index 00000000..f39ccd34 --- /dev/null +++ b/packages/ui/src/base/header/__tests__/__snapshots__/Header.test.tsx.snap @@ -0,0 +1,90 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Header renders correctly with navigation and auth elements 1`] = ` +
+ +
+
+`; From d3b333f095b6df9a43769b5be23e13a9ec8fae9e Mon Sep 17 00:00:00 2001 From: "murashkin.i" Date: Mon, 13 Mar 2023 12:29:32 +0200 Subject: [PATCH 13/17] chore: updating versions --- .changeset/nine-queens-retire.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/nine-queens-retire.md diff --git a/.changeset/nine-queens-retire.md b/.changeset/nine-queens-retire.md new file mode 100644 index 00000000..4290c2f9 --- /dev/null +++ b/.changeset/nine-queens-retire.md @@ -0,0 +1,7 @@ +--- +'@wayofdev/storybook': minor +'@wayofdev/ui': minor +'@wayofdev/web': minor +--- + +feat: header and dropdown refactor From 53edb96f178609d728111bae7727f7cb80a31df7 Mon Sep 17 00:00:00 2001 From: lotyp Date: Tue, 21 Mar 2023 14:40:35 +0200 Subject: [PATCH 14/17] chore: remove page.tsx --- apps/storybook/src/stories/Page.tsx | 72 ----------------------------- 1 file changed, 72 deletions(-) delete mode 100644 apps/storybook/src/stories/Page.tsx diff --git a/apps/storybook/src/stories/Page.tsx b/apps/storybook/src/stories/Page.tsx deleted file mode 100644 index 02c88087..00000000 --- a/apps/storybook/src/stories/Page.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { type FC, useState } from 'react' -import { Header } from './Header' -import './page.css' - -type User = { - name: string -} - -export const Page: FC = () => { - const [user, setUser] = useState() - - return ( -
-
setUser({ name: 'Jane Doe' })} - onLogout={() => setUser(undefined)} - onCreateAccount={() => setUser({ name: 'Jane Doe' })} - /> - -
-

Pages in Storybook

-

- We recommend building UIs with a{' '} - - component-driven - {' '} - process starting with atomic components and ending with pages. -

-

- Render pages with mock data. This makes it easy to build and review page states without - needing to navigate to them in your app. Here are some handy patterns for managing page - data in Storybook: -

-
    -
  • - Use a higher-level connected component. Storybook helps you compose such data from the - "args" of child component stories -
  • -
  • - Assemble data in the page component from your services. You can mock these services out - using Storybook. -
  • -
-

- Get a guided tutorial on component-driven development at{' '} - - Storybook tutorials - - . Read more in the{' '} - - docs - - . -

-
- Tip Adjust the width of the canvas with the{' '} - - - - - - Viewports addon in the toolbar -
-
-
- ) -} From c0c114b6bce408e72a752358aadca995d1ef7f6e Mon Sep 17 00:00:00 2001 From: lotyp Date: Wed, 22 Mar 2023 12:35:57 +0200 Subject: [PATCH 15/17] refactor: move dropdown item into separate file --- apps/storybook/src/stories/Header.stories.tsx | 18 +- apps/web/package.json | 1 - apps/web/src/components/nav/MainNav.tsx | 18 +- packages/ui/src/base/dropdown/Dropdown.tsx | 48 +- .../ui/src/base/dropdown/DropdownItem.tsx | 45 + .../base/dropdown/__tests__/Dropdown.test.tsx | 7 +- packages/ui/src/base/header/Header.tsx | 16 +- .../src/base/header/__tests__/Header.test.tsx | 2 +- .../__snapshots__/Header.test.tsx.snap | 2 +- packages/ui/src/base/index.ts | 12 +- pnpm-lock.yaml | 3605 ++++++----------- 11 files changed, 1218 insertions(+), 2556 deletions(-) create mode 100644 packages/ui/src/base/dropdown/DropdownItem.tsx diff --git a/apps/storybook/src/stories/Header.stories.tsx b/apps/storybook/src/stories/Header.stories.tsx index 9a123072..954f0cf5 100644 --- a/apps/storybook/src/stories/Header.stories.tsx +++ b/apps/storybook/src/stories/Header.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react' import type { HeaderProps } from '@wayofdev/ui/src' -import { Button, Dropdown, DropdownVariant, Header } from '@wayofdev/ui/src' +import { Button, Dropdown, DropdownItemVariant, Header } from '@wayofdev/ui/src' import Img from './assets/colors.svg' const triggerContent = ( @@ -20,9 +20,9 @@ const triggerContent = ( ) const dropdownItems = [ - { variant: DropdownVariant.Button, element: 'Settings', props: { href: '/#Settings' } }, - { variant: DropdownVariant.Button, element: 'My orders', props: { href: '/#MyOrders' } }, - { variant: DropdownVariant.Button, element: 'Logout' }, + { variant: DropdownItemVariant.Button, element: 'Settings', props: { href: '/#Settings' } }, + { variant: DropdownItemVariant.Button, element: 'My orders', props: { href: '/#MyOrders' } }, + { variant: DropdownItemVariant.Button, element: 'Logout' }, ] const meta = { @@ -35,7 +35,7 @@ const meta = { layout: 'fullscreen', }, args: { - isAuth: false, + isAuthenticated: false, activePath: '/', navigation: [ { title: 'Home', href: '/' }, @@ -47,7 +47,7 @@ const meta = { ], logoutConfig: { label: 'Logout' }, logo: logo, - unAuthBlock:
) } + export default Dropdown diff --git a/packages/ui/src/base/dropdown/DropdownItem.tsx b/packages/ui/src/base/dropdown/DropdownItem.tsx new file mode 100644 index 00000000..dd4910ac --- /dev/null +++ b/packages/ui/src/base/dropdown/DropdownItem.tsx @@ -0,0 +1,45 @@ +import { clsx } from 'clsx' +import Link from 'next/link' +import type { LinkProps } from 'next/link' +import { type ButtonHTMLAttributes, type ReactNode, forwardRef } from 'react' + +export enum DropdownItemVariant { + Button = 'button', + Link = 'link', +} + +export type DropdownItemType = { + variant?: DropdownItemVariant + element?: ReactNode + props?: LinkProps | ButtonHTMLAttributes +} + +const DropdownItem = forwardRef( + ({ item, isActive = false }, ref) => { + if (!item.variant) { + return <>{item.element} + } + + const classNames = clsx( + 'w-full text-left block px-4 py-2 text-sm leading-5 text-gray-700 focus:outline-none transition duration-150 ease-in-out', + { 'bg-gray-100': isActive } + ) + + const linkProps = (item.props || { href: '#' }) as LinkProps + const btnProps = (item.props || {}) as ButtonHTMLAttributes + + return item.variant === DropdownItemVariant.Link ? ( + + {item.element} + + ) : ( + + ) + } +) + +DropdownItem.displayName = 'DropdownItem' + +export default DropdownItem diff --git a/packages/ui/src/base/dropdown/__tests__/Dropdown.test.tsx b/packages/ui/src/base/dropdown/__tests__/Dropdown.test.tsx index b057eca5..1326936b 100644 --- a/packages/ui/src/base/dropdown/__tests__/Dropdown.test.tsx +++ b/packages/ui/src/base/dropdown/__tests__/Dropdown.test.tsx @@ -1,10 +1,11 @@ import renderer from 'react-test-renderer' -import Dropdown, { DropdownAlign, DropdownVariant } from '../Dropdown' +import Dropdown, { DropdownAlign } from '../Dropdown' +import { DropdownItemVariant } from '../DropdownItem' describe('Dropdown', () => { const variants = { - link: { variant: DropdownVariant.Link, element: 'Profile', props: { href: '/#Profile' } }, - button: { variant: DropdownVariant.Button, element: 'Action' }, + link: { variant: DropdownItemVariant.Link, element: 'Profile', props: { href: '/#Profile' } }, + button: { variant: DropdownItemVariant.Button, element: 'Action' }, custom: { element:
Profile
}, } diff --git a/packages/ui/src/base/header/Header.tsx b/packages/ui/src/base/header/Header.tsx index e107e5b7..d697c5cc 100644 --- a/packages/ui/src/base/header/Header.tsx +++ b/packages/ui/src/base/header/Header.tsx @@ -26,7 +26,7 @@ export type LogoutBtnType = { label: string; onClick?: MouseEventHandler = props => { @@ -45,11 +45,11 @@ const Header: FC = props => { navigation, userNavigation, logoutConfig, - isAuth, + isAuthenticated, userBlock, triggerContent, authBlock, - unAuthBlock, + guestBlock, } = props const [mobileMenuOpen, setMobileMenuOpen] = useState(false) @@ -83,12 +83,12 @@ const Header: FC = props => { )} - {isAuth ? ( + {isAuthenticated ? (
{authBlock}
) : ( -
{unAuthBlock}
+
{guestBlock}
)}
@@ -109,7 +109,7 @@ const Header: FC = props => { {!!logo && <>{logo}}
- {!isAuth && unAuthBlock} + {!isAuthenticated && guestBlock}