diff --git a/.changeset/brave-bats-look.md b/.changeset/brave-bats-look.md new file mode 100644 index 0000000000..8f2b4d3cac --- /dev/null +++ b/.changeset/brave-bats-look.md @@ -0,0 +1,5 @@ +--- +'@kadena/kode-ui': minor +--- + +add SideBar Component diff --git a/packages/apps/dev-wallet/src/App/BetaHeader.tsx b/packages/apps/dev-wallet/src/App/BetaHeader.tsx index 5180c9731d..0c14ccdbb1 100644 --- a/packages/apps/dev-wallet/src/App/BetaHeader.tsx +++ b/packages/apps/dev-wallet/src/App/BetaHeader.tsx @@ -27,6 +27,7 @@ export const BetaHeader = () => { return ( <> { + const { theme, setTheme } = useTheme(); + const { breadCrumbs, setBreadCrumbs, setAppContext } = useSideBar(); + const location = useLocation(); + + useEffect(() => { + if ( + breadCrumbs.length > 0 && + location.pathname !== breadCrumbs[breadCrumbs.length - 1].url + ) { + setBreadCrumbs([]); + setAppContext(); + } + }, [location]); + + const toggleTheme = (): void => { + const newTheme = theme === Themes.dark ? Themes.light : Themes.dark; + setTheme(newTheme); + }; + return ( + <> + } + breadcrumbs={} + activeUrl={location.pathname} + sidebar={} + footer={ + + } + label="Profile" + /> + + } + label="Keys" + /> + + } + label="Select network" + > + + + } + label="Change theme" + onPress={toggleTheme} + /> + + } + > + + + +
+ + ); +}; diff --git a/packages/apps/dev-wallet/src/App/Layout/SideBar.tsx b/packages/apps/dev-wallet/src/App/Layout/SideBar.tsx new file mode 100644 index 0000000000..32a54169c9 --- /dev/null +++ b/packages/apps/dev-wallet/src/App/Layout/SideBar.tsx @@ -0,0 +1,157 @@ +import { + MonoContacts, + MonoContrast, + MonoDashboardCustomize, + MonoDataThresholding, + MonoLogout, + MonoNetworkCheck, + MonoSwapHoriz, + MonoTableRows, + MonoWallet, +} from '@kadena/kode-icons/system'; + +import { NetworkSelector } from '@/Components/NetworkSelector/NetworkSelector'; +import { useWallet } from '@/modules/wallet/wallet.hook'; +import { + Button, + ContextMenu, + ContextMenuItem, + Themes, + useTheme, +} from '@kadena/kode-ui'; +import { + SideBarItem, + SideBarItemsInline, + SideBarTree, + SideBarTreeItem, + SideBar as SideBarUI, + useSideBar, +} from '@kadena/kode-ui/patterns'; +import { FC } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; + +export const SideBar: FC = () => { + const { theme, setTheme } = useTheme(); + const { isExpanded } = useSideBar(); + const navigate = useNavigate(); + const { lockProfile } = useWallet(); + + const toggleTheme = (): void => { + const newTheme = theme === Themes.dark ? Themes.light : Themes.dark; + setTheme(newTheme); + }; + + const handleLogout = () => { + lockProfile(); + }; + return ( + } label="Select network"> + + + } + navigation={ + <> + } + label="Dashboard" + component={Link} + href="/" + /> + + } label="My Wallets"> + + + } label="Transactions"> + + + } label="Utilities"> + + + + + + } + label="Transfer" + component={Link} + href="/transfer" + /> + + } + label="Contacts" + component={Link} + href="/contacts" + /> + + } + context={ + <> + + } label="Profile"> + } + > + {isExpanded ? 'Profile' : undefined} + + } + > + navigate('/profile')} + label="Profile" + /> + } + label="Logout" + onClick={handleLogout} + /> + + + } + onPress={toggleTheme} + label="Change theme" + > + + } + > + {networks.map((network) => ( + handleNetworkUpdate(network.uuid)} + /> + ))} + + } + onClick={handlePress} + /> + + ); +}; diff --git a/packages/apps/dev-wallet/src/pages/BreadCrumbs/BreadCrumbs.tsx b/packages/apps/dev-wallet/src/pages/BreadCrumbs/BreadCrumbs.tsx new file mode 100644 index 0000000000..d36a696843 --- /dev/null +++ b/packages/apps/dev-wallet/src/pages/BreadCrumbs/BreadCrumbs.tsx @@ -0,0 +1,24 @@ +import { BreadcrumbsItem, Breadcrumbs as BreadcrumbsUI } from '@kadena/kode-ui'; +import { useSideBar } from '@kadena/kode-ui/patterns'; +import { FC } from 'react'; +import { Link } from 'react-router-dom'; + +export const BreadCrumbs: FC = () => { + const { breadCrumbs } = useSideBar(); + + if (!breadCrumbs.length) return null; + + return ( + + <> + {breadCrumbs.map((crumb) => ( + + + {crumb.label} + + + ))} + + + ); +}; diff --git a/packages/apps/dev-wallet/src/pages/networks/networks.tsx b/packages/apps/dev-wallet/src/pages/networks/networks.tsx index f446e45f27..6a98b668cd 100644 --- a/packages/apps/dev-wallet/src/pages/networks/networks.tsx +++ b/packages/apps/dev-wallet/src/pages/networks/networks.tsx @@ -1,6 +1,7 @@ import { ListItem } from '@/Components/ListItem/ListItem'; import { networkRepository } from '@/modules/network/network.repository'; import { useWallet } from '@/modules/wallet/wallet.hook'; +import { Mono123, MonoWifiTethering } from '@kadena/kode-icons/system'; import { Button, Dialog, @@ -10,7 +11,8 @@ import { Stack, Text, } from '@kadena/kode-ui'; -import { useState } from 'react'; +import { useSideBar } from '@kadena/kode-ui/patterns'; +import { useEffect, useState } from 'react'; import { panelClass } from '../home/style.css'; import { INetworkWithOptionalUuid, @@ -33,9 +35,22 @@ const getNewNetwork = (): INetworkWithOptionalUuid => ({ export function Networks() { const { networks } = useWallet(); + const { setAppContext, setBreadCrumbs } = useSideBar(); const [showNetworkModal, setShowNetworkModal] = useState(false); const [selectedNetwork, setSelectedNetwork] = useState(() => getNewNetwork()); + + useEffect(() => { + setAppContext({ + visual: , + label: 'test', + onPress: () => alert(111), + }); + + setBreadCrumbs([ + { label: 'Networks', visual: , url: '/networks' }, + ]); + }, []); return ( <> diff --git a/packages/libs/kode-ui/package.json b/packages/libs/kode-ui/package.json index e250476c69..877c05f137 100644 --- a/packages/libs/kode-ui/package.json +++ b/packages/libs/kode-ui/package.json @@ -80,6 +80,7 @@ "lint:pkg": "lint-package", "lint:src": "eslint src --ext .js,.ts,.tsx", "storybook": "storybook dev -p 6006", + "storybook:prod": "storybook", "test": "vitest run", "test:watch": "vitest", "tokens:sync": "design-sync sync", @@ -107,7 +108,8 @@ "react": "^18.2.0", "react-aria": "^3.31.1", "react-dom": "^18.2.0", - "react-stately": "^3.29.1" + "react-stately": "^3.29.1", + "react-use": "^17.4.0" }, "devDependencies": { "@crackle/cli": "^0.15.1", diff --git a/packages/libs/kode-ui/src/components/Link/Link.tsx b/packages/libs/kode-ui/src/components/Link/Link.tsx index ea6c837cb4..5e8b8e1f56 100644 --- a/packages/libs/kode-ui/src/components/Link/Link.tsx +++ b/packages/libs/kode-ui/src/components/Link/Link.tsx @@ -2,7 +2,7 @@ import { mergeProps, useObjectRef } from '@react-aria/utils'; import type { RecipeVariants } from '@vanilla-extract/recipes'; import classNames from 'classnames'; import type { ForwardedRef, ReactElement } from 'react'; -import React, { forwardRef } from 'react'; +import React, { forwardRef, useMemo } from 'react'; import type { AriaButtonProps, AriaFocusRingProps } from 'react-aria'; import { useFocusRing, useHover, useLink } from 'react-aria'; import { @@ -22,7 +22,10 @@ type Variants = NonNullable>; export type ILinkProps = Omit & Variants & - Pick, 'aria-label' | 'href' | 'type' | 'target'> & { + Pick< + AriaButtonProps<'button'>, + 'aria-label' | 'href' | 'type' | 'target' | 'onPress' + > & { className?: string; isLoading?: boolean; isDisabled?: boolean; @@ -32,6 +35,7 @@ export type ILinkProps = Omit & children?: string | number | ReactElement; startVisual?: ReactElement; endVisual?: ReactElement; + component?: any; }; /** @@ -49,6 +53,15 @@ export type ILinkProps = Omit & * @param title - title to be shown as HTML tooltip */ +const Anchor = forwardRef( + ({ children, ...props }, ref) => ( + + {children} + + ), +); +Anchor.displayName = 'Anchor'; + const Link = forwardRef( ( { @@ -59,6 +72,7 @@ const Link = forwardRef( loadingLabel = 'Loading', variant = 'transparent', className, + component, ...props }: ILinkProps, forwardedRef: ForwardedRef, @@ -71,6 +85,10 @@ const Link = forwardRef( const { focusProps, isFocused, isFocusVisible } = useFocusRing(props); const { isLoading, isDisabled, style, title } = props; + const LinkWrapper = useMemo(() => { + return component ?? Anchor; + }, [component]); + const iconOnly = Boolean( // check if children is a ReactElement (typeof children !== 'string' && typeof children !== 'number') || @@ -86,7 +104,8 @@ const Link = forwardRef( } loading`.trim(); return ( - )} - + ); }, ); diff --git a/packages/libs/kode-ui/src/patterns/SideBarLayout/SideBar.tsx b/packages/libs/kode-ui/src/patterns/SideBarLayout/SideBar.tsx new file mode 100644 index 0000000000..0848872fd3 --- /dev/null +++ b/packages/libs/kode-ui/src/patterns/SideBarLayout/SideBar.tsx @@ -0,0 +1,83 @@ +import { MonoMenu, MonoMenuOpen } from '@kadena/kode-icons/system'; +import classNames from 'classnames'; +import type { FC, PropsWithChildren, ReactElement } from 'react'; +import React from 'react'; +import type { PressEvent } from './../../components'; +import { Button, Media, Stack } from './../../components'; +import { KLogo } from './components/Logo/KLogo'; +import { KadenaLogo } from './components/Logo/KadenaLogo'; +import { SideBarAppContext } from './components/SideBarAppContext'; +import { SideBarContext } from './components/SideBarContext'; +import { SideBarNavigation } from './components/SideBarNavigation'; +import { useSideBar } from './components/SideBarProvider'; +import { + menuBackdropClass, + menuMenuIconClass, + menuWrapperClass, + menuWrapperMobileExpandedClass, +} from './sidebar.css'; + +export interface ISideBarProps extends PropsWithChildren { + activeUrl?: string; + appContext?: ReactElement; + navigation?: ReactElement; + context?: ReactElement; + logo?: ReactElement; + minifiedLogo?: ReactElement; +} + +export const SideBar: FC = ({ + children, + appContext, + navigation, + context, + logo, + minifiedLogo, +}) => { + const { isExpanded, handleToggleExpand } = useSideBar(); + + const handleExpand = (e: PressEvent) => { + if (handleToggleExpand) { + handleToggleExpand(e); + } + }; + + const ShowLogo = () => { + if (!isExpanded) { + return minifiedLogo ? minifiedLogo : ; + } + + return logo ? logo : ; + }; + + return ( + <> + + + + ); +}; diff --git a/packages/libs/kode-ui/src/patterns/SideBarLayout/SideBarLayout.stories.tsx b/packages/libs/kode-ui/src/patterns/SideBarLayout/SideBarLayout.stories.tsx new file mode 100644 index 0000000000..ba145c22e4 --- /dev/null +++ b/packages/libs/kode-ui/src/patterns/SideBarLayout/SideBarLayout.stories.tsx @@ -0,0 +1,234 @@ +import { + MonoAccountTree, + MonoControlPointDuplicate, + MonoLightMode, + MonoWallet, + MonoWifiTethering, + MonoWindow, + MonoWorkspaces, +} from '@kadena/kode-icons/system'; +import type { Meta, StoryObj } from '@storybook/react'; +import type { FC, PropsWithChildren } from 'react'; +import React, { useEffect } from 'react'; +import { + Breadcrumbs, + BreadcrumbsItem, + Button, + Stack, +} from './../../components'; +import { KadenaLogo } from './components/Logo/KadenaLogo'; +import { KLogo } from './components/Logo/KLogo'; +import { SideBarFooter } from './components/SideBarFooter'; +import { SideBarFooterItem } from './components/SideBarFooterItem'; +import { SideBarItem } from './components/SideBarItem'; +import { SideBarItemsInline } from './components/SideBarItemsInline'; +import { SideBarProvider, useSideBar } from './components/SideBarProvider'; +import { SideBarTree } from './components/SideBarTree'; +import { SideBarTreeItem } from './components/SideBarTreeItem'; +import type { ISideBarProps } from './SideBar'; +import { SideBar } from './SideBar'; +import { SideBarLayout } from './SideBarLayout'; +import './storybook.css'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const sampleNetworkItems: string[] = ['Mainnet', 'Testnet']; + +const meta: Meta = { + title: 'Patterns/SideBarLayout', + parameters: { + status: { + type: ['releaseCandidate'], + }, + controls: { + hideNoControlsWarning: true, + sort: 'requiredFirst', + }, + docs: { + description: { + component: + 'A component that will set the layout for your app. All with header, footer and sidebar. The layout is setup that you can choose your own routing system. whether its Next or reactRouter. \nThe actual routes are set in your app', + }, + }, + }, + argTypes: {}, +}; + +type IStory = StoryObj; + +const LinkComponent: FC> = ({ + children, + ...props +}) => { + return {children}; +}; + +const InnerLayout = () => { + const { setAppContext, isExpanded } = useSideBar(); + + useEffect(() => { + setAppContext({ + visual: , + label: 'New Transfer', + href: 'https://kadena.io', + }); + }, []); + + return ( + + + + } + activeUrl="https://kadena.io" + // topBanner={ + //
+ // topbanner + //
+ // } + breadcrumbs={ + }> + He-man + Skeletor + + } + sidebar={ + + + + } + minifiedLogo={ + + + + } + appContext={ + } + label="Mainnet" + href="javascript:void()" + /> + } + navigation={ + <> + } label="My Wallet"> + + {}} /> + + } + label="Dashboard" + onPress={() => {}} + /> + + } + context={ + <> + } + label="New Transfer" + onPress={() => {}} + /> + + + } + label="Profile" + onPress={() => {}} + /> + + } + label="Change theme" + onPress={() => {}} + > + + + + {!isExpanded ? ( + + )} + + + {children && isExpanded && treeisExpaned &&
    {children}
} + + + ); + + // return ( + //