diff --git a/docusaurus.config.js b/docusaurus.config.js
index 62578b81..4c65c77e 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -45,7 +45,24 @@ const config = {
logo: {
alt: 'Darwinia',
src: 'img/favicon.svg',
- }
+ },
+ items: [
+ /*{to: 'blog', label: 'Blog', position: 'left'},
+ {
+ href: 'https://github.com/darwinia-network/darwinia/tree/master/runtime/crab',
+ label: 'GitHub',
+ position: 'right',
+ },
+ {
+ href: 'https://darwinia.network/Darwinia_Genepaper_EN.pdf',
+ label: 'WhitePaper',
+ position: 'right',
+ },
+ {
+ type: 'localeDropdown',
+ position: 'right',
+ },*/
+ ],
},
footer: {
style: 'dark',
diff --git a/package.json b/package.json
index b8336a10..72d8cbd9 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,8 @@
"clsx": "^1.1.1",
"prism-react-renderer": "^1.3.1",
"react": "^17.0.2",
- "react-dom": "^17.0.2"
+ "react-dom": "^17.0.2",
+ "react-transition-group": "^4.4.5"
},
"browserslist": {
"production": [
diff --git a/src/components/ConnectWalletButton/index.js b/src/components/ConnectWalletButton/index.js
new file mode 100644
index 00000000..6ab3cda2
--- /dev/null
+++ b/src/components/ConnectWalletButton/index.js
@@ -0,0 +1,170 @@
+import React, {useRef, useState} from 'react';
+import styles from './styles.module.css';
+import Notification from "../Notification";
+import Dialog from "../Dialog";
+
+
+
+const networksList = [
+ {
+ chainId: "0x2e",
+ chainName: "Darwinia Smart Chain",
+ nativeCurrency: {
+ name: "RING",
+ symbol: "RING",
+ decimals: 18,
+ },
+ rpcUrls: ["https://rpc.darwinia.network"],
+ blockExplorerUrls: ["https://darwinia.subscan.io/"],
+ },
+ {
+ chainId: "0x2d",
+ chainName: "Pangoro Smart Chain",
+ nativeCurrency: {
+ name: "ORING",
+ symbol: "ORING",
+ decimals: 18,
+ },
+ rpcUrls: ["https://pangoro-rpc.darwinia.network"],
+ blockExplorerUrls: ["https://pangoro.subscan.io/"],
+ },
+];
+
+const ellipsisAddress = (address) => {
+ return `${address.substring(0, 6)}...${address.substring(address.length - 4)}`;
+};
+
+const ConnectWalletButton = () => {
+ const [connected, setConnected] = useState(null);
+ const provider = useRef();
+ const notificationRef = useRef(null);
+ const dialogRef = useRef(null);
+ const handleConnectToWallet = () => {
+ if(!dialogRef.current) {
+ return;
+ }
+ dialogRef.current.show();
+ }
+
+ const getMetamaskAccounts = async (networkIndex, chainName) => {
+ const accounts = await provider.current.request({
+ method: "eth_requestAccounts",
+ });
+ setConnected({
+ index: networkIndex,
+ account: accounts[0],
+ chainName,
+ });
+ return true;
+ }
+
+ const onSelectNetwork = async (index) => {
+ if (!provider.current) {
+ provider.current = window.ethereum;
+ }
+
+ if (typeof provider.current !== 'undefined') {
+ // Metamask is installed
+ const networkParams = networksList[index];
+ try {
+ const response = await provider.current.request({
+ method: "wallet_switchEthereumChain",
+ params: [{chainId: networkParams.chainId}],
+ });
+ // chainId already exists in Metamask
+ if (!response) {
+ await getMetamaskAccounts(index, networkParams.chainName);
+ }
+ } catch (switchNetworkError) {
+ if (switchNetworkError.code === 4902) {
+ // chainId doesn't exist in Metamask, add it with wallet_addEthereumChain
+ try {
+ const response = await provider.current.request({
+ method: "wallet_addEthereumChain",
+ params: [
+ {
+ chainId: networkParams.chainId,
+ chainName: networkParams.chainName,
+ nativeCurrency: networkParams.nativeCurrency,
+ rpcUrls: [...networkParams.rpcUrls],
+ blockExplorerUrls: [...networkParams.blockExplorerUrls],
+ },
+ ],
+ });
+ if (!response) {
+ await getMetamaskAccounts(index, networkParams.chainName);
+ }
+ } catch (addNetworkError) {
+ notificationRef.current.show({
+ title: "Oops, something wrong",
+ message: (addNetworkError).message,
+ type: 'danger'
+ });
+ }
+ } else {
+ notificationRef.current.show({
+ title: "Oops, something wrong",
+ message: (switchNetworkError).message,
+ type: 'danger'
+ });
+ }
+ }
+ } else {
+ // Metamask is not installed
+ notificationRef.current.show({
+ title: "Oops, something is not quite right.",
+ message: (
+
+ It looks like MetaMask hasn't been installed. Please{" "}
+
+ install MetaMask
+ {" "}
+ and try again.
+
+ ),
+ });
+ }
+ }
+
+
+ const dialogBody = () => {
+ return (
+
+ {
+ networksList.map((item, index) => {
+ return (
+ -
+ {
+ connected && connected.index === index ? (
+
+ Connected to {connected.chainName}: {ellipsisAddress(connected.account)}
+
+ ): (
+
+ )
+ }
+
+ )
+ })
+ }
+
+ )
+ }
+
+ return (
+
+
+
}
+ body={dialogBody()}/>
+
+
+ )
+}
+
+export default ConnectWalletButton;
diff --git a/src/components/ConnectWalletButton/styles.module.css b/src/components/ConnectWalletButton/styles.module.css
new file mode 100644
index 00000000..b1cafaca
--- /dev/null
+++ b/src/components/ConnectWalletButton/styles.module.css
@@ -0,0 +1,45 @@
+.connectWalletBtn {
+ outline: none;
+ border: 1px solid rgb(216,0,115);
+ border-radius: 0.25rem;
+ padding: 6px 15px;
+ background: transparent;
+ color: rgb(216,0,115);
+ margin-right: 10px;
+ font-size: medium;
+}
+.connectWalletBtn:hover {
+ opacity: 0.7;
+ cursor: pointer;
+}
+.chainSelectModalTitle {
+ font-weight: 400;
+ font-size: 19px;
+}
+.networkItem {
+ margin: 10px 0;
+}
+.networkOption {
+ padding: 8px 20px;
+ display: inline-block;
+ border: 1px solid rgb(216,0,115);
+ border-radius: 0.25rem;
+ min-width: 110px;
+ text-align: center;
+ background: transparent;
+ font-size: 16px;
+}
+.networkOption:hover {
+ opacity: 0.7;
+ cursor: pointer;
+}
+.connectedTo {
+ padding: 4.55px 0;
+ display: inline-block;
+}
+
+@media (max-width: 996px) {
+ .connectWalletBtn {
+ margin-right: 0;
+ }
+}
diff --git a/src/components/Dialog/index.js b/src/components/Dialog/index.js
new file mode 100644
index 00000000..21658d6c
--- /dev/null
+++ b/src/components/Dialog/index.js
@@ -0,0 +1,46 @@
+import React, {forwardRef, useImperativeHandle, useState} from "react";
+import styles from './styles.module.css';
+import {CSSTransition} from "react-transition-group";
+
+const Dialog = forwardRef(({title, body}, ref) => {
+ const [isVisible, setVisible] = useState(false);
+
+ const show = () => {
+ setVisible(true);
+ }
+
+ const hide = () => {
+ setVisible(false);
+ }
+
+ useImperativeHandle(ref, ()=> {
+ return {
+ show
+ }
+ })
+
+ return (
+
+ {hide()}} className={styles.wrapper}>
+
{e.stopPropagation()}} className={`${styles.enterDone} ${styles.content}`}>
+
+ {title}
+
{hide()}} className={styles.closeBtn}>
+
+
+
+
{body}
+
+
+
+ );
+})
+
+export default Dialog
diff --git a/src/components/Dialog/styles.module.css b/src/components/Dialog/styles.module.css
new file mode 100644
index 00000000..cd906a98
--- /dev/null
+++ b/src/components/Dialog/styles.module.css
@@ -0,0 +1,93 @@
+.wrapper {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ overflow: auto;
+ outline: 0;
+ background: rgba(0, 0, 0, 0.45);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 200;
+}
+html[data-theme='dark'] .content {
+ background: #444444;
+ color: white;
+}
+.content {
+ position: absolute;
+ width: 85%;
+ max-width: 520px;
+ background: white;
+ border-radius: 8px;
+}
+.title {
+ text-transform: capitalize;
+ font-weight: bold;
+ text-align: center;
+ border-bottom: 1px solid #f0f0f0;
+ padding: 15px 45px;
+ position: relative;
+}
+html[data-theme='dark'] .title {
+ border-bottom-color: #606770;
+}
+.closeBtn {
+ position: absolute;
+ right: 15px;
+ top: 50%;
+ transform: translateY(-50%);
+}
+.closeBtn:hover {
+ opacity: 0.7;
+ cursor: pointer;
+}
+.body {
+ padding: 10px;
+}
+
+.enter {
+ opacity: 0;
+}
+.enterActive {
+ transition: opacity 200ms ease-in-out;
+ opacity: 1;
+}
+.enterDone {
+ opacity: 1;
+}
+.exit {
+ opacity: 1;
+}
+.exitActive {
+ transition: opacity 200ms ease-in-out;
+ opacity: 0;
+}
+.exitDone {
+ opacity: 0;
+}
+.enter .content {
+ transform: scale(0.5);
+}
+.enterActive .content {
+ transform: scale(1.15);
+ transition: transform 150ms ease-in-out;
+ transition-delay: 50ms;
+}
+.enterDone .content {
+ transform: scale(1);
+ transition: transform 150ms ease-in-out;
+}
+.exit .content {
+ transform: scale(1);
+}
+.exitActive .content {
+ transform: scale(0.5);
+ transition: transform 150ms ease-in-out;
+}
+.exitDone .content {
+ transform: scale(0.5);
+ transition: transform 150ms ease-in-out;
+}
diff --git a/src/components/Notification/index.js b/src/components/Notification/index.js
new file mode 100644
index 00000000..71bfaf31
--- /dev/null
+++ b/src/components/Notification/index.js
@@ -0,0 +1,52 @@
+import React, {useState, forwardRef, useImperativeHandle, useRef} from "react";
+import styles from './styles.module.css';
+import {CSSTransition} from 'react-transition-group';
+
+const Notification = forwardRef(({}, ref) => {
+ const [isVisible, setVisible] = useState(false);
+ const [notification, setNotification] = useState({title:'title', message: 'message', type: 'success'});
+ const timerRef = useRef(null);
+ const show = (notification) => {
+ setNotification(notification);
+ setVisible(true);
+ timerRef.current = setTimeout(()=> {
+ setVisible(false);
+ },4000);
+ }
+ const hide = () => {
+ try {
+ if(timerRef.current) {
+ clearTimeout(timerRef.current);
+ }
+ setVisible(false);
+ }catch (e) {
+ //ignore
+ }
+ }
+ useImperativeHandle(ref, ()=> {
+ return {
+ show
+ }
+ });
+ const titleClasses = `${notification.type === 'danger' ? styles.danger : ''}`;
+ return (
+
+
+
{notification.title}
+
{notification.message}
+
{hide()}} className={styles.closeBtn}>
+
+
+
+
+ )
+});
+
+export default Notification
diff --git a/src/components/Notification/styles.module.css b/src/components/Notification/styles.module.css
new file mode 100644
index 00000000..5c1073d9
--- /dev/null
+++ b/src/components/Notification/styles.module.css
@@ -0,0 +1,55 @@
+.wrapper {
+ box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d;
+ position: fixed;
+ top: 40px;
+ right: 20px;
+ width: 80%;
+ max-width: 320px;
+ z-index: 9999;
+ background: white;
+ border-radius: 8px;
+ padding: 10px;
+ min-height: 90px;
+}
+html[data-theme='dark'] .wrapper {
+ background: #444444;
+ color: white;
+}
+.title {
+ text-transform: capitalize;
+ font-weight: bold;
+ margin-bottom: 5px;
+ padding-right: 20px;
+}
+.danger {
+ color: red;
+}
+.closeBtn {
+ position: absolute;
+ right: 10px;
+ top: 14px;
+}
+.closeBtn:hover {
+ opacity: 0.7;
+ cursor: pointer;
+}
+.enter {
+ transform: translate3d(110%, 0, 0);
+}
+.enterActive {
+ transition: transform 200ms ease-in-out;
+ transform: translate3d(1px, 0, 0);
+}
+.enterDone {
+ transform: translate3d(0px, 0, 0);
+}
+.exit {
+ transform: translate3d(1px, 0, 0);
+}
+.exitActive {
+ transition: transform 200ms ease-in-out;
+ transform: translate3d(109%, 0, 0);
+}
+.exitDone {
+ transform: translate3d(110%, 0, 0);
+}
diff --git a/src/theme/Navbar/Content/index.js b/src/theme/Navbar/Content/index.js
new file mode 100644
index 00000000..f571eb0d
--- /dev/null
+++ b/src/theme/Navbar/Content/index.js
@@ -0,0 +1,68 @@
+import React from 'react';
+import NavbarItem from '@theme/NavbarItem';
+import NavbarColorModeToggle from '@theme/Navbar/ColorModeToggle';
+import SearchBar from '@theme/SearchBar';
+import {
+ splitNavbarItems,
+ useNavbarMobileSidebar,
+ useThemeConfig,
+} from '@docusaurus/theme-common';
+import NavbarMobileSidebarToggle from '@theme/Navbar/MobileSidebar/Toggle';
+import NavbarLogo from '@theme/Navbar/Logo';
+import styles from './styles.module.css';
+import ConnectWalletButton from "../../../components/ConnectWalletButton";
+
+function useNavbarItems() {
+ return useThemeConfig().navbar.items || [];
+}
+
+function NavbarItems({items}) {
+ return (
+ <>
+ {items.map((item, i) => (
+
+ ))}
+ >
+ );
+}
+
+function NavbarContentLayout({
+ left,
+ right,
+}) {
+ return (
+
+ );
+}
+
+export default function NavbarContent() {
+ const mobileSidebar = useNavbarMobileSidebar();
+
+ const items = useNavbarItems();
+ const [leftItems, rightItems] = splitNavbarItems(items);
+
+ const autoAddSearchBar = !items.some((item) => item.type === 'search');
+
+ return (
+
+ {!mobileSidebar.disabled && }
+
+
+ >
+ }
+ right={
+ <>
+
+
+
+ {autoAddSearchBar && }
+ >
+ }
+ />
+ );
+}
diff --git a/src/theme/Navbar/Content/styles.module.css b/src/theme/Navbar/Content/styles.module.css
new file mode 100644
index 00000000..3cbe701f
--- /dev/null
+++ b/src/theme/Navbar/Content/styles.module.css
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+/*
+Hide color mode toggle in small viewports
+ */
+@media (max-width: 996px) {
+ .colorModeToggle {
+ display: none;
+ }
+}
diff --git a/src/theme/Navbar/index.js b/src/theme/Navbar/index.js
new file mode 100644
index 00000000..2d7687b9
--- /dev/null
+++ b/src/theme/Navbar/index.js
@@ -0,0 +1,14 @@
+import React from 'react';
+import NavbarLayout from '@theme/Navbar/Layout';
+import NavbarContent from './Content';
+
+const Navbar = () => {
+ return (
+
+
+
+ );
+}
+
+export default Navbar;
+
diff --git a/yarn.lock b/yarn.lock
index a3e9aa77..fd8f94d3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1131,6 +1131,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
+ version "7.19.0"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259"
+ integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.12.7", "@babel/template@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
@@ -3678,6 +3685,14 @@ dom-converter@^0.2.0:
dependencies:
utila "~0.4"
+dom-helpers@^5.0.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
+ integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
+ dependencies:
+ "@babel/runtime" "^7.8.7"
+ csstype "^3.0.2"
+
dom-iterator@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dom-iterator/-/dom-iterator-1.0.0.tgz#9c09899846ec41c2d257adc4d6015e4759ef05ad"
@@ -6435,6 +6450,16 @@ react-textarea-autosize@^8.3.2:
use-composed-ref "^1.3.0"
use-latest "^1.2.1"
+react-transition-group@^4.4.5:
+ version "4.4.5"
+ resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
+ integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
+ dependencies:
+ "@babel/runtime" "^7.5.5"
+ dom-helpers "^5.0.1"
+ loose-envify "^1.4.0"
+ prop-types "^15.6.2"
+
react@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"